Compare commits

..

92 Commits

Author SHA1 Message Date
Jordi Bunster
881cef6157 Typescript strict mode in editor
This does not change the strictness of what we build. It doesn't
make things more lax either, that stays exactly the same. But the
editor will now complain more.
2021-04-30 14:04:12 -07: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
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
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
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
Tanuj Mittal
5ecc3d67b0 Add support for Schema Analyzer (#411)
* New MongoSchemaTab

* Address feedback and updates

* Build fixes

* Rename to SchemaAnalyzer

* Format

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
2021-04-22 21:45:21 -04:00
Tanuj Mittal
448566146f Add CellOutputViewer for SandboxOutputs (#686)
* Initial commit

* Optimizations

* Optimize notebookOutputViewer bundle size by lazy loading transforms

* Update package-lock.json

* More optimizations

* Updates

* Fix unit test and other updates

* Address feedback

* Update package-lock.json

* Update test snapshots

* Fix build

* Reduce cellOutputViewer bundle size

* Renaming
2021-04-22 13:37:12 -04:00
vaidankarswapnil
c6766dd69e Migrate new graph vertex panel to react (#702)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-22 09:47:59 -05:00
Hardikkumar Nai
9d411c57b0 Remove Explorer.isPreferredApiTable (#656)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-21 16:41:08 -05:00
Hardikkumar Nai
ff58eb3724 Migrate Copy Notebook Pane to React (#640)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-21 14:09:19 -05:00
Jordi Bunster
e49bcc524f Remove deprecated calls to logConsoleMessage (#608)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-21 13:52:01 -05:00
Hardikkumar Nai
cdd6d32990 Rename index.tsx to {class name}.tsx (#689)
* Rename index.tsx to {class name}.tsx

* Update tests

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-21 13:35:32 -05:00
Steve Faulkner
c1dcd0e90b Disable Emulator Test (#712) 2021-04-21 13:33:19 -05:00
Steve Faulkner
e705c490c9 Retry E2E tests up to 3 times (#711) 2021-04-21 12:45:34 -05:00
Sunil Kumar Yadav
72ce5fc813 Migrate Add Table Entity Pane to React (#642)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-21 11:33:29 -05:00
Steve Faulkner
d5f3230f6f Retry flaky tests 2021-04-21 11:01:16 -05:00
victor-meng
a07aff1e8c resetData should not set isAutoPilotSelected flag (#708) 2021-04-21 10:11:33 -05:00
vaidankarswapnil
d58fececac Move setup notebooks panel to react (#673)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-20 21:51:03 -05:00
Steve Faulkner
b6d60dcc7b Remove unused @types/prop-types (#706) 2021-04-20 18:26:07 -05:00
Steve Faulkner
2fd6305944 Fix E2E tests. Add Playwright (#698) 2021-04-19 22:08:25 -05:00
Tanuj Mittal
914e969083 Add a feature flag to override Juno endpoint (#700)
* Add a feature flag to override Juno endpoint

* Fix build
2021-04-20 06:08:53 +05:30
Jordi Bunster
f2585bba14 TabsManager in react (#500) 2021-04-19 13:11:48 -07:00
Jordi Bunster
19cf203606 Misc fixes from #500 (#701)
* Ensure TabsBase.tabPath is always observable

* In tests, Date.getTime() is not different enough

* Add class name only in the Tab that needs it
2021-04-19 12:58:53 -07:00
dependabot[bot]
19e39ea62f Bump ssri from 6.0.1 to 6.0.2 (#699)
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-19 11:28:35 -07:00
dependabot[bot]
f8510659de Bump typescript from 4.2.3 to 4.2.4 (#671)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.2.3 to 4.2.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.2.3...v4.2.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-19 11:01:27 -07:00
Steve Faulkner
7265708c15 Fix Mongo Parition Keys for Connection String mode (#692) 2021-04-18 23:21:10 -05:00
Steve Faulkner
a53c203286 Parse Custom sproc parameters (#693)
* Parse Custom sproc parameters

* Fix PK value parsing too
2021-04-18 23:20:58 -05:00
Jordi Bunster
e0060b12e5 Keep active tab state in one place (manager) (#683)
With this change TabsBase objects will retain a reference to the TabsManager they belong to, so they can ask it if they're the active tab or not.

This removes the possibility for bugs like activating an unmanaged tab, or having more than one active tab, etc.
2021-04-18 17:48:39 -07:00
Jordi Bunster
a9fd01f9b4 Remove stfaul's subscription and account from URL (#694) 2021-04-18 11:22:27 -05:00
Hardikkumar Nai
d74da34742 Remove explorer.is preferred api graph (#655)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-17 16:24:17 -05:00
Hardikkumar Nai
02ea26da71 Remove Explorer.isPreferredCassandraAPI (#654)
Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2021-04-17 15:54:47 -05:00
Steve Faulkner
a264ea2275 Adds retry logic to Upload JSON (#684) 2021-04-16 13:23:03 -05:00
Steve Faulkner
649b6a93b4 Fix Stored Procedures for Non-Partitioned Collections (#685) 2021-04-15 22:22:40 -05:00
victor-meng
2bccb7885f Hide TTL in settings tab for Mongo API (#677)
* Hide ttl for mongo

* Fix typo

* Refine info message
2021-04-15 18:51:59 -05:00
Steve Faulkner
3f8e394952 Fix Upload Items (#682)
* Fix Upload Items

* Remove debugger

* Switch to bulk APIs

* Address TODO

Co-authored-by: Jordi Bunster <jbunster@microsoft.com>
2021-04-15 18:25:43 -05:00
Tanuj Mittal
f94f95e788 Hide favorite button from Standalone gallery (#675) 2021-04-15 05:27:04 +05:30
Tanuj Mittal
6dba2e4792 Fix height for SandboxFrame (#676) 2021-04-15 05:26:53 +05:30
Laurent Nguyen
5d4b193865 Fix Markdown Source style to show editor (#674) 2021-04-14 23:51:11 +05:30
Tanuj Mittal
68789c5069 Tighten notebook code cell output sandbox and enable it by default (#664)
* Tighten notebook code cell output sandbox

* Enable sandboxnotebookoutputs by default

* Address feedback
2021-04-14 23:36:44 +05:30
Hardikkumar Nai
1685b34e2a Remove Explorer.isPreferredDocumentDB (#653) 2021-04-13 20:53:14 -05:00
Hardikkumar Nai
56f430ebd8 Migrate Delete Collection Panel to React (#628) 2021-04-13 19:56:58 -05:00
Steve Faulkner
e8033f0bbc Alpha sort subscriptions and accounts in dropdown (#663) 2021-04-13 18:53:59 -05:00
Steve Faulkner
d96cecdfe8 Expose Settings in Hosted Mode (#660) 2021-04-13 18:13:24 -05:00
Steve Faulkner
d90a065e63 Fix feature flags in Portal (#659) 2021-04-13 15:43:05 -05:00
Jordi Bunster
f449328f26 Remove unused tab finder methods from Explorer (#549) 2021-04-13 12:42:00 -05:00
Laurent Nguyen
41800f9ee5 Fix Markdown HTML issue (#658)
This change enables notebooks to escape HTML (which is a vector for malicious attacks).
We import `MarkdownCell` from the `@nteract/stateful-components` sources so that we can point it to the version of `@nteract/markdown` which contains [this fix](e19c7cc590).
This is a temporary workaround from upgrading to `@nteract/stateful-components` to `7.0.0` which causes build and runtime issues see #599).
2021-04-13 17:07:33 +00:00
Steve Faulkner
7bdc31aa67 Fix for Mongo shard key loaded via ARM (#657) 2021-04-13 12:03:25 -05:00
Jordi Bunster
1e6ad113dd Add rel='noreferrer' (#651) 2021-04-12 17:24:11 -07:00
Hardikkumar Nai
05932e1d38 Resolve Lint errors in NotificationConsoleComponent.ts (#527) 2021-04-12 18:06:30 -05:00
Hardikkumar Nai
02e6d8442b Add GraphUtil to Eslint (#626) 2021-04-12 18:04:52 -05:00
Sunil Kumar Yadav
8cf09acc19 Migration/table query select pane to react (#615) 2021-04-12 17:53:56 -05:00
Jordi Bunster
5cd4e93c65 Fix preview URL branch display, include link in new PRs (#644)
* Make it easy to include preview links

* Fix display of currently previewed branch
2021-04-12 15:07:37 -07:00
Jordi Bunster
76e3b7e6f1 Warn on React hook misuse (#632) 2021-04-12 15:13:17 -05:00
Jordi Bunster
dc5679ffd3 Switch to accessibility insights's version of these tools (#603)
* Switch to accessibility insights's version of these tools

* auto-add files meeting strict checks
2021-04-12 15:12:19 -05:00
Jordi Bunster
88f5e7485a Pull request preview URLs (#625)
* Dynamic link to HEAD of a given PR

* Display pr name and link in notification console

* Pass along query string to Explorer

This means you can write a URL like:

https://cosmos-explorer-preview.azurewebsites.net/pull/123?feature.enableFoo=true

and when Explorer loads it'll have enableFoo set to true in the features
object.
2021-04-12 15:10:31 -05:00
Steve Faulkner
662c03580a Call readCollections for Mongo using ARM (#636) 2021-04-12 11:28:52 -05:00
Tanuj Mittal
14fd9054dd Sandbox all outputs in iFrame (#624) 2021-04-12 08:59:18 -07:00
Tanuj Mittal
37e0f50ef2 Fix telemetry from child windows of Data Explorer (#633)
* Fix telemetry from child windows of Data Explorer

* Address feedback
2021-04-09 12:52:41 +05:30
Jordi Bunster
3ab6b2a05d Apease eslint (#631) 2021-04-08 12:31:36 -07:00
Srinath Narayanan
f060d4b1b8 Made webpack changes (#629) 2021-04-07 16:10:26 -07:00
Steve Faulkner
e20c9569e8 Remove dynamic loading status (#616) 2021-04-07 13:31:50 -05:00
Srinath Narayanan
d2423f28dc Added className to SelfServeBaseClass (#627)
* Added className to SelfServeBaseClass

* addressed PR comments

* addressed PR comments

* fixed lint errors
2021-04-07 11:17:15 -07:00
Jordi Bunster
4f22d308b3 Move tabs state out into React (#621)
This PR is just about moving the tabs array. I'm hoping to let it bake for a bit before merging the rest of the tabs in react work.

Preview here: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Fcosmos-explorer-preview.azurewebsites.net%2Fcommit%2Fda809beb82bb54dc82da18eda41caaf7b9b6597f%2Fexplorer.html#@microsoft.onmicrosoft.com/resource/subscriptions/b9c77f10-b438-4c32-9819-eef8a654e478/resourceGroups/stfaul/providers/Microsoft.DocumentDb/databaseAccounts/stfaul-sql/dataExplorer
2021-04-07 16:15:00 +00:00
Srinath Narayanan
9c6178d0ed Added debug for selfserve (#623) 2021-04-06 14:58:49 -05:00
Steve Faulkner
0f88176a27 Remvoe Explorer.subscriptionType (#622) 2021-04-06 14:35:14 -05:00
Steve Faulkner
cb7760b3f6 Remove Explorer.flight and Explorer.hasWriteAccess (#618)
* Remove Explorer.flight

* Update snapshots

* Remove Explorere.hasWriteAccess

* Update snapshot
2021-04-06 13:33:12 -05:00
Sunil Kumar Yadav
c75618862e Remove unused table-column-options-panel (#620)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-04-06 09:43:15 -05:00
Steve Faulkner
ba3f4829fa Add second App Insights instance (#609) 2021-04-05 18:03:17 -05:00
Srinath Narayanan
250faa5206 SelfServe - Telemetry and Localization improvements (#617)
* made selfServeTelemetry use existing functions

* removed "data" from SelfServeTelemetryType

* fixed localization bugs

* added comment
2021-04-05 14:08:57 -07:00
Jordi Bunster
b150e53814 Remove (unused) dbsettings tab (#607) 2021-04-05 13:51:44 -07:00
Sunil Kumar Yadav
de5a11ff1b Migration/browse queries pane to react (#598)
Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2021-04-04 22:04:34 -05:00
Jordi Bunster
b34c81b3ab TypeScript 4.2 (#600)
Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2021-04-04 22:00:32 -05:00
Sunil Kumar Yadav
2bf9313951 Migrate Load Query Pane to React (#579)
Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2021-04-02 15:44:50 -05:00
Sunil Kumar Yadav
36f8fc1d22 Migrate save query pane to react (#578)
Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2021-04-02 15:10:43 -05:00
Armando Trejo Oliver
1b9070605e Make MongoShell ready message handler backwards compatible (#606)
* Make MongShell message handler backwards compatible

* Fix test title and add one more test case
2021-04-02 12:38:53 -07:00
Steve Faulkner
bd9bdad78a Automated Preview URLs (#601) 2021-04-02 12:24:01 -05:00
Steve Faulkner
ba24eabe7c Remove File upload size check (#605) 2021-04-02 12:23:29 -05:00
381 changed files with 41299 additions and 20174 deletions

View File

@@ -5,7 +5,6 @@ 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 +12,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
@@ -45,7 +41,6 @@ src/Definitions/jquery.d.ts
src/Definitions/plotly.js-cartesian-dist.d-min.ts src/Definitions/plotly.js-cartesian-dist.d-min.ts
src/Definitions/png.d.ts src/Definitions/png.d.ts
src/Definitions/svg.d.ts src/Definitions/svg.d.ts
src/Definitions/worker.d.ts
src/Explorer/ComponentRegisterer.test.ts src/Explorer/ComponentRegisterer.test.ts
src/Explorer/ComponentRegisterer.ts src/Explorer/ComponentRegisterer.ts
src/Explorer/ContextMenuButtonFactory.ts src/Explorer/ContextMenuButtonFactory.ts
@@ -91,8 +86,7 @@ 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/Graph/NewVertexComponent/NewVertex.test.ts
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.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
src/Explorer/Menus/ContextMenu.ts src/Explorer/Menus/ContextMenu.ts
@@ -121,24 +115,16 @@ src/Explorer/Panes/AddDatabasePane.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/DeleteCollectionConfirmationPane.test.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.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/LoadQueryPane.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/SaveQueryPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/StringInputPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/AddTableEntityPane.ts
src/Explorer/Panes/Tables/EditTableEntityPane.ts src/Explorer/Panes/Tables/EditTableEntityPane.ts
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts src/Explorer/Panes/Tables/EntityPropertyViewModel.ts
src/Explorer/Panes/Tables/QuerySelectPane.ts
src/Explorer/Panes/Tables/TableColumnOptionsPane.ts
src/Explorer/Panes/Tables/TableEntityPane.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
@@ -254,7 +240,6 @@ src/Utils/QueryUtils.test.ts
src/applyExplorerBindings.ts src/applyExplorerBindings.ts
src/global.d.ts src/global.d.ts
src/setupTests.ts src/setupTests.ts
src/workers/upload/index.ts
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
src/Explorer/Controls/Accordion/AccordionComponent.tsx src/Explorer/Controls/Accordion/AccordionComponent.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
@@ -302,8 +287,6 @@ src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.t
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx

View File

@@ -3,7 +3,7 @@ module.exports = {
browser: true, browser: true,
es6: true, es6: true,
}, },
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"], plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
@@ -20,7 +20,7 @@ module.exports = {
overrides: [ overrides: [
{ {
files: ["**/*.tsx"], files: ["**/*.tsx"],
extends: ["plugin:react/recommended"], // TODO: Add react-hooks extends: ["plugin:react/recommended"],
plugins: ["react"], plugins: ["react"],
}, },
{ {
@@ -42,6 +42,8 @@ module.exports = {
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }], "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error", eqeqeq: "error",
"react/display-name": "off", "react/display-name": "off",
"react-hooks/rules-of-hooks": "warn", // TODO: error
"react-hooks/exhaustive-deps": "warn", // TODO: error
"no-restricted-syntax": [ "no-restricted-syntax": [
"error", "error",
{ {

1
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1 @@
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)

View File

@@ -70,7 +70,6 @@ jobs:
- run: npm run test - run: npm run test
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, format, compile, unittest]
name: "Build" name: "Build"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -92,9 +91,18 @@ jobs:
with: with:
name: dist name: dist
path: dist/ path: dist/
- name: Upload build to preview blob storage
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --destination-path "${{github.event.pull_request.head.sha}}" --account-key="${PREVIEW_STORAGE_KEY}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
- name: Upload preview config to blob storage
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --subscription cosmosdb-portalteam-generaldemo --name "${{github.event.pull_request.head.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator: endtoendemulator:
name: "End To End Emulator Tests" name: "End To End Emulator Tests"
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') # Temporarily disabled. This test needs to be rewritten in playwright
if: false
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -119,58 +127,21 @@ jobs:
with: with:
name: screenshots name: screenshots
path: failed-* path: failed-*
accessibility: endtoend:
name: "Accessibility | Hosted" name: "E2E"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Accessibility Check
run: |
# Ubuntu gets mad when webpack runs too many files watchers
cat /proc/sys/fs/inotify/max_user_watches
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl -p
npm ci
npm start &
npx wait-on -i 5000 https-get://0.0.0.0:1234/
node utils/accesibilityCheck.js
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
endtoendhosted:
name: "End to End Tests"
needs: [cleanupaccounts]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }}
PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }}
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }}
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }}
NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
test-file: test-file:
- ./test/cassandra/container.spec.ts - ./test/cassandra/container.spec.ts
- ./test/mongo/mongoIndexPolicy.spec.ts
- ./test/notebooks/uploadAndOpenNotebook.spec.ts
- ./test/selfServe/selfServeExample.spec.ts
- ./test/sql/container.spec.ts - ./test/sql/container.spec.ts
- ./test/mongo/container.spec.ts
- ./test/selfServe/selfServeExample.spec.ts
- ./test/notebooks/upload.spec.ts
- ./test/sql/resourceToken.spec.ts - ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts - ./test/tables/container.spec.ts
steps: steps:
@@ -181,30 +152,17 @@ jobs:
node-version: 14.x node-version: 14.x
- run: npm ci - run: npm ci
- run: npm start & - run: npm start &
- run: node utils/cleanupDBs.js
- run: npm run wait-for-server - run: npm run wait-for-server
- name: ${{ matrix['test-file'] }} - name: ${{ matrix['test-file'] }}
run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }} run: |
# Run tests up to three times
for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s)
shell: bash shell: bash
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: screenshots name: screenshots
path: failed-* path: screenshots/
cleanupaccounts:
name: "Cleanup Test Database Accounts"
runs-on: ubuntu-latest
env:
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: node utils/cleanupDBs.js
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')

28
.github/workflows/cleanup.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
# This is a basic workflow to help you get started with Actions
name: Cleanup End to End Account Resources
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
schedule:
# Once every hour
- cron: "0 * * * *"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
cleanupaccounts:
name: "Cleanup Test Database Accounts"
runs-on: ubuntu-latest
env:
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: node utils/cleanupDBs.js

2
.gitignore vendored
View File

@@ -15,3 +15,5 @@ Contracts/*
.cache/ .cache/
.env .env
failure.png failure.png
screenshots/*
GettingStarted-ignore*.ipynb

View File

@@ -153,7 +153,7 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul
✅ DO ✅ DO
- Use [Puppeteer](https://developers.google.com/web/tools/puppeteer) and [Jest](https://jestjs.io/) - Use [Playwright](https://github.com/microsoft/playwright) and [Jest](https://jestjs.io/)
- Write or modify an existing E2E test that covers the primary use case of any major feature. - Write or modify an existing E2E test that covers the primary use case of any major feature.
- Use caution. Do not try to cover every case. End to End tests can be slow and brittle. - Use caution. Do not try to cover every case. End to End tests can be slow and brittle.

File diff suppressed because one or more lines are too long

13
jest-playwright.config.js Normal file
View File

@@ -0,0 +1,13 @@
const isCI = require("is-ci");
module.exports = {
exitOnPageError: false,
launchOptions: {
headless: isCI,
slowMo: 10,
timeout: 60000,
},
contextOptions: {
ignoreHTTPSErrors: true,
},
};

View File

@@ -1,12 +0,0 @@
const isCI = require("is-ci");
module.exports = {
launch: {
headless: isCI,
slowMo: 55,
defaultViewport: null,
ignoreHTTPSErrors: true,
args: ["--disable-web-security"],
exitOnPageError: false,
},
};

View File

@@ -1,5 +0,0 @@
module.exports = {
preset: "jest-puppeteer",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"],
};

View File

@@ -69,7 +69,6 @@ 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",
"worker-loader": "<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 "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
"^dnd-core$": "dnd-core/dist/cjs", "^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs",

View File

@@ -0,0 +1,7 @@
module.exports = {
preset: "jest-playwright-preset",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"],
testEnvironment: "./test/playwrightEnv.js",
setupFilesAfterEnv: ["expect-playwright"],
};

14766
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.9.0", "@azure/cosmos": "3.10.5",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1", "@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7", "@azure/ms-rest-nodeauth": "3.0.7",
@@ -13,7 +13,7 @@
"@babel/plugin-proposal-decorators": "7.12.12", "@babel/plugin-proposal-decorators": "7.12.12",
"@jupyterlab/services": "6.0.2", "@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3", "@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.5.9", "@microsoft/applicationinsights-web": "2.6.1",
"@nteract/commutable": "7.4.2", "@nteract/commutable": "7.4.2",
"@nteract/connected-components": "6.8.2", "@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.0", "@nteract/core": "15.1.0",
@@ -25,7 +25,7 @@
"@nteract/iron-icons": "1.0.0", "@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0", "@nteract/jupyter-widgets": "2.0.0",
"@nteract/logos": "1.0.0", "@nteract/logos": "1.0.0",
"@nteract/markdown": "4.4.0", "@nteract/markdown": "4.6.0",
"@nteract/monaco-editor": "3.2.2", "@nteract/monaco-editor": "3.2.2",
"@nteract/octicons": "2.0.0", "@nteract/octicons": "2.0.0",
"@nteract/outputs": "3.0.9", "@nteract/outputs": "3.0.9",
@@ -43,7 +43,6 @@
"@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/react-cards": "0.109.110",
"@uifabric/react-hooks": "7.14.0",
"@uifabric/styling": "7.13.7", "@uifabric/styling": "7.13.7",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
@@ -66,6 +65,7 @@
"i18next": "19.8.4", "i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1", "i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23", "i18next-http-backend": "1.0.23",
"iframe-resizer-react": "1.1.0",
"immutable": "4.0.0-rc.12", "immutable": "4.0.0-rc.12",
"is-ci": "2.0.0", "is-ci": "2.0.0",
"jquery": "3.5.1", "jquery": "3.5.1",
@@ -79,6 +79,7 @@
"office-ui-fabric-react": "7.164.2", "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",
"q": "1.5.1", "q": "1.5.1",
"react": "16.13.1", "react": "16.13.1",
"react-animate-height": "2.0.8", "react-animate-height": "2.0.8",
@@ -93,6 +94,7 @@
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
"rxjs": "6.6.3", "rxjs": "6.6.3",
"sanitize-html": "2.3.3",
"styled-components": "4.3.2", "styled-components": "4.3.2",
"swr": "0.4.0", "swr": "0.4.0",
"terser-webpack-plugin": "3.1.0", "terser-webpack-plugin": "3.1.0",
@@ -111,26 +113,23 @@
"@types/d3": "5.9.2", "@types/d3": "5.9.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/expect-puppeteer": "4.4.5",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.31",
"@types/jest": "26.0.20", "@types/jest": "26.0.20",
"@types/jest-environment-puppeteer": "4.4.1",
"@types/memoize-one": "4.1.1", "@types/memoize-one": "4.1.1",
"@types/node": "12.11.1", "@types/node": "12.11.1",
"@types/post-robot": "10.0.1",
"@types/promise.prototype.finally": "2.0.3", "@types/promise.prototype.finally": "2.0.3",
"@types/prop-types": "15.5.8",
"@types/puppeteer": "5.4.3",
"@types/q": "1.5.1", "@types/q": "1.5.1",
"@types/react": "17.0.0", "@types/react": "17.0.3",
"@types/react-dom": "17.0.0", "@types/react-dom": "17.0.3",
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36", "@types/underscore": "1.7.36",
"@typescript-eslint/eslint-plugin": "4.0.1", "@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1", "@typescript-eslint/parser": "4.0.1",
"axe-puppeteer": "1.1.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",
@@ -145,16 +144,18 @@
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.2", "eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-react-hooks": "4.2.0", "eslint-plugin-react-hooks": "4.2.0",
"expect-playwright": "0.3.3",
"expose-loader": "0.7.5", "expose-loader": "0.7.5",
"fast-glob": "3.2.5", "fast-glob": "3.2.5",
"file-loader": "2.0.0", "file-loader": "2.0.0",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.0",
"html-loader": "0.5.5", "html-loader": "0.5.5",
"html-loader-jest": "0.2.1", "html-loader-jest": "0.2.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "4.5.2",
"jest": "25.5.4", "jest": "25.5.4",
"jest-canvas-mock": "2.1.0", "jest-canvas-mock": "2.1.0",
"jest-puppeteer": "4.4.0", "jest-playwright-preset": "1.5.1",
"jest-trx-results-processor": "0.0.7", "jest-trx-results-processor": "0.0.7",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "4.1.0", "less-loader": "4.1.0",
@@ -162,23 +163,23 @@
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"playwright": "1.10.0",
"prettier": "2.2.1", "prettier": "2.2.1",
"puppeteer": "8.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"react-dev-utils": "11.0.4",
"rimraf": "3.0.0", "rimraf": "3.0.0",
"sinon": "3.2.1", "sinon": "3.2.1",
"style-loader": "0.23.0", "style-loader": "0.23.0",
"ts-loader": "6.2.2", "ts-loader": "6.2.2",
"tslint": "5.11.0", "tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0", "tslint-microsoft-contrib": "6.0.0",
"typescript": "4.0.2", "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"
"worker-loader": "2.0.0"
}, },
"scripts": { "scripts": {
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js", "start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
@@ -194,16 +195,16 @@
"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",
"compile": "tsc", "compile": "tsc -p ./tsconfig.build.json",
"compile:contracts": "tsc -p ./tsconfig.contracts.json", "compile:contracts": "tsc -p ./tsconfig.contracts.json",
"compile:strict": "tsc -p ./tsconfig.strict.json", "compile:strict": "tsc -p ./tsconfig.strict.json",
"format": "prettier --write \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"", "format": "prettier --write \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"", "format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"", "lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
"build:contracts": "npm run compile:contracts", "build:contracts": "npm run compile:contracts",
"strictEligibleFiles": "node ./strict-migration-tools/index.js", "strict:find": "node ./strict-null-checks/find.js",
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.js", "strict:add": "node ./strict-null-checks/auto-add.js",
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks", "compile:fullStrict": "tsc",
"generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts" "generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
}, },
"repository": { "repository": {

7
preview/.azure/config Normal file
View File

@@ -0,0 +1,7 @@
[defaults]
group = stfaul
sku = P1v2
appserviceplan = stfaul_asp_Linux_centralus_0
location = centralus
web = cosmos-explorer-preview

20
preview/README.md Normal file
View File

@@ -0,0 +1,20 @@
# Cosmos Explorer Preview
Cosmos Explorer Preview makes it possible to try a working version of any commit on master or in a PR. No need to run the app locally or deploy to staging.
Initial support is for Hosted (Connection string only) or the Azure Portal. Examples:
Connection string URLs: https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/hostedExplorer.html
Portal URLs: https://ms.portal.azure.com/?dataExplorerSource=https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/explorer.html#home
In both cases replace `COMMIT_SHA` with the commit you want to view. It must have already completed its build on GitHub Actions.
### Architechture
- This folder contains a NodeJS app deployed to Azure App Service that powers preview URLs:
- Paths starting with `/commit/` are proxied to an Azure Storage account containing build artifacts
- Paths starting with `/proxy/` are proxied dynamically to Cosmos account endpoints. Required otherwise CORS would need to be configured for every account accessed.
- Paths starting with `/api/` are proxied to Portal APIs that do not support CORS.
- On GitHub Actions build completion:
- All files in dist are uploaded to an Azure Storage account namespaced by the SHA of the commit
- `/preview/config.json` is uploaded to the same folder with preview specific configuration

3
preview/config.json Normal file
View File

@@ -0,0 +1,3 @@
{
"PROXY_PATH": "/proxy"
}

68
preview/index.js Normal file
View File

@@ -0,0 +1,68 @@
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const port = process.env.PORT || 3000;
const fetch = require("node-fetch");
const api = createProxyMiddleware("/api", {
target: "https://main.documentdb.ext.azure.com",
changeOrigin: true,
logLevel: "debug",
bypass: (req, res) => {
if (req.method === "OPTIONS") {
res.statusCode = 200;
res.send();
}
},
});
const proxy = createProxyMiddleware("/proxy", {
target: "https://main.documentdb.ext.azure.com",
changeOrigin: true,
secure: false,
logLevel: "debug",
pathRewrite: { "^/proxy": "" },
router: (req) => {
let newTarget = req.headers["x-ms-proxy-target"];
return newTarget;
},
});
const commit = createProxyMiddleware("/commit", {
target: "https://cosmosexplorerpreview.blob.core.windows.net",
changeOrigin: true,
secure: false,
logLevel: "debug",
pathRewrite: { "^/commit": "$web/" },
});
const app = express();
app.use(api);
app.use(proxy);
app.use(commit);
app.get("/pull/:pr(\\d+)", (req, res) => {
const pr = req.params.pr;
const [, query] = req.originalUrl.split("?");
const search = new URLSearchParams(query);
fetch("https://api.github.com/repos/Azure/cosmos-explorer/pulls/" + pr)
.then((response) => response.json())
.then(({ head: { ref, sha } }) => {
const prUrl = new URL("https://github.com/Azure/cosmos-explorer/pull/" + pr);
prUrl.hash = ref;
search.set("feature.pr", prUrl.href);
const explorer = new URL("https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/explorer.html");
explorer.search = search.toString();
const portal = new URL("https://ms.portal.azure.com/");
portal.searchParams.set("dataExplorerSource", explorer.href);
return res.redirect(portal.href);
})
.catch(() => res.sendStatus(500));
});
app.listen(port, () => {
console.log(`Example app listening on port: ${port}`);
});

1146
preview/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
preview/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "cosmos-explorer-preview",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"deploy": "az webapp up -n cosmos-explorer-preview --subscription cosmosdb-portalteam-generaldemo -g stfaul",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Microsoft Corporation",
"dependencies": {
"express": "^4.17.1",
"http-proxy-middleware": "^1.1.0",
"node-fetch": "^2.6.1"
}
}

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,68 @@
import { createImmutableOutput, JSONObject, OnDiskOutput } from "@nteract/commutable";
// import outputs individually to avoid increasing the bundle size
import { KernelOutputError } from "@nteract/outputs/lib/components/kernel-output-error";
import { Output } from "@nteract/outputs/lib/components/output";
import { StreamText } from "@nteract/outputs/lib/components/stream-text";
import { ContentRef } from "@nteract/types";
import "bootstrap/dist/css/bootstrap.css";
import postRobot from "post-robot";
import * as React from "react";
import * as ReactDOM from "react-dom";
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
import "../Explorer/Notebook/NotebookRenderer/base.css";
import "../Explorer/Notebook/NotebookRenderer/default.css";
import { TransformMedia } from "./TransformMedia";
export interface CellOutputViewerProps {
id: string;
contentRef: ContentRef;
hidden: boolean;
expanded: boolean;
outputs: OnDiskOutput[];
onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void;
}
const onInit = async () => {
postRobot.on(
"props",
{
window: window.parent,
domain: window.location.origin,
},
(event) => {
// Typescript definition for event is wrong. So read props by casting to <any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (event as any).data as CellOutputViewerProps;
const outputs = (
<div
data-iframe-height
className={`nteract-cell-outputs ${props.hidden ? "hidden" : ""} ${props.expanded ? "expanded" : ""}`}
>
{props.outputs?.map((output, index) => (
<Output output={createImmutableOutput(output)} key={index}>
<TransformMedia
output_type={"display_data"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<TransformMedia
output_type={"execute_result"}
id={props.id}
contentRef={props.contentRef}
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
/>
<KernelOutputError />
<StreamText />
</Output>
))}
</div>
);
ReactDOM.render(outputs, document.getElementById("cellOutput"));
}
);
};
// Entry point
window.addEventListener("load", onInit);

View File

@@ -0,0 +1,138 @@
import { ImmutableDisplayData, ImmutableExecuteResult, JSONObject } from "@nteract/commutable";
// import outputs individually to avoid increasing the bundle size
import { HTML } from "@nteract/outputs/lib/components/media/html";
import { Image } from "@nteract/outputs/lib/components/media/image";
import { JavaScript } from "@nteract/outputs/lib/components/media/javascript";
import { Json } from "@nteract/outputs/lib/components/media/json";
import { LaTeX } from "@nteract/outputs/lib/components/media/latex";
import { Plain } from "@nteract/outputs/lib/components/media/plain";
import { SVG } from "@nteract/outputs/lib/components/media/svg";
import { ContentRef } from "@nteract/types";
import React, { Suspense } from "react";
const EmptyTransform = (): JSX.Element => <></>;
const displayOrder = [
"application/vnd.jupyter.widget-view+json",
"application/vnd.vega.v5+json",
"application/vnd.vega.v4+json",
"application/vnd.vega.v3+json",
"application/vnd.vega.v2+json",
"application/vnd.vegalite.v4+json",
"application/vnd.vegalite.v3+json",
"application/vnd.vegalite.v2+json",
"application/vnd.vegalite.v1+json",
"application/geo+json",
"application/vnd.plotly.v1+json",
"text/vnd.plotly.v1+html",
"application/x-nteract-model-debug+json",
"application/vnd.dataresource+json",
"application/vdom.v1+json",
"application/json",
"application/javascript",
"text/html",
"text/markdown",
"text/latex",
"image/svg+xml",
"image/gif",
"image/png",
"image/jpeg",
"text/plain",
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const transformsById = new Map<string, React.ComponentType<any>>([
["text/vnd.plotly.v1+html", React.lazy(() => import("@nteract/transform-plotly"))],
["application/vnd.plotly.v1+json", React.lazy(() => import("@nteract/transform-plotly"))],
["application/geo+json", EmptyTransform], // TODO: The geojson transform will likely need some work because of the basemap URL(s)
["application/x-nteract-model-debug+json", React.lazy(() => import("@nteract/transform-model-debug"))],
["application/vnd.dataresource+json", React.lazy(() => import("@nteract/data-explorer"))],
["application/vnd.jupyter.widget-view+json", React.lazy(() => import("./transforms/WidgetDisplay"))],
["application/vnd.vegalite.v1+json", React.lazy(() => import("./transforms/VegaLite1"))],
["application/vnd.vegalite.v2+json", React.lazy(() => import("./transforms/VegaLite2"))],
["application/vnd.vegalite.v3+json", React.lazy(() => import("./transforms/VegaLite3"))],
["application/vnd.vegalite.v4+json", React.lazy(() => import("./transforms/VegaLite4"))],
["application/vnd.vega.v2+json", React.lazy(() => import("./transforms/Vega2"))],
["application/vnd.vega.v3+json", React.lazy(() => import("./transforms/Vega3"))],
["application/vnd.vega.v4+json", React.lazy(() => import("./transforms/Vega4"))],
["application/vnd.vega.v5+json", React.lazy(() => import("./transforms/Vega5"))],
["application/vdom.v1+json", React.lazy(() => import("@nteract/transform-vdom"))],
["application/json", Json],
["application/javascript", JavaScript],
["text/html", HTML],
["text/markdown", React.lazy(() => import("@nteract/outputs/lib/components/media/markdown"))], // Markdown increases the bundle size so lazy load it
["text/latex", LaTeX],
["image/svg+xml", SVG],
["image/gif", Image],
["image/png", Image],
["image/jpeg", Image],
["text/plain", Plain],
]);
interface TransformMediaProps {
output_type: string;
id: string;
contentRef: ContentRef;
output?: ImmutableDisplayData | ImmutableExecuteResult;
onMetadataChange: (metadata: JSONObject, mediaType: string) => void;
}
export const TransformMedia = (props: TransformMediaProps): JSX.Element => {
const { Media, mediaType, data, metadata } = getMediaInfo(props);
// If we had no valid result, return an empty output
if (!mediaType || !data) {
return <></>;
}
return (
<Suspense fallback={<div>Loading...</div>}>
<Media
onMetadataChange={props.onMetadataChange}
data={data}
metadata={metadata}
contentRef={props.contentRef}
id={props.id}
/>
</Suspense>
);
};
const getMediaInfo = (props: TransformMediaProps) => {
const { output, output_type } = props;
// This component should only be used with display data and execute result
if (!output || !(output_type === "display_data" || output_type === "execute_result")) {
console.warn("connected transform media managed to get a non media bundle output");
return {
Media: EmptyTransform,
};
}
// Find the first mediaType in the output data that we support with a handler
const mediaType = displayOrder.find(
(key) =>
Object.prototype.hasOwnProperty.call(output.data, key) &&
(Object.prototype.hasOwnProperty.call(transformsById, key) || transformsById.get(key))
);
if (mediaType) {
const metadata = output.metadata.get(mediaType);
const data = output.data[mediaType];
const Media = transformsById.get(mediaType);
return {
Media,
mediaType,
data,
metadata,
};
}
return {
Media: EmptyTransform,
mediaType,
output,
};
};
export default TransformMedia;

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
<title>Cell Output Viewer</title>
</head>
<body>
<div class="cellOutput" id="cellOutput"></div>
</body>
</html>

View File

@@ -0,0 +1 @@
export { Vega2 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { Vega3 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { Vega4 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { Vega5 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { VegaLite1 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { VegaLite2 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { VegaLite3 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { VegaLite4 as default } from "@nteract/transform-vega";

View File

@@ -0,0 +1 @@
export { WidgetDisplay as default } from "@nteract/jupyter-widgets";

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

@@ -32,7 +32,7 @@ export const tokenProvider = async (requestInfo: RequestInfo) => {
}; };
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => { export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => {
requestContext.endpoint = configContext.PROXY_PATH; requestContext.endpoint = new URL(configContext.PROXY_PATH, window.location.href).href;
requestContext.headers["x-ms-proxy-target"] = endpoint(); requestContext.headers["x-ms-proxy-target"] = endpoint();
return next(requestContext); return next(requestContext);
}; };
@@ -75,7 +75,10 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
} }
} }
let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient { export function client(): Cosmos.CosmosClient {
if (_client) return _client;
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey, key: userContext.masterKey,
@@ -89,5 +92,6 @@ export function client(): Cosmos.CosmosClient {
if (configContext.PROXY_PATH !== undefined) { if (configContext.PROXY_PATH !== undefined) {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }]; (options as any).plugins = [{ on: "request", plugin: requestPlugin }];
} }
return new Cosmos.CosmosClient(options); _client = new Cosmos.CosmosClient(options);
return _client;
} }

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

@@ -0,0 +1,66 @@
import { DatePicker, TextField } from "office-ui-fabric-react";
import React, { FunctionComponent } from "react";
export interface TableEntityProps {
entityValueLabel?: string;
entityValuePlaceholder: string;
entityValue: string | Date;
isEntityTypeDate: boolean;
isEntityValueDisable?: boolean;
entityTimeValue: string;
entityValueType: string;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onSelectDate: (date: Date | null | undefined) => void;
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
}
export const EntityValue: FunctionComponent<TableEntityProps> = ({
entityValueLabel,
entityValuePlaceholder,
entityValue,
isEntityTypeDate,
entityTimeValue,
entityValueType,
onEntityValueChange,
onSelectDate,
isEntityValueDisable,
onEntityTimeValueChange,
}: TableEntityProps): JSX.Element => {
if (isEntityTypeDate) {
return (
<>
<DatePicker
className="addEntityDatePicker"
placeholder={entityValuePlaceholder}
value={entityValue && new Date(entityValue)}
ariaLabel={entityValuePlaceholder}
onSelectDate={onSelectDate}
disabled={isEntityValueDisable}
/>
<TextField
label={entityValueLabel && entityValueLabel}
id="entityTimeId"
autoFocus
type="time"
value={entityTimeValue}
onChange={onEntityTimeValueChange}
disabled={isEntityValueDisable}
/>
</>
);
}
return (
<TextField
label={entityValueLabel && entityValueLabel}
className="addEntityTextField"
id="entityValueId"
autoFocus
disabled={isEntityValueDisable}
type={entityValueType}
placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" && entityValue}
onChange={onEntityValueChange}
/>
);
};

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

@@ -1,7 +1,7 @@
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web"; import { SeverityLevel } from "@microsoft/applicationinsights-web";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { trackTrace } from "../Shared/appInsights";
import { sendMessage } from "./MessageHandler";
// TODO: Move to a separate Diagnostics folder // TODO: Move to a separate Diagnostics folder
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -46,7 +46,7 @@ function _logEntry(entry: Diagnostics.LogEntry): void {
return SeverityLevel.Information; return SeverityLevel.Information;
} }
})(entry.level); })(entry.level);
appInsights.trackTrace({ message: entry.message, severityLevel }, { area: entry.area }); trackTrace({ message: entry.message, severityLevel }, { area: entry.area });
} }
function _generateLogEntry( function _generateLogEntry(

View File

@@ -48,32 +48,18 @@ export function sendCachedDataMessage<TResponseDataModel>(
} }
export function sendMessage(data: any): void { export function sendMessage(data: any): void {
if (canSendMessage()) { _sendMessage({
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe", signature: "pcIframe",
data: data, data: data,
}, });
portalChildWindow.document.referrer
);
}
} }
export function sendReadyMessage(): void { export function sendReadyMessage(): void {
if (canSendMessage()) { _sendMessage({
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe", signature: "pcIframe",
kind: "ready", kind: "ready",
data: "ready", data: "ready",
}, });
portalChildWindow.document.referrer
);
}
} }
export function canSendMessage(): boolean { export function canSendMessage(): boolean {
@@ -89,3 +75,17 @@ export function runGarbageCollector() {
} }
}); });
} }
const _sendMessage = (message: any): void => {
if (canSendMessage()) {
// Portal window can receive messages from only child windows
const portalChildWindow = getDataExplorerWindow(window) || window;
if (portalChildWindow === window) {
// Current window is a child of portal, send message to portal window
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*");
} else {
// Current window is not a child of portal, send message to the child window instead (which is data explorer)
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*");
}
}
};

View File

@@ -5,11 +5,10 @@ import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { Collection } from "../Contracts/ViewModels"; import { Collection } from "../Contracts/ViewModels";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { MinimalQueryIterator } from "./IteratorUtilities"; import { MinimalQueryIterator } from "./IteratorUtilities";
import { sendMessage } from "./MessageHandler"; import { sendMessage } from "./MessageHandler";
@@ -348,10 +347,7 @@ export function getEndpoint(): string {
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> { async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
const errorMessage = await response.text(); const errorMessage = await response.text();
// Log the error where the user can see it // Log the error where the user can see it
NotificationConsoleUtils.logConsoleMessage( logConsoleError(`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`);
ConsoleDataType.Error,
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
);
if (response.status === HttpStatusCodes.Forbidden) { if (response.status === HttpStatusCodes.Forbidden) {
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage }); sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return; return;

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 { return this.touch(key, value), this;
}
private touch(key: string, value = super.get(key)) {
// Map keeps (re) insertion order according to ES6 spec
if (value) {
this.delete(key);
super.set(key, value); super.set(key, value);
this.markKeyAsTouched(key);
if (super.size() > this.maxNbElements && key !== this.keyQueue[0]) {
this.reduceCacheSize();
}
} }
/** return value;
* Invalidate elements to keep the total number below the limit
*/
private reduceCacheSize(): void {
// remove a key
const oldKey = this.keyQueue.shift();
if (oldKey) {
super.delete(oldKey);
}
}
/**
* 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

@@ -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 = {
@@ -211,7 +211,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

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

140
src/Common/TableEntity.tsx Normal file
View File

@@ -0,0 +1,140 @@
import {
Dropdown,
IDropdownOption,
IDropdownStyles,
IImageProps,
Image,
IStackTokens,
Stack,
TextField,
TooltipHost,
} from "office-ui-fabric-react";
import React, { FunctionComponent } from "react";
import DeleteIcon from "../../images/delete.svg";
import EditIcon from "../../images/Edit_entity.svg";
import { CassandraType, TableType } from "../Explorer/Tables/Constants";
import { userContext } from "../UserContext";
import { EntityValue } from "./EntityValue";
const dropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 100 } };
export interface TableEntityProps {
entityTypeLabel?: string;
entityPropertyLabel?: string;
entityValueLabel?: string;
isDeleteOptionVisible: boolean;
entityProperty: string;
entityPropertyPlaceHolder: string;
selectedKey: string | number;
entityValuePlaceholder: string;
entityValue: string | Date;
isEntityTypeDate: boolean;
options: { key: string; text: string }[];
isPropertyTypeDisable: boolean;
entityTimeValue: string;
isEntityValueDisable?: boolean;
onDeleteEntity?: () => void;
onEditEntity?: () => void;
onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onEntityTypeChange: (event: React.FormEvent<HTMLElement>, selectedParam: IDropdownOption) => void;
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onSelectDate: (date: Date | null | undefined) => void;
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
}
export const TableEntity: FunctionComponent<TableEntityProps> = ({
entityTypeLabel,
entityPropertyLabel,
isDeleteOptionVisible,
entityProperty,
selectedKey,
entityPropertyPlaceHolder,
entityValueLabel,
entityValuePlaceholder,
entityValue,
options,
isPropertyTypeDisable,
isEntityTypeDate,
entityTimeValue,
isEntityValueDisable,
onEditEntity,
onDeleteEntity,
onEntityPropertyChange,
onEntityTypeChange,
onEntityValueChange,
onSelectDate,
onEntityTimeValueChange,
}: TableEntityProps): JSX.Element => {
const imageProps: IImageProps = {
width: 16,
height: 30,
className: entityPropertyLabel ? "addRemoveIconLabel" : "addRemoveIcon",
};
const sectionStackTokens: IStackTokens = { childrenGap: 12 };
const getEntityValueType = (): string => {
const { Int, Smallint, Tinyint } = CassandraType;
const { Double, Int32, Int64 } = TableType;
if (
selectedKey === Double ||
selectedKey === Int32 ||
selectedKey === Int64 ||
selectedKey === Int ||
selectedKey === Smallint ||
selectedKey === Tinyint
) {
return "number";
}
return "string";
};
return (
<>
<Stack horizontal tokens={sectionStackTokens}>
<TextField
label={entityPropertyLabel && entityPropertyLabel}
id="entityPropertyId"
autoFocus
disabled={isPropertyTypeDisable}
placeholder={entityPropertyPlaceHolder}
value={entityProperty}
onChange={onEntityPropertyChange}
required
/>
<Dropdown
label={entityTypeLabel && entityTypeLabel}
selectedKey={selectedKey}
onChange={onEntityTypeChange}
options={options}
disabled={isPropertyTypeDisable}
id="entityTypeId"
styles={dropdownStyles}
/>
<EntityValue
entityValueLabel={entityValueLabel}
entityValueType={getEntityValueType()}
isEntityValueDisable={isEntityValueDisable}
entityValuePlaceholder={entityValuePlaceholder}
entityValue={entityValue}
isEntityTypeDate={isEntityTypeDate}
entityTimeValue={entityTimeValue}
onEntityValueChange={onEntityValueChange}
onSelectDate={onSelectDate}
onEntityTimeValueChange={onEntityTimeValueChange}
/>
{!isEntityValueDisable && (
<TooltipHost content="Edit property" id="editTooltip">
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
</TooltipHost>
)}
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
<TooltipHost content="Delete property" id="deleteTooltip">
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
</TooltipHost>
)}
</Stack>
</>
);
};

View File

@@ -1,8 +1,8 @@
import { Image, Stack, TextField } from "office-ui-fabric-react"; import { Image, Stack, TextField } from "office-ui-fabric-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 "../../Common/Constants"; import * as Constants from "../Constants";
import { Tooltip } from "../Tooltip"; import { Tooltip } from "../Tooltip/Tooltip";
interface UploadProps { interface UploadProps {
label: string; label: string;

View File

@@ -2,7 +2,7 @@
exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = ` exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = `
Object { Object {
"endpoint": "/proxy", "endpoint": "http://localhost/proxy",
"headers": Object { "headers": Object {
"x-ms-proxy-target": "http://localhost", "x-ms-proxy-target": "http://localhost",
}, },
@@ -12,7 +12,7 @@ Object {
exports[`requestPlugin Hosted builds a proxy URL in development 1`] = ` exports[`requestPlugin Hosted builds a proxy URL in development 1`] = `
Object { Object {
"endpoint": "/proxy", "endpoint": "http://localhost/proxy",
"headers": Object { "headers": Object {
"x-ms-proxy-target": "baz", "x-ms-proxy-target": "baz",
}, },

View File

@@ -0,0 +1,39 @@
import { JSONObject, OperationResponse } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export const bulkCreateDocument = async (
collection: CollectionBase,
documents: JSONObject[]
): Promise<OperationResponse[]> => {
const clearMessage = logConsoleProgress(
`Executing ${documents.length} bulk operations for container ${collection.id()}`
);
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.items.bulk(
documents.map((doc) => ({ operationType: "Create", resourceBody: doc })),
{ continueOnError: true }
);
const successCount = response.filter((r) => r.statusCode === 201).length;
const throttledCount = response.filter((r) => r.statusCode === 429).length;
logConsoleInfo(
`${
documents.length
} operations completed for container ${collection.id()}. ${successCount} operations succeeded. ${throttledCount} operations throttled`
);
return response;
} catch (error) {
handleError(error, "BulkCreateDocument", `Error bulk creating items for container ${collection.id()}`);
throw error;
} finally {
clearMessage();
}
};

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,17 +62,17 @@ 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 defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
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: ${defaultExperience}`);

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,15 +48,15 @@ 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 defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: 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: ${defaultExperience}`);

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}`);
@@ -31,18 +30,18 @@ function deleteCollectionWithARM(databaseId: string, collectionId: string): Prom
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
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: ${defaultExperience}`);

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) {
@@ -34,16 +33,16 @@ function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
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: ${defaultExperience}`);

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,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);
} }
@@ -36,12 +31,12 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
let rpResponse; let rpResponse;
try { try {
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case "SQL":
rpResponse = await getSqlContainerThroughput( rpResponse = await getSqlContainerThroughput(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
@@ -50,7 +45,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 +54,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 +63,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,7 +72,7 @@ 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:

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,25 +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 { client } from "../CosmosClient"; import { userContext } from "../../UserContext";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
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 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.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionsWithARM(databaseId); return await readCollectionsWithARM(databaseId);
} }
@@ -38,22 +32,22 @@ async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Co
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: 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:

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);
} }
@@ -35,21 +30,21 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
let rpResponse; let rpResponse;
try { try {
switch (defaultExperience) { switch (defaultExperience) {
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:

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();
@@ -37,19 +32,19 @@ async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
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:

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,11 +1,10 @@
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,
@@ -13,11 +12,7 @@ export async function readUserDefinedFunctions(
): 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}`);
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 listSqlUserDefinedFunctions( const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -1,51 +1,43 @@
import { ContainerDefinition } from "@azure/cosmos";
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 { ContainerDefinition } from "@azure/cosmos"; import { userContext } from "../../UserContext";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
CreateUpdateOptions,
ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters,
MongoDBCollectionResource,
SqlContainerCreateUpdateParameters,
SqlContainerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
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 { handleError } from "../ErrorHandlingUtils"; import {
ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters,
SqlContainerCreateUpdateParameters,
SqlContainerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function updateCollection( export async function updateCollection(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
newCollection: Collection, newCollection: Partial<Collection>,
options: RequestOptions = {} options: RequestOptions = {}
): Promise<Collection> { ): Promise<Collection> {
let collection: Collection; let collection: Collection;
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.MongoDB &&
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()
@@ -69,22 +61,31 @@ export async function updateCollection(
async function updateCollectionWithARM( async function updateCollectionWithARM(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
newCollection: Collection newCollection: Partial<Collection>
): Promise<Collection> { ): Promise<Collection> {
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.apiType;
switch (defaultExperience) { switch (defaultExperience) {
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 "Mongo":
return updateMongoDBCollection(
databaseId,
collectionId,
subscriptionId,
resourceGroup,
accountName,
newCollection
);
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);
} }
@@ -96,7 +97,7 @@ async function updateSqlContainer(
subscriptionId: string, subscriptionId: string,
resourceGroup: string, resourceGroup: string,
accountName: string, accountName: string,
newCollection: Collection newCollection: Partial<Collection>
): Promise<Collection> { ): Promise<Collection> {
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId); const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) { if (getResponse && getResponse.properties && getResponse.properties.resource) {
@@ -115,35 +116,26 @@ async function updateSqlContainer(
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`); throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
} }
export async function updateMongoDBCollectionThroughRP( export async function updateMongoDBCollection(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
newCollection: MongoDBCollectionResource, subscriptionId: string,
updateOptions?: CreateUpdateOptions resourceGroup: string,
): Promise<MongoDBCollectionResource> { accountName: string,
const subscriptionId = userContext.subscriptionId; newCollection: Partial<Collection>
const resourceGroup = userContext.resourceGroup; ): Promise<Collection> {
const accountName = userContext.databaseAccount.name;
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId); const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) { if (getResponse && getResponse.properties && getResponse.properties.resource) {
const updateParams: MongoDBCollectionCreateUpdateParameters = { getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
properties: {
resource: newCollection,
options: updateOptions,
},
};
const updateResponse = await createUpdateMongoDBCollection( const updateResponse = await createUpdateMongoDBCollection(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
accountName, accountName,
databaseId, databaseId,
collectionId, collectionId,
updateParams getResponse as MongoDBCollectionCreateUpdateParameters
); );
return updateResponse && (updateResponse.properties.resource as Collection);
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
} }
throw new Error( throw new Error(
@@ -157,7 +149,7 @@ async function updateCassandraTable(
subscriptionId: string, subscriptionId: string,
resourceGroup: string, resourceGroup: string,
accountName: string, accountName: string,
newCollection: Collection newCollection: Partial<Collection>
): Promise<Collection> { ): Promise<Collection> {
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId); const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) { if (getResponse && getResponse.properties && getResponse.properties.resource) {
@@ -184,7 +176,7 @@ async function updateGremlinGraph(
subscriptionId: string, subscriptionId: string,
resourceGroup: string, resourceGroup: string,
accountName: string, accountName: string,
newCollection: Collection newCollection: Partial<Collection>
): Promise<Collection> { ): Promise<Collection> {
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId); const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) { if (getResponse && getResponse.properties && getResponse.properties.resource) {
@@ -208,7 +200,7 @@ async function updateTable(
subscriptionId: string, subscriptionId: string,
resourceGroup: string, resourceGroup: string,
accountName: string, accountName: string,
newCollection: Collection newCollection: Partial<Collection>
): Promise<Collection> { ): Promise<Collection> {
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId); const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) { if (getResponse && getResponse.properties && getResponse.properties.resource) {

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

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,11 +20,7 @@ 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 ( if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
userContext.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlStoredProcedure( const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

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,
@@ -18,11 +17,7 @@ export async function updateTrigger(
): Promise<TriggerDefinition> { ): Promise<TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Updating 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
) {
const getResponse = await getSqlTrigger( const getResponse = await getSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

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,
@@ -21,11 +20,7 @@ export async function updateUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Updating 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
) {
const getResponse = await getSqlUserDefinedFunction( const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,
userContext.resourceGroup, userContext.resourceGroup,

View File

@@ -26,6 +26,7 @@ export interface ConfigContext {
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
allowedJunoOrigins: string[];
} }
// Default configuration // Default configuration
@@ -53,6 +54,13 @@ let configContext: Readonly<ConfigContext> = {
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306 GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com", JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
allowedJunoOrigins: [
"https://juno-test.documents-dev.windows-int.net",
"https://juno-test2.documents-dev.windows-int.net",
"https://tools.cosmos.azure.com",
"https://tools-staging.cosmos.azure.com",
"https://localhost",
],
}; };
export function resetConfigContext(): void { export function resetConfigContext(): void {
@@ -86,13 +94,18 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
}); });
if (response.status === 200) { if (response.status === 200) {
try { try {
const { allowedParentFrameOrigins, ...externalConfig } = await response.json(); const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
Object.assign(configContext, externalConfig); Object.assign(configContext, externalConfig);
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) { if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
updateConfigContext({ updateConfigContext({
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins], allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
}); });
} }
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
updateConfigContext({
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
});
}
} catch (error) { } catch (error) {
console.error("Unable to parse json in config file"); console.error("Unable to parse json in config file");
console.error(error); console.error(error);

View File

@@ -4,6 +4,7 @@
export enum TabKind { export enum TabKind {
SQLDocuments, SQLDocuments,
MongoDocuments, MongoDocuments,
SchemaAnalyzer,
TableEntities, TableEntities,
Graph, Graph,
SQLQuery, SQLQuery,

View File

@@ -121,6 +121,10 @@ export interface ISchemaRequest {
} }
export interface Collection extends Resource { export interface Collection extends Resource {
// Only in Mongo collections loaded via ARM
shardKey?: {
[key: string]: string;
};
defaultTtl?: number; defaultTtl?: number;
indexingPolicy?: IndexingPolicy; indexingPolicy?: IndexingPolicy;
partitionKey?: PartitionKey; partitionKey?: PartitionKey;

View File

@@ -15,7 +15,6 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import Trigger from "../Explorer/Tree/Trigger"; import Trigger from "../Explorer/Tree/Trigger";
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction"; import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import { SelfServeType } from "../SelfServe/SelfServeUtils"; import { SelfServeType } from "../SelfServe/SelfServeUtils";
import { UploadDetails } from "../workers/upload/definitions";
import * as DataModels from "./DataModels"; import * as DataModels from "./DataModels";
import { SubscriptionType } from "./SubscriptionType"; import { SubscriptionType } from "./SubscriptionType";
@@ -23,6 +22,14 @@ export interface TokenProvider {
getAuthHeader(): Promise<Headers>; getAuthHeader(): Promise<Headers>;
} }
export interface UploadDetailsRecord {
fileName: string;
numSucceeded: number;
numFailed: number;
numThrottled: number;
errors: string[];
}
export interface QueryResultsMetadata { export interface QueryResultsMetadata {
hasMoreResults: boolean; hasMoreResults: boolean;
firstItemIndex: number; firstItemIndex: number;
@@ -134,6 +141,7 @@ export interface Collection extends CollectionBase {
onTableEntitiesClick(): void; onTableEntitiesClick(): void;
onGraphDocumentsClick(): void; onGraphDocumentsClick(): void;
onMongoDBDocumentsClick(): void; onMongoDBDocumentsClick(): void;
onSchemaAnalyzerClick(): void;
openTab(): void; openTab(): void;
onSettingsClick: () => Promise<void>; onSettingsClick: () => Promise<void>;
@@ -174,7 +182,7 @@ export interface Collection extends CollectionBase {
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void; onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
onDrop(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Promise<UploadDetails>; uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
getLabel(): string; getLabel(): string;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>; getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
@@ -269,7 +277,6 @@ export interface TabOptions {
tabKind: CollectionTabKind; tabKind: CollectionTabKind;
title: string; title: string;
tabPath: string; tabPath: string;
isActive: ko.Observable<boolean>;
hashLocation: string; hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void; onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>; isTabsContentExpanded?: ko.Observable<boolean>;
@@ -360,6 +367,7 @@ export enum CollectionTabKind {
Schema = 19, Schema = 19,
CollectionSettingsV2 = 20, CollectionSettingsV2 = 20,
DatabaseSettingsV2 = 21, DatabaseSettingsV2 = 21,
SchemaAnalyzer = 22,
} }
export enum TerminalKind { export enum TerminalKind {
@@ -390,6 +398,9 @@ export interface DataExplorerInputsFrame {
dataExplorerVersion?: string; dataExplorerVersion?: string;
defaultCollectionThroughput?: CollectionCreationDefaults; defaultCollectionThroughput?: CollectionCreationDefaults;
flights?: readonly string[]; flights?: readonly string[];
features?: {
[key: string]: string;
};
} }
export interface SelfServeFrameInputs { export interface SelfServeFrameInputs {

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

@@ -1,7 +0,0 @@
declare module "worker-loader!*" {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}

View File

@@ -8,10 +8,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true); expect(ko.components.isRegistered("input-typeahead")).toBe(true);
}); });
it("should register new-vertex-form component", () => {
expect(ko.components.isRegistered("new-vertex-form")).toBe(true);
});
it("should register error-display component", () => { it("should register error-display component", () => {
expect(ko.components.isRegistered("error-display")).toBe(true); expect(ko.components.isRegistered("error-display")).toBe(true);
}); });
@@ -24,83 +20,14 @@ describe("Component Registerer", () => {
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", () => { it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true); expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
}); });
it("should register delete-collection-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true);
});
it("should register save-query-pane component", () => {
expect(ko.components.isRegistered("save-query-pane")).toBe(true);
});
it("should register browse-queries-pane component", () => {
expect(ko.components.isRegistered("browse-queries-pane")).toBe(true);
});
it("should register graph-new-vertex-pane component", () => {
expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true);
});
it("should register graph-styling-pane component", () => { it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true); 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 setup-notebooks-pane component", () => {
expect(ko.components.isRegistered("setup-notebooks-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

@@ -7,27 +7,9 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
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 { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import ConflictsTab from "./Tabs/ConflictsTab";
import DatabaseSettingsTab from "./Tabs/DatabaseSettingsTab";
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 { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
import TabsManagerTemplate from "./Tabs/TabsManager.html";
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("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent); ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("editor", new EditorComponent()); ko.components.register("editor", new EditorComponent());
@@ -35,46 +17,12 @@ 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);
ko.components.register("tabs-manager", { template: TabsManagerTemplate });
// Collection Tabs
[
DocumentsTab,
StoredProcedureTab,
TriggerTab,
UserDefinedFunctionTab,
SettingsTabV2,
QueryTab,
QueryTablesTab,
GraphTab,
MongoShellTab,
ConflictsTab,
NotebookTabV2,
TerminalTab,
GalleryTab,
NotebookViewerTab,
DatabaseSettingsTab,
DatabaseSettingsTabV2,
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
// Panes // Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register(
"delete-collection-confirmation-pane",
new PaneComponents.DeleteCollectionConfirmationPaneComponent()
);
ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent());
ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent());
ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent()); 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(),
@@ -55,7 +54,7 @@ export class ResourceTreeContextMenuButtonFactory {
selectedCollection: ViewModels.Collection selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = []; const items: TreeNodeMenuItem[] = [];
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) { if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
@@ -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),
@@ -80,7 +79,7 @@ export class ResourceTreeContextMenuButtonFactory {
}); });
} }
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) { if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({ items.push({
iconSrc: AddStoredProcedureIcon, iconSrc: AddStoredProcedureIcon,
onClick: () => { onClick: () => {
@@ -123,7 +122,7 @@ export class ResourceTreeContextMenuButtonFactory {
container: Explorer, container: Explorer,
storedProcedure: StoredProcedure storedProcedure: StoredProcedure
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (userContext.apiType === "Cassandra") {
return []; return [];
} }
@@ -137,7 +136,7 @@ export class ResourceTreeContextMenuButtonFactory {
} }
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] { public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (userContext.apiType === "Cassandra") {
return []; return [];
} }
@@ -154,7 +153,7 @@ export class ResourceTreeContextMenuButtonFactory {
container: Explorer, container: Explorer,
userDefinedFunction: UserDefinedFunction userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (userContext.apiType === "Cassandra") {
return []; return [];
} }

View File

@@ -5,6 +5,7 @@ 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,8 +1,3 @@
import * as React from "react";
import { Dialog as FluentDialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link";
import { import {
ChoiceGroup, ChoiceGroup,
FontIcon, FontIcon,
@@ -10,6 +5,11 @@ import {
IProgressIndicatorProps, IProgressIndicatorProps,
ProgressIndicator, ProgressIndicator,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { DefaultButton, IButtonProps, PrimaryButton } from "office-ui-fabric-react/lib/Button";
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";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
@@ -50,43 +50,52 @@ const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400; const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px"; const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export class Dialog extends React.Component<DialogProps> { export const Dialog: FunctionComponent<DialogProps> = ({
constructor(props: DialogProps) { title,
super(props); subText,
} isModal,
visible,
public render(): JSX.Element { choiceGroupProps,
textFieldProps,
linkProps,
progressIndicatorProps,
primaryButtonText,
secondaryButtonText,
onPrimaryButtonClick,
onSecondaryButtonClick,
primaryButtonDisabled,
type,
showCloseButton,
onDismiss,
}: DialogProps) => {
const dialogProps: IDialogProps = { const dialogProps: IDialogProps = {
hidden: !this.props.visible, hidden: !visible,
dialogContentProps: { dialogContentProps: {
type: this.props.type || DialogType.normal, type: type || DialogType.normal,
title: this.props.title, title,
subText: this.props.subText, subText,
styles: { styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT }, title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }, subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
}, },
showCloseButton: this.props.showCloseButton || false, showCloseButton: showCloseButton || false,
onDismiss: this.props.onDismiss, onDismiss,
}, },
modalProps: { isBlocking: this.props.isModal, isDarkOverlay: false }, modalProps: { isBlocking: isModal, isDarkOverlay: false },
minWidth: DIALOG_MIN_WIDTH, minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH, maxWidth: DIALOG_MAX_WIDTH,
}; };
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps;
const progressIndicatorProps: IProgressIndicatorProps = this.props.progressIndicatorProps;
const primaryButtonProps: IButtonProps = { const primaryButtonProps: IButtonProps = {
text: this.props.primaryButtonText, text: primaryButtonText,
disabled: this.props.primaryButtonDisabled || false, disabled: primaryButtonDisabled || false,
onClick: this.props.onPrimaryButtonClick, onClick: onPrimaryButtonClick,
}; };
const secondaryButtonProps: IButtonProps = const secondaryButtonProps: IButtonProps =
this.props.secondaryButtonText && this.props.onSecondaryButtonClick secondaryButtonText && onSecondaryButtonClick
? { ? {
text: this.props.secondaryButtonText, text: secondaryButtonText,
onClick: this.props.onSecondaryButtonClick, onClick: onSecondaryButtonClick,
} }
: undefined; : undefined;
@@ -106,5 +115,4 @@ export class Dialog extends React.Component<DialogProps> {
</DialogFooter> </DialogFooter>
</FluentDialog> </FluentDialog>
); );
} };
}

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

@@ -350,11 +350,11 @@ exports[`test render renders with filters 1`] = `
} }
> >
<div <div
className="ms-ScrollablePane root-72" className="ms-ScrollablePane root-40"
data-is-scrollable="true" data-is-scrollable="true"
> >
<div <div
className="stickyAbove-74" className="stickyAbove-42"
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-73" className="ms-ScrollablePane--contentContainer contentContainer-41"
data-is-scrollable={true} data-is-scrollable={true}
> >
<Sticky <Sticky
@@ -691,18 +691,18 @@ exports[`test render renders with filters 1`] = `
validateOnLoad={true} validateOnLoad={true}
> >
<div <div
className="ms-TextField directoryListFilterTextBox root-78" className="ms-TextField directoryListFilterTextBox root-46"
> >
<div <div
className="ms-TextField-wrapper" className="ms-TextField-wrapper"
> >
<div <div
className="ms-TextField-fieldGroup fieldGroup-79" className="ms-TextField-fieldGroup fieldGroup-47"
> >
<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-80" className="ms-TextField-field field-48"
id="TextField0" id="TextField0"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
@@ -1900,7 +1900,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-89" className="ms-Button ms-Button--default is-disabled directoryListButton root-57"
data-is-focusable={false} data-is-focusable={false}
disabled={true} disabled={true}
onClick={[Function]} onClick={[Function]}
@@ -1912,7 +1912,7 @@ exports[`test render renders with filters 1`] = `
type="button" type="button"
> >
<span <span
className="ms-Button-flexContainer flexContainer-90" className="ms-Button-flexContainer flexContainer-58"
data-automationid="splitbuttonprimary" data-automationid="splitbuttonprimary"
> >
<div <div
@@ -1943,7 +1943,7 @@ exports[`test render renders with filters 1`] = `
</List> </List>
</div> </div>
<div <div
className="stickyBelow-75" className="stickyBelow-43"
style={ style={
Object { Object {
"bottom": "0px", "bottom": "0px",
@@ -1954,7 +1954,7 @@ exports[`test render renders with filters 1`] = `
} }
> >
<div <div
className="stickyBelowItems-76" className="stickyBelowItems-44"
/> />
</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

@@ -128,21 +128,21 @@ class InputTypeaheadViewModel {
}, },
}, },
callback: { callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => { onClick: (_node: unknown, _a: unknown, item: OnClickItem) => {
cache.selection = item; cache.selection = item;
if (params.selection) { if (params.selection) {
params.selection(item); params.selection(item);
} }
}, },
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) { onResult(_node: unknown, query: any) {
cache.inputValue = query; cache.inputValue = query;
if (params.inputValue) { if (params.inputValue) {
params.inputValue(query); params.inputValue(query);
} }
}, },
}, },
template: (query: string, item: any) => { template: (_query: string, item: any) => {
// Don't display id if caption *IS* the id // Don't display id if caption *IS* the id
return item.caption === item.value return item.caption === item.value
? "<span>{{caption}}</span>" ? "<span>{{caption}}</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

@@ -1,25 +1,25 @@
import { Card } from "@uifabric/react-cards"; import { Card } from "@uifabric/react-cards";
import { import {
BaseButton,
Button,
FontWeights, FontWeights,
Icon, Icon,
IconButton, IconButton,
Image, Image,
ImageFit, ImageFit,
Persona,
Text,
Link, Link,
BaseButton,
Button,
LinkBase, LinkBase,
Persona,
Separator, Separator,
TooltipHost,
Spinner, Spinner,
SpinnerSize, SpinnerSize,
Text,
TooltipHost,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "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 +34,48 @@ 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 cardItemGapBig = 10;
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);
this.state = {
isDeletingPublishedNotebook: false,
};
}
public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
const options: Intl.DateTimeFormatOptions = { const options: Intl.DateTimeFormatOptions = {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric", day: "numeric",
}; };
const dateString = new Date(this.props.data.created).toLocaleString("default", options); const dateString = new Date(data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb"); const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb");
return ( const renderTruncatedDescription = (): string => {
<Card let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars);
style={{ background: "white" }} if (data.description.length > cardDescriptionMaxChars) {
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 +85,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,13 +102,13 @@ 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 | LinkBase, MouseEvent>
| React.MouseEvent< | React.MouseEvent<
@@ -239,4 +121,112 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
event.preventDefault(); event.preventDefault();
activate(); activate();
}; };
}
return (
<Card
style={{ background: "white" }}
aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: CARD_WIDTH, childrenGap: 0 }}
onClick={(event) => handlerOnClick(event, onClick)}
>
{isDeletingPublishedNotebook && (
<Card.Item tokens={{ padding: cardItemGapBig }}>
<Spinner
size={SpinnerSize.large}
label={`Deleting '${cardTitle}'`}
styles={{ root: { height: cardDeleteSpinnerHeight } }}
/>
</Card.Item>
)}
{!isDeletingPublishedNotebook && (
<>
<Card.Item tokens={{ padding: cardItemGapBig }}>
<Persona imageUrl={data.isSample && CosmosDBLogo} text={data.author} secondaryText={dateString} />
</Card.Item>
<Card.Item>
<Image
src={data.thumbnailUrl}
width={CARD_WIDTH}
height={cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: cardItemGapBig } }}>
<Text variant="small" nowrap styles={{ root: { height: smallTextLineHeight } }}>
{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>
<Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: cardItemGapSmall,
paddingBottom: cardItemGapSmall,
},
}}
nowrap
>
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: smallTextLineHeight * 2 } }}>
{renderTruncatedDescription()}
</Text>
<span>
{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>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: cardItemGapBig,
marginRight: cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<span>
{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>
</Card.Section>
)}
</>
)}
</Card>
);
};

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