Compare commits

...

83 Commits

Author SHA1 Message Date
hardiknai-techm
8ed4465885 Panel should not use GenericRightPaneComponent 2021-05-17 16:30:28 +05:30
hardiknai-techm
af5d77d754 merge master 2021-05-17 06:48:15 +05:30
Hardikkumar Nai
a52a156005 Remove Old Add Database Pane Code (#784)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-14 12:05:00 -05:00
Steve Faulkner
f9e8b5eaaa Remove Unused Knockout Components (#783) 2021-05-13 18:03:29 -05:00
vaidankarswapnil
a6b82c8340 Migrate graph style panel to react (#619)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-13 15:45:00 -05:00
Tanuj Mittal
404b1fc0f1 Prep Schema Analyzer for flighting (#760)
* Prepare for flighting Schema Analyzer

* Rename SchemaAnalyzerComponent -> SchemaAnalyzer

* Only show Schema option if notebooks enabled
2021-05-13 10:34:09 -07:00
Sunil Kumar Yadav
d7c62ac7f1 Migrate Collapse/Open Resource Tree to React (#733)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-12 20:03:52 -05:00
Sunil Kumar Yadav
8e6d274b11 Add Files to TypeScript Strict (#776)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-12 19:56:48 -05:00
Sunil Kumar Yadav
2d506f0312 Add Files to TypeScript Strict Mode (#777) 2021-05-12 19:23:10 -05:00
victor-meng
d76aaca0dd Improve lazy load database/collection offer logic (#768) 2021-05-12 19:13:15 -05:00
victor-meng
14e58e5519 Batch of small fixes for RightPaneForm and AddDatabasePane components (#780) 2021-05-12 19:12:03 -05:00
victor-meng
2f6dbd83f3 Fix throughput input component and add database panel (#773) 2021-05-12 13:56:24 -05:00
Steve Faulkner
0a6c7c0ff9 Add Mongo 3.2 End to End Test (#779) 2021-05-12 13:41:44 -05:00
Steve Faulkner
66281447df Pass undefined analyticalTTL if Synapse is disabled (#778) 2021-05-12 11:49:25 -05:00
victor-meng
c5f76ac2a9 Fix isFixedCollectionWithSharedThroughputSupported flag (#774) 2021-05-12 09:16:13 -05:00
Laurent Nguyen
861042c27e Fix bug publish screenshot (#762)
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/762?feature.someFeatureFlagYouMightNeed=true)

The main change in this PR fixes the snapshot functionality in the Publish pane-related components. Because the code cell outputs are now rendered in their own iframes for security reasons, a single snapshot of the notebook is no longer possible: each cell output takes its own snapshot and the snapshots are collated on the main notebook snapshot.
- Move the snapshot functionality to notebook components: this removes the reference of the notebook DOM node that we must pass to the Publish pane via explorer.
- Add slice in the state and actions in notebook redux for notebook snapshot requests and result
- Add post robot message to take snapshots and receive results
- Add logic in `NotebookRenderer` to wait for all output snapshots done before taking the main one collating.
- Use `zustand` to share snapshot between Redux world and React world. This solves the issue of keeping the `PanelContainer` component generic, while being able to update its children (`PublishPanel` component) with the new snapshot.

Additional changes:
- Add `local()` in `@font-face` to check if font is already installed before downloading the font (must be done for Safari, but not Edge/Chrome)
- Add "Export output to image" menu item in notebook cell, since each cell output can take its own snapshot (which can be downloaded)
![image](https://user-images.githubusercontent.com/21954022/117454706-b5f16600-af46-11eb-8535-6bf99f3d9170.png)
2021-05-11 18:24:05 +00:00
victor-meng
4ed8fe9e7d Remove old add collection pane (#767) 2021-05-10 20:10:48 -05:00
Srinath Narayanan
4c506da7b9 Added metrics blade link and fixed SelfServe bugs (#764)
* Added metrics blade link

* fixed lint error
2021-05-10 17:36:50 -07:00
Hardikkumar Nai
a81b1a40a3 Use @fluentui/react DocumentCard (#715)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-10 14:17:37 -05:00
Hardikkumar Nai
9d5c9d6296 Migrate Add Database Panel to React (#597)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-10 14:02:14 -05:00
Steve Faulkner
7efa8ca58f Remove unused Switch Directory Pane (#766) 2021-05-09 22:37:18 -05:00
Hardikkumar Nai
487fbf2072 Remove genericRightPaneComponent and create RightPaneWrapper with form (#679)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-09 19:22:44 -05:00
Sunil Kumar Yadav
aa308b3e4d Enable TypeScript noImplicitThis (#761) 2021-05-07 10:25:19 -05:00
Steve Faulkner
db227084be Remove IE11 from Coding Guidelines 2021-05-07 10:04:47 -05:00
hardiknai-techm
ead93b9fa5 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into genericRightPaneComponent 2021-05-07 12:56:45 +05:30
victor-meng
d62baf327b Change create wildcard index default value to false for mongo 3.2 (#759)
* Change create wildcard index default value to false for mongo 3.2

* Update snapshots
2021-05-06 21:27:47 -05:00
Jordi Bunster
78eafe1aec Remove NotebookViewerTab (#749)
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/749)
2021-05-07 00:20:25 +00:00
Sunil Kumar Yadav
a91ea6c1e4 Remove old Add/Edit Table Entity code (#755) 2021-05-06 18:51:45 -05:00
victor-meng
5606ef3266 Fix table edit entity bug and add collection panel bug for connection string users (#757)
* Fix table edit entity bug and add collection panel bug for connection string users

* Remove parseInt for int64
2021-05-06 18:51:22 -05:00
Steve Faulkner
503f044a70 Update strict mode files (#753) 2021-05-06 12:35:24 -05:00
hardiknai-techm
88491ba6a9 resolve master merge conflict 2021-05-06 18:59:00 +05:30
hardiknai-techm
fc83484b6c resolve merge conflict 2021-05-06 17:12:03 +05:30
Hardikkumar Nai
23223cfb23 Upgrade Fluent UI v8 (#688)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-05 18:26:03 -05:00
Steve Faulkner
bd47e5ed49 Remove unused Explorer methods (#750) 2021-05-05 17:35:35 -05:00
Hardikkumar Nai
8c05ac740c Remove Explorer.databaseAccount (#717)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-05 16:54:50 -05:00
Hardikkumar Nai
fdd12b41c4 Remove Explorer.defaultExperience (#680)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-05 13:00:01 -05:00
Tanuj Mittal
d1d28885d0 Use CellOutputViewer for SchemaAnalyzer (#747) 2021-05-05 11:12:27 +05:30
Hardikkumar Nai
aab624e241 Create End to End Graph Test (#745) 2021-05-04 23:01:13 -05:00
Tanuj Mittal
181b53c858 Disable HTML in markdown cell for NotebookReadOnlyRenderer (#746)
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/746)

We have a custom implementation of `Markdown` cell that we use in `NotebookRenderer`. This custom implementation disables HTML rendering in markdown cells. This change uses the same for `NotebookReadOnlyRenderer` which we use for Gallery.
2021-05-04 21:43:39 +00:00
Srinath Narayanan
1fdb339fbf Enable the "Enable notebooks" button (#734)
* enable notebooks initial commit

* use only first write location

* addressed PR comments

* Minor edits
2021-05-04 13:06:27 -07:00
Jordi Bunster
b7579d5c8b eslint switch/case exhaustiveness check rule (#739) 2021-05-04 09:12:54 -07:00
hardiknai-techm
e91145234f resolve e2e test error 2021-05-04 18:34:27 +05:30
hardiknai-techm
7c4bc9e0c0 Merge branch 'remove_explorer.defaultExperience' of https://github.com/Azure/cosmos-explorer into genericRightPaneComponent 2021-05-04 08:22:23 +05:30
hardiknai-techm
40d71d3d7a resolve marge conflict 2021-05-04 08:13:34 +05:30
hardiknai-techm
0c3f8bd625 resolve merge conflict 2021-05-04 07:40:09 +05:30
vaidankarswapnil
038f3ee684 Move GitHub repos panel to react (#638)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-03 19:56:47 -05:00
victor-meng
4efacace16 add collection panel improvements (#630)
Co-authored-by: Jordi Bunster <jbunster@microsoft.com>
2021-04-30 10:23:34 -07:00
victor-meng
9878bf0d5e Fix table entity boolean and number type property values (#737) 2021-04-29 19:23:21 -05:00
Jordi Bunster
5e0523c7d9 Remove GraphExplorerAdapter (#736) 2021-04-29 16:46:31 -05:00
Jordi Bunster
9d0bc86197 Remove 'explorer' from a few Panes (#650)
While working on #549 I realized there were a few places where 'explorer' was only needed to expand the notifications console, so I stripped those out where it was easy.
2021-04-29 10:20:57 -07:00
hardiknai-techm
6bdf1c7f7c update snapshot test 2021-04-29 20:23:21 +05:30
hardiknai-techm
f048f21def resolve format issue 2021-04-29 08:46:59 +05:30
hardiknai-techm
38ffa6a003 merge master branch 2021-04-29 08:35:26 +05:30
hardiknai-techm
7902df4d16 Merge branch 'remove_explorer.defaultExperience' of https://github.com/Azure/cosmos-explorer into remove_explorer.defaultExperience 2021-04-29 08:15:03 +05:30
hardiknai-techm
e701dcc881 resolve master branch conflict 2021-04-29 08:13:06 +05:30
Steve Faulkner
3a6c7f9f94 Fixes 2021-04-28 18:59:13 -05:00
Steve Faulkner
7b5b752d9c Fix strict 2021-04-28 18:46:51 -05:00
Steve Faulkner
a0d22960ff Merge branch 'master' into move_graph_style_panel_to_react 2021-04-28 15:20:57 -05:00
Sunil Kumar Yadav
531df811da Remove userContext.defaultExperience (#730)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-28 14:25:04 -05:00
Steve Faulkner
5a019eb431 Remove Explorer.isPreferredAPIMongo (#557)
Co-authored-by: hardiknai-techm <HN00734461@TechMahindra.com>
2021-04-27 20:50:01 -05:00
Steve Faulkner
4c6650760b Merge branch 'master' into remove_explorer.defaultExperience 2021-04-27 19:51:06 -05:00
Hardikkumar Nai
8f3cb7282b Migrate Publish Notebook Pane to React (#641)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-27 19:40:03 -05:00
Jordi Bunster
154db1dcd5 Get our previously strict files a bit tighter (#604)
Now they meet noUnusedParameters
2021-04-27 15:27:17 -07:00
Tanuj Mittal
e8b79d6260 Use postRobot to listen for GitHub OAuth messages (#729) 2021-04-27 22:22:52 +05:30
Jordi Bunster
10c4dd0f19 This is creating a warning in tests (#731) 2021-04-27 09:05:25 -07:00
Jordi Bunster
5cf16d01b5 use ES6 Map if we can (#602) 2021-04-27 08:14:21 -07:00
Jordi Bunster
127784abdd Bypass Knockout and adapters in GalleryTab (#728) 2021-04-27 08:14:07 -07:00
Jordi Bunster
c7b9ff6794 Lazy loaded Monaco (#720)
Lazy loaded Monaco
2021-04-25 21:31:10 -07:00
Hardikkumar Nai
71e7ad4547 Migrate String Input Pane to React (#678)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-25 20:22:46 -05:00
Steve Faulkner
0b1ac8f445 WIP 2021-04-25 15:57:00 -07:00
Steve Faulkner
96305f50f8 Merge branch 'master' into remove_explorer.defaultExperience 2021-04-25 15:54:03 -07:00
Sunil Kumar Yadav
67062c18aa Migration/edit table entity panel to react (#690)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-25 17:51:27 -05:00
Sunil Kumar Yadav
ab283cb8ff Update webpack v4.46.0 (#718) 2021-04-24 18:54:59 -05:00
Jordi Bunster
045a28b7a4 Remove 'any' from existing lazy loaded tabs (#721)
* Typesafe lazy loaded GalleryTab

* Typesafe lazy loaded NotebookViewerTab

* Typesafe lazy loaded NotebookManager
2021-04-23 19:54:21 -07:00
Jordi Bunster
b7c911d19a Remove Tabs from ComponentRegisterer (#713)
Now that Tabs are being rendered via Tabs.tsx the knockout component names are not needed either.
2021-04-23 19:53:48 -07:00
Jordi Bunster
5323f6ca4b Lazy load SchemaAnalyzerTab (#722) 2021-04-23 19:52:18 -07:00
hardiknai-techm
3e011f939d resolve conflict master branch 2021-04-23 19:13:13 +05:30
hardiknai-techm
10961a2f9f marge master 2021-04-23 18:47:05 +05:30
hardiknai-techm
ba25eea41e update sanpshort test case 2021-04-16 15:59:48 +05:30
hardiknai-techm
e17fe25292 Some panel use GenenricRightPanel and Some panel use RightPanelForm 2021-04-16 15:53:44 +05:30
hardiknai-techm
9494c9cd55 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into genericRightPaneComponent 2021-04-16 14:55:40 +05:30
hardiknai-techm
8f0bb1add8 Remove Explorer.defaultExperience 2021-04-15 13:04:51 +05:30
hardiknai-techm
5c9ab15b3a remove genericRightPaneComponent and create RightPaneWrapper with form 2021-04-15 11:47:05 +05:30
382 changed files with 35699 additions and 32686 deletions

View File

@@ -1,11 +1,11 @@
**/node_modules/ **/node_modules/
src/**/__mocks__/**/*
dist/ dist/
Contracts/ Contracts/
src/Api/Apis.ts src/Api/Apis.ts
src/AuthType.ts src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts src/Bindings/BindingHandlersRegisterer.ts
src/Bindings/ReactBindingHandler.ts src/Bindings/ReactBindingHandler.ts
src/Common/ArrayHashMap.ts
src/Common/Constants.ts src/Common/Constants.ts
src/Common/CosmosClient.test.ts src/Common/CosmosClient.test.ts
src/Common/CosmosClient.ts src/Common/CosmosClient.ts
@@ -13,15 +13,12 @@ src/Common/DataAccessUtilityBase.test.ts
src/Common/DataAccessUtilityBase.ts src/Common/DataAccessUtilityBase.ts
src/Common/EditableUtility.ts src/Common/EditableUtility.ts
src/Common/HashMap.test.ts src/Common/HashMap.test.ts
src/Common/HashMap.ts
src/Common/Logger.test.ts src/Common/Logger.test.ts
src/Common/MessageHandler.test.ts src/Common/MessageHandler.test.ts
src/Common/MessageHandler.ts src/Common/MessageHandler.ts
src/Common/MongoProxyClient.test.ts src/Common/MongoProxyClient.test.ts
src/Common/MongoUtility.ts src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts src/Common/NotificationsClientBase.ts
src/Common/ObjectCache.test.ts
src/Common/ObjectCache.ts
src/Common/QueriesClient.ts src/Common/QueriesClient.ts
src/Common/Splitter.ts src/Common/Splitter.ts
src/Config.ts src/Config.ts
@@ -57,7 +54,6 @@ src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
src/Explorer/Controls/Toolbar/IToolbarAction.ts src/Explorer/Controls/Toolbar/IToolbarAction.ts
@@ -88,8 +84,8 @@ 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/GraphStyle.test.ts
src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts # src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
@@ -112,24 +108,19 @@ src/Explorer/Notebook/NotebookUtil.ts
src/Explorer/OpenActions.test.ts src/Explorer/OpenActions.test.ts
src/Explorer/OpenActions.ts src/Explorer/OpenActions.ts
src/Explorer/OpenActionsStubs.ts src/Explorer/OpenActionsStubs.ts
src/Explorer/Panes/AddCollectionPane.test.ts
src/Explorer/Panes/AddCollectionPane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/AddDatabasePane.ts src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/BrowseQueriesPane.ts src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ContextualPaneBase.ts src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/GraphStylingPane.ts # src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts # src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/StringInputPane.ts src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/EditTableEntityPane.ts
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts
src/Explorer/Panes/Tables/TableEntityPane.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

View File

@@ -11,6 +11,7 @@ module.exports = {
}, },
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
parserOptions: { parserOptions: {
project: ["./tsconfig.json", "./tsconfig.test.json"],
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
}, },
@@ -35,6 +36,7 @@ module.exports = {
rules: { rules: {
"no-console": ["error", { allow: ["error", "warn", "dir"] }], "no-console": ["error", { allow: ["error", "warn", "dir"] }],
curly: "error", curly: "error",
"@typescript-eslint/switch-exhaustiveness-check": "error",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error", "no-null/no-null": "error",

View File

@@ -138,8 +138,10 @@ jobs:
matrix: matrix:
test-file: test-file:
- ./test/cassandra/container.spec.ts - ./test/cassandra/container.spec.ts
- ./test/graph/container.spec.ts
- ./test/sql/container.spec.ts - ./test/sql/container.spec.ts
- ./test/mongo/container.spec.ts - ./test/mongo/container.spec.ts
- ./test/mongo/container32.spec.ts
- ./test/selfServe/selfServeExample.spec.ts - ./test/selfServe/selfServeExample.spec.ts
- ./test/notebooks/upload.spec.ts - ./test/notebooks/upload.spec.ts
- ./test/sql/resourceToken.spec.ts - ./test/sql/resourceToken.spec.ts

View File

@@ -188,7 +188,3 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul
✅ DO ✅ DO
- Support all [browsers supported by the Azure Portal](https://docs.microsoft.com/en-us/azure/azure-portal/azure-portal-supported-browsers-devices) - Support all [browsers supported by the Azure Portal](https://docs.microsoft.com/en-us/azure/azure-portal/azure-portal-supported-browsers-devices)
- Support IE11
- In practice, this should not need to be considered as part of a normal development workflow
- Polyfills and transpilation are already provided by our engineering systems.
- This requirement will be removed on March 30th, 2021 when Azure drops IE11 support.

View File

@@ -69,7 +69,8 @@ module.exports = {
moduleNameMapper: { moduleNameMapper: {
"^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule", "^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule",
"@nteract/stateful-components/(.*)$": "<rootDir>/mockModule", "@nteract/stateful-components/(.*)$": "<rootDir>/mockModule",
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes "@fluentui/react/lib/(.*)$": "@fluentui/react/lib-commonjs/$1", // https://github.com/microsoft/fluentui/wiki/Version-8-release-notes
"monaco-editor/(.*)$": "<rootDir>/__mocks__/monaco-editor",
"^dnd-core$": "dnd-core/dist/cjs", "^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs", "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",

View File

@@ -4,7 +4,7 @@
@font-face { @font-face {
font-family: wf_segoe-ui_normal; font-family: wf_segoe-ui_normal;
src: url("../../fonts/segoe-ui/west-european/normal/latest.woff"); src: local("Segoe UI"), url("../../fonts/segoe-ui/west-european/normal/latest.woff");
} }
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; @DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;

View File

@@ -1757,7 +1757,7 @@ input::-webkit-calendar-picker-indicator {
cursor: pointer; cursor: pointer;
} }
.contextual-pane .paneMainContent { .paneMainContent {
flex: 1; flex: 1;
padding-left: 34px; padding-left: 34px;
padding-right: 34px; padding-right: 34px;
@@ -2099,7 +2099,7 @@ a:link {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
overflow-x: auto; overflow-x: auto;
overflow-y: auto; overflow-y: hidden;
height: 100%; height: 100%;
} }
@@ -3085,3 +3085,7 @@ settings-pane {
padding-left: @SmallSpace; padding-left: @SmallSpace;
} }
} }
.hiddenMain {
visibility: hidden;
height: 0px;
}

View File

@@ -3,6 +3,7 @@
.resourceTree { .resourceTree {
height: 100%; height: 100%;
width: 20%;
flex: 0 0 auto; flex: 0 0 auto;
.main { .main {
height: 100%; height: 100%;

1062
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"@azure/ms-rest-nodeauth": "3.0.7", "@azure/ms-rest-nodeauth": "3.0.7",
"@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12", "@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.14.3",
"@jupyterlab/services": "6.0.2", "@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3", "@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1", "@microsoft/applicationinsights-web": "2.6.1",
@@ -42,8 +43,6 @@
"@testing-library/jest-dom": "5.11.9", "@testing-library/jest-dom": "5.11.9",
"@types/mkdirp": "1.0.1", "@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "file:./canvas", "canvas": "file:./canvas",
@@ -57,6 +56,7 @@
"datatables.net-dt": "1.10.19", "datatables.net-dt": "1.10.19",
"date-fns": "1.29.0", "date-fns": "1.29.0",
"dayjs": "1.8.19", "dayjs": "1.8.19",
"dom-to-image": "2.6.0",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"eslint-plugin-jest": "23.13.2", "eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0", "eslint-plugin-react": "7.20.0",
@@ -76,7 +76,6 @@
"monaco-editor": "0.18.1", "monaco-editor": "0.18.1",
"ms": "2.1.3", "ms": "2.1.3",
"msal": "1.4.4", "msal": "1.4.4",
"office-ui-fabric-react": "7.164.2",
"p-retry": "4.2.0", "p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42", "post-robot": "10.0.42",
@@ -99,7 +98,8 @@
"swr": "0.4.0", "swr": "0.4.0",
"terser-webpack-plugin": "3.1.0", "terser-webpack-plugin": "3.1.0",
"underscore": "1.9.1", "underscore": "1.9.1",
"utility-types": "3.10.0" "utility-types": "3.10.0",
"zustand": "3.5.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.9.0", "@babel/core": "7.9.0",
@@ -111,6 +111,7 @@
"@types/codemirror": "0.0.56", "@types/codemirror": "0.0.56",
"@types/crossroads": "0.0.30", "@types/crossroads": "0.0.30",
"@types/d3": "5.9.2", "@types/d3": "5.9.2",
"@types/dom-to-image": "2.6.2",
"@types/enzyme": "3.10.7", "@types/enzyme": "3.10.7",
"@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",
@@ -128,8 +129,8 @@
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36", "@types/underscore": "1.7.36",
"@typescript-eslint/eslint-plugin": "4.0.1", "@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.0.1", "@typescript-eslint/parser": "4.22.0",
"babel-jest": "24.9.0", "babel-jest": "24.9.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
"buffer": "5.1.0", "buffer": "5.1.0",
@@ -176,7 +177,7 @@
"typescript": "4.2.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.43.0", "webpack": "4.46.0",
"webpack-bundle-analyzer": "3.6.1", "webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "3.3.10", "webpack-cli": "3.3.10",
"webpack-dev-server": "3.11.0" "webpack-dev-server": "3.11.0"
@@ -191,7 +192,7 @@
"pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress", "pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
"copyToConsumers": "node copyToConsumers", "copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest", "test": "rimraf coverage && jest",
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles", "test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
"watch": "npm run start", "watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"build:ase": "gulp build:ase", "build:ase": "gulp build:ase",

View File

@@ -22,13 +22,7 @@ export interface ReactAdapter {
export class Registerer { export class Registerer {
public static register(): void { public static register(): void {
ko.bindingHandlers.react = { ko.bindingHandlers.react = {
init: ( init: (element: any, wrappedValueAccessor: () => any) => {
element: any,
wrappedValueAccessor: () => any,
allBindings?: ko.AllBindings,
viewModel?: any,
bindingContext?: ko.BindingContext
) => {
const adapter: ReactAdapter = wrappedValueAccessor(); const adapter: ReactAdapter = wrappedValueAccessor();
if (adapter.setElement) { if (adapter.setElement) {

View File

@@ -0,0 +1,15 @@
.schema-analyzer-cell-outputs {
padding: 10px 2px;
}
// Mimic FluentUI8's DocumentCard style
.schema-analyzer-cell-output {
margin-bottom: 20px;
padding: 14px 20px;
border: 1px solid rgb(237, 235, 233);
}
.schema-analyzer-cell-output:hover {
border-color: rgb(200, 198, 196);
box-shadow: inset 0 0 0 1px rgb(200, 198, 196)
}

View File

@@ -9,15 +9,22 @@ import postRobot from "post-robot";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
import { SnapshotRequest } from "../Explorer/Notebook/NotebookComponent/types";
import "../Explorer/Notebook/NotebookRenderer/base.css"; import "../Explorer/Notebook/NotebookRenderer/base.css";
import "../Explorer/Notebook/NotebookRenderer/default.css"; import "../Explorer/Notebook/NotebookRenderer/default.css";
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
import "./CellOutputViewer.less";
import { TransformMedia } from "./TransformMedia"; import { TransformMedia } from "./TransformMedia";
export interface SnapshotResponse {
imageSrc: string;
requestId: string;
}
export interface CellOutputViewerProps { export interface CellOutputViewerProps {
id: string; id: string;
contentRef: ContentRef; contentRef: ContentRef;
hidden: boolean; outputsContainerClassName: string;
expanded: boolean; outputClassName: string;
outputs: OnDiskOutput[]; outputs: OnDiskOutput[];
onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void; onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void;
} }
@@ -34,27 +41,26 @@ const onInit = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (event as any).data as CellOutputViewerProps; const props = (event as any).data as CellOutputViewerProps;
const outputs = ( const outputs = (
<div <div data-iframe-height className={props.outputsContainerClassName}>
data-iframe-height
className={`nteract-cell-outputs ${props.hidden ? "hidden" : ""} ${props.expanded ? "expanded" : ""}`}
>
{props.outputs?.map((output, index) => ( {props.outputs?.map((output, index) => (
<Output output={createImmutableOutput(output)} key={index}> <div className={props.outputClassName} key={index}>
<TransformMedia <Output output={createImmutableOutput(output)} key={index}>
output_type={"display_data"} <TransformMedia
id={props.id} output_type={"display_data"}
contentRef={props.contentRef} id={props.id}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)} contentRef={props.contentRef}
/> onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
<TransformMedia />
output_type={"execute_result"} <TransformMedia
id={props.id} output_type={"execute_result"}
contentRef={props.contentRef} id={props.id}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)} contentRef={props.contentRef}
/> onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
<KernelOutputError /> />
<StreamText /> <KernelOutputError />
</Output> <StreamText />
</Output>
</div>
))} ))}
</div> </div>
); );
@@ -62,6 +68,36 @@ const onInit = async () => {
ReactDOM.render(outputs, document.getElementById("cellOutput")); ReactDOM.render(outputs, document.getElementById("cellOutput"));
} }
); );
postRobot.on(
"snapshotRequest",
{
window: window.parent,
domain: window.location.origin,
},
async (event): Promise<SnapshotResponse> => {
const topNode = document.getElementById("cellOutput");
if (!topNode) {
const errorMsg = "No top node to snapshot";
return Promise.reject(new Error(errorMsg));
}
// Typescript definition for event is wrong. So read props by casting to <any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const snapshotRequest = (event as any).data as SnapshotRequest;
const result = await NotebookUtil.takeScreenshotDomToImage(
topNode,
snapshotRequest.aspectRatio,
undefined,
snapshotRequest.downloadFilename
);
return {
imageSrc: result.imageSrc,
requestId: snapshotRequest.requestId,
};
}
);
}; };
// Entry point // Entry point

View File

@@ -1,49 +1,9 @@
import { HashMap } from "./HashMap";
/** /**
* Hash map of arrays which allows to: * Hash map of arrays which allows to:
* - push an item by key: add to array and create array if needed * - push an item by key: add to array and create array if needed
* - remove item by key: remove from array and delete array if needed * - remove item by key: remove from array and delete array if needed
*/ */
export class ArrayHashMap<T> extends Map<string, T[]> {
export class ArrayHashMap<T> {
private store: HashMap<T[]>;
constructor() {
this.store = new HashMap();
}
public has(key: string): boolean {
return this.store.has(key);
}
public get(key: string): T[] {
return this.store.get(key);
}
public size(): number {
return this.store.size();
}
public clear(): void {
this.store.clear();
}
public keys(): string[] {
return this.store.keys();
}
public delete(key: string): boolean {
return this.store.delete(key);
}
public forEach(key: string, iteratorFct: (value: T) => void) {
const values = this.store.get(key);
if (values) {
values.forEach((value) => iteratorFct(value));
}
}
/** /**
* Insert item into array. * Insert item into array.
* If no array, create one. * If no array, create one.
@@ -52,16 +12,8 @@ export class ArrayHashMap<T> {
* @param item * @param item
*/ */
public push(key: string, item: T): void { public push(key: string, item: T): void {
let itemsArray: T[] = this.store.get(key); const array = this.get(key);
if (!itemsArray) { array ? array.includes(item) || array.push(item) : this.set(key, [item]);
itemsArray = [item];
this.store.set(key, itemsArray);
return;
}
if (itemsArray.indexOf(item) === -1) {
itemsArray.push(item);
}
} }
/** /**
@@ -70,18 +22,11 @@ export class ArrayHashMap<T> {
* @param key * @param key
* @param itemToRemove * @param itemToRemove
*/ */
public remove(key: string, itemToRemove: T) { public remove(key: string, itemToRemove: T): void {
if (!this.store.has(key)) { const array = this.get(key);
return; if (array) {
} const remaining = array.filter((item) => item !== itemToRemove);
remaining.length ? this.set(key, remaining) : this.delete(key);
const itemsArray = this.store.get(key);
const index = itemsArray.indexOf(itemToRemove);
if (index >= 0) {
itemsArray.splice(index, 1);
if (itemsArray.length === 0) {
this.store.delete(key);
}
} }
} }
} }

View File

@@ -0,0 +1,35 @@
import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
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 data-bind="text: collectionTitle" />
</span>
</li>
</ul>
</div>
</div>
);
};

View File

@@ -65,28 +65,18 @@ export class ClientDefaults {
public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000; public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000;
} }
export class AccountKind { export enum AccountKind {
public static DocumentDB: string = "DocumentDB"; DocumentDB = "DocumentDB",
public static MongoDB: string = "MongoDB"; MongoDB = "MongoDB",
public static Parse: string = "Parse"; Parse = "Parse",
public static GlobalDocumentDB: string = "GlobalDocumentDB"; GlobalDocumentDB = "GlobalDocumentDB",
public static Default: string = AccountKind.DocumentDB; Default = "DocumentDB",
} }
export class CorrelationBackend { export class CorrelationBackend {
public static Url: string = "https://aka.ms/cosmosdbanalytics"; public static Url: string = "https://aka.ms/cosmosdbanalytics";
} }
export class DefaultAccountExperience {
public static DocumentDB: string = "DocumentDB";
public static Graph: string = "Graph";
public static MongoDB: string = "MongoDB";
public static ApiForMongoDB: string = "Azure Cosmos DB for MongoDB API";
public static Table: string = "Table";
public static Cassandra: string = "Cassandra";
public static Default: string = DefaultAccountExperience.DocumentDB;
}
export class CapabilityNames { export class CapabilityNames {
public static EnableTable: string = "EnableTable"; public static EnableTable: string = "EnableTable";
public static EnableGremlin: string = "EnableGremlin"; public static EnableGremlin: string = "EnableGremlin";
@@ -104,6 +94,7 @@ export class Flights {
public static readonly MongoIndexEditor = "mongoindexeditor"; public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing"; public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest"; public static readonly AutoscaleTest = "autoscaletest";
public static readonly SchemaAnalyzer = "schemaanalyzer";
} }
export class AfecFeatures { export class AfecFeatures {

View File

@@ -1,5 +1,5 @@
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants"; import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
import { configContext, Platform, updateConfigContext, resetConfigContext } from "../ConfigContext"; import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient"; import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
@@ -91,7 +91,6 @@ describe("endpoint", () => {
location: "foo", location: "foo",
type: "foo", type: "foo",
kind: "foo", kind: "foo",
tags: [],
properties: { properties: {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",

View File

@@ -43,12 +43,7 @@ export const endpoint = () => {
const location = _global.parent ? _global.parent.location : _global.location; const location = _global.parent ? _global.parent.location : _global.location;
return configContext.EMULATOR_ENDPOINT || location.origin; return configContext.EMULATOR_ENDPOINT || location.origin;
} }
return ( return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
userContext.endpoint ||
(userContext.databaseAccount &&
userContext.databaseAccount.properties &&
userContext.databaseAccount.properties.documentEndpoint)
);
}; };
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> { export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {

View File

@@ -1,8 +1,7 @@
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
export const getEntityName = (): string => { export const getEntityName = (): string => {
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) { if (userContext.apiType === "Mongo") {
return "document"; return "document";
} }

View File

@@ -1,4 +1,4 @@
import { DatePicker, TextField } from "office-ui-fabric-react"; import { DatePicker, TextField } from "@fluentui/react";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
export interface TableEntityProps { export interface TableEntityProps {
@@ -6,6 +6,7 @@ export interface TableEntityProps {
entityValuePlaceholder: string; entityValuePlaceholder: string;
entityValue: string | Date; entityValue: string | Date;
isEntityTypeDate: boolean; isEntityTypeDate: boolean;
isEntityValueDisable?: boolean;
entityTimeValue: string; entityTimeValue: string;
entityValueType: string; entityValueType: string;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
@@ -22,6 +23,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
entityValueType, entityValueType,
onEntityValueChange, onEntityValueChange,
onSelectDate, onSelectDate,
isEntityValueDisable,
onEntityTimeValueChange, onEntityTimeValueChange,
}: TableEntityProps): JSX.Element => { }: TableEntityProps): JSX.Element => {
if (isEntityTypeDate) { if (isEntityTypeDate) {
@@ -33,6 +35,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
value={entityValue && new Date(entityValue)} value={entityValue && new Date(entityValue)}
ariaLabel={entityValuePlaceholder} ariaLabel={entityValuePlaceholder}
onSelectDate={onSelectDate} onSelectDate={onSelectDate}
disabled={isEntityValueDisable}
/> />
<TextField <TextField
label={entityValueLabel && entityValueLabel} label={entityValueLabel && entityValueLabel}
@@ -41,6 +44,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
type="time" type="time"
value={entityTimeValue} value={entityTimeValue}
onChange={onEntityTimeValueChange} onChange={onEntityTimeValueChange}
disabled={isEntityValueDisable}
/> />
</> </>
); );
@@ -52,6 +56,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
className="addEntityTextField" className="addEntityTextField"
id="entityValueId" id="entityValueId"
autoFocus autoFocus
disabled={isEntityValueDisable}
type={entityValueType} type={entityValueType}
placeholder={entityValuePlaceholder} placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" && entityValue} value={typeof entityValue === "string" && entityValue}

View File

@@ -1,70 +0,0 @@
import { HashMap } from "./HashMap";
describe("HashMap", () => {
it("should test if key/val exists", () => {
const map = new HashMap<number>();
map.set("a", 123);
expect(map.has("a")).toBe(true);
expect(map.has("b")).toBe(false);
});
it("should get object back", () => {
const map = new HashMap<string>();
map.set("a", "123");
map.set("a", "456");
expect(map.get("a")).toBe("456");
expect(map.get("a")).not.toBe("123");
});
it("should return the right size", () => {
const map = new HashMap<string>();
map.set("a", "123");
map.set("b", "456");
expect(map.size()).toBe(2);
});
it("should be iterable", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 10);
map.set("c", 100);
map.set("d", 1000);
let i = 0;
map.forEach((key: string, value: number) => {
i += value;
});
expect(i).toBe(1111);
});
it("should be deleted", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 10);
expect(map.delete("a")).toBe(true);
expect(map.delete("c")).toBe(false);
expect(map.has("a")).toBe(false);
expect(map.has("b")).toBe(true);
});
it("should clear", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.clear();
expect(map.size()).toBe(0);
expect(map.has("a")).toBe(false);
});
it("should return all keys", () => {
const map = new HashMap<number>();
map.set("a", 1);
map.set("b", 1);
expect(map.keys()).toEqual(["a", "b"]);
map.clear();
expect(map.keys().length).toBe(0);
});
});

View File

@@ -1,45 +0,0 @@
/**
* Simple hashmap implementation that doesn't rely on ES6 Map nor polyfills
*/
export class HashMap<T> {
constructor(private container: { [key: string]: T } = {}) {}
public has(key: string): boolean {
return this.container.hasOwnProperty(key);
}
public set(key: string, value: T): void {
this.container[key] = value;
}
public get(key: string): T {
return this.container[key];
}
public size(): number {
return Object.keys(this.container).length;
}
public delete(key: string): boolean {
if (this.has(key)) {
delete this.container[key];
return true;
}
return false;
}
public clear(): void {
this.container = {};
}
public keys(): string[] {
return Object.keys(this.container);
}
public forEach(iteratorFct: (key: string, value: T) => void) {
for (const k in this.container) {
iteratorFct(k, this.container[k]);
}
}
}

View File

@@ -61,7 +61,7 @@ export function queryDocuments(
query: string, query: string,
continuationToken?: string continuationToken?: string
): Promise<QueryResponse> { ): Promise<QueryResponse> {
const databaseAccount = userContext.databaseAccount; const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = { const params = {
db: databaseId, db: databaseId,
@@ -121,7 +121,7 @@ export function readDocument(
collection: Collection, collection: Collection,
documentId: DocumentId documentId: DocumentId
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount; const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 4).join("/"); const path = idComponents.slice(0, 4).join("/");
@@ -167,7 +167,7 @@ export function createDocument(
partitionKeyProperty: string, partitionKeyProperty: string,
documentContent: unknown documentContent: unknown
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount; const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = { const params = {
db: databaseId, db: databaseId,
@@ -206,7 +206,7 @@ export function updateDocument(
documentId: DocumentId, documentId: DocumentId,
documentContent: string documentContent: string
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = userContext.databaseAccount; const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/"); const path = idComponents.slice(0, 5).join("/");
@@ -247,7 +247,7 @@ export function updateDocument(
} }
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> { export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
const databaseAccount = userContext.databaseAccount; const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/"); const path = idComponents.slice(0, 5).join("/");
@@ -289,7 +289,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
export function createMongoCollectionWithProxy( export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams params: DataModels.CreateCollectionParams
): Promise<DataModels.Collection> { ): Promise<DataModels.Collection> {
const databaseAccount = userContext.databaseAccount; const { databaseAccount } = userContext;
const shardKey: string = params.partitionKey?.paths[0]; const shardKey: string = params.partitionKey?.paths[0];
const mongoParams: DataModels.MongoParameters = { const mongoParams: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,

View File

@@ -7,7 +7,7 @@ describe("Object cache", () => {
cache.set("b", 2); cache.set("b", 2);
cache.set("c", 3); cache.set("c", 3);
cache.set("d", 4); cache.set("d", 4);
expect(cache.size()).toBe(2); expect(cache.size).toBe(2);
}); });
it("should remove first added element to keep size at limit", () => { it("should remove first added element to keep size at limit", () => {

View File

@@ -1,56 +1,27 @@
import { HashMap } from "./HashMap"; export class ObjectCache<T> extends Map<string, T> {
constructor(private limit: number) {
export class ObjectCache<T> extends HashMap<T> {
private keyQueue: string[]; // Last touched key FIFO to purge cache if too big.
private maxNbElements: number;
public constructor(maxNbElements: number) {
super(); super();
this.keyQueue = [];
this.maxNbElements = maxNbElements;
this.clear();
} }
public clear(): void { public get(key: string): T | undefined {
super.clear(); return this.touch(key);
this.keyQueue = [];
} }
public get(key: string): T { public set(key: string, value: T): this {
this.markKeyAsTouched(key); if (this.size === this.limit) {
return super.get(key); this.delete(this.keys().next().value);
}
public set(key: string, value: T): void {
super.set(key, value);
this.markKeyAsTouched(key);
if (super.size() > this.maxNbElements && key !== this.keyQueue[0]) {
this.reduceCacheSize();
} }
return this.touch(key, value), this;
} }
/** private touch(key: string, value = super.get(key)) {
* Invalidate elements to keep the total number below the limit // Map keeps (re) insertion order according to ES6 spec
*/ if (value) {
private reduceCacheSize(): void { this.delete(key);
// remove a key super.set(key, value);
const oldKey = this.keyQueue.shift();
if (oldKey) {
super.delete(oldKey);
} }
}
/** return value;
* Bubble up this key as new.
* @param key
*/
private markKeyAsTouched(key: string) {
const n = this.keyQueue.indexOf(key);
if (n > -1) {
this.keyQueue.splice(n, 1);
}
this.keyQueue.push(key);
} }
} }

View File

@@ -1,8 +1,8 @@
import { configContext, Platform } from "../ConfigContext";
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 { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
const notificationsPath = () => { const notificationsPath = () => {
switch (configContext.platform) { switch (configContext.platform) {
@@ -20,9 +20,7 @@ export const fetchPortalNotifications = async (): Promise<DataModels.Notificatio
return []; return [];
} }
const databaseAccount = userContext.databaseAccount; const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${ const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`; }&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;

View File

@@ -5,16 +5,16 @@ 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 { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as QueryUtils from "../Utils/QueryUtils"; import * as QueryUtils from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext";
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { createCollection } from "./dataAccess/createCollection"; import { createCollection } from "./dataAccess/createCollection";
import { handleError } from "./ErrorHandlingUtils";
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";
export class QueriesClient { export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -182,11 +182,8 @@ export class QueriesClient {
} }
public getResourceId(): string { public getResourceId(): string {
const databaseAccount = userContext.databaseAccount; const { databaseAccount, subscriptionId = "", resourceGroup = "" } = userContext;
const databaseAccountName = (databaseAccount && databaseAccount.name) || ""; const databaseAccountName = databaseAccount?.name || "";
const subscriptionId = userContext.subscriptionId || "";
const resourceGroup = userContext.resourceGroup || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`; return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
} }
@@ -211,7 +208,7 @@ export class QueriesClient {
} }
private fetchQueriesQuery(): string { private fetchQueriesQuery(): string {
if (this.container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
return QueriesClient.FetchMongoQuery; return QueriesClient.FetchMongoQuery;
} }
return QueriesClient.FetchQuery; return QueriesClient.FetchQuery;

View File

@@ -0,0 +1,59 @@
import React, { FunctionComponent } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import refreshImg from "../../images/refresh-cosmos.svg";
import { AuthType } from "../AuthType";
import { userContext } from "../UserContext";
export interface ResourceTreeProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
}
export const ResourceTree: FunctionComponent<ResourceTreeProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
}: ResourceTreeProps): 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" data-bind="text: collectionTitle" />
<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 ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTreeForResourceToken" />
) : (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
)}
</div>
{/* Collections Window - End */}
</div>
);
};

View File

@@ -73,7 +73,7 @@ export class Splitter {
$(this.leftSide).resizable(splitterOptions); $(this.leftSide).resizable(splitterOptions);
} }
private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => { private onResizeStart: JQueryUI.ResizableEvent = () => {
if (this.direction === SplitterDirection.Vertical) { if (this.direction === SplitterDirection.Vertical) {
$(".ui-resizable-helper").height("100%"); $(".ui-resizable-helper").height("100%");
} else { } else {
@@ -82,9 +82,7 @@ export class Splitter {
$("iframe").css("pointer-events", "none"); $("iframe").css("pointer-events", "none");
}; };
private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => { private onResizeStop: JQueryUI.ResizableEvent = () => $("iframe").css("pointer-events", "auto");
$("iframe").css("pointer-events", "auto");
};
public collapseLeft() { public collapseLeft() {
this.lastX = $(this.splitter).position().left; this.lastX = $(this.splitter).position().left;

View File

@@ -8,7 +8,7 @@ import {
Stack, Stack,
TextField, TextField,
TooltipHost, TooltipHost,
} from "office-ui-fabric-react"; } from "@fluentui/react";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
import DeleteIcon from "../../images/delete.svg"; import DeleteIcon from "../../images/delete.svg";
import EditIcon from "../../images/Edit_entity.svg"; import EditIcon from "../../images/Edit_entity.svg";
@@ -32,6 +32,7 @@ export interface TableEntityProps {
options: { key: string; text: string }[]; options: { key: string; text: string }[];
isPropertyTypeDisable: boolean; isPropertyTypeDisable: boolean;
entityTimeValue: string; entityTimeValue: string;
isEntityValueDisable?: boolean;
onDeleteEntity?: () => void; onDeleteEntity?: () => void;
onEditEntity?: () => void; onEditEntity?: () => void;
onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
@@ -55,6 +56,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
isPropertyTypeDisable, isPropertyTypeDisable,
isEntityTypeDate, isEntityTypeDate,
entityTimeValue, entityTimeValue,
isEntityValueDisable,
onEditEntity, onEditEntity,
onDeleteEntity, onDeleteEntity,
onEntityPropertyChange, onEntityPropertyChange,
@@ -113,6 +115,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
<EntityValue <EntityValue
entityValueLabel={entityValueLabel} entityValueLabel={entityValueLabel}
entityValueType={getEntityValueType()} entityValueType={getEntityValueType()}
isEntityValueDisable={isEntityValueDisable}
entityValuePlaceholder={entityValuePlaceholder} entityValuePlaceholder={entityValuePlaceholder}
entityValue={entityValue} entityValue={entityValue}
isEntityTypeDate={isEntityTypeDate} isEntityTypeDate={isEntityTypeDate}
@@ -121,10 +124,11 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
onSelectDate={onSelectDate} onSelectDate={onSelectDate}
onEntityTimeValueChange={onEntityTimeValueChange} onEntityTimeValueChange={onEntityTimeValueChange}
/> />
<TooltipHost content="Edit property" id="editTooltip"> {!isEntityValueDisable && (
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} /> <TooltipHost content="Edit property" id="editTooltip">
</TooltipHost> <Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
</TooltipHost>
)}
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && ( {isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
<TooltipHost content="Delete property" id="deleteTooltip"> <TooltipHost content="Delete property" id="deleteTooltip">
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} /> <Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />

View File

@@ -0,0 +1,16 @@
import { Icon, TooltipHost } from "@fluentui/react";
import * as React from "react";
export interface TooltipProps {
children: string;
}
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }: TooltipProps) => {
return (
<span>
<TooltipHost content={children}>
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
</TooltipHost>
</span>
);
};

View File

@@ -1,24 +0,0 @@
import { useId } from "@uifabric/react-hooks";
import { ITooltipHostStyles, TooltipHost } from "office-ui-fabric-react/lib/Tooltip";
import * as React from "react";
import InfoBubble from "../../../images/info-bubble.svg";
const calloutProps = { gapSpace: 0 };
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: "inline-block" } };
export interface TooltipProps {
children: string;
}
export const Tooltip: React.FunctionComponent = ({ children }: TooltipProps) => {
const tooltipId = useId("tooltip");
return children ? (
<span>
<TooltipHost content={children} id={tooltipId} calloutProps={calloutProps} styles={hostStyles}>
<img className="infoImg" src={InfoBubble} alt="More information" />
</TooltipHost>
</span>
) : (
<></>
);
};

View File

@@ -1,8 +1,8 @@
import { Image, Stack, TextField } from "office-ui-fabric-react"; import { Image, Stack, TextField } from "@fluentui/react";
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react"; import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
import FolderIcon from "../../../images/folder_16x16.svg"; import FolderIcon from "../../../images/folder_16x16.svg";
import * as Constants from "../Constants"; import * as Constants from "../Constants";
import { Tooltip } from "../Tooltip/Tooltip"; import { InfoTooltip } from "../Tooltip/InfoTooltip";
interface UploadProps { interface UploadProps {
label: string; label: string;
@@ -51,7 +51,7 @@ export const Upload: FunctionComponent<UploadProps> = ({
return ( return (
<div> <div>
<span className="renewUploadItemsHeader">{label}</span> <span className="renewUploadItemsHeader">{label}</span>
<Tooltip>{tooltip}</Tooltip> {tooltip && <InfoTooltip>{tooltip}</InfoTooltip>}
<Stack horizontal> <Stack horizontal>
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} /> <TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
<input <input

View File

@@ -2,11 +2,10 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels"; import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { createCollection, constructRpOptions } from "./createCollection"; import { constructRpOptions, createCollection } from "./createCollection";
import { updateUserContext } from "../../UserContext";
describe("createCollection", () => { describe("createCollection", () => {
const createCollectionParams: CreateCollectionParams = { const createCollectionParams: CreateCollectionParams = {
@@ -22,7 +21,7 @@ describe("createCollection", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,33 +1,32 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos"; import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest"; import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest"; import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { AuthType } from "../../AuthType";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import * as DataModels from "../../Contracts/DataModels";
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { client } from "../CosmosClient"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { createMongoCollectionWithProxy } from "../MongoProxyClient"; import { userContext } from "../../UserContext";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
createUpdateCassandraTable, createUpdateCassandraTable,
getCassandraTable, getCassandraTable,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { import {
createUpdateGremlinGraph, createUpdateGremlinGraph,
getGremlinGraph, getGremlinGraph,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } 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 { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
import { userContext } from "../../UserContext"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createDatabase } from "./createDatabase"; import { client } from "../CosmosClient";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { createDatabase } from "./createDatabase";
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const clearMessage = logConsoleProgress( const clearMessage = logConsoleProgress(
@@ -46,7 +45,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
await createDatabase(createDatabaseParams); await createDatabase(createDatabaseParams);
} }
collection = await createCollectionWithARM(params); collection = await createCollectionWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) { } else if (userContext.apiType === "Mongo") {
collection = await createMongoCollectionWithProxy(params); collection = await createMongoCollectionWithProxy(params);
} else { } else {
collection = await createCollectionWithSDK(params); collection = await createCollectionWithSDK(params);
@@ -63,20 +62,20 @@ 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> => {
const defaultExperience = userContext.defaultExperience; const { apiType } = userContext;
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return createSqlContainer(params); return createSqlContainer(params);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return createMongoCollection(params); return createMongoCollection(params);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return createCassandraTable(params); return createCassandraTable(params);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return createGraph(params); return createGraph(params);
case DefaultAccountExperienceType.Table: case "Tables":
return createTable(params); return createTable(params);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
}; };

View File

@@ -1,37 +1,36 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DatabaseResponse } from "@azure/cosmos"; 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 { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { AuthType } from "../../AuthType";
import { import * as DataModels from "../../Contracts/DataModels";
CassandraKeyspaceCreateUpdateParameters, import { userContext } from "../../UserContext";
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
CreateUpdateOptions,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
createUpdateCassandraKeyspace, createUpdateCassandraKeyspace,
getCassandraKeyspace, getCassandraKeyspace,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBDatabase,
getMongoDBDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { import {
createUpdateGremlinDatabase, createUpdateGremlinDatabase,
getGremlinDatabase, getGremlinDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } 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 {
CassandraKeyspaceCreateUpdateParameters,
CreateUpdateOptions,
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
try { try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.apiType === "Tables") {
throw new Error("Creating database resources is not allowed for tables accounts"); throw new Error("Creating database resources is not allowed for tables accounts");
} }
const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations
@@ -49,18 +48,19 @@ 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> {
const defaultExperience = userContext.defaultExperience; const { apiType } = userContext;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: switch (apiType) {
case "SQL":
return createSqlDatabase(params); return createSqlDatabase(params);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return createMongoDatabase(params); return createMongoDatabase(params);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return createCassandraKeyspace(params); return createCassandraKeyspace(params);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return createGremlineDatabase(params); return createGremlineDatabase(params);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
} }

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlStoredProcedureCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlStoredProcedure, createUpdateSqlStoredProcedure,
getSqlStoredProcedure, getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createStoredProcedure( export async function createStoredProcedure(
databaseId: string, databaseId: string,
@@ -21,11 +20,7 @@ export async function createStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> { ): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try { try {
const getResponse = await getSqlStoredProcedure( const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,15 +1,14 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlTriggerCreateUpdateParameters, SqlTriggerCreateUpdateParameters,
SqlTriggerResource, SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createTrigger( export async function createTrigger(
databaseId: string, databaseId: string,
@@ -18,11 +17,7 @@ export async function createTrigger(
): Promise<TriggerDefinition & Resource> { ): Promise<TriggerDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try { try {
const getResponse = await getSqlTrigger( const getResponse = await getSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlUserDefinedFunctionCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlUserDefinedFunction, createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction, getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function createUserDefinedFunction( export async function createUserDefinedFunction(
databaseId: string, databaseId: string,
@@ -21,11 +20,7 @@ export async function createUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try { try {
const getResponse = await getSqlUserDefinedFunction( const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,13 +1,12 @@
jest.mock("../../Utils/arm/request"); jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler"); jest.mock("../MessageHandler");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { deleteCollection } from "./deleteCollection";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { deleteCollection } from "./deleteCollection";
describe("deleteCollection", () => { describe("deleteCollection", () => {
beforeAll(() => { beforeAll(() => {
@@ -15,7 +14,7 @@ describe("deleteCollection", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,14 +1,13 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> { export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`); const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
@@ -28,23 +27,21 @@ export async function deleteCollection(databaseId: string, collectionId: string)
} }
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> { function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId); return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Table: case "Tables":
return deleteTable(subscriptionId, resourceGroup, accountName, collectionId); return deleteTable(subscriptionId, resourceGroup, accountName, collectionId);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
} }

View File

@@ -1,13 +1,12 @@
jest.mock("../../Utils/arm/request"); jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler"); jest.mock("../MessageHandler");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { deleteDatabase } from "./deleteDatabase";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { deleteDatabase } from "./deleteDatabase";
describe("deleteDatabase", () => { describe("deleteDatabase", () => {
beforeAll(() => { beforeAll(() => {
@@ -15,7 +14,7 @@ describe("deleteDatabase", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,19 +1,18 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteDatabase(databaseId: string): Promise<void> { export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`); const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try { try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.apiType === "Tables") {
throw new Error("Deleting database resources is not allowed for tables accounts"); throw new Error("Deleting database resources is not allowed for tables accounts");
} }
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
@@ -31,21 +30,19 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
} }
function deleteDatabaseWithARM(databaseId: string): Promise<void> { function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId); return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId); return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId); return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId); return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
} }

View File

@@ -1,10 +1,9 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteStoredProcedure( export async function deleteStoredProcedure(
databaseId: string, databaseId: string,
@@ -13,11 +12,7 @@ export async function deleteStoredProcedure(
): Promise<void> { ): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`); const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlStoredProcedure( await deleteSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,19 +1,14 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> { export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`); const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlTrigger( await deleteSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,19 +1,14 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> { export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`); const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlUserDefinedFunction( await deleteSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,8 +1,8 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { armRequest } from "../../Utils/arm/request";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
import { handleError } from "../ErrorHandlingUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { handleError } from "../ErrorHandlingUtils";
interface TimeSeriesData { interface TimeSeriesData {
data: { data: {
@@ -45,9 +45,9 @@ export const getCollectionUsageSizeInKB = async (databaseName: string, container
return undefined; return undefined;
} }
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const filter = `DatabaseName eq '${databaseName}' and CollectionName eq '${containerName}'`; const filter = `DatabaseName eq '${databaseName}' and CollectionName eq '${containerName}'`;
const metricNames = "DataUsage,IndexUsage"; const metricNames = "DataUsage,IndexUsage";
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/providers/microsoft.insights/metrics`; const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/providers/microsoft.insights/metrics`;

View File

@@ -1,10 +1,9 @@
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { readCollection } from "./readCollection"; import { readCollection } from "./readCollection";
import { updateUserContext } from "../../UserContext";
describe("readCollection", () => { describe("readCollection", () => {
beforeAll(() => { beforeAll(() => {
@@ -13,7 +12,7 @@ describe("readCollection", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,25 +1,20 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/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 { readOfferWithSDK } from "./readOfferWithSDK"; import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext";
export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => { export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`); const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionOfferWithARM(params.databaseId, params.collectionId); return await readCollectionOfferWithARM(params.databaseId, params.collectionId);
} }
@@ -33,15 +28,13 @@ export const readCollectionOffer = async (params: ReadCollectionOfferParams): Pr
}; };
const readCollectionOfferWithARM = async (databaseId: string, collectionId: string): Promise<Offer> => { const readCollectionOfferWithARM = async (databaseId: string, collectionId: string): Promise<Offer> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
let rpResponse; let rpResponse;
try { try {
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await getSqlContainerThroughput( rpResponse = await getSqlContainerThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -50,7 +43,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await getMongoDBCollectionThroughput( rpResponse = await getMongoDBCollectionThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -59,7 +52,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await getCassandraTableThroughput( rpResponse = await getCassandraTableThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -68,7 +61,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await getGremlinGraphThroughput( rpResponse = await getGremlinGraphThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -77,11 +70,11 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
collectionId collectionId
); );
break; break;
case DefaultAccountExperienceType.Table: case "Tables":
rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId); rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
} catch (error) { } catch (error) {
if (error.code !== "NotFound") { if (error.code !== "NotFound") {

View File

@@ -2,11 +2,10 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { readCollections } from "./readCollections"; import { readCollections } from "./readCollections";
import { updateUserContext } from "../../UserContext";
describe("readCollections", () => { describe("readCollections", () => {
beforeAll(() => { beforeAll(() => {
@@ -14,7 +13,7 @@ describe("readCollections", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,6 +1,5 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
@@ -14,11 +13,7 @@ import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> { export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`); const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionsWithARM(databaseId); return await readCollectionsWithARM(databaseId);
} }
@@ -34,29 +29,28 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> { async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
let rpResponse; let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) { const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
case DefaultAccountExperienceType.DocumentDB: const accountName = databaseAccount.name;
switch (apiType) {
case "SQL":
rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Table: case "Tables":
rpResponse = await listTables(subscriptionId, resourceGroup, accountName); rpResponse = await listTables(subscriptionId, resourceGroup, accountName);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
return rpResponse?.value?.map((collection) => collection.properties?.resource as DataModels.Collection); return rpResponse?.value?.map((collection) => collection.properties?.resource as DataModels.Collection);

View File

@@ -1,24 +1,19 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { userContext } from "../../UserContext";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils"; import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
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 { readOfferWithSDK } from "./readOfferWithSDK"; import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext";
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => { export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readDatabaseOfferWithARM(params.databaseId); return await readDatabaseOfferWithARM(params.databaseId);
} }
@@ -32,28 +27,26 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
}; };
const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => { const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
let rpResponse; let rpResponse;
try { try {
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
} catch (error) { } catch (error) {
if (error.code !== "NotFound") { if (error.code !== "NotFound") {

View File

@@ -2,11 +2,10 @@ jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request"; import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { readDatabases } from "./readDatabases"; import { readDatabases } from "./readDatabases";
import { updateUserContext } from "../../UserContext";
describe("readDatabases", () => { describe("readDatabases", () => {
beforeAll(() => { beforeAll(() => {
@@ -14,7 +13,7 @@ describe("readDatabases", () => {
databaseAccount: { databaseAccount: {
name: "test", name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB, apiType: "SQL",
}); });
}); });

View File

@@ -1,24 +1,19 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> { export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[]; let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
databases = await readDatabasesWithARM(); databases = await readDatabasesWithARM();
} else { } else {
const sdkResponse = await client().databases.readAll().fetchAll(); const sdkResponse = await client().databases.readAll().fetchAll();
@@ -34,26 +29,24 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
async function readDatabasesWithARM(): Promise<DataModels.Database[]> { async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
let rpResponse; let rpResponse;
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName); rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName); rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName); rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName); rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database); return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database);

View File

@@ -1,9 +1,9 @@
import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/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";
import { AuthType } from "../../AuthType";
export async function readMongoDBCollectionThroughRP( export async function readMongoDBCollectionThroughRP(
databaseId: string, databaseId: string,
@@ -13,9 +13,9 @@ export async function readMongoDBCollectionThroughRP(
return undefined; return undefined;
} }
let collection: MongoDBCollectionResource; let collection: MongoDBCollectionResource;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const accountName = userContext.databaseAccount.name; const accountName = databaseAccount.name;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`); const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try { try {

View File

@@ -1,11 +1,10 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { AuthType } from "../../AuthType";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readStoredProcedures( export async function readStoredProcedures(
databaseId: string, databaseId: string,
@@ -13,11 +12,7 @@ export async function readStoredProcedures(
): Promise<(StoredProcedureDefinition & Resource)[]> { ): Promise<(StoredProcedureDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlStoredProcedures( const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,10 +1,9 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { AuthType } from "../../AuthType";
import { userContext } from "../../UserContext";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers( export async function readTriggers(
@@ -13,11 +12,7 @@ export async function readTriggers(
): Promise<(TriggerDefinition & Resource)[]> { ): Promise<(TriggerDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlTriggers( const rpResponse = await listSqlTriggers(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,27 +1,23 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { AuthType } from "../../AuthType";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readUserDefinedFunctions( export async function readUserDefinedFunctions(
databaseId: string, databaseId: string,
collectionId: string collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> { ): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try { try {
if ( if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlUserDefinedFunctions( const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId collectionId
); );

View File

@@ -2,7 +2,6 @@ import { ContainerDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels"; import { Collection } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { import {
createUpdateCassandraTable, createUpdateCassandraTable,
@@ -38,11 +37,7 @@ export async function updateCollection(
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`); const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try { try {
if ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection); collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else { } else {
const sdkResponse = await client() const sdkResponse = await client()
@@ -68,21 +63,19 @@ async function updateCollectionWithARM(
collectionId: string, collectionId: string,
newCollection: Partial<Collection> newCollection: Partial<Collection>
): Promise<Collection> { ): Promise<Collection> {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) { switch (apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Graph: case "Gremlin":
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Table: case "Tables":
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.MongoDB: case "Mongo":
return updateMongoDBCollection( return updateMongoDBCollection(
databaseId, databaseId,
collectionId, collectionId,
@@ -92,7 +85,7 @@ async function updateCollectionWithARM(
newCollection newCollection
); );
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
} }

View File

@@ -1,54 +1,53 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { OfferDefinition } from "@azure/cosmos"; import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types"; import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient"; import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { handleError } from "../ErrorHandlingUtils"; import { userContext } from "../../UserContext";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
import { import {
updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
updateCassandraKeyspaceThroughput,
migrateCassandraKeyspaceToAutoscale, migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput, migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale, migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput, migrateCassandraTableToManualThroughput,
updateCassandraKeyspaceThroughput,
updateCassandraTableThroughput, updateCassandraTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { import {
updateMongoDBDatabaseThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
updateGremlinDatabaseThroughput,
migrateGremlinDatabaseToAutoscale, migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput, migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale, migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput, migrateGremlinGraphToManualThroughput,
updateGremlinDatabaseThroughput,
updateGremlinGraphThroughput, updateGremlinGraphThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { userContext } from "../../UserContext"; import {
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
updateMongoDBCollectionThroughput,
updateMongoDBDatabaseThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
updateSqlContainerThroughput,
updateSqlDatabaseThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
migrateTableToAutoscale, migrateTableToAutoscale,
migrateTableToManualThroughput, migrateTableToManualThroughput,
updateTableThroughput, updateTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => { export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
let updatedOffer: Offer; let updatedOffer: Offer;
@@ -61,7 +60,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) { if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) { if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params); updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { } else if (userContext.apiType === "Tables") {
// update table's database offer with SDK since RP doesn't support it // update table's database offer with SDK since RP doesn't support it
updatedOffer = await updateOfferWithSDK(params); updatedOffer = await updateOfferWithSDK(params);
} else { } else {
@@ -82,24 +81,24 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => { const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try { try {
switch (userContext.defaultExperience) { switch (userContext.apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
await updateSqlContainerOffer(params); await updateSqlContainerOffer(params);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
await updateMongoCollectionOffer(params); await updateMongoCollectionOffer(params);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
await updateCassandraTableOffer(params); await updateCassandraTableOffer(params);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
await updateGremlinGraphOffer(params); await updateGremlinGraphOffer(params);
break; break;
case DefaultAccountExperienceType.Table: case "Tables":
await updateTableOffer(params); await updateTableOffer(params);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`); throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
} }
} catch (error) { } catch (error) {
if (error.code !== "MethodNotAllowed") { if (error.code !== "MethodNotAllowed") {
@@ -116,21 +115,21 @@ const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => { const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try { try {
switch (userContext.defaultExperience) { switch (userContext.apiType) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
await updateSqlDatabaseOffer(params); await updateSqlDatabaseOffer(params);
break; break;
case DefaultAccountExperienceType.MongoDB: case "Mongo":
await updateMongoDatabaseOffer(params); await updateMongoDatabaseOffer(params);
break; break;
case DefaultAccountExperienceType.Cassandra: case "Cassandra":
await updateCassandraKeyspaceOffer(params); await updateCassandraKeyspaceOffer(params);
break; break;
case DefaultAccountExperienceType.Graph: case "Gremlin":
await updateGremlinDatabaseOffer(params); await updateGremlinDatabaseOffer(params);
break; break;
default: default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`); throw new Error(`Unsupported default experience type: ${userContext.apiType}`);
} }
} catch (error) { } catch (error) {
if (error.code !== "MethodNotAllowed") { if (error.code !== "MethodNotAllowed") {
@@ -145,9 +144,8 @@ const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Of
}; };
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => { const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateSqlContainerToAutoscale( await migrateSqlContainerToAutoscale(
@@ -179,9 +177,8 @@ const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void>
}; };
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => { const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateMongoDBCollectionToAutoscale( await migrateMongoDBCollectionToAutoscale(
@@ -213,9 +210,8 @@ const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<vo
}; };
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => { const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateCassandraTableToAutoscale( await migrateCassandraTableToAutoscale(
@@ -247,9 +243,8 @@ const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<voi
}; };
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => { const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateGremlinGraphToAutoscale( await migrateGremlinGraphToAutoscale(
@@ -281,9 +276,8 @@ const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void>
}; };
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => { const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId); await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
@@ -296,9 +290,8 @@ const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
}; };
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => { const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId); await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -311,9 +304,8 @@ const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void>
}; };
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => { const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId); await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -326,9 +318,8 @@ const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void
}; };
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => { const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId); await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
@@ -341,9 +332,8 @@ const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<
}; };
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => { const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId; const { subscriptionId, resourceGroup, databaseAccount } = userContext;
const resourceGroup = userContext.resourceGroup; const accountName = databaseAccount.name;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId); await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlStoredProcedureCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlStoredProcedure, createUpdateSqlStoredProcedure,
getSqlStoredProcedure, getSqlStoredProcedure,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateStoredProcedure( export async function updateStoredProcedure(
databaseId: string, databaseId: string,
@@ -21,15 +20,13 @@ export async function updateStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> { ): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try { try {
if ( const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations && if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlStoredProcedure( const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId, collectionId,
storedProcedure.id storedProcedure.id
@@ -43,9 +40,9 @@ export async function updateStoredProcedure(
}, },
}; };
const rpResponse = await createUpdateSqlStoredProcedure( const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId, collectionId,
storedProcedure.id, storedProcedure.id,

View File

@@ -1,15 +1,14 @@
import { TriggerDefinition } from "@azure/cosmos";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { userContext } from "../../UserContext";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
SqlTriggerCreateUpdateParameters, SqlTriggerCreateUpdateParameters,
SqlTriggerResource, SqlTriggerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateTrigger( export async function updateTrigger(
databaseId: string, databaseId: string,
@@ -17,16 +16,13 @@ export async function updateTrigger(
trigger: TriggerDefinition trigger: TriggerDefinition
): Promise<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;
try { try {
if ( if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlTrigger( const getResponse = await getSqlTrigger(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId, collectionId,
trigger.id trigger.id
@@ -40,9 +36,9 @@ export async function updateTrigger(
}, },
}; };
const rpResponse = await createUpdateSqlTrigger( const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId, collectionId,
trigger.id, trigger.id,

View File

@@ -1,18 +1,17 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import { AuthType } from "../../AuthType";
SqlUserDefinedFunctionCreateUpdateParameters, import { userContext } from "../../UserContext";
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { import {
createUpdateSqlUserDefinedFunction, createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction, getSqlUserDefinedFunction,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils"; import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateUserDefinedFunction( export async function updateUserDefinedFunction(
databaseId: string, databaseId: string,
@@ -20,16 +19,13 @@ export async function updateUserDefinedFunction(
userDefinedFunction: UserDefinedFunctionDefinition userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try { try {
if ( if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlUserDefinedFunction( const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId, collectionId,
userDefinedFunction.id userDefinedFunction.id
@@ -43,9 +39,9 @@ export async function updateUserDefinedFunction(
}, },
}; };
const rpResponse = await createUpdateSqlUserDefinedFunction( const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId, subscriptionId,
userContext.resourceGroup, resourceGroup,
userContext.databaseAccount.name, databaseAccount.name,
databaseId, databaseId,
collectionId, collectionId,
userDefinedFunction.id, userDefinedFunction.id,

View File

@@ -4,7 +4,6 @@ export interface DatabaseAccount {
location: string; location: string;
type: string; type: string;
kind: string; kind: string;
tags: any;
properties: DatabaseAccountExtendedProperties; properties: DatabaseAccountExtendedProperties;
} }

View File

@@ -206,17 +206,14 @@ export enum NeighborType {
BOTH, BOTH,
} }
/** export interface IGraphConfigUiData {
* Set of observable related to graph configuration by user showNeighborType: NeighborType;
*/ nodeProperties: string[];
export interface GraphConfigUiData { nodePropertiesWithNone: string[];
showNeighborType: ko.Observable<NeighborType>; nodeCaptionChoice: string;
nodeProperties: ko.ObservableArray<string>; nodeColorKeyChoice: string;
nodePropertiesWithNone: ko.ObservableArray<string>; nodeIconChoice: string;
nodeCaptionChoice: ko.Observable<string>; nodeIconSet: string;
nodeColorKeyChoice: ko.Observable<string>;
nodeIconChoice: ko.Observable<string>;
nodeIconSet: ko.Observable<string>;
} }
/** /**

View File

@@ -1,8 +0,0 @@
export enum DefaultAccountExperienceType {
DocumentDB = "DocumentDB",
Graph = "Graph",
MongoDB = "MongoDB",
Table = "Table",
Cassandra = "Cassandra",
ApiForMongoDB = "Azure Cosmos DB for MongoDB API",
}

View File

@@ -4,79 +4,10 @@ import * as ko from "knockout";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
describe("Component Registerer", () => { describe("Component Registerer", () => {
it("should register input-typeahead component", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
});
it("should register error-display component", () => {
expect(ko.components.isRegistered("error-display")).toBe(true);
});
it("should register graph-style component", () => {
expect(ko.components.isRegistered("graph-style")).toBe(true);
});
it("should register json-editor component", () => { it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true); expect(ko.components.isRegistered("json-editor")).toBe(true);
}); });
it("should register documents-tab component", () => {
expect(ko.components.isRegistered("documents-tab")).toBe(true);
});
it("should register stored-procedure-tab component", () => {
expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true);
});
it("should register trigger-tab component", () => {
expect(ko.components.isRegistered("trigger-tab")).toBe(true);
});
it("should register user-defined-function-tab component", () => {
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
});
it("should register settings-tab-v2 component", () => {
expect(ko.components.isRegistered("database-settings-tab-v2")).toBe(true);
expect(ko.components.isRegistered("collection-settings-tab-v2")).toBe(true);
});
it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true);
});
it("should register tables-query-tab component", () => {
expect(ko.components.isRegistered("tables-query-tab")).toBe(true);
});
it("should register graph-tab component", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true);
});
it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
});
it("should register terminal-tab component", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true);
});
it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});
it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
});
it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
});
it("should register string-input-pane component", () => {
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
});
it("should register dynamic-list component", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); expect(ko.components.isRegistered("dynamic-list")).toBe(true);
}); });

View File

@@ -2,63 +2,15 @@ import * as ko from "knockout";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent"; import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent";
import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent";
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import ConflictsTab from "./Tabs/ConflictsTab";
import DocumentsTab from "./Tabs/DocumentsTab";
import GalleryTab from "./Tabs/GalleryTab";
import GraphTab from "./Tabs/GraphTab";
import MongoShellTab from "./Tabs/MongoShellTab";
import NotebookTabV2 from "./Tabs/NotebookV2Tab";
import NotebookViewerTab from "./Tabs/NotebookViewerTab";
import QueryTab from "./Tabs/QueryTab";
import QueryTablesTab from "./Tabs/QueryTablesTab";
import SchemaAnalyzerTab from "./Tabs/SchemaAnalyzerTab";
import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
import TerminalTab from "./Tabs/TerminalTab";
import TriggerTab from "./Tabs/TriggerTab";
import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab";
ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent);
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("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
// Collection Tabs
[
DocumentsTab,
StoredProcedureTab,
TriggerTab,
UserDefinedFunctionTab,
SettingsTabV2,
QueryTab,
QueryTablesTab,
GraphTab,
MongoShellTab,
ConflictsTab,
NotebookTabV2,
TerminalTab,
GalleryTab,
NotebookViewerTab,
DatabaseSettingsTabV2,
SchemaAnalyzerTab,
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
// Panes // Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -10,7 +10,6 @@ import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg"; import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg"; import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent"; import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer"; import Explorer from "./Explorer";
@@ -30,16 +29,16 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS) * New resource tree (in ReactJS)
*/ */
export class ResourceTreeContextMenuButtonFactory { export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(container: Explorer): TreeNodeMenuItem[] { public static createDatabaseContextMenu(container: Explorer, databaseId: string): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [ const items: TreeNodeMenuItem[] = [
{ {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(), onClick: () => container.onNewCollectionClicked(databaseId),
label: container.addCollectionText(), label: container.addCollectionText(),
}, },
]; ];
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) { if (userContext.apiType !== "Tables") {
items.push({ items.push({
iconSrc: DeleteDatabaseIcon, iconSrc: DeleteDatabaseIcon,
onClick: () => container.openDeleteDatabaseConfirmationPane(), onClick: () => container.openDeleteDatabaseConfirmationPane(),
@@ -63,7 +62,7 @@ export class ResourceTreeContextMenuButtonFactory {
}); });
} }
if (container.isPreferredApiMongoDB()) { if (userContext.apiType === "Mongo") {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),

View File

@@ -1,9 +1,8 @@
import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
import * as Logger from "../../../Common/Logger";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
import * as Logger from "../../../Common/Logger";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
export interface ArcadiaMenuPickerProps { export interface ArcadiaMenuPickerProps {
selectText?: string; selectText?: string;

View File

@@ -1,10 +1,11 @@
import { Icon, Label, Stack } from "office-ui-fabric-react"; import { Icon, Label, Stack } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils"; import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps { export interface CollapsibleSectionProps {
title: string; title: string;
isExpandedByDefault: boolean; isExpandedByDefault: boolean;
onExpand?: () => void;
} }
export interface CollapsibleSectionState { export interface CollapsibleSectionState {
@@ -23,6 +24,12 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
this.setState({ isExpanded: !this.state.isExpanded }); this.setState({ isExpanded: !this.state.isExpanded });
}; };
public componentDidUpdate(): void {
if (this.state.isExpanded && this.props.onExpand) {
this.props.onExpand();
}
}
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<> <>

View File

@@ -1,14 +1,20 @@
import { import {
ChoiceGroup, ChoiceGroup,
DefaultButton,
Dialog as FluentDialog,
DialogFooter,
DialogType,
FontIcon, FontIcon,
IButtonProps,
IChoiceGroupProps, IChoiceGroupProps,
IDialogProps,
IProgressIndicatorProps, IProgressIndicatorProps,
ITextFieldProps,
Link,
PrimaryButton,
ProgressIndicator, ProgressIndicator,
} from "office-ui-fabric-react"; TextField,
import { DefaultButton, IButtonProps, PrimaryButton } from "office-ui-fabric-react/lib/Button"; } from "@fluentui/react";
import { Dialog as FluentDialog, DialogFooter, DialogType, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { Link } from "office-ui-fabric-react/lib/Link";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
@@ -97,7 +103,7 @@ export const Dialog: FunctionComponent<DialogProps> = ({
text: secondaryButtonText, text: secondaryButtonText,
onClick: onSecondaryButtonClick, onClick: onSecondaryButtonClick,
} }
: undefined; : {};
return ( return (
<FluentDialog {...dialogProps}> <FluentDialog {...dialogProps}>

View File

@@ -1,6 +1,6 @@
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { loadMonaco, monaco } from "../../LazyMonaco";
import template from "./diff-editor-component.html"; import template from "./diff-editor-component.html";
import * as monaco from "monaco-editor";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
@@ -92,7 +92,7 @@ export class DiffEditorViewModel {
/** /**
* Create the monaco editor on diff mode and attach to DOM * Create the monaco editor on diff mode and attach to DOM
*/ */
protected createDiffEditor( protected async createDiffEditor(
originalContent: string, originalContent: string,
modifiedContent: string, modifiedContent: string,
createCallback: (e: monaco.editor.IStandaloneDiffEditor) => void createCallback: (e: monaco.editor.IStandaloneDiffEditor) => void
@@ -111,7 +111,7 @@ export class DiffEditorViewModel {
} }
const language = this.params.editorLanguage || "json"; const language = this.params.editorLanguage || "json";
const monaco = await loadMonaco();
const originalModel = monaco.editor.createModel(originalContent, language); const originalModel = monaco.editor.createModel(originalContent, language);
const modifiedModel = monaco.editor.createModel(modifiedContent, language); const modifiedModel = monaco.editor.createModel(modifiedContent, language);
const diffEditor: monaco.editor.IStandaloneDiffEditor = monaco.editor.createDiffEditor( const diffEditor: monaco.editor.IStandaloneDiffEditor = monaco.editor.createDiffEditor(

View File

@@ -2,9 +2,9 @@
* React component for Switch Directory * React component for Switch Directory
*/ */
import _ from "underscore"; import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown"; import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels"; import { Tenant } from "../../../Contracts/DataModels";
export interface DefaultDirectoryDropdownProps { export interface DefaultDirectoryDropdownProps {

View File

@@ -1,11 +1,15 @@
import _ from "underscore"; import {
DefaultButton,
IButtonProps,
ITextFieldProps,
List,
ScrollablePane,
Sticky,
StickyPositionType,
TextField,
} from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import _ from "underscore";
import { DefaultButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
import { List } from "office-ui-fabric-react/lib/List";
import { ScrollablePane } from "office-ui-fabric-react/lib/ScrollablePane";
import { Sticky, StickyPositionType } from "office-ui-fabric-react/lib/Sticky";
import { TextField, ITextFieldProps } from "office-ui-fabric-react/lib/TextField";
import { Tenant } from "../../../Contracts/DataModels"; import { Tenant } from "../../../Contracts/DataModels";
export interface DirectoryListProps { export interface DirectoryListProps {

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test render renders with directories and default 1`] = ` exports[`test render renders with directories and default 1`] = `
<StyledWithResponsiveMode <Dropdown
className="defaultDirectoryDropdown" className="defaultDirectoryDropdown"
defaultSelectedKey="asdfghjklzxcvbnm9876543210" defaultSelectedKey="asdfghjklzxcvbnm9876543210"
label="Set your default directory" label="Set your default directory"
@@ -26,7 +26,7 @@ exports[`test render renders with directories and default 1`] = `
`; `;
exports[`test render renders with directories and last visit default 1`] = ` exports[`test render renders with directories and last visit default 1`] = `
<StyledWithResponsiveMode <Dropdown
className="defaultDirectoryDropdown" className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited" defaultSelectedKey="lastVisited"
label="Set your default directory" label="Set your default directory"
@@ -51,7 +51,7 @@ exports[`test render renders with directories and last visit default 1`] = `
`; `;
exports[`test render renders with directories but no default 1`] = ` exports[`test render renders with directories but no default 1`] = `
<StyledWithResponsiveMode <Dropdown
className="defaultDirectoryDropdown" className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited" defaultSelectedKey="lastVisited"
label="Set your default directory" label="Set your default directory"
@@ -76,7 +76,7 @@ exports[`test render renders with directories but no default 1`] = `
`; `;
exports[`test render renders with no directories 1`] = ` exports[`test render renders with no directories 1`] = `
<StyledWithResponsiveMode <Dropdown
className="defaultDirectoryDropdown" className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited" defaultSelectedKey="lastVisited"
label="Set your default directory" label="Set your default directory"

View File

@@ -350,11 +350,11 @@ exports[`test render renders with filters 1`] = `
} }
> >
<div <div
className="ms-ScrollablePane root-40" className="ms-ScrollablePane root-53"
data-is-scrollable="true" data-is-scrollable="true"
> >
<div <div
className="stickyAbove-42" className="stickyAbove-55"
style={ style={
Object { Object {
"height": 0, "height": 0,
@@ -365,7 +365,7 @@ exports[`test render renders with filters 1`] = `
} }
/> />
<div <div
className="ms-ScrollablePane--contentContainer contentContainer-41" className="ms-ScrollablePane--contentContainer contentContainer-54"
data-is-scrollable={true} data-is-scrollable={true}
> >
<Sticky <Sticky
@@ -408,7 +408,6 @@ exports[`test render renders with filters 1`] = `
> >
<TextFieldBase <TextFieldBase
ariaLabel="Directory filter text box" ariaLabel="Directory filter text box"
canRevealPassword={false}
className="directoryListFilterTextBox" className="directoryListFilterTextBox"
deferredValidationTime={200} deferredValidationTime={200}
onChange={[Function]} onChange={[Function]}
@@ -691,18 +690,18 @@ exports[`test render renders with filters 1`] = `
validateOnLoad={true} validateOnLoad={true}
> >
<div <div
className="ms-TextField directoryListFilterTextBox root-46" className="ms-TextField directoryListFilterTextBox root-59"
> >
<div <div
className="ms-TextField-wrapper" className="ms-TextField-wrapper"
> >
<div <div
className="ms-TextField-fieldGroup fieldGroup-47" className="ms-TextField-fieldGroup fieldGroup-60"
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-label="Directory filter text box" aria-label="Directory filter text box"
className="ms-TextField-field field-48" className="ms-TextField-field field-61"
id="TextField0" id="TextField0"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
@@ -1266,7 +1265,6 @@ exports[`test render renders with filters 1`] = `
"borderColor": "#f3f2f1", "borderColor": "#f3f2f1",
"color": "#a19f9d", "color": "#a19f9d",
"cursor": "default", "cursor": "default",
"pointerEvents": "none",
"selectors": Object { "selectors": Object {
":focus": Object { ":focus": Object {
"outline": 0, "outline": 0,
@@ -1611,6 +1609,35 @@ exports[`test render renders with filters 1`] = `
}, },
}, },
}, },
"splitButtonMenuFocused": Object {
"outline": "transparent",
"position": "relative",
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
"right": -2,
"top": -2,
},
},
"top": 3,
"zIndex": 1,
},
"::-moz-focus-inner": Object {
"border": "0",
},
},
},
"splitButtonMenuIcon": Object { "splitButtonMenuIcon": Object {
"color": "#323130", "color": "#323130",
}, },
@@ -1900,7 +1927,7 @@ exports[`test render renders with filters 1`] = `
> >
<button <button
aria-disabled={true} aria-disabled={true}
className="ms-Button ms-Button--default is-disabled directoryListButton root-57" className="ms-Button ms-Button--default is-disabled directoryListButton root-70"
data-is-focusable={false} data-is-focusable={false}
disabled={true} disabled={true}
onClick={[Function]} onClick={[Function]}
@@ -1912,7 +1939,7 @@ exports[`test render renders with filters 1`] = `
type="button" type="button"
> >
<span <span
className="ms-Button-flexContainer flexContainer-58" className="ms-Button-flexContainer flexContainer-71"
data-automationid="splitbuttonprimary" data-automationid="splitbuttonprimary"
> >
<div <div
@@ -1932,7 +1959,7 @@ exports[`test render renders with filters 1`] = `
</div> </div>
</span> </span>
</button> </button>
<Component /> <FocusRects />
</BaseButton> </BaseButton>
</DefaultButton> </DefaultButton>
</CustomizedDefaultButton> </CustomizedDefaultButton>
@@ -1943,7 +1970,7 @@ exports[`test render renders with filters 1`] = `
</List> </List>
</div> </div>
<div <div
className="stickyBelow-43" className="stickyBelow-56"
style={ style={
Object { Object {
"bottom": "0px", "bottom": "0px",
@@ -1954,7 +1981,7 @@ exports[`test render renders with filters 1`] = `
} }
> >
<div <div
className="stickyBelowItems-44" className="stickyBelowItems-57"
/> />
</div> </div>
</div> </div>

View File

@@ -1,7 +1,6 @@
import { loadMonaco, monaco } from "../../LazyMonaco";
import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent"; import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent";
import template from "./editor-component.html"; import template from "./editor-component.html";
import * as monaco from "monaco-editor";
import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
@@ -49,15 +48,17 @@ class EditorViewModel extends JsonEditorViewModel {
return this.params.contentType; return this.params.contentType;
} }
protected registerCompletionItemProvider() { protected async registerCompletionItemProvider() {
let sqlCompletionItemProvider = new SqlCompletionItemProvider();
if (EditorViewModel.providerRegistered.indexOf("sql") < 0) { if (EditorViewModel.providerRegistered.indexOf("sql") < 0) {
monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider); const { SqlCompletionItemProvider } = await import("@azure/cosmos-language-service");
const monaco = await loadMonaco();
monaco.languages.registerCompletionItemProvider("sql", new SqlCompletionItemProvider());
EditorViewModel.providerRegistered.push("sql"); EditorViewModel.providerRegistered.push("sql");
} }
} }
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected async getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
const { ErrorMarkProvider } = await import("@azure/cosmos-language-service");
return ErrorMarkProvider.getErrorMark(input); return ErrorMarkProvider.getErrorMark(input);
} }
} }

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import * as monaco from "monaco-editor"; import { loadMonaco, monaco } from "../../LazyMonaco";
export interface EditorReactProps { export interface EditorReactProps {
language: string; language: string;
@@ -61,7 +61,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
/** /**
* Create the monaco editor and attach to DOM * Create the monaco editor and attach to DOM
*/ */
private createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { private async createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
const options: monaco.editor.IEditorConstructionOptions = { const options: monaco.editor.IEditorConstructionOptions = {
value: this.props.content, value: this.props.content,
language: this.props.language, language: this.props.language,
@@ -74,6 +74,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
}; };
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.rootNode, options)); createCallback(monaco.editor.create(this.rootNode, options));
} }

View File

@@ -1,27 +0,0 @@
import template from "./error-display-component.html";
/**
* Helper class for ko component registration
* This component displays an error as designed in:
* https://microsoft.sharepoint.com/teams/DPX/Modern/DocDB/_layouts/15/WopiFrame.aspx?sourcedoc={66864d4a-f925-4cbe-9eb4-79f8d191a115}&action=edit&wd=target%28DocumentDB%20emulator%2Eone%7CE617D0A7-F77C-4968-B75A-1451049F4FEA%2FError%20notification%7CAA1E4BC9-4D72-472C-B40C-2437FA217226%2F%29
* TODO: support "More details"
*/
export class ErrorDisplayComponent {
constructor() {
return {
viewModel: ErrorDisplayViewModel,
template,
};
}
}
/**
* Parameters for this component
*/
interface ErrorDisplayParams {
errorMsg: ko.Observable<string>; // Primary message
}
class ErrorDisplayViewModel {
public constructor(public params: ErrorDisplayParams) {}
}

View File

@@ -1,6 +0,0 @@
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<div class="warningErrorContent">
<span><img src="/error_red.svg" alt="Error" /></span>
<span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span>
</div>
</div>

View File

@@ -1,9 +1,14 @@
import {
Checkbox,
DefaultButton,
Dropdown,
IDropdownOption,
IDropdownStyles,
ITextFieldStyles,
Stack,
TextField,
} from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { Stack } from "office-ui-fabric-react/lib/Stack";
import { Dropdown, IDropdownOption, IDropdownStyles } from "office-ui-fabric-react/lib/Dropdown";
import { Checkbox } from "office-ui-fabric-react/lib/Checkbox";
import { TextField, ITextFieldStyles } from "office-ui-fabric-react/lib/TextField";
import { DefaultButton } from "office-ui-fabric-react";
import "./FeaturePanelComponent.less"; import "./FeaturePanelComponent.less";
export const FeaturePanelComponent: React.FunctionComponent = () => { export const FeaturePanelComponent: React.FunctionComponent = () => {

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import { FeaturePanelComponent } from "./FeaturePanelComponent"; import { FeaturePanelComponent } from "./FeaturePanelComponent";
import { getTheme, mergeStyleSets, FontWeights, Modal, IconButton, IIconProps } from "office-ui-fabric-react"; import { getTheme, mergeStyleSets, FontWeights, Modal, IconButton, IIconProps } from "@fluentui/react";
import "./FeaturePanelLauncher.less"; import "./FeaturePanelLauncher.less";
// Modal wrapper // Modal wrapper

View File

@@ -57,7 +57,7 @@ exports[`Feature panel renders all flags 1`] = `
} }
} }
> >
<StyledWithResponsiveMode <Dropdown
label="Base Url" label="Base Url"
onChange={[Function]} onChange={[Function]}
options={ options={
@@ -85,7 +85,7 @@ exports[`Feature panel renders all flags 1`] = `
} }
} }
/> />
<StyledWithResponsiveMode <Dropdown
label="Platform" label="Platform"
onChange={[Function]} onChange={[Function]}
options={ options={

View File

@@ -1,4 +1,4 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react"; import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";

View File

@@ -1,10 +1,4 @@
import { import { ChoiceGroup, IButtonProps, IChoiceGroupProps, PrimaryButton, IChoiceGroupOption } from "@fluentui/react";
ChoiceGroup,
IButtonProps,
IChoiceGroupProps,
PrimaryButton,
IChoiceGroupOption,
} from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { ChildrenMargin } from "./GitHubStyleConstants"; import { ChildrenMargin } from "./GitHubStyleConstants";

View File

@@ -1,4 +1,4 @@
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react"; import { DefaultButton, IButtonProps, Link, PrimaryButton } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient"; import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent"; import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
@@ -23,8 +23,6 @@ export interface RepoListItem {
} }
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> { export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
public static readonly ConnectToGitHubTitle = "Connect to GitHub";
public static readonly ManageGitHubRepoTitle = "Manage GitHub settings";
private static readonly ManageGitHubRepoDescription = private static readonly ManageGitHubRepoDescription =
"Select your GitHub repos and branch(es) to pin to your notebooks workspace."; "Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings."; private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
@@ -32,14 +30,6 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
private static readonly CancelButtonText = "Cancel"; private static readonly CancelButtonText = "Cancel";
public render(): JSX.Element { public render(): JSX.Element {
const header: JSX.Element = (
<p>
{this.props.showAuthorizeAccess
? GitHubReposComponent.ConnectToGitHubTitle
: GitHubReposComponent.ManageGitHubRepoTitle}
</p>
);
const content: JSX.Element = this.props.showAuthorizeAccess ? ( const content: JSX.Element = this.props.showAuthorizeAccess ? (
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} /> <AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
) : ( ) : (
@@ -66,9 +56,6 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
return ( return (
<> <>
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
{header}
</div>
<div className={"paneMainContent"}>{content}</div> <div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && ( {!this.props.showAuthorizeAccess && (
<> <>

View File

@@ -1,19 +1,21 @@
import { import {
IStyleFunctionOrObject,
ICheckboxStyleProps, ICheckboxStyleProps,
ICheckboxStyles, ICheckboxStyles,
IDropdownStyles,
IDropdownStyleProps, IDropdownStyleProps,
} from "office-ui-fabric-react"; IDropdownStyles,
IStyleFunctionOrObject,
} from "@fluentui/react";
export const ButtonsFooterStyle: React.CSSProperties = { export const ButtonsFooterStyle: React.CSSProperties = {
padding: 14, paddingTop: 14,
height: "auto", height: "auto",
borderTop: "2px solid lightGray",
}; };
export const ContentFooterStyle: React.CSSProperties = { export const ContentFooterStyle: React.CSSProperties = {
padding: "10px 24px 10px 24px", paddingTop: "10px",
height: "auto", height: "auto",
borderTop: "2px solid lightGray",
}; };
export const ChildrenMargin = 10; export const ChildrenMargin = 10;
@@ -53,6 +55,11 @@ export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
padding: 8, padding: 8,
}; };
export const ContentMainStyle: React.CSSProperties = {
display: "flex",
flexDirection: "column",
};
export const ReposListRepoColumnMinWidth = 192; export const ReposListRepoColumnMinWidth = 192;
export const ReposListBranchesColumnWidth = 116; export const ReposListBranchesColumnWidth = 116;
export const BranchesDropdownWidth = 200; export const BranchesDropdownWidth = 200;

View File

@@ -16,7 +16,7 @@ import {
ResponsiveMode, ResponsiveMode,
SelectionMode, SelectionMode,
Text, Text,
} from "office-ui-fabric-react"; } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient"; import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
import * as GitHubUtils from "../../../Utils/GitHubUtils"; import * as GitHubUtils from "../../../Utils/GitHubUtils";

View File

@@ -1,5 +1,5 @@
import { CommandButton, FontIcon, FontWeights, ITextProps, Separator, Stack, Text } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { Stack, Text, Separator, FontIcon, CommandButton, FontWeights, ITextProps } from "office-ui-fabric-react";
export class GalleryHeaderComponent extends React.Component { export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure"; private static readonly azureText = "Microsoft Azure";
@@ -61,7 +61,7 @@ export class GalleryHeaderComponent extends React.Component {
<Stack.Item> <Stack.Item>
{this.renderHeaderItem( {this.renderHeaderItem(
GalleryHeaderComponent.galleryText, GalleryHeaderComponent.galleryText,
undefined, () => "",
GalleryHeaderComponent.headerItemTextProps GalleryHeaderComponent.headerItemTextProps
)} )}
</Stack.Item> </Stack.Item>

View File

@@ -1,186 +0,0 @@
/**
* How to use this component:
*
* In your html markup, use:
* <input-typeahead params="{
choices:choices,
selection:selection,
inputValue:inputValue,
placeholder:'Enter source',
typeaheadOverrideOptions:typeaheadOverrideOptions
}"></input-typeahead>
* The parameters are documented below.
*
* Notes:
* - dynamic:true by default, this allows choices to change after initialization.
* To turn it off, use:
* typeaheadOverrideOptions: { dynamic:false }
*
*/
import "jquery-typeahead";
import template from "./input-typeahead.html";
/**
* Helper class for ko component registration
*/
export class InputTypeaheadComponent {
constructor() {
return {
viewModel: InputTypeaheadViewModel,
template,
};
}
}
export interface Item {
caption: string;
value: any;
}
/**
* Parameters for this component
*/
interface InputTypeaheadParams {
/**
* List of choices available in the dropdown.
*/
choices: ko.ObservableArray<Item>;
/**
* Gets updated when user clicks on the choice in the dropdown
*/
selection?: ko.Observable<Item>;
/**
* The current string value of <input>
*/
inputValue?: ko.Observable<string>;
/**
* Define what text you want as the input placeholder
*/
placeholder: string;
/**
* Override default jquery-typeahead options
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
*/
typeaheadOverrideOptions?: any;
/**
* This function gets called when pressing ENTER on the input box
*/
submitFct?: (inputValue: string | null, selection: Item | null) => void;
/**
* Typehead comes with a Search button that we normally remove.
* If you want to use it, turn this on
*/
showSearchButton?: boolean;
}
interface OnClickItem {
matchedKey: string;
value: any;
caption: string;
group: string;
}
interface Cache {
inputValue: string | null;
selection: Item | null;
}
class InputTypeaheadViewModel {
private static instanceCount = 0; // Generate unique id for each component's typeahead instance
private instanceNumber: number;
private params: InputTypeaheadParams;
private cache: Cache;
public constructor(params: InputTypeaheadParams) {
this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
this.params = params;
this.params.choices.subscribe(this.initializeTypeahead.bind(this));
this.cache = {
inputValue: null,
selection: null,
};
}
/**
* Must execute once ko is rendered, so that it can find the input element by id
*/
private initializeTypeahead() {
let params = this.params;
let cache = this.cache;
let options: any = {
input: `#${this.getComponentId()}`, //'.input-typeahead',
order: "asc",
minLength: 0,
searchOnFocus: true,
source: {
display: "caption",
data: () => {
return this.params.choices();
},
},
callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
cache.selection = item;
if (params.selection) {
params.selection(item);
}
},
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
cache.inputValue = query;
if (params.inputValue) {
params.inputValue(query);
}
},
},
template: (query: string, item: any) => {
// Don't display id if caption *IS* the id
return item.caption === item.value
? "<span>{{caption}}</span>"
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
},
dynamic: true,
};
// Override options
if (params.typeaheadOverrideOptions) {
for (let p in params.typeaheadOverrideOptions) {
options[p] = params.typeaheadOverrideOptions[p];
}
}
($ as any).typeahead(options);
}
/**
* Get this component id
* @return unique id per instance
*/
private getComponentId(): string {
return `input-typeahead${this.instanceNumber}`;
}
/**
* Executed once ko is done rendering bindings
* Use ko's "template: afterRender" callback to do that without actually using any template.
* Another way is to call it within setTimeout() in constructor.
*/
public afterRender(): void {
this.initializeTypeahead();
}
public submit(): void {
if (this.params.submitFct) {
this.params.submitFct(this.cache.inputValue, this.cache.selection);
}
}
}

View File

@@ -1,19 +0,0 @@
<span class="input-typeahead-container">
<form class="input-typehead" data-bind="submit:submit">
<div class="typeahead__container">
<div class="typeahead__field">
<span class="typeahead__query">
<input
name="q"
type="search"
autocomplete="off"
data-bind="attr: { placeholder: params.placeholder, id:getComponentId() }, value:params.inputValue, template: { afterRender:afterRender() }"
/>
</span>
<span class="typeahead__button" data-bind="visible:params.showSearchButton">
<button type="submit"><span class="typeahead__search-icon"></span></button>
</span>
</div>
</div>
</form>
</span>

View File

@@ -1,6 +1,5 @@
import Q from "q";
import * as monaco from "monaco-editor";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { loadMonaco, monaco } from "../../LazyMonaco";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import template from "./json-editor-component.html"; import template from "./json-editor-component.html";
@@ -88,7 +87,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
/** /**
* Create the monaco editor and attach to DOM * Create the monaco editor and attach to DOM
*/ */
protected createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { protected async createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
this.registerCompletionItemProvider(); this.registerCompletionItemProvider();
this.editorContainer = document.getElementById(this.getEditorId()); this.editorContainer = document.getElementById(this.getEditorId());
const options: monaco.editor.IEditorConstructionOptions = { const options: monaco.editor.IEditorConstructionOptions = {
@@ -102,6 +101,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
}; };
this.editorContainer.innerHTML = ""; this.editorContainer.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.editorContainer, options)); createCallback(monaco.editor.create(this.editorContainer, options));
} }
@@ -109,15 +109,16 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected registerCompletionItemProvider() {} protected registerCompletionItemProvider() {}
// Interface. Will be implemented in children editor view model such as EditorViewModel. // Interface. Will be implemented in children editor view model such as EditorViewModel.
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected async getErrorMarkers(_: string): Promise<monaco.editor.IMarkerData[]> {
return Q.Promise(() => {}); return [];
} }
protected getEditorLanguage(): string { protected getEditorLanguage(): string {
return "json"; return "json";
} }
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { protected async configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
const monaco = await loadMonaco();
this.editor = editor; this.editor = editor;
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
if (!this.params.isReadOnly && this.params.updatedContent) { if (!this.params.isReadOnly && this.params.updatedContent) {

View File

@@ -13,7 +13,6 @@ const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
gremlinEndpoint: null, gremlinEndpoint: null,
tableEndpoint: null, tableEndpoint: null,
}, },
tags: "testTags",
type: "testType", type: "testType",
}; };
}; };
@@ -30,7 +29,6 @@ const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
gremlinEndpoint: null, gremlinEndpoint: null,
tableEndpoint: null, tableEndpoint: null,
}, },
tags: "testTags",
type: "testType", type: "testType",
}; };
}; };
@@ -48,7 +46,6 @@ const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
tableEndpoint: null, tableEndpoint: null,
mongoEndpoint: "https://testMongoEndpoint.azure.com/", mongoEndpoint: "https://testMongoEndpoint.azure.com/",
}, },
tags: "testTags",
type: "testType", type: "testType",
}; };
}; };
@@ -65,7 +62,6 @@ const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
gremlinEndpoint: null, gremlinEndpoint: null,
tableEndpoint: null, tableEndpoint: null,
}, },
tags: "testTags",
type: "testType", type: "testType",
}; };
}; };

View File

@@ -1,25 +1,27 @@
import { Card } from "@uifabric/react-cards";
import { import {
FontWeights,
Icon,
IconButton,
Image,
ImageFit,
Persona,
Text,
Link,
BaseButton, BaseButton,
Button, Button,
LinkBase, DocumentCard,
DocumentCardActivity,
DocumentCardDetails,
DocumentCardPreview,
DocumentCardTitle,
Icon,
IconButton,
IDocumentCardPreviewProps,
IDocumentCardStyles,
ImageFit,
Link,
Separator, Separator,
TooltipHost,
Spinner, Spinner,
SpinnerSize, SpinnerSize,
} from "office-ui-fabric-react"; Text,
import * as React from "react"; TooltipHost,
} from "@fluentui/react";
import React, { FunctionComponent, useState } from "react";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
import * as FileSystemUtil from "../../../Notebook/FileSystemUtil"; import * as FileSystemUtil from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
export interface GalleryCardComponentProps { export interface GalleryCardComponentProps {
data: IGalleryItem; data: IGalleryItem;
@@ -34,166 +36,47 @@ export interface GalleryCardComponentProps {
onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void; onDeleteClick: (beforeDelete: () => void, afterDelete: () => void) => void;
} }
interface GalleryCardComponentState { export const GalleryCardComponent: FunctionComponent<GalleryCardComponentProps> = ({
isDeletingPublishedNotebook: boolean; data,
} isFavorite,
showDownload,
showDelete,
onClick,
onTagClick,
onFavoriteClick,
onUnfavoriteClick,
onDownloadClick,
onDeleteClick,
}: GalleryCardComponentProps) => {
const CARD_WIDTH = 256;
const cardImageHeight = 144;
const cardDescriptionMaxChars = 80;
const cardItemGapSmall = 8;
const cardDeleteSpinnerHeight = 360;
const smallTextLineHeight = 18;
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps, GalleryCardComponentState> { const [isDeletingPublishedNotebook, setIsDeletingPublishedNotebook] = useState<boolean>(false);
public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio =
GalleryCardComponent.cardImageHeight / GalleryCardComponent.CARD_WIDTH;
private static readonly cardDescriptionMaxChars = 80;
private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8;
private static readonly cardDeleteSpinnerHeight = 360;
private static readonly smallTextLineHeight = 18;
constructor(props: GalleryCardComponentProps) { const cardButtonsVisible = isFavorite !== undefined || showDownload || showDelete;
super(props); const options: Intl.DateTimeFormatOptions = {
this.state = { year: "numeric",
isDeletingPublishedNotebook: false, month: "short",
}; day: "numeric",
} };
const dateString = new Date(data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
public render(): JSX.Element { const renderTruncated = (text: string, totalLength: number): string => {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete; let truncatedDescription = text.substr(0, totalLength);
const options: Intl.DateTimeFormatOptions = { if (text.length > totalLength) {
year: "numeric",
month: "short",
day: "numeric",
};
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
return (
<Card
style={{ background: "white" }}
aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => this.onClick(event, this.props.onClick)}
>
{this.state.isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: GalleryCardComponent.cardDeleteSpinnerHeight } }}
/>
</Card.Item>
)}
{!this.state.isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<Card.Item>
<Image
src={this.props.data.thumbnailUrl}
width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap styles={{ root: { height: GalleryCardComponent.smallTextLineHeight } }}>
{this.props.data.tags ? (
this.props.data.tags.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))
) : (
<br />
)}
</Text>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: GalleryCardComponent.smallTextLineHeight * 2 } }}>
{this.renderTruncatedDescription()}
</Text>
<span>
{this.props.data.views !== undefined &&
this.generateIconText("RedEye", this.props.data.views.toString())}
{this.props.data.downloads !== undefined &&
this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.data.favorites !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unfavorite" : "Favorite",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)}
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
this.props.onDeleteClick(
() => this.setState({ isDeletingPublishedNotebook: true }),
() => this.setState({ isDeletingPublishedNotebook: false })
)
)}
</span>
</Card.Section>
)}
</>
)}
</Card>
);
}
private renderTruncatedDescription = (): string => {
let truncatedDescription = this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars);
if (this.props.data.description.length > GalleryCardComponent.cardDescriptionMaxChars) {
truncatedDescription = `${truncatedDescription} ...`; truncatedDescription = `${truncatedDescription} ...`;
} }
return truncatedDescription; return truncatedDescription;
}; };
private generateIconText = (iconName: string, text: string): JSX.Element => { const generateIconText = (iconName: string, text: string): JSX.Element => {
return ( return (
<Text variant="tiny" styles={{ root: { color: "#605E5C", paddingRight: GalleryCardComponent.cardItemGapSmall } }}> <Text variant="tiny" styles={{ root: { color: "#605E5C", paddingRight: cardItemGapSmall } }}>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text} <Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text> </Text>
); );
@@ -203,7 +86,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
* Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is * Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is
* to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button) * to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button)
*/ */
private generateIconButtonWithTooltip = ( const generateIconButtonWithTooltip = (
iconName: string, iconName: string,
title: string, title: string,
horizontalAlign: "right" | "left", horizontalAlign: "right" | "left",
@@ -220,15 +103,15 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
iconProps={{ iconName }} iconProps={{ iconName }}
title={title} title={title}
ariaLabel={title} ariaLabel={title}
onClick={(event) => this.onClick(event, activate)} onClick={(event) => handlerOnClick(event, activate)}
/> />
</TooltipHost> </TooltipHost>
); );
}; };
private onClick = ( const handlerOnClick = (
event: event:
| React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent> | React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | MouseEvent>
| React.MouseEvent< | React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement, HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent MouseEvent
@@ -239,4 +122,84 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
event.preventDefault(); event.preventDefault();
activate(); activate();
}; };
} const DocumentCardActivityPeople = [{ name: data.author, profileImageSrc: data.isSample && CosmosDBLogo }];
const previewProps: IDocumentCardPreviewProps = {
previewImages: [
{
previewImageSrc: data.thumbnailUrl,
imageFit: ImageFit.cover,
width: CARD_WIDTH,
height: cardImageHeight,
},
],
};
const cardStyles: IDocumentCardStyles = {
root: { display: "inline-block", marginRight: 20, width: CARD_WIDTH },
};
return (
<DocumentCard aria-label={cardTitle} styles={cardStyles} onClick={onClick}>
{isDeletingPublishedNotebook && (
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: cardDeleteSpinnerHeight } }}
/>
)}
{!isDeletingPublishedNotebook && (
<>
<DocumentCardActivity activity={dateString} people={DocumentCardActivityPeople} />
<DocumentCardPreview {...previewProps} />
<DocumentCardDetails>
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight, padding: "2px 16px" } }}>
{data.tags ? (
data.tags.map((tag, index, array) => (
<span key={tag}>
<Link onClick={(event) => handlerOnClick(event, () => onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))
) : (
<br />
)}
</Text>
<DocumentCardTitle title={renderTruncated(cardTitle, 20)} shouldTruncate />
<DocumentCardTitle
title={renderTruncated(data.description, cardDescriptionMaxChars)}
showAsSecondaryTitle
/>
<span style={{ padding: "8px 16px" }}>
{data.views !== undefined && generateIconText("RedEye", data.views.toString())}
{data.downloads !== undefined && generateIconText("Download", data.downloads.toString())}
{data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())}
</span>
</DocumentCardDetails>
{cardButtonsVisible && (
<DocumentCardDetails>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span style={{ padding: "0px 16px" }}>
{isFavorite !== undefined &&
generateIconButtonWithTooltip(
isFavorite ? "HeartFill" : "Heart",
isFavorite ? "Unfavorite" : "Favorite",
"left",
isFavorite ? onUnfavoriteClick : onFavoriteClick
)}
{showDownload && generateIconButtonWithTooltip("Download", "Download", "left", onDownloadClick)}
{showDelete &&
generateIconButtonWithTooltip("Delete", "Remove", "right", () =>
onDeleteClick(
() => setIsDeletingPublishedNotebook(true),
() => setIsDeletingPublishedNotebook(false)
)
)}
</span>
</DocumentCardDetails>
)}
</>
)}
</DocumentCard>
);
};

View File

@@ -1,59 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GalleryCardComponent renders 1`] = ` exports[`GalleryCardComponent renders 1`] = `
<Card <StyledDocumentCardBase
aria-label="name" aria-label="name"
data-is-focusable="true" styles={
onClick={[Function]}
style={
Object { Object {
"background": "white", "root": Object {
} "display": "inline-block",
} "marginRight": 20,
tokens={ "width": 256,
Object { },
"childrenGap": 0,
"width": 256,
} }
} }
> >
<CardItem <StyledDocumentCardActivityBase
tokens={ activity="Invalid Date"
Object { people={
"padding": 10, Array [
} Object {
} "name": "author",
> "profileImageSrc": false,
<StyledPersonaBase
imageUrl={false}
secondaryText="Invalid Date"
text="author"
/>
</CardItem>
<CardItem>
<StyledImageBase
alt="name cover image"
height={144}
imageFit={2}
src="thumbnailUrl"
width={256}
/>
</CardItem>
<CardSection
styles={
Object {
"root": Object {
"padding": 10,
}, },
} ]
} }
> />
<StyledDocumentCardPreviewBase
previewImages={
Array [
Object {
"height": 144,
"imageFit": 2,
"previewImageSrc": "thumbnailUrl",
"width": 256,
},
]
}
/>
<StyledDocumentCardDetailsBase>
<Text <Text
nowrap={true} nowrap={true}
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"height": 18, "height": 18,
"padding": "2px 16px",
}, },
} }
} }
@@ -69,33 +59,21 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledLinkBase> </StyledLinkBase>
</span> </span>
</Text> </Text>
<Text <StyledDocumentCardTitleBase
nowrap={true} shouldTruncate={true}
styles={ title="name"
/>
<StyledDocumentCardTitleBase
showAsSecondaryTitle={true}
title="description"
/>
<span
style={
Object { Object {
"root": Object { "padding": "8px 16px",
"fontWeight": 600,
"paddingBottom": 8,
"paddingTop": 8,
},
} }
} }
> >
name
</Text>
<Text
styles={
Object {
"root": Object {
"height": 36,
},
}
}
variant="small"
>
description
</Text>
<span>
<Text <Text
styles={ styles={
Object { Object {
@@ -169,17 +147,8 @@ exports[`GalleryCardComponent renders 1`] = `
0 0
</Text> </Text>
</span> </span>
</CardSection> </StyledDocumentCardDetailsBase>
<CardSection <StyledDocumentCardDetailsBase>
styles={
Object {
"root": Object {
"marginLeft": 10,
"marginRight": 10,
},
}
}
>
<Separator <Separator
styles={ styles={
Object { Object {
@@ -190,7 +159,13 @@ exports[`GalleryCardComponent renders 1`] = `
} }
} }
/> />
<span> <span
style={
Object {
"padding": "0px 16px",
}
}
>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { Object {
@@ -276,6 +251,6 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
</span> </span>
</CardSection> </StyledDocumentCardDetailsBase>
</Card> </StyledDocumentCardBase>
`; `;

View File

@@ -1,15 +1,15 @@
jest.mock("../../../Juno/JunoClient"); jest.mock("../../../../Juno/JunoClient");
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { CodeOfConductComponent, CodeOfConductComponentProps } from "./CodeOfConductComponent"; import { HttpStatusCodes } from "../../../../Common/Constants";
import { JunoClient } from "../../../Juno/JunoClient"; import { JunoClient } from "../../../../Juno/JunoClient";
import { HttpStatusCodes } from "../../../Common/Constants"; import { CodeOfConduct, CodeOfConductProps } from "./CodeOfConduct";
describe("CodeOfConductComponent", () => { describe("CodeOfConduct", () => {
let codeOfConductProps: CodeOfConductComponentProps; let codeOfConductProps: CodeOfConductProps;
beforeEach(() => { beforeEach(() => {
const junoClient = new JunoClient(undefined); const junoClient = new JunoClient();
junoClient.acceptCodeOfConduct = jest.fn().mockReturnValue({ junoClient.acceptCodeOfConduct = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
data: true, data: true,
@@ -21,12 +21,12 @@ describe("CodeOfConductComponent", () => {
}); });
it("renders", () => { it("renders", () => {
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />); const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("onAcceptedCodeOfConductCalled", async () => { it("onAcceptedCodeOfConductCalled", async () => {
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />); const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
wrapper.find(".genericPaneSubmitBtn").first().simulate("click"); wrapper.find(".genericPaneSubmitBtn").first().simulate("click");
await Promise.resolve(); await Promise.resolve();
expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled(); expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled();

View File

@@ -0,0 +1,110 @@
import { Checkbox, Link, PrimaryButton, Stack, Text } from "@fluentui/react";
import React, { FunctionComponent, useEffect, useState } from "react";
import { CodeOfConductEndpoints, HttpStatusCodes } from "../../../../Common/Constants";
import { getErrorMessage, getErrorStack, handleError } from "../../../../Common/ErrorHandlingUtils";
import { JunoClient } from "../../../../Juno/JunoClient";
import { Action } from "../../../../Shared/Telemetry/TelemetryConstants";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../../Shared/Telemetry/TelemetryProcessor";
export interface CodeOfConductProps {
junoClient: JunoClient;
onAcceptCodeOfConduct: (result: boolean) => void;
}
export const CodeOfConduct: FunctionComponent<CodeOfConductProps> = ({
junoClient,
onAcceptCodeOfConduct,
}: CodeOfConductProps) => {
const descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
const descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
const descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
const link1: { label: string; url: string } = {
label: "code of conduct.",
url: CodeOfConductEndpoints.codeOfConduct,
};
const [readCodeOfConduct, setReadCodeOfConduct] = useState<boolean>(false);
const acceptCodeOfConduct = async (): Promise<void> => {
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
try {
const response = await junoClient.acceptCodeOfConduct();
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
onAcceptCodeOfConduct(response.data);
} catch (error) {
traceFailure(
Action.NotebooksGalleryAcceptCodeOfConduct,
{
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
handleError(error, "CodeOfConduct/acceptCodeOfConduct", "Failed to accept code of conduct");
}
};
const onChangeCheckbox = (): void => {
setReadCodeOfConduct(!readCodeOfConduct);
};
useEffect(() => {
trace(Action.NotebooksGalleryViewCodeOfConduct);
}, []);
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<Text>
{descriptionPara3}
<Link href={link1.url} target="_blank">
{link1.label}
</Link>
</Text>
</Stack.Item>
<Stack.Item>
<Checkbox
styles={{
label: {
margin: 0,
padding: "2 0 2 0",
},
text: {
fontSize: 12,
},
}}
label="I have read and accept the code of conduct."
onChange={onChangeCheckbox}
/>
</Stack.Item>
<Stack.Item>
<PrimaryButton
ariaLabel="Continue"
title="Continue"
onClick={async () => await acceptCodeOfConduct()}
tabIndex={0}
className="genericPaneSubmitBtn"
text="Continue"
disabled={!readCodeOfConduct}
/>
</Stack.Item>
</Stack>
);
};

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CodeOfConductComponent renders 1`] = ` exports[`CodeOfConduct renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { Object {

View File

@@ -1,123 +0,0 @@
import * as React from "react";
import { JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
export interface CodeOfConductComponentProps {
junoClient: JunoClient;
onAcceptCodeOfConduct: (result: boolean) => void;
}
interface CodeOfConductComponentState {
readCodeOfConduct: boolean;
}
export class CodeOfConductComponent extends React.Component<CodeOfConductComponentProps, CodeOfConductComponentState> {
private viewCodeOfConductTraced: boolean;
private descriptionPara1: string;
private descriptionPara2: string;
private descriptionPara3: string;
private link1: { label: string; url: string };
constructor(props: CodeOfConductComponentProps) {
super(props);
this.state = {
readCodeOfConduct: false,
};
this.descriptionPara1 = "Azure Cosmos DB Notebook Gallery - Code of Conduct";
this.descriptionPara2 = "The notebook public gallery contains notebook samples shared by users of Azure Cosmos DB.";
this.descriptionPara3 = "In order to view and publish your samples to the gallery, you must accept the ";
this.link1 = { label: "code of conduct.", url: CodeOfConductEndpoints.codeOfConduct };
}
private async acceptCodeOfConduct(): Promise<void> {
const startKey = traceStart(Action.NotebooksGalleryAcceptCodeOfConduct);
try {
const response = await this.props.junoClient.acceptCodeOfConduct();
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
}
traceSuccess(Action.NotebooksGalleryAcceptCodeOfConduct, {}, startKey);
this.props.onAcceptCodeOfConduct(response.data);
} catch (error) {
traceFailure(
Action.NotebooksGalleryAcceptCodeOfConduct,
{
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
}
}
private onChangeCheckbox = (): void => {
this.setState({ readCodeOfConduct: !this.state.readCodeOfConduct });
};
public render(): JSX.Element {
if (!this.viewCodeOfConductTraced) {
this.viewCodeOfConductTraced = true;
trace(Action.NotebooksGalleryViewCodeOfConduct);
}
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text style={{ fontWeight: 500, fontSize: "20px" }}>{this.descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{this.descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<Text>
{this.descriptionPara3}
<Link href={this.link1.url} target="_blank">
{this.link1.label}
</Link>
</Text>
</Stack.Item>
<Stack.Item>
<Checkbox
styles={{
label: {
margin: 0,
padding: "2 0 2 0",
},
text: {
fontSize: 12,
},
}}
label="I have read and accept the code of conduct."
onChange={this.onChangeCheckbox}
/>
</Stack.Item>
<Stack.Item>
<PrimaryButton
ariaLabel="Continue"
title="Continue"
onClick={async () => await this.acceptCodeOfConduct()}
tabIndex={0}
className="genericPaneSubmitBtn"
text="Continue"
disabled={!this.state.readCodeOfConduct}
/>
</Stack.Item>
</Stack>
);
}
}

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