Compare commits

...

76 Commits

Author SHA1 Message Date
Chris-MS-896
b000115afe Merge branch 'master' into v-yiqcao/nullCheck 2021-01-28 17:59:16 -06:00
Tim Sander
88d8200c14 Check that customer is using Mongo 3.6 before applying index everything policy (#410) 2021-01-28 15:26:47 -06:00
Srinath Narayanan
6aaddd9c60 Added localization for the Self Serve Model (#406)
* added localization for selfserve model

* added comment

* addressed PR comments

* fixed format errors

* Addressed PR comments
2021-01-28 11:17:02 -08:00
Chris-MS-896
e6eb59a7b2 "refactor" 2021-01-28 12:46:25 -06:00
Jordi Bunster
f8ede0cc1e Remove Q from ViewModels (#390)
I got cold feet at the thought of merging #324 in one go, so I'm going to split it into smaller chunks and keep rebasing the large one until there's no more Q.
2021-01-28 18:13:26 +00:00
Laurent Nguyen
bddb288a89 Update package versions and package-lock.json (#404)
The file `package-lock.json` is not in sync with `package.json` anymore. This causes build issues when upgrading a package.
This change sync's `package-lock.json` and fixes the build issues.
2021-01-28 08:50:24 +00:00
Steve Faulkner
a14d20a88e Fix applyExplorerBindings call in Portal (#408) 2021-01-27 20:37:14 -06:00
Steve Faulkner
f1db1ed978 Region Select Button (#407) 2021-01-27 15:32:53 -06:00
Laurent Nguyen
86a483c3a4 Fix notebook cell selection bug (#402)
This fixes a bug that prevents getting focus to a text cell (effectively preventing editing) when the window height is small after double-clicking on a neighboring code cell.
The issue is that selecting a text cell is broken likely because there's a behavior change in MonacoEditor that keeps the focus on the code cell. The selection issue will probably be resolved when migrating the text cell to Monaco (which will acquire and keep focus the same way), but for now, this will disable the faulty code which doesn't appear to work anymore (presumably auto-scrolling to the cell).
2021-01-27 09:09:54 +00:00
Tanuj Mittal
263262a040 Update Juno endpoints to pass subscriptionId (#339)
Corresponding [server side change](https://msdata.visualstudio.com/CosmosDB/_git/CosmosDB-portal/pullrequest/464443?_a=overview) has been deployed to Prod so now we can go ahead with DE side changes.
2021-01-27 08:08:58 +00:00
Chris-MS-896
9590e8da3c Merge branch 'master' into v-yiqcao/nullCheck 2021-01-26 17:50:56 -06:00
victor-meng
bd4d8da065 Move notification console to react (#400) 2021-01-26 15:32:37 -08:00
Chris-MS-896
1d2a7663f5 no message 2021-01-26 17:21:52 -06:00
Steve Faulkner
59ec18cd9b Add basic static code metrics (#396) 2021-01-26 13:13:13 -06:00
Srinath Narayanan
49bf8c60db Added more Self Serve functionalities (#401)
* added recursion and inition decorators

* working version

* added todo comment and removed console.log

* Added Recursive add

* removed type requirement

* proper resolution of promises

* added custom element and base class

* Made selfServe standalone page

* Added custom renderer as async type

* Added overall defaults

* added inital open from data explorer

* removed landingpage

* added feature for self serve type

* renamed sqlx->example and added invalid type

* Added comments for Example

* removed unnecessary changes

* Resolved PR comments

Added tests
Moved onSubmt and initialize inside base class
Moved testExplorer to separate folder
made fields of SelfServe Class non static

* fixed lint errors

* fixed compilation errors

* Removed reactbinding changes

* renamed dropdown -> choice

* Added SelfServeComponent

* Addressed PR comments

* added toggle, visibility, text display,commandbar

* added sqlx example

* added onRefrssh

* formatting changes

* rmoved radioswitch display

* updated smartui tests

* Added more tests

* onSubmit -> onSave

* Resolved PR comments
2021-01-26 09:44:14 -08:00
Steve Faulkner
b0b973b21a Refactor explorer config into useKnockoutExplorer hook (#397)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2021-01-25 13:56:15 -06:00
Chris-MS-896
3529e80f0d no message (#398) 2021-01-22 10:02:35 -06:00
Srinath Narayanan
a298fd8389 Added message to indicate compound indexes are not supported in Mongo Index editor (#395)
* mongo message

* Added test and bug fix in Main.tsx

* format changes

* added new formatting

* added null check
2021-01-21 10:56:05 -08:00
Steve Faulkner
1ecc467f60 Remove IE nuget (#394) 2021-01-20 12:46:12 -06:00
Steve Faulkner
b3cafe3468 Add telemetry to Spark+Synapse Pools (#392) 2021-01-20 11:08:29 -06:00
Steve Faulkner
4be53284b5 Prettier 2.0 (#393) 2021-01-20 09:15:01 -06:00
Srinath Narayanan
c1937ca464 Added the Self Serve Data Model (#367)
* added recursion and inition decorators

* working version

* added todo comment and removed console.log

* Added Recursive add

* removed type requirement

* proper resolution of promises

* added custom element and base class

* Made selfServe standalone page

* Added custom renderer as async type

* Added overall defaults

* added inital open from data explorer

* removed landingpage

* added feature for self serve type

* renamed sqlx->example and added invalid type

* Added comments for Example

* removed unnecessary changes

* Resolved PR comments

Added tests
Moved onSubmt and initialize inside base class
Moved testExplorer to separate folder
made fields of SelfServe Class non static

* fixed lint errors

* fixed compilation errors

* Removed reactbinding changes

* renamed dropdown -> choice

* Added SelfServeComponent

* Addressed PR comments

* merged master

* added selfservetype.none for emulator and hosted experience

* fixed formatting errors

* Removed "any" type

* undid package.json changes
2021-01-19 22:42:45 -08:00
Steve Faulkner
2b2de7c645 Migrated Hosted Explorer to React (#360)
Co-authored-by: Victor Meng <vimeng@microsoft.com>
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2021-01-19 16:31:55 -06:00
Deborah Chen
8c40df0fa1 Adding in experimentation for autoscale test (#345)
* Adding autoscale flight info

* Add flight info to cassandra collection pane

* Add telemetry for autoscale toggle on/off in create resource blade and scale/settings

* Run formatting and add expected properties to test file

* removing empty line

* Updating to pass unit tests

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-01-15 17:15:15 -06:00
Steve Faulkner
fcbc9474ea Remove Preview for Synapse Link (#389) 2021-01-15 09:51:14 -06:00
Steve Faulkner
81f861af39 Empty commit to refresh nuget after transient failures 2021-01-14 17:37:24 -06:00
victor-meng
9afa29cdb6 Properly construct the query to delete Cassandra row (#388) 2021-01-14 16:59:31 -06:00
Chris-MS-896
9a1e8b2d87 Add rest of three utils files to Matser (#370)
* 'minor change'
2021-01-13 17:49:06 -06:00
Tim Sander
babda4d9cb fix issue where Mongo indexing checkbox stops adding wildcard index (#384) 2021-01-12 18:38:16 -06:00
Steve Faulkner
9d20a13dd4 Warn on SubQuery (#378) 2021-01-12 13:53:15 -06:00
Chris-MS-896
3effbe6991 no message (#372) 2021-01-12 13:09:20 -06:00
Chris-MS-896
af53697ff4 Add file of Terminal to Master (#371)
* "minor changes"
2021-01-12 12:55:47 -06:00
Chris-MS-896
b1ad80480e Add two files of notebook component in Matser (#363)
* “minor changes”
2021-01-12 12:55:21 -06:00
Armando Trejo Oliver
9247a6c4a2 A11y fixes - Add a skip link and remove duplicate ids (#381)
* Add a skip link to allow people who navigate sequentially through content more direct access to the primary content of the Data Explorer

Co-authored-by: Chris Cao (Zen3 Infosolutions America Inc) <v-yiqcao@microsoft.com>

* Rename id of partition key field in  Add Collection Pane to ensure no  elements contain duplicate attributes.

Co-authored-by: Chris Cao (Zen3 Infosolutions America Inc) <v-yiqcao@microsoft.com>
2021-01-12 09:55:04 -08:00
Steve Faulkner
767d46480e Revert TablesEntitiyListViewModel changes (#382) 2021-01-11 16:16:40 -06:00
Chris-MS-896
2d98c5d269 add ArraysByKeyCache.ts (#366)
* 'add ArraysByKeyCache'
* "minor change"
2021-01-08 22:51:50 -06:00
Steve Faulkner
6627172a52 Add Architecture Diagram to README (#380) 2021-01-08 22:20:40 -06:00
Steve Faulkner
19fa5e17a5 Fix JSONEditor bug with undefined value (#379) 2021-01-08 22:20:06 -06:00
Chris-MS-896
a4a367a212 Add all arm request related files to Matser (#373)
* “minor changes”
* 'changes for unit test'
2021-01-08 21:56:29 -06:00
Chris-MS-896
983c9201bb Add two files of GraphExplorer component in Master (#365) 2021-01-08 21:14:53 -06:00
Chris-MS-896
76d7f00a90 Add two files of Table to master (#364) 2021-01-08 20:56:59 -06:00
Chris-MS-896
6490597736 add CollapsiblePanel/CollapsiblePanelComponent.ts and /ErrorDisplayComponent to Master (#357) 2021-01-08 20:29:15 -06:00
Chris-MS-896
229119e697 add file offerUtility to tsconfig (#356) 2021-01-08 20:14:12 -06:00
Steve Faulkner
ceefd7c615 Fix Conflict Resolution path setting (#377)
* Fix Conflict Resolution path setting

* Fix test
2021-01-08 12:36:44 -06:00
Laurent Nguyen
6e619175c6 Fix missing scrollbar in left pane when too many collections/notebooks (#375)
Constrain left pane container to height: 100% so that scrollbar show up when content wants to overflow.
The `main` classname seems too generic, but I left it alone (so I don't break anything), since this part will eventually be ported to React.
2021-01-08 14:00:26 +00:00
victor-meng
08e8bf4bcf Fix two settings tab issues (#374) 2021-01-07 15:38:13 -06:00
Chris-MS-896
89dc0f394b Add Spliter file to Master (#358) 2021-01-06 12:51:42 -06:00
Chris-MS-896
30e0001b7f no message (#359) 2021-01-05 16:45:13 -06:00
Steve Faulkner
4a8f408112 Add UX for Mongo indexing experiment (#368)
Co-authored-by: Tim Sander <tisande@microsoft.com>
2021-01-05 16:04:55 -06:00
Armando Trejo Oliver
e801364800 Remove stale .main class from tree.less (#362)
.main CSS class has a naming conflict with Moncao editor CSS classes and this is causing  A11y issues with Moncao editor.

This class should no longer be used since we moved to the new tree component in REACT, so I am removing it. From my testing, this is not affecting anything.

If we find any styling issue later, we should fix without adding back this class.
2021-01-05 10:53:55 -08:00
victor-meng
a55f2d0de9 Free tier improvements in DE (#348)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-01-04 12:56:55 -08:00
Steve Faulkner
d40b1aa9b5 Remove Empty Query Logging (#361) 2021-01-04 13:58:01 -06:00
Steve Faulkner
cc63cdc1fd Remove dependency on canvas (#354) 2020-12-26 21:56:37 -06:00
Steve Faulkner
c3058ee5a9 Check for undefined query results (#350) 2020-12-18 19:55:32 -06:00
Steve Faulkner
b000631a0c Revert web.config changes (#349) 2020-12-18 19:26:10 -06:00
vchske
e8f4c8f93c Cost Estimate Changes (#342)
* Initial change of estimated cost to table format

* Converted cost estimate to table format and added different data for current vs updated cost estimates.

* lint fixes

* Changed the names of some interfaces

* Refactored a unit call to use an argument interface to avoid future confusion.

* Changed the severity of the save warning

* Format fix

* Fixed test due to styling change

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2020-12-18 16:15:55 -08:00
Steve Faulkner
16bde97e47 Rewrite URL for IE users (#340) 2020-12-18 16:08:40 -06:00
Steve Faulkner
6da43ee27b Publish IE specific Nuget package (#347)
* Publish IE specific Nuget package

* Require ally tests to pass
2020-12-17 17:41:38 -06:00
Gahl Levy
ebae484b8f Fix duplicate settings tabs (#343)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2020-12-17 13:01:36 -08:00
Steve Faulkner
dfb1b50621 Explorer.ts Cleanup (#341)
Co-authored-by: victor-meng <56978073+victor-meng@users.noreply.github.com>
2020-12-16 20:00:39 -06:00
victor-meng
f54e8eb692 Move queryDocuments out of DataAccessUtility (#334) 2020-12-16 15:27:17 -08:00
Steve Faulkner
ea39c1d092 Fix offer update notification for AAD users (#338) 2020-12-11 13:38:57 -06:00
vchske
c21f42159f Updated cost messaging for new db/container pane (#333)
* Adds information text further explaining estimated cost.

* Minor tweak to cost messaging

* Updated unit tests

* Added text and link for capacity planner when choosing manual RUs
2020-12-11 10:06:43 -08:00
victor-meng
31e4b49f11 Only call getCollectionDataUsageSize for AAD users (#337) 2020-12-10 14:13:08 -08:00
Tanuj Mittal
40491ec9c5 Gallery related fixes (#312)
* AVERT fixes

* Remove enableCodeOfConduct feature flag

* Fix reporting abuse

* Add empty screen for Liked and Published tabs in Gallery

* fix build

* Remove unused code

* Fix standalone public gallery
2020-12-10 13:09:18 -08:00
Tanuj Mittal
e133df18dd Record baseUrl for OpenTerminal success/failure telemetry (#335)
This is useful to know which terminal is opening.
2020-12-10 19:54:21 +00:00
Steve Faulkner
0532ed26a2 Remove runner workflow that is no longer functioning (#332) 2020-12-01 10:23:18 -06:00
victor-meng
fd60c9c15e Remove RUPM (#328)
Remove all RUPM code
2020-12-01 07:06:38 +00:00
Chris-MS-896
04ab1f3918 '[Visual Requirement-Data Explorer (iframe)] On the Data Explorer page, luminosity contrast ratio of the borderline button is less than 3.:1.' (#331) 2020-11-30 15:32:28 -06:00
Chris-MS-896
b784ac0f86 [967093][Screen Readers- CosmosDB – Notification] Screen reader does not pass the combo-box list information (#329)
* ‘Bug fix: Screen reader does not pass the combo-box list information under notification field.’

* ‘update for comments’

* ‘load path refator’
2020-11-30 14:33:18 -06:00
Srinath Narayanan
28899f63d7 Fixed bug in fetching 'index transformation progress' header (#330)
* bug fix

* fixed formatting errors
2020-11-24 10:32:18 -08:00
victor-meng
9cbf632577 Get collection usage size with ARM metrics call (#327)
- Removed `readCollectionQuotaInfo` call. The only data we need is the usage size which we can get via the ARM metrics call instead.
- Added `getCollectionUsageSize` which fetches the `DataUsage` and `IndexUsage` metrics, converts them to KB, and returns the sum as the total usage size
2020-11-20 20:21:16 +00:00
victor-meng
17fd2185dc Move read offer to RP (#326) 2020-11-19 17:13:11 -08:00
Srinath Narayanan
a93c8509cd Added testExplorer and notebooks UI automated tests (#323)
* initial commit for notbooks pupeteer tests

* Added Auth

* added try catch block with screenshot for error

* Addressed PR comments

* renamed params

* renamed param

* fixed formatting error

* Updates mongo spec to remove waitFor on already awaited selector

* added logging statements

* format errors fixed

* added ci env variables

* increased delay for render

* removed logging

* added delay

* fix format error

* removed deletion

* reverted package.json change

Co-authored-by: zfoster <notzachfoster@gmail.com>
2020-11-19 09:29:38 -08:00
Laurent Nguyen
5c93c11bd9 Bug fix: match monaco-editor version with @nteract/monaco-editor (#322)
Opening notebook (which contains code cell), then "Items"-> document editor is broken. Our JsonEditor component will hang at `monaco.editor.create()`.

Matching the `monaco-editor` version with `@nteract/monaco-editor` fixes it.

I looked into using one, but we cannot rely nteract, because it does not get loaded if notebook isn't enabled. Forcing nteract to use ours (if at all possible) isn't a good idea, since their code is tuned to their version.

For now, we'll have to keep the versions in sync.
2020-11-19 14:33:23 +00:00
Srinath Narayanan
85d2378d3a Removed SettingsV1 code paths (#325)
* removed settingsv1 code path in collection.ts

* removed Settingsv1 code

* Moved AAD error message up the chain
2020-11-18 12:11:25 -08:00
595 changed files with 55857 additions and 58044 deletions

View File

@@ -3,7 +3,11 @@ PORTAL_RUNNER_PASSWORD=
PORTAL_RUNNER_SUBSCRIPTION= PORTAL_RUNNER_SUBSCRIPTION=
PORTAL_RUNNER_RESOURCE_GROUP= PORTAL_RUNNER_RESOURCE_GROUP=
PORTAL_RUNNER_DATABASE_ACCOUNT= PORTAL_RUNNER_DATABASE_ACCOUNT=
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY=
PORTAL_RUNNER_CONNECTION_STRING= PORTAL_RUNNER_CONNECTION_STRING=
NOTEBOOKS_TEST_RUNNER_TENANT_ID=
NOTEBOOKS_TEST_RUNNER_CLIENT_ID=
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET=
CASSANDRA_CONNECTION_STRING= CASSANDRA_CONNECTION_STRING=
MONGO_CONNECTION_STRING= MONGO_CONNECTION_STRING=
TABLES_CONNECTION_STRING= TABLES_CONNECTION_STRING=

View File

@@ -14,7 +14,6 @@ src/Common/DataAccessUtilityBase.ts
src/Common/DeleteFeedback.ts src/Common/DeleteFeedback.ts
src/Common/DocumentClientUtilityBase.ts src/Common/DocumentClientUtilityBase.ts
src/Common/EditableUtility.ts src/Common/EditableUtility.ts
src/Common/EnvironmentUtility.ts
src/Common/HashMap.test.ts src/Common/HashMap.test.ts
src/Common/HashMap.ts src/Common/HashMap.ts
src/Common/HeadersUtility.test.ts src/Common/HeadersUtility.test.ts
@@ -43,7 +42,6 @@ src/Contracts/ViewModels.ts
src/Controls/Heatmap/Heatmap.test.ts src/Controls/Heatmap/Heatmap.test.ts
src/Controls/Heatmap/Heatmap.ts src/Controls/Heatmap/Heatmap.ts
src/Controls/Heatmap/HeatmapDatatypes.ts src/Controls/Heatmap/HeatmapDatatypes.ts
src/Definitions/adal.d.ts
src/Definitions/datatables.d.ts src/Definitions/datatables.d.ts
src/Definitions/gif.d.ts src/Definitions/gif.d.ts
src/Definitions/globals.d.ts src/Definitions/globals.d.ts
@@ -202,8 +200,6 @@ src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts src/Explorer/Tabs/QueryTab.ts
src/Explorer/Tabs/QueryTablesTab.ts src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/SettingsTab.test.ts
src/Explorer/Tabs/SettingsTab.ts
src/Explorer/Tabs/SparkMasterTab.ts src/Explorer/Tabs/SparkMasterTab.ts
src/Explorer/Tabs/StoredProcedureTab.ts src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts src/Explorer/Tabs/TabComponents.ts
@@ -245,9 +241,6 @@ src/Platform/Hosted/Authorization.ts
src/Platform/Hosted/DataAccessUtility.ts src/Platform/Hosted/DataAccessUtility.ts
src/Platform/Hosted/ExplorerFactory.ts src/Platform/Hosted/ExplorerFactory.ts
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
src/Platform/Hosted/Helpers/ConnectionStringParser.ts
src/Platform/Hosted/HostedUtils.test.ts
src/Platform/Hosted/HostedUtils.ts
src/Platform/Hosted/Main.ts src/Platform/Hosted/Main.ts
src/Platform/Hosted/Maint.test.ts src/Platform/Hosted/Maint.test.ts
src/Platform/Hosted/NotificationsClient.ts src/Platform/Hosted/NotificationsClient.ts
@@ -290,8 +283,6 @@ src/Utils/DatabaseAccountUtils.ts
src/Utils/JunoUtils.ts src/Utils/JunoUtils.ts
src/Utils/MessageValidation.ts src/Utils/MessageValidation.ts
src/Utils/NotebookConfigurationUtils.ts src/Utils/NotebookConfigurationUtils.ts
src/Utils/OfferUtils.test.ts
src/Utils/OfferUtils.ts
src/Utils/PricingUtils.test.ts src/Utils/PricingUtils.test.ts
src/Utils/QueryUtils.test.ts src/Utils/QueryUtils.test.ts
src/Utils/QueryUtils.ts src/Utils/QueryUtils.ts

View File

@@ -1,41 +1,39 @@
module.exports = { module.exports = {
env: { env: {
browser: true, browser: true,
es6: true es6: true,
}, },
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"], plugins: ["@typescript-eslint", "no-null", "prefer-arrow"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
SharedArrayBuffer: "readonly" SharedArrayBuffer: "readonly",
}, },
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
parserOptions: { parserOptions: {
ecmaFeatures: { ecmaFeatures: {
jsx: true jsx: true,
}, },
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: "module" sourceType: "module",
}, },
overrides: [ overrides: [
{ {
files: ["**/*.tsx"], files: ["**/*.tsx"],
env: { extends: ["plugin:react/recommended"], // TODO: Add react-hooks
jest: true plugins: ["react"],
},
extends: ["plugin:react/recommended"],
plugins: ["react"]
}, },
{ {
files: ["**/*.{test,spec}.{ts,tsx}"], files: ["**/*.{test,spec}.{ts,tsx}"],
env: { env: {
jest: true jest: true,
}, },
extends: ["plugin:jest/recommended"], extends: ["plugin:jest/recommended"],
plugins: ["jest"] plugins: ["jest"],
} },
], ],
rules: { rules: {
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
curly: "error", curly: "error",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
@@ -43,12 +41,13 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }], "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error", eqeqeq: "error",
"react/display-name": "off",
"no-restricted-syntax": [ "no-restricted-syntax": [
"error", "error",
{ {
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]", selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
message: "Do not use JSON.stringify(error). It will print '{}'" message: "Do not use JSON.stringify(error). It will print '{}'",
} },
] ],
} },
}; };

View File

@@ -9,6 +9,20 @@ on:
branches: branches:
- master - master
jobs: jobs:
codemetrics:
runs-on: ubuntu-latest
name: "Log Code Metrics"
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm ci
- run: node utils/codeMetrics.js
env:
CODE_METRICS_APP_ID: ${{ secrets.CODE_METRICS_APP_ID }}
compile: compile:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "Compile TypeScript" name: "Compile TypeScript"
@@ -94,13 +108,14 @@ jobs:
npm ci npm ci
npm start & npm start &
npm run wait-for-server npm run wait-for-server
npx jest -c ./jest.config.e2e.js --detectOpenHandles sql npx jest -c ./jest.config.e2e.js --detectOpenHandles test/sql/container.spec.ts
shell: bash shell: bash
env: env:
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator" DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
PLATFORM: "Emulator" PLATFORM: "Emulator"
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
if: failure()
with: with:
name: screenshots name: screenshots
path: failed-* path: failed-*
@@ -141,24 +156,33 @@ jobs:
run: | run: |
npm ci npm ci
npm start & npm start &
node utils/cleanupDBs.js
npm run wait-for-server npm run wait-for-server
npm run test:e2e npm run test:e2e
shell: bash shell: bash
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 }}
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 }}
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }} PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }} MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }} CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }} TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html" DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
if: failure()
with: with:
name: screenshots name: screenshots
path: failed-* path: failed-*
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/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -182,7 +206,7 @@ jobs:
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
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/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}

View File

@@ -1,25 +0,0 @@
name: Runners
on:
schedule:
- cron: "0 * 1 * *"
jobs:
sqlcreatecollection:
runs-on: ubuntu-latest
name: "SQL | Create Collection"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm ci
- run: npm run test:e2e
env:
PORTAL_RUNNER_APP_INSIGHTS_KEY: ${{ secrets.PORTAL_RUNNER_APP_INSIGHTS_KEY }}
PORTAL_RUNNER_USERNAME: ${{ secrets.PORTAL_RUNNER_USERNAME }}
PORTAL_RUNNER_PASSWORD: ${{ secrets.PORTAL_RUNNER_PASSWORD }}
PORTAL_RUNNER_SUBSCRIPTION: 69e02f2d-f059-4409-9eac-97e8a276ae2c
PORTAL_RUNNER_RESOURCE_GROUP: runners
PORTAL_RUNNER_DATABASE_ACCOUNT: portal-sql-runner
- uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshots
path: failure.png

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

View File

@@ -13,29 +13,18 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht
### Watch mode ### Watch mode
Run `npm run watch` to start the development server and automatically rebuild on changes Run `npm start` to start the development server and automatically rebuild on changes
### Specifying Development Platform ### Hosted Development (https://cosmos.azure.com)
Setting the environment variable `PLATFORM` during the build process will force the explorer to load the specified platform. By default in development it will run in `Hosted` mode. Valid options: - Visit: `https://localhost:1234/hostedExplorer.html`
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
- Hosted - The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
- Emulator
- Portal
`PLATFORM=Emulator npm run watch`
### Hosted Development
The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin to use hostedExplorer.html and change entry for index to use HostedExplorer.ts.
### Emulator Development ### Emulator Development
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you. - Start the Cosmos Emulator
- Visit: https://localhost:1234/index.html
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
#### Setting up a Remote Emulator #### Setting up a Remote Emulator
@@ -55,16 +44,8 @@ The Cosmos emulator currently only runs in Windows environments. You can still d
### Portal Development ### Portal Development
The Cosmos Portal that consumes this repo is not currently open source. If you have access to this project, `npm run build` will copy the built files over to the portal where they will be loaded by the portal development environment - Visit: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
- You may have to manually visit https://localhost:1234/explorer.html first and click through any SSL certificate warnings
You can however load a local running instance of data explorer in the production portal.
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
Live reload will occur, but data explorer will not properly integrate again with the parent iframe. You will have to manually reload the page.
### Testing ### Testing
@@ -88,6 +69,10 @@ Jest and Puppeteer are used for end to end browser based tests and are contained
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details. We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
### Architechture
[![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)
# Contributing # Contributing
Please read the [contribution guidelines](./CONTRIBUTING.md). Please read the [contribution guidelines](./CONTRIBUTING.md).

View File

@@ -1,3 +1,4 @@
module.exports = { module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"] presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"],
plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]],
}; };

7
canvas/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Why?
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved

1
canvas/index.js Normal file
View File

@@ -0,0 +1 @@
module.exports = {}

11
canvas/package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "canvas",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

1963
externals/adal.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,6 @@ module.exports = {
slowMo: 55, slowMo: 55,
defaultViewport: null, defaultViewport: null,
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
args: ["--disable-web-security"] args: ["--disable-web-security"],
} },
}; };

View File

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

View File

@@ -39,11 +39,11 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results // An object that configures minimum threshold enforcement for coverage results
coverageThreshold: { coverageThreshold: {
global: { global: {
branches: 20, branches: 22,
functions: 24, functions: 28,
lines: 30, lines: 33,
statements: 29.0 statements: 31,
} },
}, },
// Make calling deprecated APIs throw helpful error messages // Make calling deprecated APIs throw helpful error messages
@@ -76,7 +76,7 @@ module.exports = {
"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",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs" "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",
}, },
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
@@ -164,11 +164,11 @@ module.exports = {
// A map from regular expressions to paths to transformers // A map from regular expressions to paths to transformers
transform: { transform: {
"^.+\\.html?$": "html-loader-jest", "^.+\\.html?$": "html-loader-jest",
"^.+\\.[t|j]sx?$": "babel-jest" "^.+\\.[t|j]sx?$": "babel-jest",
}, },
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: ["/node_modules/", "/externals/"] transformIgnorePatterns: ["/node_modules/", "/externals/"],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined, // unmockedModulePathPatterns: undefined,

View File

@@ -3,8 +3,8 @@
/******************************************************************************/ /******************************************************************************/
@font-face { @font-face {
font-family: wf_segoe-ui_normal; font-family: wf_segoe-ui_normal;
src: url('../../fonts/segoe-ui/west-european/normal/latest.woff'); src: url("../../fonts/segoe-ui/west-european/normal/latest.woff");
} }
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; @DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@@ -20,26 +20,26 @@
COLORS COLORS
/******************************************************************************/ /******************************************************************************/
@AccentMediumHigh: #0058AD; @AccentMediumHigh: #0058ad;
@AccentMedium: #004E87; @AccentMedium: #004e87;
@AccentHigh: #1EBAED; @AccentHigh: #1ebaed;
@AccentExtraHigh: #55B3FF; @AccentExtraHigh: #55b3ff;
@AccentLow: #EDF6FF; @AccentLow: #edf6ff;
@AccentMediumLow: #DDEEFE; @AccentMediumLow: #ddeefe;
@AccentLight: #EEF7FF; @AccentLight: #eef7ff;
@AccentExtra: #DDF0FF; @AccentExtra: #ddf0ff;
@SelectionHigh: #B91F26; @SelectionHigh: #b91f26;
@BaseLight: #FFFFFF; @BaseLight: #ffffff;
@BaseDark: #000000; @BaseDark: #000000;
@NotificationLow: #FFF4CE; @NotificationLow: #fff4ce;
@NotificationHigh: #F9E9B0; @NotificationHigh: #f9e9b0;
@Purple1: #8A2DA5; @Purple1: #8a2da5;
@Dirty: #9b4f96; @Dirty: #9b4f96;
@BaseLow: #F2F2F2; @BaseLow: #f2f2f2;
@BaseMediumLow: #E6E6E6; @BaseMediumLow: #e6e6e6;
@BaseMedium: #CCCCCC; @BaseMedium: #cccccc;
@BaseMediumHigh: #767676; @BaseMediumHigh: #767676;
@BaseHigh: #393939; @BaseHigh: #393939;
@@ -53,7 +53,7 @@
@ErrorColor: @SelectionHigh; @ErrorColor: @SelectionHigh;
@SelectionColor: #3074B0; @SelectionColor: #3074b0;
@FocusColor: #605e5c; @FocusColor: #605e5c;
@@ -80,7 +80,7 @@
@ImgWidth: 14px; @ImgWidth: 14px;
@ImgHeight: 14px; @ImgHeight: 14px;
@toggleFontWeight:700; @toggleFontWeight: 700;
//Resource Tree //Resource Tree
@TreeLineHeight: 17px; @TreeLineHeight: 17px;
@@ -144,16 +144,16 @@
/**********************************************************************************/ /**********************************************************************************/
.flex-display(@display: flex) { .flex-display(@display: flex) {
display: ~"-webkit-@{display}"; display: ~"-webkit-@{display}";
display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox
display: ~"-ms-@{display}"; // IE11 display: ~"-ms-@{display}"; // IE11
display: @display; display: @display;
} }
.flex-direction(@direction: column) { .flex-direction(@direction: column) {
-webkit-flex-direction: @direction; -webkit-flex-direction: @direction;
-ms-flex-direction: @direction; -ms-flex-direction: @direction;
flex-direction: @direction; flex-direction: @direction;
} }
/************************************************************************************* /*************************************************************************************
@@ -161,32 +161,31 @@
**************************************************************************************/ **************************************************************************************/
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.selectedRadio, .selectedRadio,
.selectedRadio:hover, .selectedRadio:hover,
.selectedRadio:active, .selectedRadio:active,
.selectedRadio.dirty, .selectedRadio.dirty,
.tab [type=radio]:checked ~ label, .tab [type="radio"]:checked ~ label,
.tab [type=radio]:checked ~ label:hover { .tab [type="radio"]:checked ~ label:hover {
-ms-high-contrast-adjust: none; -ms-high-contrast-adjust: none;
-webkit-text-fill-color: HighlightText; -webkit-text-fill-color: HighlightText;
color: HighlightText; color: HighlightText;
border-color: HighlightText; border-color: HighlightText;
background-color: Highlight; background-color: Highlight;
} }
.queryMetricsSummaryTuple { .queryMetricsSummaryTuple {
th,
th, td { td {
&:nth-child(2) {
&:nth-child(2) { width: @IETableDataWidth;
width: @IETableDataWidth; }
}
&:nth-child(3) {
&:nth-child(3) { width: 50%;
width: 50%; }
}
}
} }
}
} }
/******************************************************************************************** /********************************************************************************************
@@ -194,15 +193,15 @@
*********************************************************************************************/ *********************************************************************************************/
.hover() { .hover() {
background-color: @AccentLight; background-color: @AccentLight;
} }
.active() { .active() {
background-color: @AccentExtra; background-color: @AccentExtra;
} }
.focus() { .focus() {
outline: 1px dashed @FocusColor; outline: 1px dashed @FocusColor;
} }
/************************************************************************************************ /************************************************************************************************
@@ -212,63 +211,87 @@
@ToggleWidth: 180px; @ToggleWidth: 180px;
.toggleSwitch() { .toggleSwitch() {
max-width: 100%; max-width: 100%;
margin-bottom: @SmallSpace; margin-bottom: @SmallSpace;
padding: @SmallSpace; padding: @SmallSpace;
cursor: pointer; cursor: pointer;
color: @BaseHigh; color: @BaseHigh;
font-weight: 400; font-weight: 400;
font-size: @mediumFontSize; font-size: @mediumFontSize;
font-family: @DataExplorerFont; font-family: @DataExplorerFont;
} }
.selectedToggle() { .selectedToggle() {
border-bottom: 2px solid @BaseHigh; border-bottom: 2px solid @BaseHigh;
} }
.unselectedToggle() { .unselectedToggle() {
color: @AccentMediumHigh; color: @AccentMediumHigh;
} }
/******************************************************************************************************** /********************************************************************************************************
Common Data Explorer Icons Common Data Explorer Icons
*********************************************************************************************************/ *********************************************************************************************************/
.dataExplorerIcons() { .dataExplorerIcons() {
cursor: pointer; cursor: pointer;
width: @ImgWidth; width: @ImgWidth;
height: @ImgHeight; height: @ImgHeight;
} }
/********************************************************************************************************* /*********************************************************************************************************
Info Tooltip Info Tooltip
**********************************************************************************************************/ **********************************************************************************************************/
.infoTooltip() { .infoTooltip() {
position: relative; position: relative;
display: inline-block; display: inline-block;
} }
.tooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) { .tooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) {
visibility: hidden; visibility: hidden;
background-color: @backgroundColor; background-color: @backgroundColor;
color: @textColor; color: @textColor;
position: absolute; position: absolute;
z-index: 1; z-index: 1;
left: @MediumSpace; left: @MediumSpace;
padding: @MediumSpace; padding: @MediumSpace;
} }
.tooltipTextAfter(@color: @BaseDark) { .tooltipTextAfter(@color: @BaseDark) {
content: ""; content: "";
position: absolute; position: absolute;
right: 100%; right: 100%;
border-style: solid; border-style: solid;
border-color: transparent @color transparent transparent; border-color: transparent @color transparent transparent;
left: 0px; left: 0px;
width: 0; width: 0;
height: 0; height: 0;
border-color: @InfoPointerColor transparent; border-color: @InfoPointerColor transparent;
} }
.tooltipVisible() { .tooltipVisible() {
visibility: visible; visibility: visible;
}
.inputTooltip() {
position: relative;
}
.inputTooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) {
background-color: @backgroundColor;
color: @textColor;
position: absolute;
z-index: 1;
padding: @MediumSpace;
}
.inputTooltipTextAfter(@color: @BaseDark) {
content: "";
position: absolute;
right: 100%;
border-style: solid;
border-color: transparent @color transparent transparent;
left: 10px;
width: 0;
height: 0;
border-color: @InfoPointerColor transparent;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,11 @@
@NavMediumSpace: 10px; @NavMediumSpace: 10px;
@NavLargeSpace: 15px; @NavLargeSpace: 15px;
.skip-link {
position: fixed;
top: -200px;
}
html { html {
font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
padding: 0px; padding: 0px;

View File

@@ -1,20 +1,12 @@
@import "./Common/Constants"; @import "./Common/Constants";
.main {
width: 100%;
float: left;
transition: all .0s ease-in-out;
-ms-transition: all 0s ease-in-out;
-webkit-transition: all 0s ease-in-out;
-moz-transition: all .0s ease-in-out;
height: 100%;
background-color: white;
border-left: 0px solid white;
}
.resourceTree { .resourceTree {
height: 100%; height: 100%;
flex: 0 0 auto; flex: 0 0 auto;
.main {
height: 100%;
}
} }
.resourceTreeScroll { .resourceTreeScroll {

6505
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,16 @@
"description": "Cosmos Explorer", "description": "Cosmos Explorer",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.9.0", "@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.4", "@azure/cosmos-language-service": "0.0.5",
"@jupyterlab/services": "6.0.0-rc.2", "@azure/identity": "1.2.1",
"@jupyterlab/terminal": "3.0.0-rc.2", "@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.5.9", "@microsoft/applicationinsights-web": "2.5.9",
"@nteract/commutable": "7.3.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",
"@nteract/data-explorer": "8.0.3", "@nteract/data-explorer": "8.0.3",
@@ -34,6 +38,7 @@
"@nteract/transform-vega": "7.0.6", "@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2", "@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3", "@phosphor/widgets": "1.9.3",
"@testing-library/jest-dom": "5.11.9",
"@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",
@@ -42,7 +47,7 @@
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "2.6.1", "canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19", "clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2", "copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2", "crossroads": "0.12.2",
@@ -59,6 +64,9 @@
"eslint-plugin-react": "7.20.0", "eslint-plugin-react": "7.20.0",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5", "html2canvas": "1.0.0-rc.5",
"i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23",
"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",
@@ -66,7 +74,8 @@
"jquery-ui-dist": "1.12.1", "jquery-ui-dist": "1.12.1",
"knockout": "3.5.1", "knockout": "3.5.1",
"mkdirp": "1.0.4", "mkdirp": "1.0.4",
"monaco-editor": "0.15.6", "monaco-editor": "0.18.1",
"msal": "1.4.4",
"object.entries": "1.1.0", "object.entries": "1.1.0",
"office-ui-fabric-react": "7.134.1", "office-ui-fabric-react": "7.134.1",
"p-retry": "4.2.0", "p-retry": "4.2.0",
@@ -78,14 +87,17 @@
"react-animate-height": "2.0.8", "react-animate-height": "2.0.8",
"react-dnd": "9.4.0", "react-dnd": "9.4.0",
"react-dnd-html5-backend": "9.4.0", "react-dnd-html5-backend": "9.4.0",
"react-dom": "16.9.0", "react-dom": "16.13.1",
"react-hotkeys": "2.0.0", "react-hotkeys": "2.0.0",
"react-i18next": "11.8.5",
"react-notification-system": "0.2.17", "react-notification-system": "0.2.17",
"react-redux": "7.1.3", "react-redux": "7.1.3",
"redux": "4.0.4", "redux": "4.0.4",
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
"rxjs": "6.6.3", "rxjs": "6.6.3",
"styled-components": "4.3.2", "styled-components": "4.3.2",
"swr": "0.4.0",
"text-encoding": "0.7.0", "text-encoding": "0.7.0",
"underscore": "1.9.1", "underscore": "1.9.1",
"url-polyfill": "1.1.7", "url-polyfill": "1.1.7",
@@ -99,6 +111,7 @@
"@babel/preset-env": "7.9.0", "@babel/preset-env": "7.9.0",
"@babel/preset-react": "7.9.4", "@babel/preset-react": "7.9.4",
"@babel/preset-typescript": "7.9.0", "@babel/preset-typescript": "7.9.0",
"@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56", "@types/codemirror": "0.0.56",
"@types/crossroads": "0.0.30", "@types/crossroads": "0.0.30",
@@ -107,7 +120,7 @@
"@types/enzyme-adapter-react-16": "1.0.6", "@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.3", "@types/expect-puppeteer": "4.4.3",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.31",
"@types/jest": "23.3.10", "@types/jest": "26.0.20",
"@types/jest-environment-puppeteer": "4.3.2", "@types/jest-environment-puppeteer": "4.3.2",
"@types/memoize-one": "4.1.1", "@types/memoize-one": "4.1.1",
"@types/node": "12.11.1", "@types/node": "12.11.1",
@@ -115,8 +128,8 @@
"@types/prop-types": "15.5.8", "@types/prop-types": "15.5.8",
"@types/puppeteer": "3.0.1", "@types/puppeteer": "3.0.1",
"@types/q": "1.5.1", "@types/q": "1.5.1",
"@types/react": "16.9.49", "@types/react": "17.0.0",
"@types/react-dom": "16.0.7", "@types/react-dom": "17.0.0",
"@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/sinon": "2.3.3", "@types/sinon": "2.3.3",
@@ -126,7 +139,6 @@
"@types/webfontloader": "1.6.29", "@types/webfontloader": "1.6.29",
"@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",
"adal-angular": "1.0.15",
"axe-puppeteer": "1.1.0", "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",
@@ -141,7 +153,9 @@
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"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",
"expose-loader": "0.7.5", "expose-loader": "0.7.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-loader": "0.5.5", "html-loader": "0.5.5",
@@ -158,7 +172,7 @@
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"prettier": "1.19.1", "prettier": "2.2.1",
"puppeteer": "4.0.0", "puppeteer": "4.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"rimraf": "3.0.0", "rimraf": "3.0.0",

View File

@@ -3,7 +3,6 @@
"offerThroughput": 400, "offerThroughput": 400,
"databaseLevelThroughput": false, "databaseLevelThroughput": false,
"collectionId": "Persons", "collectionId": "Persons",
"rupmEnabled": false,
"partitionKey": { "kind": "Hash", "paths": ["/name"] }, "partitionKey": { "kind": "Hash", "paths": ["/name"] },
"data": [ "data": [
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)", "g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",
@@ -13,4 +12,4 @@
"g.V('1').addE('knows').to(g.V('2')).outV().addE('knows').to(g.V('3'))", "g.V('1').addE('knows').to(g.V('2')).outV().addE('knows').to(g.V('3'))",
"g.V('3').addE('knows').to(g.V('4'))" "g.V('3').addE('knows').to(g.V('4'))"
] ]
} }

View File

@@ -1,6 +1,7 @@
export enum AuthType { export enum AuthType {
AAD = "aad", AAD = "aad",
EncryptedToken = "encryptedtoken", EncryptedToken = "encryptedtoken",
MasterKey = "masterkey", MasterKey = "masterkey",
ResourceToken = "resourcetoken" ResourceToken = "resourcetoken",
} ConnectionString = "connectionstring",
}

View File

@@ -1,21 +1,22 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ReactBindingHandler from "./ReactBindingHandler"; import * as ReactBindingHandler from "./ReactBindingHandler";
import "../Explorer/Tables/DataTable/DataTableBindingManager";
export class BindingHandlersRegisterer {
public static registerBindingHandlers() { export class BindingHandlersRegisterer {
ko.bindingHandlers.setTemplateReady = { public static registerBindingHandlers() {
init( ko.bindingHandlers.setTemplateReady = {
element: any, init(
wrappedValueAccessor: () => any, element: any,
allBindings?: ko.AllBindings, wrappedValueAccessor: () => any,
viewModel?: any, allBindings?: ko.AllBindings,
bindingContext?: ko.BindingContext viewModel?: any,
) { bindingContext?: ko.BindingContext
const value = ko.unwrap(wrappedValueAccessor()); ) {
bindingContext?.$data.isTemplateReady(value); const value = ko.unwrap(wrappedValueAccessor());
} bindingContext?.$data.isTemplateReady(value);
} as ko.BindingHandler; },
} as ko.BindingHandler;
ReactBindingHandler.Registerer.register();
} ReactBindingHandler.Registerer.register();
} }
}

View File

@@ -42,7 +42,7 @@ export class Registerer {
// Initial rendering at mount point // Initial rendering at mount point
ReactDOM.render(adapter.renderComponent(), element); ReactDOM.render(adapter.renderComponent(), element);
} },
} as ko.BindingHandler; } as ko.BindingHandler;
} }
} }

View File

@@ -40,7 +40,7 @@ export class ArrayHashMap<T> {
public forEach(key: string, iteratorFct: (value: T) => void) { public forEach(key: string, iteratorFct: (value: T) => void) {
const values = this.store.get(key); const values = this.store.get(key);
if (values) { if (values) {
values.forEach(value => iteratorFct(value)); values.forEach((value) => iteratorFct(value));
} }
} }

View File

@@ -1,441 +1,414 @@
import { HashMap } from "./HashMap"; export class CodeOfConductEndpoints {
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy";
export class AuthorizationEndpoints { public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct";
public static arm: string = "https://management.core.windows.net/"; public static termsOfUse: string = "https://aka.ms/ms-terms-of-use";
public static common: string = "https://login.windows.net/"; }
}
export class EndpointsRegex {
export class CodeOfConductEndpoints { public static readonly cassandra = [
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy"; "AccountEndpoint=(.*).cassandra.cosmosdb.azure.com",
public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct"; "HostName=(.*).cassandra.cosmos.azure.com",
public static termsOfUse: string = "https://aka.ms/ms-terms-of-use"; ];
} public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com";
public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com";
export class EndpointsRegex { public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com";
public static readonly cassandra = [ public static readonly table = "TableEndpoint=https://(.*).table.cosmosdb.azure.com";
"AccountEndpoint=(.*).cassandra.cosmosdb.azure.com", }
"HostName=(.*).cassandra.cosmos.azure.com"
]; export class ApiEndpoints {
public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com"; public static runtimeProxy: string = "/api/RuntimeProxy";
public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com"; public static guestRuntimeProxy: string = "/api/guest/RuntimeProxy";
public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com"; }
public static readonly table = "TableEndpoint=https://(.*).table.cosmosdb.azure.com";
} export class ServerIds {
public static localhost: string = "localhost";
export class ApiEndpoints { public static blackforest: string = "blackforest";
public static runtimeProxy: string = "/api/RuntimeProxy"; public static fairfax: string = "fairfax";
public static guestRuntimeProxy: string = "/api/guest/RuntimeProxy"; public static mooncake: string = "mooncake";
} public static productionPortal: string = "prod";
public static dev: string = "dev";
export class ServerIds { }
public static localhost: string = "localhost";
public static blackforest: string = "blackforest"; export class ArmApiVersions {
public static fairfax: string = "fairfax"; public static readonly documentDB: string = "2015-11-06";
public static mooncake: string = "mooncake"; public static readonly arcadia: string = "2019-06-01-preview";
public static productionPortal: string = "prod"; public static readonly arcadiaLivy: string = "2019-11-01-preview";
public static dev: string = "dev"; public static readonly arm: string = "2015-11-01";
} public static readonly armFeatures: string = "2014-08-01-preview";
public static readonly publicVersion = "2020-04-01";
export class ArmApiVersions { }
public static readonly documentDB: string = "2015-11-06";
public static readonly arcadia: string = "2019-06-01-preview"; export class ArmResourceTypes {
public static readonly arcadiaLivy: string = "2019-11-01-preview"; public static readonly notebookWorkspaces = "Microsoft.DocumentDB/databaseAccounts/notebookWorkspaces";
public static readonly arm: string = "2015-11-01"; public static readonly synapseWorkspaces = "Microsoft.Synapse/workspaces";
public static readonly armFeatures: string = "2014-08-01-preview"; }
public static readonly publicVersion = "2020-04-01";
} export class BackendDefaults {
public static partitionKeyKind: string = "Hash";
export class ArmResourceTypes { public static singlePartitionStorageInGb: string = "10";
public static readonly notebookWorkspaces = "Microsoft.DocumentDB/databaseAccounts/notebookWorkspaces"; public static multiPartitionStorageInGb: string = "100";
public static readonly synapseWorkspaces = "Microsoft.Synapse/workspaces"; public static maxChangeFeedRetentionDuration: number = 10;
} public static partitionKeyVersion = 2;
}
export class BackendDefaults {
public static partitionKeyKind: string = "Hash"; export class ClientDefaults {
public static singlePartitionStorageInGb: string = "10"; public static requestTimeoutMs: number = 60000;
public static multiPartitionStorageInGb: string = "100"; public static portalCacheTimeoutMs: number = 10000;
public static maxChangeFeedRetentionDuration: number = 10; public static errorNotificationTimeoutMs: number = 5000;
public static partitionKeyVersion = 2; public static copyHelperTimeoutMs: number = 2000;
} public static waitForDOMElementMs: number = 500;
public static cacheBustingTimeoutMs: number =
export class ClientDefaults { 10 /** minutes **/ * 60 /** to seconds **/ * 1000 /** to milliseconds **/;
public static requestTimeoutMs: number = 60000; public static databaseThroughputIncreaseFactor: number = 100;
public static portalCacheTimeoutMs: number = 10000; public static readonly arcadiaTokenRefreshInterval: number =
public static errorNotificationTimeoutMs: number = 5000; 20 /** minutes **/ * 60 /** to seconds **/ * 1000 /** to milliseconds **/;
public static copyHelperTimeoutMs: number = 2000; public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000;
public static waitForDOMElementMs: number = 500; }
public static cacheBustingTimeoutMs: number =
10 /** minutes **/ * 60 /** to seconds **/ * 1000 /** to milliseconds **/; export class AccountKind {
public static databaseThroughputIncreaseFactor: number = 100; public static DocumentDB: string = "DocumentDB";
public static readonly arcadiaTokenRefreshInterval: number = public static MongoDB: string = "MongoDB";
20 /** minutes **/ * 60 /** to seconds **/ * 1000 /** to milliseconds **/; public static Parse: string = "Parse";
public static readonly arcadiaTokenRefreshIntervalPaddingMs: number = 2000; public static GlobalDocumentDB: string = "GlobalDocumentDB";
} public static Default: string = AccountKind.DocumentDB;
}
export class AccountKind {
public static DocumentDB: string = "DocumentDB"; export class CorrelationBackend {
public static MongoDB: string = "MongoDB"; public static Url: string = "https://aka.ms/cosmosdbanalytics";
public static Parse: string = "Parse"; }
public static GlobalDocumentDB: string = "GlobalDocumentDB";
public static Default: string = AccountKind.DocumentDB; export class DefaultAccountExperience {
} public static DocumentDB: string = "DocumentDB";
public static Graph: string = "Graph";
export class CorrelationBackend { public static MongoDB: string = "MongoDB";
public static Url: string = "https://aka.ms/cosmosdbanalytics"; public static ApiForMongoDB: string = "Azure Cosmos DB for MongoDB API";
} public static Table: string = "Table";
public static Cassandra: string = "Cassandra";
export class DefaultAccountExperience { public static Default: string = DefaultAccountExperience.DocumentDB;
public static DocumentDB: string = "DocumentDB"; }
public static Graph: string = "Graph";
public static MongoDB: string = "MongoDB"; export class CapabilityNames {
public static ApiForMongoDB: string = "Azure Cosmos DB for MongoDB API"; public static EnableTable: string = "EnableTable";
public static Table: string = "Table"; public static EnableGremlin: string = "EnableGremlin";
public static Cassandra: string = "Cassandra"; public static EnableCassandra: string = "EnableCassandra";
public static Default: string = DefaultAccountExperience.DocumentDB; public static EnableAutoScale: string = "EnableAutoScale";
} public static readonly EnableNotebooks: string = "EnableNotebooks";
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics";
export class CapabilityNames { public static readonly EnableMongo: string = "EnableMongo";
public static EnableTable: string = "EnableTable"; public static readonly EnableServerless: string = "EnableServerless";
public static EnableGremlin: string = "EnableGremlin"; }
public static EnableCassandra: string = "EnableCassandra";
public static EnableAutoScale: string = "EnableAutoScale"; export class Features {
public static readonly EnableNotebooks: string = "EnableNotebooks"; public static readonly cosmosdb = "cosmosdb";
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics"; public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
public static readonly EnableMongo: string = "EnableMongo"; public static readonly executeSproc = "dataexplorerexecutesproc";
public static readonly EnableServerless: string = "EnableServerless"; public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
} public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks";
export class Features { public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly cosmosdb = "cosmosdb"; public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy"; public static readonly enableSpark = "enablespark";
public static readonly enableRupm = "enablerupm"; public static readonly livyEndpoint = "livyendpoint";
public static readonly executeSproc = "dataexplorerexecutesproc"; public static readonly notebookServerUrl = "notebookserverurl";
public static readonly hostedDataExplorer = "hosteddataexplorerenabled"; public static readonly notebookServerToken = "notebookservertoken";
public static readonly enableTtl = "enablettl"; public static readonly notebookBasePath = "notebookbasepath";
public static readonly enableNotebooks = "enablenotebooks"; public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableGalleryPublish = "enablegallerypublish"; public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
public static readonly enableCodeOfConduct = "enablecodeofconduct"; public static readonly ttl90Days = "ttl90days";
public static readonly enableLinkInjection = "enablelinkinjection"; public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSpark = "enablespark"; public static readonly enableSchema = "enableschema";
public static readonly livyEndpoint = "livyendpoint"; public static readonly enableSDKoperations = "enablesdkoperations";
public static readonly notebookServerUrl = "notebookserverurl"; public static readonly showMinRUSurvey = "showminrusurvey";
public static readonly notebookServerToken = "notebookservertoken"; public static readonly selfServeType = "selfservetype";
public static readonly notebookBasePath = "notebookbasepath"; }
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput"; // flight names returned from the portal are always lowercase
public static readonly ttl90Days = "ttl90days"; export class Flights {
public static readonly enableRightPanelV2 = "enablerightpanelv2"; public static readonly SettingsV2 = "settingsv2";
public static readonly enableSchema = "enableschema"; public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly enableSDKoperations = "enablesdkoperations"; public static readonly MongoIndexing = "mongoindexing";
public static readonly showMinRUSurvey = "showminrusurvey"; public static readonly AutoscaleTest = "autoscaletest";
} }
// flight names returned from the portal are always lowercase export class AfecFeatures {
export class Flights { public static readonly Spark = "spark-public-preview";
public static readonly SettingsV2 = "settingsv2"; public static readonly Notebooks = "sparknotebooks-public-preview";
public static readonly MongoIndexEditor = "mongoindexeditor"; public static readonly StorageAnalytics = "storageanalytics-public-preview";
} }
export class AfecFeatures { export class TagNames {
public static readonly Spark = "spark-public-preview"; public static defaultExperience: string = "defaultExperience";
public static readonly Notebooks = "sparknotebooks-public-preview"; }
public static readonly StorageAnalytics = "storageanalytics-public-preview";
} export class MongoDBAccounts {
public static protocol: string = "https";
export class Spark { public static defaultPort: string = "10255";
public static readonly MaxWorkerCount = 10; }
public static readonly SKUs: HashMap<string> = new HashMap({
"Cosmos.Spark.D1s": "D1s / 1 core / 4GB RAM", export enum MongoBackendEndpointType {
"Cosmos.Spark.D2s": "D2s / 2 cores / 8GB RAM", local,
"Cosmos.Spark.D4s": "D4s / 4 cores / 16GB RAM", remote,
"Cosmos.Spark.D8s": "D8s / 8 cores / 32GB RAM", }
"Cosmos.Spark.D16s": "D16s / 16 cores / 64GB RAM",
"Cosmos.Spark.D32s": "D32s / 32 cores / 128GB RAM", // TODO: 435619 Add default endpoints per cloud and use regional only when available
"Cosmos.Spark.D64s": "D64s / 64 cores / 256GB RAM" export class CassandraBackend {
}); public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
} public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra";
export class TagNames { public static readonly guestQueryApi: string = "api/guest/cassandra";
public static defaultExperience: string = "defaultExperience"; public static readonly keysApi: string = "api/cassandra/keys";
} public static readonly guestKeysApi: string = "api/guest/cassandra/keys";
public static readonly schemaApi: string = "api/cassandra/schema";
export class MongoDBAccounts { public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
public static protocol: string = "https"; }
public static defaultPort: string = "10255";
} export class Queries {
public static CustomPageOption: string = "custom";
export enum MongoBackendEndpointType { public static UnlimitedPageOption: string = "unlimited";
local, public static itemsPerPage: number = 100;
remote public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
}
public static QueryEditorMinHeightRatio: number = 0.1;
// TODO: 435619 Add default endpoints per cloud and use regional only when available public static QueryEditorMaxHeightRatio: number = 0.4;
export class CassandraBackend { public static readonly DefaultMaxDegreeOfParallelism = 6;
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; }
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra"; export class SavedQueries {
public static readonly guestQueryApi: string = "api/guest/cassandra"; public static readonly CollectionName: string = "___Query";
public static readonly keysApi: string = "api/cassandra/keys"; public static readonly DatabaseName: string = "___Cosmos";
public static readonly guestKeysApi: string = "api/guest/cassandra/keys"; public static readonly OfferThroughput: number = 400;
public static readonly schemaApi: string = "api/cassandra/schema"; public static readonly PartitionKeyProperty: string = "id";
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; }
}
export class DocumentsGridMetrics {
export class RUPMStates { public static DocumentsPerPage: number = 100;
public static on: string = "on"; public static IndividualRowHeight: number = 34;
public static off: string = "off"; public static BufferHeight: number = 28;
} public static SplitterMinWidth: number = 200;
public static SplitterMaxWidth: number = 360;
export class Queries {
public static CustomPageOption: string = "custom"; public static DocumentEditorMinWidthRatio: number = 0.2;
public static UnlimitedPageOption: string = "unlimited"; public static DocumentEditorMaxWidthRatio: number = 0.4;
public static itemsPerPage: number = 100; }
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
export class ExplorerMetrics {
public static QueryEditorMinHeightRatio: number = 0.1; public static SplitterMinWidth: number = 240;
public static QueryEditorMaxHeightRatio: number = 0.4; public static SplitterMaxWidth: number = 400;
public static readonly DefaultMaxDegreeOfParallelism = 6; public static CollapsedResourceTreeWidth: number = 36;
} }
export class SavedQueries { export class SplitterMetrics {
public static readonly CollectionName: string = "___Query"; public static CollapsedPositionLeft: number = ExplorerMetrics.CollapsedResourceTreeWidth;
public static readonly DatabaseName: string = "___Cosmos"; }
public static readonly OfferThroughput: number = 400;
public static readonly PartitionKeyProperty: string = "id"; export class Areas {
} public static ResourceTree: string = "Resource Tree";
public static ContextualPane: string = "Contextual Pane";
export class DocumentsGridMetrics { public static Tab: string = "Tab";
public static DocumentsPerPage: number = 100; public static ShareDialog: string = "Share Access Dialog";
public static IndividualRowHeight: number = 34; public static Notebook: string = "Notebook";
public static BufferHeight: number = 28; }
public static SplitterMinWidth: number = 200;
public static SplitterMaxWidth: number = 360; export class HttpHeaders {
public static activityId: string = "x-ms-activity-id";
public static DocumentEditorMinWidthRatio: number = 0.2; public static apiType: string = "x-ms-cosmos-apitype";
public static DocumentEditorMaxWidthRatio: number = 0.4; public static authorization: string = "authorization";
} public static collectionIndexTransformationProgress: string =
"x-ms-documentdb-collection-index-transformation-progress";
export class ExplorerMetrics { public static continuation: string = "x-ms-continuation";
public static SplitterMinWidth: number = 240; public static correlationRequestId: string = "x-ms-correlation-request-id";
public static SplitterMaxWidth: number = 400; public static enableScriptLogging: string = "x-ms-documentdb-script-enable-logging";
public static CollapsedResourceTreeWidth: number = 36; public static guestAccessToken: string = "x-ms-encrypted-auth-token";
} public static getReadOnlyKey: string = "x-ms-get-read-only-key";
public static connectionString: string = "x-ms-connection-string";
export class SplitterMetrics { public static msDate: string = "x-ms-date";
public static CollapsedPositionLeft: number = ExplorerMetrics.CollapsedResourceTreeWidth; public static location: string = "Location";
} public static contentType: string = "Content-Type";
public static offerReplacePending: string = "x-ms-offer-replace-pending";
export class Areas { public static user: string = "x-ms-user";
public static ResourceTree: string = "Resource Tree"; public static populatePartitionStatistics: string = "x-ms-documentdb-populatepartitionstatistics";
public static ContextualPane: string = "Contextual Pane"; public static queryMetrics: string = "x-ms-documentdb-query-metrics";
public static Tab: string = "Tab"; public static requestCharge: string = "x-ms-request-charge";
public static ShareDialog: string = "Share Access Dialog"; public static resourceQuota: string = "x-ms-resource-quota";
public static Notebook: string = "Notebook"; public static resourceUsage: string = "x-ms-resource-usage";
} public static retryAfterMs: string = "x-ms-retry-after-ms";
public static scriptLogResults: string = "x-ms-documentdb-script-log-results";
export class HttpHeaders { public static populateCollectionThroughputInfo = "x-ms-documentdb-populatecollectionthroughputinfo";
public static activityId: string = "x-ms-activity-id"; public static supportSpatialLegacyCoordinates = "x-ms-documentdb-supportspatiallegacycoordinates";
public static apiType: string = "x-ms-cosmos-apitype"; public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
public static authorization: string = "authorization"; public static autoPilotThroughput = "autoscaleSettings";
public static collectionIndexTransformationProgress: string = public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
"x-ms-documentdb-collection-index-transformation-progress"; public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static continuation: string = "x-ms-continuation"; public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static correlationRequestId: string = "x-ms-correlation-request-id"; public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
public static enableScriptLogging: string = "x-ms-documentdb-script-enable-logging"; }
public static guestAccessToken: string = "x-ms-encrypted-auth-token";
public static getReadOnlyKey: string = "x-ms-get-read-only-key"; export class ApiType {
public static connectionString: string = "x-ms-connection-string"; // Mapped to hexadecimal values in the backend
public static msDate: string = "x-ms-date"; public static readonly MongoDB: number = 1;
public static location: string = "Location"; public static readonly Gremlin: number = 2;
public static contentType: string = "Content-Type"; public static readonly Cassandra: number = 4;
public static offerReplacePending: string = "x-ms-offer-replace-pending"; public static readonly Table: number = 8;
public static user: string = "x-ms-user"; public static readonly SQL: number = 16;
public static populatePartitionStatistics: string = "x-ms-documentdb-populatepartitionstatistics"; }
public static queryMetrics: string = "x-ms-documentdb-query-metrics";
public static requestCharge: string = "x-ms-request-charge"; export class HttpStatusCodes {
public static resourceQuota: string = "x-ms-resource-quota"; public static readonly OK: number = 200;
public static resourceUsage: string = "x-ms-resource-usage"; public static readonly Created: number = 201;
public static retryAfterMs: string = "x-ms-retry-after-ms"; public static readonly Accepted: number = 202;
public static scriptLogResults: string = "x-ms-documentdb-script-log-results"; public static readonly NoContent: number = 204;
public static populateCollectionThroughputInfo = "x-ms-documentdb-populatecollectionthroughputinfo"; public static readonly NotModified: number = 304;
public static supportSpatialLegacyCoordinates = "x-ms-documentdb-supportspatiallegacycoordinates"; public static readonly Unauthorized: number = 401;
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere"; public static readonly Forbidden: number = 403;
public static autoPilotThroughput = "autoscaleSettings"; public static readonly NotFound: number = 404;
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings"; public static readonly TooManyRequests: number = 429;
public static partitionKey: string = "x-ms-documentdb-partitionkey"; public static readonly Conflict: number = 409;
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot"; public static readonly InternalServerError: number = 500;
} public static readonly BadGateway: number = 502;
public static readonly ServiceUnavailable: number = 503;
export class ApiType { public static readonly GatewayTimeout: number = 504;
// Mapped to hexadecimal values in the backend
public static readonly MongoDB: number = 1; public static readonly RetryableStatusCodes: number[] = [
public static readonly Gremlin: number = 2; HttpStatusCodes.TooManyRequests,
public static readonly Cassandra: number = 4; HttpStatusCodes.InternalServerError, // TODO: Handle all 500s on Portal backend and remove from retries list
public static readonly Table: number = 8; HttpStatusCodes.BadGateway,
public static readonly SQL: number = 16; HttpStatusCodes.ServiceUnavailable,
} HttpStatusCodes.GatewayTimeout,
];
export class HttpStatusCodes { }
public static readonly OK: number = 200;
public static readonly Created: number = 201; export class Urls {
public static readonly Accepted: number = 202; public static feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback";
public static readonly NoContent: number = 204; public static autoscaleMigration = "https://aka.ms/cosmos-autoscale-migration";
public static readonly NotModified: number = 304; public static freeTierInformation = "https://aka.ms/cosmos-free-tier";
public static readonly Unauthorized: number = 401; public static cosmosPricing = "https://aka.ms/azure-cosmos-db-pricing";
public static readonly Forbidden: number = 403; }
public static readonly NotFound: number = 404;
public static readonly TooManyRequests: number = 429; export class HashRoutePrefixes {
public static readonly Conflict: number = 409; public static databases: string = "/dbs/{db_id}";
public static collections: string = "/dbs/{db_id}/colls/{coll_id}";
public static readonly InternalServerError: number = 500; public static sprocHash: string = "/sprocs/";
public static readonly BadGateway: number = 502; public static sprocs: string = HashRoutePrefixes.collections + HashRoutePrefixes.sprocHash + "{sproc_id}";
public static readonly ServiceUnavailable: number = 503; public static docs: string = HashRoutePrefixes.collections + "/docs/{doc_id}/";
public static readonly GatewayTimeout: number = 504; public static conflicts: string = HashRoutePrefixes.collections + "/conflicts";
public static readonly RetryableStatusCodes: number[] = [ public static databasesWithId(databaseId: string): string {
HttpStatusCodes.TooManyRequests, return this.databases.replace("{db_id}", databaseId).replace("/", ""); // strip the first slash since hasher adds it
HttpStatusCodes.InternalServerError, // TODO: Handle all 500s on Portal backend and remove from retries list }
HttpStatusCodes.BadGateway,
HttpStatusCodes.ServiceUnavailable, public static collectionsWithIds(databaseId: string, collectionId: string): string {
HttpStatusCodes.GatewayTimeout const transformedDatabasePrefix: string = this.collections.replace("{db_id}", databaseId);
];
} return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it
}
export class Urls {
public static feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback"; public static sprocWithIds(
public static autoscaleMigration = "https://aka.ms/cosmos-autoscale-migration"; databaseId: string,
public static freeTierInformation = "https://aka.ms/cosmos-free-tier"; collectionId: string,
public static cosmosPricing = "https://aka.ms/azure-cosmos-db-pricing"; sprocId: string,
} stripFirstSlash: boolean = true
): string {
export class HashRoutePrefixes { const transformedDatabasePrefix: string = this.sprocs.replace("{db_id}", databaseId);
public static databases: string = "/dbs/{db_id}";
public static collections: string = "/dbs/{db_id}/colls/{coll_id}"; const transformedSprocRoute: string = transformedDatabasePrefix
public static sprocHash: string = "/sprocs/"; .replace("{coll_id}", collectionId)
public static sprocs: string = HashRoutePrefixes.collections + HashRoutePrefixes.sprocHash + "{sproc_id}"; .replace("{sproc_id}", sprocId);
public static docs: string = HashRoutePrefixes.collections + "/docs/{doc_id}/"; if (!!stripFirstSlash) {
public static conflicts: string = HashRoutePrefixes.collections + "/conflicts"; return transformedSprocRoute.replace("/", ""); // strip the first slash since hasher adds it
}
public static databasesWithId(databaseId: string): string {
return this.databases.replace("{db_id}", databaseId).replace("/", ""); // strip the first slash since hasher adds it return transformedSprocRoute;
} }
public static collectionsWithIds(databaseId: string, collectionId: string): string { public static conflictsWithIds(databaseId: string, collectionId: string) {
const transformedDatabasePrefix: string = this.collections.replace("{db_id}", databaseId); const transformedDatabasePrefix: string = this.conflicts.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it;
} }
public static sprocWithIds( public static docsWithIds(databaseId: string, collectionId: string, docId: string) {
databaseId: string, const transformedDatabasePrefix: string = this.docs.replace("{db_id}", databaseId);
collectionId: string,
sprocId: string, return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("{doc_id}", docId).replace("/", ""); // strip the first slash since hasher adds it
stripFirstSlash: boolean = true }
): string { }
const transformedDatabasePrefix: string = this.sprocs.replace("{db_id}", databaseId);
export class ConfigurationOverridesValues {
const transformedSprocRoute: string = transformedDatabasePrefix public static IsBsonSchemaV2: string = "true";
.replace("{coll_id}", collectionId) }
.replace("{sproc_id}", sprocId);
if (!!stripFirstSlash) { export class KeyCodes {
return transformedSprocRoute.replace("/", ""); // strip the first slash since hasher adds it public static Space: number = 32;
} public static Enter: number = 13;
public static Escape: number = 27;
return transformedSprocRoute; public static UpArrow: number = 38;
} public static DownArrow: number = 40;
public static LeftArrow: number = 37;
public static conflictsWithIds(databaseId: string, collectionId: string) { public static RightArrow: number = 39;
const transformedDatabasePrefix: string = this.conflicts.replace("{db_id}", databaseId); public static Tab: number = 9;
}
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("/", ""); // strip the first slash since hasher adds it;
} // Normalized per: https://www.w3.org/TR/uievents-key/#named-key-attribute-values
export class NormalizedEventKey {
public static docsWithIds(databaseId: string, collectionId: string, docId: string) { public static readonly Space = " ";
const transformedDatabasePrefix: string = this.docs.replace("{db_id}", databaseId); public static readonly Enter = "Enter";
public static readonly Escape = "Escape";
return transformedDatabasePrefix public static readonly UpArrow = "ArrowUp";
.replace("{coll_id}", collectionId) public static readonly DownArrow = "ArrowDown";
.replace("{doc_id}", docId) public static readonly LeftArrow = "ArrowLeft";
.replace("/", ""); // strip the first slash since hasher adds it public static readonly RightArrow = "ArrowRight";
} }
}
export class TryCosmosExperience {
export class ConfigurationOverridesValues { public static extendUrl: string = "https://trycosmosdb.azure.com/api/resource/extendportal?userId={0}";
public static IsBsonSchemaV2: string = "true"; public static deleteUrl: string = "https://trycosmosdb.azure.com/api/resource/deleteportal?userId={0}";
} public static collectionsPerAccount: number = 3;
public static maxRU: number = 5000;
export class KeyCodes { public static defaultRU: number = 3000;
public static Space: number = 32; }
public static Enter: number = 13;
public static Escape: number = 27; export class OfferVersions {
public static UpArrow: number = 38; public static V1: string = "V1";
public static DownArrow: number = 40; public static V2: string = "V2";
public static LeftArrow: number = 37; }
public static RightArrow: number = 39;
public static Tab: number = 9; export enum ConflictOperationType {
} Replace = "replace",
Create = "create",
// Normalized per: https://www.w3.org/TR/uievents-key/#named-key-attribute-values Delete = "delete",
export class NormalizedEventKey { }
public static readonly Space = " ";
public static readonly Enter = "Enter"; export const EmulatorMasterKey =
public static readonly Escape = "Escape"; //[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
public static readonly UpArrow = "ArrowUp"; "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
public static readonly DownArrow = "ArrowDown";
public static readonly LeftArrow = "ArrowLeft"; // A variable @MyVariable defined in Constants.less is accessible as StyleConstants.MyVariable
public static readonly RightArrow = "ArrowRight"; export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
}
export class Notebook {
export class TryCosmosExperience { public static readonly defaultBasePath = "./notebooks";
public static extendUrl: string = "https://trycosmosdb.azure.com/api/resource/extendportal?userId={0}"; public static readonly heartbeatDelayMs = 5000;
public static deleteUrl: string = "https://trycosmosdb.azure.com/api/resource/deleteportal?userId={0}"; public static readonly kernelRestartInitialDelayMs = 1000;
public static collectionsPerAccount: number = 3; public static readonly kernelRestartMaxDelayMs = 20000;
public static maxRU: number = 5000; public static readonly autoSaveIntervalMs = 120000;
public static defaultRU: number = 3000; }
}
export class SparkLibrary {
export class OfferVersions { public static readonly nameMinLength = 3;
public static V1: string = "V1"; public static readonly nameMaxLength = 63;
public static V2: string = "V2"; }
}
export class AnalyticalStorageTtl {
export enum ConflictOperationType { public static readonly Days90: number = 7776000;
Replace = "replace", public static readonly Infinite: number = -1;
Create = "create", public static readonly Disabled: number = 0;
Delete = "delete" }
}
export class TerminalQueryParams {
export const EmulatorMasterKey = public static readonly Terminal = "terminal";
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")] public static readonly Server = "server";
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; public static readonly Token = "token";
public static readonly SubscriptionId = "subscriptionId";
// A variable @MyVariable defined in Constants.less is accessible as StyleConstants.MyVariable public static readonly TerminalEndpoint = "terminalEndpoint";
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less"); }
export class Notebook {
public static readonly defaultBasePath = "./notebooks";
public static readonly heartbeatDelayMs = 5000;
public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 120000;
}
export class SparkLibrary {
public static readonly nameMinLength = 3;
public static readonly nameMaxLength = 63;
}
export class AnalyticalStorageTtl {
public static readonly Days90: number = 7776000;
public static readonly Infinite: number = -1;
public static readonly Disabled: number = 0;
}
export class TerminalQueryParams {
public static readonly Terminal = "terminal";
public static readonly Server = "server";
public static readonly Token = "token";
public static readonly SubscriptionId = "subscriptionId";
public static readonly TerminalEndpoint = "terminalEndpoint";
}

View File

@@ -10,17 +10,17 @@ describe("tokenProvider", () => {
resourceId: "", resourceId: "",
resourceType: "dbs" as ResourceType, resourceType: "dbs" as ResourceType,
headers: {}, headers: {},
getAuthorizationTokenUsingMasterKey: () => "" getAuthorizationTokenUsingMasterKey: () => "",
}; };
beforeEach(() => { beforeEach(() => {
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
headers: new Map() headers: new Map(),
}; };
}); });
}); });
@@ -36,7 +36,7 @@ describe("tokenProvider", () => {
it("does not call the auth service if a master key is set", async () => { it("does not call the auth service if a master key is set", async () => {
updateUserContext({ updateUserContext({
masterKey: "foo" masterKey: "foo",
}); });
await tokenProvider(options); await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(0); expect((window.fetch as any).mock.calls.length).toBe(0);
@@ -50,7 +50,7 @@ describe("getTokenFromAuthService", () => {
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
headers: new Map() headers: new Map(),
}; };
}); });
}); });
@@ -61,7 +61,7 @@ describe("getTokenFromAuthService", () => {
it("builds the correct URL in production", () => { it("builds the correct URL in production", () => {
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
getTokenFromAuthService("GET", "dbs", "foo"); getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
@@ -72,7 +72,7 @@ describe("getTokenFromAuthService", () => {
it("builds the correct URL in dev", () => { it("builds the correct URL in dev", () => {
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://localhost:1234" BACKEND_ENDPOINT: "https://localhost:1234",
}); });
getTokenFromAuthService("GET", "dbs", "foo"); getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
@@ -96,15 +96,15 @@ describe("endpoint", () => {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo" cassandraEndpoint: "foo",
} },
} },
}); });
expect(endpoint()).toEqual("bar"); expect(endpoint()).toEqual("bar");
}); });
it("uses _endpoint if set", () => { it("uses _endpoint if set", () => {
updateUserContext({ updateUserContext({
endpoint: "baz" endpoint: "baz",
}); });
expect(endpoint()).toEqual("baz"); expect(endpoint()).toEqual("baz");
}); });
@@ -121,7 +121,7 @@ describe("requestPlugin", () => {
updateConfigContext({ updateConfigContext({
platform: Platform.Hosted, platform: Platform.Hosted,
BACKEND_ENDPOINT: "https://localhost:1234", BACKEND_ENDPOINT: "https://localhost:1234",
PROXY_PATH: "/proxy" PROXY_PATH: "/proxy",
}); });
const headers = {}; const headers = {};
const endpoint = "https://docs.azure.com"; const endpoint = "https://docs.azure.com";

View File

@@ -58,13 +58,13 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"x-ms-encrypted-auth-token": userContext.accessToken "x-ms-encrypted-auth-token": userContext.accessToken,
}, },
body: JSON.stringify({ body: JSON.stringify({
verb, verb,
resourceType, resourceType,
resourceId resourceId,
}) }),
}); });
//TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json() //TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json()
const result = JSON.parse(await response.json()); const result = JSON.parse(await response.json());
@@ -77,13 +77,13 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
export function client(): Cosmos.CosmosClient { export function client(): Cosmos.CosmosClient {
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here 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,
tokenProvider, tokenProvider,
connectionPolicy: { connectionPolicy: {
enableEndpointDiscovery: false enableEndpointDiscovery: false,
}, },
userAgentSuffix: "Azure Portal" userAgentSuffix: "Azure Portal",
}; };
if (configContext.PROXY_PATH !== undefined) { if (configContext.PROXY_PATH !== undefined) {

View File

@@ -1,182 +0,0 @@
import {
ConflictDefinition,
FeedOptions,
ItemDefinition,
OfferDefinition,
QueryIterator,
Resource
} from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import Q from "q";
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import * as ViewModels from "../Contracts/ViewModels";
import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import { OfferUtils } from "../Utils/OfferUtils";
import * as Constants from "./Constants";
import { client } from "./CosmosClient";
import * as HeadersUtility from "./HeadersUtility";
import { sendCachedDataMessage } from "./MessageHandler";
export function getCommonQueryOptions(options: FeedOptions): any {
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
options = options || {};
options.populateQueryMetrics = true;
options.enableScanInQuery = options.enableScanInQuery || true;
if (!options.partitionKey) {
options.forceQueryPlan = true;
}
options.maxItemCount =
options.maxItemCount ||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
Constants.Queries.itemsPerPage;
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
return options;
}
export function queryDocuments(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
options = getCommonQueryOptions(options);
const documentsIterator = client()
.database(databaseId)
.container(containerId)
.items.query(query, options);
return Q(documentsIterator);
}
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
const partitionKeyValue: any = conflictId.partitionKeyValue;
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
}
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
if (!partitionKeyDefinition) {
return undefined;
}
if (partitionKeyValue === undefined) {
return [{}];
}
return [partitionKeyValue];
}
export function updateDocument(
collection: ViewModels.CollectionBase,
documentId: DocumentId,
newDocument: any
): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
return Q(
client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), partitionKey)
.replace(newDocument)
.then(response => response.resource)
);
}
export function executeStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: StoredProcedure,
partitionKeyValue: any,
params: any[]
): Q.Promise<any> {
// TODO remove this deferred. Kept it because of timeout code at bottom of function
const deferred = Q.defer<any>();
client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id())
.execute(partitionKeyValue, params, { enableScriptLogging: true })
.then(response =>
deferred.resolve({
result: response.resource,
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
})
)
.catch(error => deferred.reject(error));
return deferred.promise.timeout(
Constants.ClientDefaults.requestTimeoutMs,
`Request timed out while executing stored procedure ${storedProcedure.id()}`
);
}
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
return Q(
client()
.database(collection.databaseId)
.container(collection.id())
.items.create(newDocument)
.then(response => response.resource)
);
}
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
return Q(
client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), partitionKey)
.read()
.then(response => response.resource)
);
}
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
return Q(
client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), partitionKey)
.delete()
);
}
export function deleteConflict(
collection: ViewModels.CollectionBase,
conflictId: ConflictId,
options: any = {}
): Q.Promise<any> {
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
return Q(
client()
.database(collection.databaseId)
.container(collection.id())
.conflict(conflictId.id())
.delete(options)
);
}
export function queryConflicts(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
const documentsIterator = client()
.database(databaseId)
.container(containerId)
.conflicts.query(query, options);
return Q(documentsIterator);
}

View File

@@ -1,217 +0,0 @@
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import Q from "q";
import * as ViewModels from "../Contracts/ViewModels";
import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import * as Constants from "./Constants";
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
import { handleError } from "./ErrorHandlingUtils";
// TODO: Log all promise resolutions and errors with verbosity levels
export function queryDocuments(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
}
export function queryConflicts(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
}
export function getEntityName() {
const defaultExperience =
window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience();
if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) {
return "document";
}
return "item";
}
export function executeStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: StoredProcedure,
partitionKeyValue: any,
params: any[]
): Q.Promise<any> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
.then(
(response: any) => {
deferred.resolve(response);
logConsoleInfo(
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
);
},
(error: any) => {
handleError(
error,
"ExecuteStoredProcedure",
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function queryDocumentsPage(
resourceName: string,
documentsIterator: MinimalQueryIterator,
firstItemIndex: number,
options: any
): Q.Promise<ViewModels.QueryResults> {
var deferred = Q.defer<ViewModels.QueryResults>();
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
Q(nextPage(documentsIterator, firstItemIndex))
.then(
(result: ViewModels.QueryResults) => {
const itemCount = (result.documents && result.documents.length) || 0;
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
deferred.resolve(result);
},
(error: any) => {
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
DataAccessUtilityBase.readDocument(collection, documentId)
.then(
(document: any) => {
deferred.resolve(document);
},
(error: any) => {
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function updateDocument(
collection: ViewModels.CollectionBase,
documentId: DocumentId,
newDocument: any
): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
.then(
(updatedDocument: any) => {
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
deferred.resolve(updatedDocument);
},
(error: any) => {
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
DataAccessUtilityBase.createDocument(collection, newDocument)
.then(
(savedDocument: any) => {
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
deferred.resolve(savedDocument);
},
(error: any) => {
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
DataAccessUtilityBase.deleteDocument(collection, documentId)
.then(
(response: any) => {
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
deferred.resolve(response);
},
(error: any) => {
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function deleteConflict(
collection: ViewModels.CollectionBase,
conflictId: ConflictId,
options?: any
): Q.Promise<any> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
.then(
(response: any) => {
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
deferred.resolve(response);
},
(error: any) => {
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}

View File

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

View File

@@ -1,94 +1,94 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
export default class EditableUtility { export default class EditableUtility {
public static observable<T>(initialValue?: T): ViewModels.Editable<T> { public static observable<T>(initialValue?: T): ViewModels.Editable<T> {
var observable: ViewModels.Editable<T> = <ViewModels.Editable<T>>ko.observable<T>(initialValue); var observable: ViewModels.Editable<T> = <ViewModels.Editable<T>>ko.observable<T>(initialValue);
observable.edits = ko.observableArray<T>([initialValue]); observable.edits = ko.observableArray<T>([initialValue]);
observable.validations = ko.observableArray<(value: T) => boolean>([]); observable.validations = ko.observableArray<(value: T) => boolean>([]);
observable.setBaseline = (baseline: T) => { observable.setBaseline = (baseline: T) => {
observable(baseline); observable(baseline);
observable.edits([baseline]); observable.edits([baseline]);
}; };
observable.getEditableCurrentValue = ko.computed<T>(() => { observable.getEditableCurrentValue = ko.computed<T>(() => {
const edits = (observable.edits && observable.edits()) || []; const edits = (observable.edits && observable.edits()) || [];
if (edits.length === 0) { if (edits.length === 0) {
return undefined; return undefined;
} }
return edits[edits.length - 1]; return edits[edits.length - 1];
}); });
observable.getEditableOriginalValue = ko.computed<T>(() => { observable.getEditableOriginalValue = ko.computed<T>(() => {
const edits = (observable.edits && observable.edits()) || []; const edits = (observable.edits && observable.edits()) || [];
if (edits.length === 0) { if (edits.length === 0) {
return undefined; return undefined;
} }
return edits[0]; return edits[0];
}); });
observable.editableIsDirty = ko.computed<boolean>(() => { observable.editableIsDirty = ko.computed<boolean>(() => {
const edits = (observable.edits && observable.edits()) || []; const edits = (observable.edits && observable.edits()) || [];
if (edits.length <= 1) { if (edits.length <= 1) {
return false; return false;
} }
let current: any = observable.getEditableCurrentValue(); let current: any = observable.getEditableCurrentValue();
let original: any = observable.getEditableOriginalValue(); let original: any = observable.getEditableOriginalValue();
switch (typeof current) { switch (typeof current) {
case "string": case "string":
case "undefined": case "undefined":
case "number": case "number":
case "boolean": case "boolean":
current = current && current.toString(); current = current && current.toString();
break; break;
default: default:
current = JSON.stringify(current); current = JSON.stringify(current);
break; break;
} }
switch (typeof original) { switch (typeof original) {
case "string": case "string":
case "undefined": case "undefined":
case "number": case "number":
case "boolean": case "boolean":
original = original && original.toString(); original = original && original.toString();
break; break;
default: default:
original = JSON.stringify(original); original = JSON.stringify(original);
break; break;
} }
if (current !== original) { if (current !== original) {
return true; return true;
} }
return false; return false;
}); });
observable.subscribe(edit => { observable.subscribe((edit) => {
var edits = observable.edits && observable.edits(); var edits = observable.edits && observable.edits();
if (!edits) { if (!edits) {
return; return;
} }
edits.push(edit); edits.push(edit);
observable.edits(edits); observable.edits(edits);
}); });
observable.editableIsValid = ko.observable<boolean>(true); observable.editableIsValid = ko.observable<boolean>(true);
observable.subscribe(value => { observable.subscribe((value) => {
const validations: ((value: T) => boolean)[] = (observable.validations && observable.validations()) || []; const validations: ((value: T) => boolean)[] = (observable.validations && observable.validations()) || [];
const isValid = validations.every(validate => validate(value)); const isValid = validations.every((validate) => validate(value));
observable.editableIsValid(isValid); observable.editableIsValid(isValid);
}); });
return observable; return observable;
} }
} }

View File

@@ -1,8 +1,6 @@
export default class EnvironmentUtility { export function normalizeArmEndpoint(uri: string): string {
public static normalizeArmEndpointUri(uri: string): string { if (uri && uri.slice(-1) !== "/") {
if (uri && uri.slice(-1) !== "/") { return `${uri}/`;
return `${uri}/`; }
} return uri;
return uri; }
}
}

View File

@@ -21,7 +21,7 @@ export const handleError = (error: string | ARMError | Error, area: string, cons
sendNotificationForError(errorMessage, errorCode); sendNotificationForError(errorMessage, errorCode);
}; };
export const getErrorMessage = (error: string | Error): string => { export const getErrorMessage = (error: string | Error = ""): string => {
const errorMessage = typeof error === "string" ? error : error.message; const errorMessage = typeof error === "string" ? error : error.message;
return replaceKnownError(errorMessage); return replaceKnownError(errorMessage);
}; };
@@ -37,7 +37,7 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
} }
sendMessage({ sendMessage({
type: MessageTypes.ForbiddenError, type: MessageTypes.ForbiddenError,
reason: errorMessage reason: errorMessage,
}); });
} }
}; };
@@ -45,10 +45,10 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
const replaceKnownError = (errorMessage: string): string => { const replaceKnownError = (errorMessage: string): string => {
if ( if (
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal && window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0 errorMessage?.indexOf("SharedOffer is Disabled for your account") >= 0
) { ) {
return "Database throughput is not supported for internal subscriptions."; return "Database throughput is not supported for internal subscriptions.";
} else if (errorMessage.indexOf("Partition key paths must contain only valid") >= 0) { } else if (errorMessage?.indexOf("Partition key paths must contain only valid") >= 0) {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character."; return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
} }

View File

@@ -1,25 +1,25 @@
import * as HeadersUtility from "./HeadersUtility"; import * as HeadersUtility from "./HeadersUtility";
import { ExplorerSettings } from "../Shared/ExplorerSettings"; import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
describe("Headers Utility", () => { describe("Headers Utility", () => {
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => { describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
beforeEach(() => { beforeEach(() => {
ExplorerSettings.createDefaultSettings(); ExplorerSettings.createDefaultSettings();
}); });
it("should return true by default", () => { it("should return true by default", () => {
expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true); expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true);
}); });
it("should return false if the enable cross partition key feed option is false", () => { it("should return false if the enable cross partition key feed option is false", () => {
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "false"); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "false");
expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(false); expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(false);
}); });
it("should return true if the enable cross partition key feed option is true", () => { it("should return true if the enable cross partition key feed option is true", () => {
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true"); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true");
expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true); expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true);
}); });
}); });
}); });

View File

@@ -1,28 +1,28 @@
import * as Constants from "./Constants"; import * as Constants from "./Constants";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
// x-ms-resource-quota: databases = 100; collections = 5000; users = 500000; permissions = 2000000; // x-ms-resource-quota: databases = 100; collections = 5000; users = 500000; permissions = 2000000;
export function getQuota(responseHeaders: any): any { export function getQuota(responseHeaders: any): any {
return responseHeaders && responseHeaders[Constants.HttpHeaders.resourceQuota] return responseHeaders && responseHeaders[Constants.HttpHeaders.resourceQuota]
? parseStringIntoObject(responseHeaders[Constants.HttpHeaders.resourceQuota]) ? parseStringIntoObject(responseHeaders[Constants.HttpHeaders.resourceQuota])
: null; : null;
} }
export function shouldEnableCrossPartitionKey(): boolean { export function shouldEnableCrossPartitionKey(): boolean {
return LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"; return LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true";
} }
function parseStringIntoObject(resourceString: string) { function parseStringIntoObject(resourceString: string) {
var entityObject: any = {}; var entityObject: any = {};
if (resourceString) { if (resourceString) {
var entitiesArray: string[] = resourceString.split(";"); var entitiesArray: string[] = resourceString.split(";");
for (var i: any = 0; i < entitiesArray.length; i++) { for (var i: any = 0; i < entitiesArray.length; i++) {
var entity: string[] = entitiesArray[i].split("="); var entity: string[] = entitiesArray[i].split("=");
entityObject[entity[0]] = entity[1]; entityObject[entity[0]] = entity[1];
} }
} }
return entityObject; return entityObject;
} }

View File

@@ -11,8 +11,8 @@ describe("nextPage", () => {
queryMetrics: {}, queryMetrics: {},
requestCharge: 1, requestCharge: 1,
headers: {}, headers: {},
activityId: "foo" activityId: "foo",
}) }),
}; };
expect(await nextPage(fakeIterator, 10)).toMatchSnapshot(); expect(await nextPage(fakeIterator, 10)).toMatchSnapshot();

View File

@@ -14,7 +14,7 @@ export interface MinimalQueryIterator {
// Pick<QueryIterator<any>, "fetchNext">; // Pick<QueryIterator<any>, "fetchNext">;
export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> { export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
return documentsIterator.fetchNext().then(response => { return documentsIterator.fetchNext().then((response) => {
const documents = response.resources; const documents = response.resources;
const headers = (response as any).headers || {}; // TODO this is a private key. Remove any const headers = (response as any).headers || {}; // TODO this is a private key. Remove any
const itemCount = (documents && documents.length) || 0; const itemCount = (documents && documents.length) || 0;
@@ -26,7 +26,7 @@ export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex
lastItemIndex: Number(firstItemIndex) + Number(itemCount), lastItemIndex: Number(firstItemIndex) + Number(itemCount),
headers, headers,
activityId: response.activityId, activityId: response.activityId,
requestCharge: response.requestCharge requestCharge: response.requestCharge,
}; };
}); });
} }

View File

@@ -1,26 +1,26 @@
jest.mock("./MessageHandler"); jest.mock("./MessageHandler");
import { LogEntryLevel } from "../Contracts/Diagnostics"; import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { sendMessage } from "./MessageHandler"; import { sendMessage } from "./MessageHandler";
describe("Logger", () => { describe("Logger", () => {
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
it("should log info messages", () => { it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB"); Logger.logInfo("Test info", "DocDB");
expect(sendMessage).toBeCalled(); expect(sendMessage).toBeCalled();
}); });
it("should log error messages", () => { it("should log error messages", () => {
Logger.logError("Test error", "DocDB"); Logger.logError("Test error", "DocDB");
expect(sendMessage).toBeCalled(); expect(sendMessage).toBeCalled();
}); });
it("should log warnings", () => { it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB"); Logger.logWarning("Test warning", "DocDB");
expect(sendMessage).toBeCalled(); expect(sendMessage).toBeCalled();
}); });
}); });

View File

@@ -29,7 +29,7 @@ export function logError(errorMessage: string, area: string, code?: number | str
function _logEntry(entry: Diagnostics.LogEntry): void { function _logEntry(entry: Diagnostics.LogEntry): void {
sendMessage({ sendMessage({
type: MessageTypes.LogInfo, type: MessageTypes.LogInfo,
data: JSON.stringify(entry) data: JSON.stringify(entry),
}); });
const severityLevel = ((level: Diagnostics.LogEntryLevel): SeverityLevel => { const severityLevel = ((level: Diagnostics.LogEntryLevel): SeverityLevel => {
@@ -60,6 +60,6 @@ function _generateLogEntry(
level, level,
message, message,
area, area,
code code,
}; };
} }

View File

@@ -1,28 +1,28 @@
import Q from "q"; import Q from "q";
import * as MessageHandler from "./MessageHandler"; import * as MessageHandler from "./MessageHandler";
describe("Message Handler", () => { describe("Message Handler", () => {
it("should handle cached message", async () => { it("should handle cached message", async () => {
let mockPromise = { let mockPromise = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>() deferred: Q.defer<any>(),
}; };
let mockMessage = { message: { id: "123", data: "{}" } }; let mockMessage = { message: { id: "123", data: "{}" } };
MessageHandler.RequestMap[mockPromise.id] = mockPromise; MessageHandler.RequestMap[mockPromise.id] = mockPromise;
MessageHandler.handleCachedDataMessage(mockMessage); MessageHandler.handleCachedDataMessage(mockMessage);
expect(mockPromise.deferred.promise.isFulfilled()).toBe(true); expect(mockPromise.deferred.promise.isFulfilled()).toBe(true);
}); });
it("should delete fulfilled promises on running the garbage collector", async () => { it("should delete fulfilled promises on running the garbage collector", async () => {
let message = { let message = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>() deferred: Q.defer<any>(),
}; };
MessageHandler.handleCachedDataMessage(message); MessageHandler.handleCachedDataMessage(message);
MessageHandler.runGarbageCollector(); MessageHandler.runGarbageCollector();
expect(MessageHandler.RequestMap["123"]).toBeUndefined(); expect(MessageHandler.RequestMap["123"]).toBeUndefined();
}); });
}); });

View File

@@ -35,7 +35,7 @@ export function sendCachedDataMessage<TResponseDataModel>(
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = { let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(), deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(), startTime: new Date(),
id: _.uniqueId() id: _.uniqueId(),
}; };
RequestMap[cachedDataPromise.id] = cachedDataPromise; RequestMap[cachedDataPromise.id] = cachedDataPromise;
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id }); sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
@@ -54,7 +54,7 @@ export function sendMessage(data: any): void {
portalChildWindow.parent.postMessage( portalChildWindow.parent.postMessage(
{ {
signature: "pcIframe", signature: "pcIframe",
data: data data: data,
}, },
portalChildWindow.document.referrer portalChildWindow.document.referrer
); );

View File

@@ -14,7 +14,7 @@ const fetchMock = () => {
ok: true, ok: true,
text: () => "{}", text: () => "{}",
json: () => "{}", json: () => "{}",
headers: new Map() headers: new Map(),
}); });
}; };
@@ -27,8 +27,8 @@ const collection = {
partitionKey: { partitionKey: {
paths: ["/pk"], paths: ["/pk"],
kind: "Hash", kind: "Hash",
version: 1 version: 1,
} },
} as Collection; } as Collection;
const documentId = ({ const documentId = ({
@@ -38,8 +38,8 @@ const documentId = ({
partitionKey: { partitionKey: {
paths: ["/pk"], paths: ["/pk"],
kind: "Hash", kind: "Hash",
version: 1 version: 1,
} },
} as unknown) as DocumentId; } as unknown) as DocumentId;
const databaseAccount = { const databaseAccount = {
@@ -52,8 +52,8 @@ const databaseAccount = {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo" cassandraEndpoint: "foo",
} },
} as DatabaseAccount; } as DatabaseAccount;
describe("MongoProxyClient", () => { describe("MongoProxyClient", () => {
@@ -61,10 +61,10 @@ describe("MongoProxyClient", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
updateUserContext({ updateUserContext({
databaseAccount databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -93,10 +93,10 @@ describe("MongoProxyClient", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
updateUserContext({ updateUserContext({
databaseAccount databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -125,10 +125,10 @@ describe("MongoProxyClient", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
updateUserContext({ updateUserContext({
databaseAccount databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -157,10 +157,10 @@ describe("MongoProxyClient", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
updateUserContext({ updateUserContext({
databaseAccount databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -189,10 +189,10 @@ describe("MongoProxyClient", () => {
beforeEach(() => { beforeEach(() => {
resetConfigContext(); resetConfigContext();
updateUserContext({ updateUserContext({
databaseAccount databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -222,10 +222,10 @@ describe("MongoProxyClient", () => {
resetConfigContext(); resetConfigContext();
delete window.authType; delete window.authType;
updateUserContext({ updateUserContext({
databaseAccount databaseAccount,
}); });
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com" BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
}); });
}); });

View File

@@ -16,7 +16,7 @@ import { sendMessage } from "./MessageHandler";
const defaultHeaders = { const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(), [HttpHeaders.apiType]: ApiType.MongoDB.toString(),
[CosmosSDKConstants.HttpHeaders.MaxEntityCount]: "100", [CosmosSDKConstants.HttpHeaders.MaxEntityCount]: "100",
[CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15" [CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15",
}; };
function authHeaders() { function authHeaders() {
@@ -31,7 +31,7 @@ export function queryIterator(databaseId: string, collection: Collection, query:
let continuationToken: string; let continuationToken: string;
return { return {
fetchNext: () => { fetchNext: () => {
return queryDocuments(databaseId, collection, false, query).then(response => { return queryDocuments(databaseId, collection, false, query).then((response) => {
continuationToken = response.continuationToken; continuationToken = response.continuationToken;
const headers: { [key: string]: string | number } = {}; const headers: { [key: string]: string | number } = {};
response.headers.forEach((value, key) => { response.headers.forEach((value, key) => {
@@ -42,10 +42,10 @@ export function queryIterator(databaseId: string, collection: Collection, query:
headers, headers,
requestCharge: Number(headers[CosmosSDKConstants.HttpHeaders.RequestCharge]), requestCharge: Number(headers[CosmosSDKConstants.HttpHeaders.RequestCharge]),
activityId: String(headers[CosmosSDKConstants.HttpHeaders.ActivityId]), activityId: String(headers[CosmosSDKConstants.HttpHeaders.ActivityId]),
hasMoreResults: !!continuationToken hasMoreResults: !!continuationToken,
}; };
}); });
} },
}; };
} }
@@ -74,7 +74,9 @@ export function queryDocuments(
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : "" collection && collection.partitionKey && !collection.partitionKey.systemKey
? collection.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint() || ""; const endpoint = getEndpoint() || "";
@@ -87,7 +89,7 @@ export function queryDocuments(
[CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true", [CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true",
[CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true", [CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true",
[CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true", [CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true",
[HttpHeaders.contentType]: "application/query+json" [HttpHeaders.contentType]: "application/query+json",
}; };
if (continuationToken) { if (continuationToken) {
@@ -100,14 +102,14 @@ export function queryDocuments(
.fetch(`${endpoint}${path}?${queryString.stringify(params)}`, { .fetch(`${endpoint}${path}?${queryString.stringify(params)}`, {
method: "POST", method: "POST",
body: JSON.stringify({ query }), body: JSON.stringify({ query }),
headers headers,
}) })
.then(async response => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return { return {
continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation), continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation),
documents: (await response.json()).Documents as DataModels.DocumentId[], documents: (await response.json()).Documents as DataModels.DocumentId[],
headers: response.headers headers: response.headers,
}; };
} }
errorHandling(response, "querying documents", params); errorHandling(response, "querying documents", params);
@@ -135,7 +137,9 @@ export function readDocument(
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(); const endpoint = getEndpoint();
@@ -147,10 +151,10 @@ export function readDocument(
...authHeaders(), ...authHeaders(),
[CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent( [CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent(
JSON.stringify(documentId.partitionKeyHeader()) JSON.stringify(documentId.partitionKeyHeader())
) ),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@@ -175,7 +179,7 @@ export function createDocument(
sid: userContext.subscriptionId, sid: userContext.subscriptionId,
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, dba: databaseAccount.name,
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "" pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
}; };
const endpoint = getEndpoint(); const endpoint = getEndpoint();
@@ -186,10 +190,10 @@ export function createDocument(
body: JSON.stringify(documentContent), body: JSON.stringify(documentContent),
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders() ...authHeaders(),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@@ -218,7 +222,9 @@ export function updateDocument(
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(); const endpoint = getEndpoint();
@@ -230,10 +236,10 @@ export function updateDocument(
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()) [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@@ -257,7 +263,9 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(); const endpoint = getEndpoint();
@@ -268,10 +276,10 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()) [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return undefined; return undefined;
} }
@@ -299,7 +307,7 @@ export function createMongoCollectionWithProxy(
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, dba: databaseAccount.name,
isAutoPilot: !!params.autoPilotMaxThroughput, isAutoPilot: !!params.autoPilotMaxThroughput,
autoPilotThroughput: params.autoPilotMaxThroughput?.toString() autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
}; };
const endpoint = getEndpoint(); const endpoint = getEndpoint();
@@ -314,11 +322,11 @@ export function createMongoCollectionWithProxy(
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json" [HttpHeaders.contentType]: "application/json",
} },
} }
) )
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }

View File

@@ -1,168 +1,168 @@
/* Copyright 2013 10gen Inc. /* Copyright 2013 10gen Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export default class MongoUtility { export default class MongoUtility {
public static tojson = function(x: any, indent: string, nolint: boolean) { public static tojson = function (x: any, indent: string, nolint: boolean) {
if (x === null || x === undefined) { if (x === null || x === undefined) {
return String(x); return String(x);
} }
indent = indent || ""; indent = indent || "";
switch (typeof x) { switch (typeof x) {
case "string": case "string":
var out = new Array(x.length + 1); var out = new Array(x.length + 1);
out[0] = '"'; out[0] = '"';
for (var i = 0; i < x.length; i++) { for (var i = 0; i < x.length; i++) {
if (x[i] === '"') { if (x[i] === '"') {
out[out.length] = '\\"'; out[out.length] = '\\"';
} else if (x[i] === "\\") { } else if (x[i] === "\\") {
out[out.length] = "\\\\"; out[out.length] = "\\\\";
} else if (x[i] === "\b") { } else if (x[i] === "\b") {
out[out.length] = "\\b"; out[out.length] = "\\b";
} else if (x[i] === "\f") { } else if (x[i] === "\f") {
out[out.length] = "\\f"; out[out.length] = "\\f";
} else if (x[i] === "\n") { } else if (x[i] === "\n") {
out[out.length] = "\\n"; out[out.length] = "\\n";
} else if (x[i] === "\r") { } else if (x[i] === "\r") {
out[out.length] = "\\r"; out[out.length] = "\\r";
} else if (x[i] === "\t") { } else if (x[i] === "\t") {
out[out.length] = "\\t"; out[out.length] = "\\t";
} else { } else {
var code = x.charCodeAt(i); var code = x.charCodeAt(i);
if (code < 0x20) { if (code < 0x20) {
out[out.length] = (code < 0x10 ? "\\u000" : "\\u00") + code.toString(16); out[out.length] = (code < 0x10 ? "\\u000" : "\\u00") + code.toString(16);
} else { } else {
out[out.length] = x[i]; out[out.length] = x[i];
} }
} }
} }
return out.join("") + '"'; return out.join("") + '"';
case "number": case "number":
/* falls through */ /* falls through */
case "boolean": case "boolean":
return "" + x; return "" + x;
case "object": case "object":
var func = $.isArray(x) ? MongoUtility.tojsonArray : MongoUtility.tojsonObject; var func = $.isArray(x) ? MongoUtility.tojsonArray : MongoUtility.tojsonObject;
var s = func(x, indent, nolint); var s = func(x, indent, nolint);
if ( if (
(nolint === null || nolint === undefined || nolint === true) && (nolint === null || nolint === undefined || nolint === true) &&
s.length < 80 && s.length < 80 &&
(indent === null || indent.length === 0) (indent === null || indent.length === 0)
) { ) {
s = s.replace(/[\t\r\n]+/gm, " "); s = s.replace(/[\t\r\n]+/gm, " ");
} }
return s; return s;
case "function": case "function":
return x.toString(); return x.toString();
default: default:
throw new Error("tojson can't handle type " + typeof x); throw new Error("tojson can't handle type " + typeof x);
} }
}; };
private static tojsonObject = function(x: any, indent: string, nolint: boolean) { private static tojsonObject = function (x: any, indent: string, nolint: boolean) {
var lineEnding = nolint ? " " : "\n"; var lineEnding = nolint ? " " : "\n";
var tabSpace = nolint ? "" : "\t"; var tabSpace = nolint ? "" : "\t";
indent = indent || ""; indent = indent || "";
if (typeof x.tojson === "function" && x.tojson !== MongoUtility.tojson) { if (typeof x.tojson === "function" && x.tojson !== MongoUtility.tojson) {
return x.tojson(indent, nolint); return x.tojson(indent, nolint);
} }
if (x.constructor && typeof x.constructor.tojson === "function" && x.constructor.tojson !== MongoUtility.tojson) { if (x.constructor && typeof x.constructor.tojson === "function" && x.constructor.tojson !== MongoUtility.tojson) {
return x.constructor.tojson(x, indent, nolint); return x.constructor.tojson(x, indent, nolint);
} }
if (MongoUtility.hasDefinedProperty(x, "toString") && !$.isArray(x)) { if (MongoUtility.hasDefinedProperty(x, "toString") && !$.isArray(x)) {
return x.toString(); return x.toString();
} }
if (x instanceof Error) { if (x instanceof Error) {
return x.toString(); return x.toString();
} }
if (MongoUtility.isObjectId(x)) { if (MongoUtility.isObjectId(x)) {
return 'ObjectId("' + x.$oid + '")'; return 'ObjectId("' + x.$oid + '")';
} }
// push one level of indent // push one level of indent
indent += tabSpace; indent += tabSpace;
var s = "{"; var s = "{";
var pairs = []; var pairs = [];
for (var k in x) { for (var k in x) {
if (x.hasOwnProperty(k)) { if (x.hasOwnProperty(k)) {
var val = x[k]; var val = x[k];
var pair = '"' + k + '" : ' + MongoUtility.tojson(val, indent, nolint); var pair = '"' + k + '" : ' + MongoUtility.tojson(val, indent, nolint);
if (k === "_id") { if (k === "_id") {
pairs.unshift(pair); pairs.unshift(pair);
} else { } else {
pairs.push(pair); pairs.push(pair);
} }
} }
} }
// Add proper line endings, indents, and commas to each line // Add proper line endings, indents, and commas to each line
s += $.map(pairs, function(pair) { s += $.map(pairs, function (pair) {
return lineEnding + indent + pair; return lineEnding + indent + pair;
}).join(","); }).join(",");
s += lineEnding; s += lineEnding;
// pop one level of indent // pop one level of indent
indent = indent.substring(1); indent = indent.substring(1);
return s + indent + "}"; return s + indent + "}";
}; };
private static tojsonArray = function(a: any, indent: string, nolint: boolean) { private static tojsonArray = function (a: any, indent: string, nolint: boolean) {
if (a.length === 0) { if (a.length === 0) {
return "[ ]"; return "[ ]";
} }
var lineEnding = nolint ? " " : "\n"; var lineEnding = nolint ? " " : "\n";
if (!indent || nolint) { if (!indent || nolint) {
indent = ""; indent = "";
} }
var s = "[" + lineEnding; var s = "[" + lineEnding;
indent += "\t"; indent += "\t";
for (var i = 0; i < a.length; i++) { for (var i = 0; i < a.length; i++) {
s += indent + MongoUtility.tojson(a[i], indent, nolint); s += indent + MongoUtility.tojson(a[i], indent, nolint);
if (i < a.length - 1) { if (i < a.length - 1) {
s += "," + lineEnding; s += "," + lineEnding;
} }
} }
if (a.length === 0) { if (a.length === 0) {
s += indent; s += indent;
} }
indent = indent.substring(1); indent = indent.substring(1);
s += lineEnding + indent + "]"; s += lineEnding + indent + "]";
return s; return s;
}; };
private static hasDefinedProperty = function(obj: any, prop: string): boolean { private static hasDefinedProperty = function (obj: any, prop: string): boolean {
if (Object.getPrototypeOf === undefined || Object.getPrototypeOf(obj) === null) { if (Object.getPrototypeOf === undefined || Object.getPrototypeOf(obj) === null) {
return false; return false;
} else if (obj.hasOwnProperty(prop)) { } else if (obj.hasOwnProperty(prop)) {
return true; return true;
} else { } else {
return MongoUtility.hasDefinedProperty(Object.getPrototypeOf(obj), prop); return MongoUtility.hasDefinedProperty(Object.getPrototypeOf(obj), prop);
} }
}; };
private static isObjectId(obj: any): boolean { private static isObjectId(obj: any): boolean {
var keys = Object.keys(obj); var keys = Object.keys(obj);
return keys.length === 1 && keys[0] === "$oid" && typeof obj.$oid === "string" && /^[0-9a-f]{24}$/.test(obj.$oid); return keys.length === 1 && keys[0] === "$oid" && typeof obj.$oid === "string" && /^[0-9a-f]{24}$/.test(obj.$oid);
} }
} }

View File

@@ -0,0 +1,64 @@
import * as OfferUtility from "./OfferUtility";
import { SDKOfferDefinition, Offer } from "../Contracts/DataModels";
import { OfferResponse } from "@azure/cosmos";
describe("parseSDKOfferResponse", () => {
it("manual throughput", () => {
const mockOfferDefinition = {
content: {
offerThroughput: 500,
collectionThroughputInfo: {
minimumRUForCollection: 400,
numPhysicalPartitions: 1,
},
},
id: "test",
} as SDKOfferDefinition;
const mockResponse = {
resource: mockOfferDefinition,
} as OfferResponse;
const expectedResult: Offer = {
manualThroughput: 500,
autoscaleMaxThroughput: undefined,
minimumThroughput: 400,
id: "test",
offerDefinition: mockOfferDefinition,
offerReplacePending: false,
};
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
});
it("autoscale throughput", () => {
const mockOfferDefinition = {
content: {
offerThroughput: 400,
collectionThroughputInfo: {
minimumRUForCollection: 400,
numPhysicalPartitions: 1,
},
offerAutopilotSettings: {
maxThroughput: 5000,
},
},
id: "test",
} as SDKOfferDefinition;
const mockResponse = {
resource: mockOfferDefinition,
} as OfferResponse;
const expectedResult: Offer = {
manualThroughput: undefined,
autoscaleMaxThroughput: 5000,
minimumThroughput: 400,
id: "test",
offerDefinition: mockOfferDefinition,
offerReplacePending: false,
};
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
});
});

View File

@@ -0,0 +1,37 @@
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
import { OfferResponse } from "@azure/cosmos";
import { HttpHeaders } from "./Constants";
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer | undefined => {
const offerDefinition: SDKOfferDefinition | undefined = offerResponse?.resource;
if (!offerDefinition) {
return undefined;
}
const offerContent = offerDefinition.content;
if (!offerContent) {
return undefined;
}
const minimumThroughput = offerContent.collectionThroughputInfo?.minimumRUForCollection;
const autopilotSettings = offerContent.offerAutopilotSettings;
if (autopilotSettings && autopilotSettings.maxThroughput && minimumThroughput) {
return {
id: offerDefinition.id,
autoscaleMaxThroughput: autopilotSettings.maxThroughput,
manualThroughput: undefined,
minimumThroughput,
offerDefinition,
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true",
};
}
return {
id: offerDefinition.id,
autoscaleMaxThroughput: undefined,
manualThroughput: offerContent.offerThroughput,
minimumThroughput,
offerDefinition,
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true",
};
};

View File

@@ -16,7 +16,7 @@ const notificationsPath = () => {
}; };
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => { export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator || configContext.platform === Platform.Hosted) {
return []; return [];
} }
@@ -30,7 +30,7 @@ export const fetchPortalNotifications = async (): Promise<DataModels.Notificatio
const headers = { [authorizationHeader.header]: authorizationHeader.token }; const headers = { [authorizationHeader.header]: authorizationHeader.token };
const response = await window.fetch(url, { const response = await window.fetch(url, {
headers headers,
}); });
if (!response.ok) { if (!response.ok) {

View File

@@ -1,253 +1,219 @@
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as _ from "underscore"; import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; 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 * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { QueryUtils } from "../Utils/QueryUtils";
import { QueryUtils } from "../Utils/QueryUtils"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { userContext } from "../UserContext";
import { userContext } from "../UserContext"; import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase"; import { createCollection } from "./dataAccess/createCollection";
import { createCollection } from "./dataAccess/createCollection"; import { handleError } from "./ErrorHandlingUtils";
import { handleError } from "./ErrorHandlingUtils"; import { createDocument } from "./dataAccess/createDocument";
import { deleteDocument } from "./dataAccess/deleteDocument";
export class QueriesClient { import { queryDocuments } from "./dataAccess/queryDocuments";
private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`], export class QueriesClient {
kind: BackendDefaults.partitionKeyKind, private static readonly PartitionKey: DataModels.PartitionKey = {
version: BackendDefaults.partitionKeyVersion paths: [`/${SavedQueries.PartitionKeyProperty}`],
}; kind: BackendDefaults.partitionKeyKind,
private static readonly FetchQuery: string = "SELECT * FROM c"; version: BackendDefaults.partitionKeyVersion,
private static readonly FetchMongoQuery: string = "{}"; };
private static readonly FetchQuery: string = "SELECT * FROM c";
public constructor(private container: Explorer) {} private static readonly FetchMongoQuery: string = "{}";
public async setupQueriesCollection(): Promise<DataModels.Collection> { public constructor(private container: Explorer) {}
const queriesCollection: ViewModels.Collection = this.findQueriesCollection();
if (queriesCollection) { public async setupQueriesCollection(): Promise<DataModels.Collection> {
return Promise.resolve(queriesCollection.rawDataModel); const queriesCollection: ViewModels.Collection = this.findQueriesCollection();
} if (queriesCollection) {
return Promise.resolve(queriesCollection.rawDataModel);
const id = NotificationConsoleUtils.logConsoleMessage( }
ConsoleDataType.InProgress,
"Setting up account for saving queries" const clearMessage = NotificationConsoleUtils.logConsoleProgress("Setting up account for saving queries");
); return createCollection({
return createCollection({ collectionId: SavedQueries.CollectionName,
collectionId: SavedQueries.CollectionName, createNewDatabase: true,
createNewDatabase: true, databaseId: SavedQueries.DatabaseName,
databaseId: SavedQueries.DatabaseName, partitionKey: QueriesClient.PartitionKey,
partitionKey: QueriesClient.PartitionKey, offerThroughput: SavedQueries.OfferThroughput,
offerThroughput: SavedQueries.OfferThroughput, databaseLevelThroughput: false,
databaseLevelThroughput: false })
}) .then(
.then( (collection: DataModels.Collection) => {
(collection: DataModels.Collection) => { NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
NotificationConsoleUtils.logConsoleMessage( return Promise.resolve(collection);
ConsoleDataType.Info, },
"Successfully set up account for saving queries" (error: any) => {
); handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
return Promise.resolve(collection); return Promise.reject(error);
}, }
(error: any) => { )
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries"); .finally(() => clearMessage());
return Promise.reject(error); }
}
) public async saveQuery(query: DataModels.Query): Promise<void> {
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); const queriesCollection = this.findQueriesCollection();
} if (!queriesCollection) {
const errorMessage: string = "Account not set up to perform saved query operations";
public async saveQuery(query: DataModels.Query): Promise<void> { NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
const queriesCollection = this.findQueriesCollection(); return Promise.reject(errorMessage);
if (!queriesCollection) { }
const errorMessage: string = "Account not set up to perform saved query operations";
NotificationConsoleUtils.logConsoleMessage( try {
ConsoleDataType.Error, this.validateQuery(query);
`Failed to save query ${query.queryName}: ${errorMessage}` } catch (error) {
); const errorMessage: string = "Invalid query specified";
return Promise.reject(errorMessage); NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
} return Promise.reject(errorMessage);
}
try {
this.validateQuery(query); const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Saving query ${query.queryName}`);
} catch (error) { query.id = query.queryName;
const errorMessage: string = "Invalid query specified"; return createDocument(queriesCollection, query)
NotificationConsoleUtils.logConsoleMessage( .then(
ConsoleDataType.Error, (savedQuery: DataModels.Query) => {
`Failed to save query ${query.queryName}: ${errorMessage}` NotificationConsoleUtils.logConsoleInfo(`Successfully saved query ${query.queryName}`);
); return Promise.resolve();
return Promise.reject(errorMessage); },
} (error: any) => {
if (error.code === HttpStatusCodes.Conflict.toString()) {
const id = NotificationConsoleUtils.logConsoleMessage( error = `Query ${query.queryName} already exists`;
ConsoleDataType.InProgress, }
`Saving query ${query.queryName}` handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
); return Promise.reject(error);
query.id = query.queryName; }
return createDocument(queriesCollection, query) )
.then( .finally(() => clearMessage());
(savedQuery: DataModels.Query) => { }
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info, public async getQueries(): Promise<DataModels.Query[]> {
`Successfully saved query ${query.queryName}` const queriesCollection = this.findQueriesCollection();
); if (!queriesCollection) {
return Promise.resolve(); const errorMessage: string = "Account not set up to perform saved query operations";
}, NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
(error: any) => { return Promise.reject(errorMessage);
if (error.code === HttpStatusCodes.Conflict.toString()) { }
error = `Query ${query.queryName} already exists`;
} const options: any = { enableCrossPartitionQuery: true };
handleError(error, "saveQuery", `Failed to save query ${query.queryName}`); const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
return Promise.reject(error); const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
} SavedQueries.DatabaseName,
) SavedQueries.CollectionName,
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); this.fetchQueriesQuery(),
} options
);
public async getQueries(): Promise<DataModels.Query[]> { const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
const queriesCollection = this.findQueriesCollection(); await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
if (!queriesCollection) { return QueryUtils.queryAllPages(fetchQueries)
const errorMessage: string = "Account not set up to perform saved query operations"; .then(
NotificationConsoleUtils.logConsoleMessage( (results: ViewModels.QueryResults) => {
ConsoleDataType.Error, let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
`Failed to fetch saved queries: ${errorMessage}` if (!document) {
); return undefined;
return Promise.reject(errorMessage); }
} const { id, resourceId, query, queryName } = document;
const parsedQuery: DataModels.Query = {
const options: any = { enableCrossPartitionQuery: true }; resourceId: resourceId,
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries"); queryName: queryName,
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options) query: query,
.then( id: id,
(queryIterator: QueryIterator<ItemDefinition & Resource>) => { };
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> => try {
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options); this.validateQuery(parsedQuery);
return QueryUtils.queryAllPages(fetchQueries).then( return parsedQuery;
(results: ViewModels.QueryResults) => { } catch (error) {
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => { return undefined;
if (!document) { }
return undefined; });
} queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
const { id, resourceId, query, queryName } = document; NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
const parsedQuery: DataModels.Query = { return Promise.resolve(queries);
resourceId: resourceId, },
queryName: queryName, (error: any) => {
query: query, handleError(error, "getSavedQueries", "Failed to fetch saved queries");
id: id return Promise.reject(error);
}; }
try { )
this.validateQuery(parsedQuery); .finally(() => clearMessage());
return parsedQuery; }
} catch (error) {
return undefined; public async deleteQuery(query: DataModels.Query): Promise<void> {
} const queriesCollection = this.findQueriesCollection();
}); if (!queriesCollection) {
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery); const errorMessage: string = "Account not set up to perform saved query operations";
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully fetched saved queries"); NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
return Promise.resolve(queries); return Promise.reject(errorMessage);
}, }
(error: any) => {
handleError(error, "getSavedQueries", "Failed to fetch saved queries"); try {
return Promise.reject(error); this.validateQuery(query);
} } catch (error) {
); const errorMessage: string = "Invalid query specified";
}, NotificationConsoleUtils.logConsoleError(`Failed to delete query ${query.queryName}: ${errorMessage}`);
(error: any) => { }
// should never get into this state but we handle this regardless
handleError(error, "getSavedQueries", "Failed to fetch saved queries"); const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting query ${query.queryName}`);
return Promise.reject(error); query.id = query.queryName;
} const documentId = new DocumentId(
) {
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); partitionKey: QueriesClient.PartitionKey,
} partitionKeyProperty: "id",
} as DocumentsTab,
public async deleteQuery(query: DataModels.Query): Promise<void> { query,
const queriesCollection = this.findQueriesCollection(); query.queryName
if (!queriesCollection) { ); // TODO: Remove DocumentId's dependency on DocumentsTab
const errorMessage: string = "Account not set up to perform saved query operations"; const options: any = { partitionKey: query.resourceId };
NotificationConsoleUtils.logConsoleMessage( return deleteDocument(queriesCollection, documentId)
ConsoleDataType.Error, .then(
`Failed to fetch saved queries: ${errorMessage}` () => {
); NotificationConsoleUtils.logConsoleInfo(`Successfully deleted query ${query.queryName}`);
return Promise.reject(errorMessage); return Promise.resolve();
} },
(error: any) => {
try { handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
this.validateQuery(query); return Promise.reject(error);
} catch (error) { }
const errorMessage: string = "Invalid query specified"; )
NotificationConsoleUtils.logConsoleMessage( .finally(() => clearMessage());
ConsoleDataType.Error, }
`Failed to delete query ${query.queryName}: ${errorMessage}`
); public getResourceId(): string {
} const databaseAccount = userContext.databaseAccount;
const databaseAccountName = (databaseAccount && databaseAccount.name) || "";
const id = NotificationConsoleUtils.logConsoleMessage( const subscriptionId = userContext.subscriptionId || "";
ConsoleDataType.InProgress, const resourceGroup = userContext.resourceGroup || "";
`Deleting query ${query.queryName}`
); return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
query.id = query.queryName; }
const documentId = new DocumentId(
{ private findQueriesCollection(): ViewModels.Collection {
partitionKey: QueriesClient.PartitionKey, const queriesDatabase: ViewModels.Database = _.find(
partitionKeyProperty: "id" this.container.databases(),
} as DocumentsTab, (database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
query, );
query.queryName if (!queriesDatabase) {
); // TODO: Remove DocumentId's dependency on DocumentsTab return undefined;
const options: any = { partitionKey: query.resourceId }; }
return deleteDocument(queriesCollection, documentId) return _.find(
.then( queriesDatabase.collections(),
() => { (collection: ViewModels.Collection) => collection.id() === SavedQueries.CollectionName
NotificationConsoleUtils.logConsoleMessage( );
ConsoleDataType.Info, }
`Successfully deleted query ${query.queryName}`
); private validateQuery(query: DataModels.Query): void {
return Promise.resolve(); if (!query || query.queryName == null || query.query == null || query.resourceId == null) {
}, throw new Error("Invalid query specified");
(error: any) => { }
handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`); }
return Promise.reject(error);
} private fetchQueriesQuery(): string {
) if (this.container.isPreferredApiMongoDB()) {
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); return QueriesClient.FetchMongoQuery;
} }
return QueriesClient.FetchQuery;
public getResourceId(): string { }
const databaseAccount = userContext.databaseAccount; }
const databaseAccountName = (databaseAccount && databaseAccount.name) || "";
const subscriptionId = userContext.subscriptionId || "";
const resourceGroup = userContext.resourceGroup || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
}
private findQueriesCollection(): ViewModels.Collection {
const queriesDatabase: ViewModels.Database = _.find(
this.container.databases(),
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
);
if (!queriesDatabase) {
return undefined;
}
return _.find(
queriesDatabase.collections(),
(collection: ViewModels.Collection) => collection.id() === SavedQueries.CollectionName
);
}
private validateQuery(query: DataModels.Query): void {
if (!query || query.queryName == null || query.query == null || query.resourceId == null) {
throw new Error("Invalid query specified");
}
}
private fetchQueriesQuery(): string {
if (this.container.isPreferredApiMongoDB()) {
return QueriesClient.FetchMongoQuery;
}
return QueriesClient.FetchQuery;
}
}

View File

@@ -1,108 +1,107 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { SplitterMetrics } from "./Constants"; import { SplitterMetrics } from "./Constants";
export enum SplitterDirection { export enum SplitterDirection {
Horizontal = "horizontal", Horizontal = "horizontal",
Vertical = "vertical" Vertical = "vertical",
} }
export interface SplitterBounds { export interface SplitterBounds {
max: number; max: number;
min: number; min: number;
} }
export interface SplitterOptions { export interface SplitterOptions {
splitterId: string; splitterId: string;
leftId: string; leftId: string;
bounds: SplitterBounds; bounds: SplitterBounds;
direction: SplitterDirection; direction: SplitterDirection;
} }
export class Splitter { export class Splitter {
public splitterId: string; public splitterId: string;
public leftSideId: string; public leftSideId: string;
public splitter: HTMLElement; public splitter!: HTMLElement;
public leftSide: HTMLElement; public leftSide!: HTMLElement;
public lastX: number; public lastX!: number;
public lastWidth: number; public lastWidth!: number;
private isCollapsed: ko.Observable<boolean>; private isCollapsed: ko.Observable<boolean>;
private bounds: SplitterBounds; private bounds: SplitterBounds;
private direction: SplitterDirection; private direction: SplitterDirection;
constructor(options: SplitterOptions) { constructor(options: SplitterOptions) {
this.splitterId = options.splitterId; this.splitterId = options.splitterId;
this.leftSideId = options.leftId; this.leftSideId = options.leftId;
this.isCollapsed = ko.observable<boolean>(false); this.isCollapsed = ko.observable<boolean>(false);
this.bounds = options.bounds; this.bounds = options.bounds;
this.direction = options.direction; this.direction = options.direction;
this.initialize(); this.initialize();
} }
public initialize() { public initialize() {
this.splitter = document.getElementById(this.splitterId); if (document.getElementById(this.splitterId) !== null && document.getElementById(this.leftSideId) != null) {
this.leftSide = document.getElementById(this.leftSideId); this.splitter = <HTMLElement>document.getElementById(this.splitterId);
this.leftSide = <HTMLElement>document.getElementById(this.leftSideId);
const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical; }
const splitterOptions: JQueryUI.ResizableOptions = { const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical;
animate: true, const splitterOptions: JQueryUI.ResizableOptions = {
animateDuration: "fast", animate: true,
start: this.onResizeStart, animateDuration: "fast",
stop: this.onResizeStop start: this.onResizeStart,
}; stop: this.onResizeStop,
};
if (isVerticalSplitter) {
$(this.leftSide).css("width", this.bounds.min); if (isVerticalSplitter) {
$(this.splitter).css("height", "100%"); $(this.leftSide).css("width", this.bounds.min);
$(this.splitter).css("height", "100%");
splitterOptions.maxWidth = this.bounds.max;
splitterOptions.minWidth = this.bounds.min; splitterOptions.maxWidth = this.bounds.max;
splitterOptions.handles = { e: "#" + this.splitterId }; splitterOptions.minWidth = this.bounds.min;
} else { splitterOptions.handles = { e: "#" + this.splitterId };
$(this.leftSide).css("height", this.bounds.min); } else {
$(this.splitter).css("width", "100%"); $(this.leftSide).css("height", this.bounds.min);
$(this.splitter).css("width", "100%");
splitterOptions.maxHeight = this.bounds.max;
splitterOptions.minHeight = this.bounds.min; splitterOptions.maxHeight = this.bounds.max;
splitterOptions.handles = { s: "#" + this.splitterId }; splitterOptions.minHeight = this.bounds.min;
} splitterOptions.handles = { s: "#" + this.splitterId };
}
$(this.leftSide).resizable(splitterOptions);
} $(this.leftSide).resizable(splitterOptions);
}
private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
if (this.direction === SplitterDirection.Vertical) { private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
$(".ui-resizable-helper").height("100%"); if (this.direction === SplitterDirection.Vertical) {
} else { $(".ui-resizable-helper").height("100%");
$(".ui-resizable-helper").width("100%"); } else {
} $(".ui-resizable-helper").width("100%");
$("iframe").css("pointer-events", "none"); }
}; $("iframe").css("pointer-events", "none");
};
private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
$("iframe").css("pointer-events", "auto"); private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
}; $("iframe").css("pointer-events", "auto");
};
public collapseLeft() {
this.lastX = $(this.splitter).position().left; public collapseLeft() {
this.lastWidth = $(this.leftSide).width(); this.lastX = $(this.splitter).position().left;
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft); this.lastWidth = $(this.leftSide).width();
$(this.leftSide).css("width", ""); $(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
$(this.leftSide) $(this.leftSide).css("width", "");
.resizable("option", "disabled", true) $(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible
.removeClass("ui-resizable-disabled"); // remove class so splitter is visible $(this.splitter).removeClass("ui-resizable-e");
$(this.splitter).removeClass("ui-resizable-e"); this.isCollapsed(true);
this.isCollapsed(true); }
}
public expandLeft() {
public expandLeft() { $(this.splitter).addClass("ui-resizable-e");
$(this.splitter).addClass("ui-resizable-e"); $(this.leftSide).css("width", this.lastWidth);
$(this.leftSide).css("width", this.lastWidth); $(this.splitter).css("left", this.lastX);
$(this.splitter).css("left", this.lastX); $(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing
$(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing $(this.leftSide).resizable("enable");
$(this.leftSide).resizable("enable"); this.isCollapsed(false);
this.isCollapsed(false); }
} }
}

View File

@@ -32,8 +32,8 @@ export default class UrlUtility {
type: type, type: type,
objectBody: { objectBody: {
id: id, id: id,
self: resourcePath self: resourcePath,
} },
}; };
return result; return result;

View File

@@ -1,6 +1,5 @@
jest.mock("../../Utils/arm/request"); jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient"); jest.mock("../CosmosClient");
jest.mock("../DataAccessUtilityBase");
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 { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
@@ -15,15 +14,15 @@ describe("createCollection", () => {
collectionId: "testContainer", collectionId: "testContainer",
databaseId: "testDatabase", databaseId: "testDatabase",
databaseLevelThroughput: true, databaseLevelThroughput: true,
offerThroughput: 400 offerThroughput: 400,
}; };
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
name: "test" name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB,
}); });
}); });
@@ -41,12 +40,12 @@ describe("createCollection", () => {
return { return {
database: { database: {
containers: { containers: {
create: () => ({}) create: () => ({}),
} },
} },
}; };
} },
} },
}); });
await createCollection(createCollectionParams); await createCollection(createCollectionParams);
expect(client).toHaveBeenCalled(); expect(client).toHaveBeenCalled();
@@ -60,7 +59,7 @@ describe("createCollection", () => {
collectionId: "testContainer", collectionId: "testContainer",
databaseId: "testDatabase", databaseId: "testDatabase",
databaseLevelThroughput: false, databaseLevelThroughput: false,
offerThroughput: 400 offerThroughput: 400,
}; };
expect(constructRpOptions(manualThroughputParams)).toEqual({ throughput: 400 }); expect(constructRpOptions(manualThroughputParams)).toEqual({ throughput: 400 });
@@ -70,12 +69,12 @@ describe("createCollection", () => {
databaseId: "testDatabase", databaseId: "testDatabase",
databaseLevelThroughput: false, databaseLevelThroughput: false,
offerThroughput: 400, offerThroughput: 400,
autoPilotMaxThroughput: 4000 autoPilotMaxThroughput: 4000,
}; };
expect(constructRpOptions(autoPilotThroughputParams)).toEqual({ expect(constructRpOptions(autoPilotThroughputParams)).toEqual({
autoscaleSettings: { autoscaleSettings: {
maxThroughput: 4000 maxThroughput: 4000,
} },
}); });
}); });
}); });

View File

@@ -11,15 +11,15 @@ import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; 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 { import {
createUpdateMongoDBCollection, createUpdateMongoDBCollection,
getMongoDBCollection getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; } 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 { 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 { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
@@ -41,7 +41,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
autoPilotMaxThroughput: params.autoPilotMaxThroughput, autoPilotMaxThroughput: params.autoPilotMaxThroughput,
databaseId: params.databaseId, databaseId: params.databaseId,
databaseLevelThroughput: params.databaseLevelThroughput, databaseLevelThroughput: params.databaseLevelThroughput,
offerThroughput: params.offerThroughput offerThroughput: params.offerThroughput,
}; };
await createDatabase(createDatabaseParams); await createDatabase(createDatabaseParams);
} }
@@ -100,7 +100,7 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.SqlContainerResource = { const resource: ARMTypes.SqlContainerResource = {
id: params.collectionId id: params.collectionId,
}; };
if (params.analyticalStorageTtl) { if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl; resource.analyticalStorageTtl = params.analyticalStorageTtl;
@@ -118,8 +118,8 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = { const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
properties: { properties: {
resource, resource,
options options,
} },
}; };
const createResponse = await createUpdateSqlContainer( const createResponse = await createUpdateSqlContainer(
@@ -154,7 +154,7 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.MongoDBCollectionResource = { const resource: ARMTypes.MongoDBCollectionResource = {
id: params.collectionId id: params.collectionId,
}; };
if (params.analyticalStorageTtl) { if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl; resource.analyticalStorageTtl = params.analyticalStorageTtl;
@@ -170,8 +170,8 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = { const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = {
properties: { properties: {
resource, resource,
options options,
} },
}; };
const createResponse = await createUpdateMongoDBCollection( const createResponse = await createUpdateMongoDBCollection(
@@ -185,7 +185,7 @@ const createMongoCollection = async (params: DataModels.CreateCollectionParams):
if (params.createMongoWildcardIndex) { if (params.createMongoWildcardIndex) {
TelemetryProcessor.trace(Action.CreateMongoCollectionWithWildcardIndex, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.CreateMongoCollectionWithWildcardIndex, ActionModifiers.Mark, {
message: "Mongo Collection created with wildcard index on all fields." message: "Mongo Collection created with wildcard index on all fields.",
}); });
} }
@@ -212,7 +212,7 @@ const createCassandraTable = async (params: DataModels.CreateCollectionParams):
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.CassandraTableResource = { const resource: ARMTypes.CassandraTableResource = {
id: params.collectionId id: params.collectionId,
}; };
if (params.analyticalStorageTtl) { if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl; resource.analyticalStorageTtl = params.analyticalStorageTtl;
@@ -221,8 +221,8 @@ const createCassandraTable = async (params: DataModels.CreateCollectionParams):
const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = { const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = {
properties: { properties: {
resource, resource,
options options,
} },
}; };
const createResponse = await createUpdateCassandraTable( const createResponse = await createUpdateCassandraTable(
@@ -256,7 +256,7 @@ const createGraph = async (params: DataModels.CreateCollectionParams): Promise<D
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.GremlinGraphResource = { const resource: ARMTypes.GremlinGraphResource = {
id: params.collectionId id: params.collectionId,
}; };
if (params.indexingPolicy) { if (params.indexingPolicy) {
@@ -272,8 +272,8 @@ const createGraph = async (params: DataModels.CreateCollectionParams): Promise<D
const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = { const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = {
properties: { properties: {
resource, resource,
options options,
} },
}; };
const createResponse = await createUpdateGremlinGraph( const createResponse = await createUpdateGremlinGraph(
@@ -306,14 +306,14 @@ const createTable = async (params: DataModels.CreateCollectionParams): Promise<D
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params); const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.TableResource = { const resource: ARMTypes.TableResource = {
id: params.collectionId id: params.collectionId,
}; };
const rpPayload: ARMTypes.TableCreateUpdateParameters = { const rpPayload: ARMTypes.TableCreateUpdateParameters = {
properties: { properties: {
resource, resource,
options options,
} },
}; };
const createResponse = await createUpdateTable( const createResponse = await createUpdateTable(
@@ -334,13 +334,13 @@ export const constructRpOptions = (params: DataModels.CreateDatabaseParams): ARM
if (params.autoPilotMaxThroughput) { if (params.autoPilotMaxThroughput) {
return { return {
autoscaleSettings: { autoscaleSettings: {
maxThroughput: params.autoPilotMaxThroughput maxThroughput: params.autoPilotMaxThroughput,
} },
}; };
} }
return { return {
throughput: params.offerThroughput throughput: params.offerThroughput,
}; };
}; };
@@ -350,7 +350,7 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
partitionKey: params.partitionKey || undefined, partitionKey: params.partitionKey || undefined,
indexingPolicy: params.indexingPolicy || undefined, indexingPolicy: params.indexingPolicy || undefined,
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined, uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
analyticalStorageTtl: params.analyticalStorageTtl analyticalStorageTtl: params.analyticalStorageTtl,
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed } as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
const collectionOptions: RequestOptions = {}; const collectionOptions: RequestOptions = {};
const createDatabaseBody: DatabaseRequest = { id: params.databaseId }; const createDatabaseBody: DatabaseRequest = { id: params.databaseId };

View File

@@ -8,21 +8,21 @@ import {
GremlinDatabaseCreateUpdateParameters, GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters, MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters, SqlDatabaseCreateUpdateParameters,
CreateUpdateOptions CreateUpdateOptions,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; 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 { import {
createUpdateMongoDBDatabase, createUpdateMongoDBDatabase,
getMongoDBDatabase getMongoDBDatabase,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; } 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 { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
@@ -85,10 +85,10 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
const rpPayload: SqlDatabaseCreateUpdateParameters = { const rpPayload: SqlDatabaseCreateUpdateParameters = {
properties: { properties: {
resource: { resource: {
id: params.databaseId id: params.databaseId,
}, },
options options,
} },
}; };
const createResponse = await createUpdateSqlDatabase( const createResponse = await createUpdateSqlDatabase(
userContext.subscriptionId, userContext.subscriptionId,
@@ -121,10 +121,10 @@ async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Pro
const rpPayload: MongoDBDatabaseCreateUpdateParameters = { const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
properties: { properties: {
resource: { resource: {
id: params.databaseId id: params.databaseId,
}, },
options options,
} },
}; };
const createResponse = await createUpdateMongoDBDatabase( const createResponse = await createUpdateMongoDBDatabase(
userContext.subscriptionId, userContext.subscriptionId,
@@ -157,10 +157,10 @@ async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams):
const rpPayload: CassandraKeyspaceCreateUpdateParameters = { const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
properties: { properties: {
resource: { resource: {
id: params.databaseId id: params.databaseId,
}, },
options options,
} },
}; };
const createResponse = await createUpdateCassandraKeyspace( const createResponse = await createUpdateCassandraKeyspace(
userContext.subscriptionId, userContext.subscriptionId,
@@ -193,10 +193,10 @@ async function createGremlineDatabase(params: DataModels.CreateDatabaseParams):
const rpPayload: GremlinDatabaseCreateUpdateParameters = { const rpPayload: GremlinDatabaseCreateUpdateParameters = {
properties: { properties: {
resource: { resource: {
id: params.databaseId id: params.databaseId,
}, },
options options,
} },
}; };
const createResponse = await createUpdateGremlinDatabase( const createResponse = await createUpdateGremlinDatabase(
userContext.subscriptionId, userContext.subscriptionId,
@@ -231,12 +231,12 @@ function constructRpOptions(params: DataModels.CreateDatabaseParams): CreateUpda
if (params.autoPilotMaxThroughput) { if (params.autoPilotMaxThroughput) {
return { return {
autoscaleSettings: { autoscaleSettings: {
maxThroughput: params.autoPilotMaxThroughput maxThroughput: params.autoPilotMaxThroughput,
} },
}; };
} }
return { return {
throughput: params.offerThroughput throughput: params.offerThroughput,
}; };
} }

View File

@@ -0,0 +1,25 @@
import { CollectionBase } from "../../Contracts/ViewModels";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.items.create(newDocument);
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
return response?.resource;
} catch (error) {
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -3,12 +3,12 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import {
SqlStoredProcedureCreateUpdateParameters, SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; 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 { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@@ -49,8 +49,8 @@ export async function createStoredProcedure(
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = { const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: { properties: {
resource: storedProcedure as SqlStoredProcedureResource, resource: storedProcedure as SqlStoredProcedureResource,
options: {} options: {},
} },
}; };
const rpResponse = await createUpdateSqlStoredProcedure( const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -3,7 +3,7 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
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 { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
@@ -44,8 +44,8 @@ export async function createTrigger(
const createTriggerParams: SqlTriggerCreateUpdateParameters = { const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: { properties: {
resource: trigger as SqlTriggerResource, resource: trigger as SqlTriggerResource,
options: {} options: {},
} },
}; };
const rpResponse = await createUpdateSqlTrigger( const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,
@@ -59,10 +59,7 @@ export async function createTrigger(
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource); return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
} }
const response = await client() const response = await client().database(databaseId).container(collectionId).scripts.triggers.create(trigger);
.database(databaseId)
.container(collectionId)
.scripts.triggers.create(trigger);
return response.resource; return response.resource;
} catch (error) { } catch (error) {
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`); handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);

View File

@@ -3,12 +3,12 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import {
SqlUserDefinedFunctionCreateUpdateParameters, SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; 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 { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@@ -49,8 +49,8 @@ export async function createUserDefinedFunction(
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = { const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: { properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource, resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {} options: {},
} },
}; };
const rpResponse = await createUpdateSqlUserDefinedFunction( const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -13,9 +13,9 @@ describe("deleteCollection", () => {
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
name: "test" name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB,
}); });
}); });
@@ -32,11 +32,11 @@ describe("deleteCollection", () => {
return { return {
container: () => { container: () => {
return { return {
delete: (): unknown => undefined delete: (): unknown => undefined,
}; };
} },
}; };
} },
}); });
await deleteCollection("database", "collection"); await deleteCollection("database", "collection");
expect(client).toHaveBeenCalled(); expect(client).toHaveBeenCalled();

View File

@@ -16,10 +16,7 @@ export async function deleteCollection(databaseId: string, collectionId: string)
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteCollectionWithARM(databaseId, collectionId); await deleteCollectionWithARM(databaseId, collectionId);
} else { } else {
await client() await client().database(databaseId).container(collectionId).delete();
.database(databaseId)
.container(collectionId)
.delete();
} }
logConsoleInfo(`Successfully deleted container ${collectionId}`); logConsoleInfo(`Successfully deleted container ${collectionId}`);
} catch (error) { } catch (error) {

View File

@@ -0,0 +1,36 @@
import ConflictId from "../../Explorer/Tree/ConflictId";
import { CollectionBase } from "../../Contracts/ViewModels";
import { RequestOptions } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
export const deleteConflict = async (collection: CollectionBase, conflictId: ConflictId): Promise<void> => {
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
try {
const options = {
partitionKey: getPartitionKeyHeaderForConflict(conflictId),
};
await client()
.database(collection.databaseId)
.container(collection.id())
.conflict(conflictId.id())
.delete(options as RequestOptions);
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
} catch (error) {
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
throw error;
} finally {
clearMessage();
}
};
const getPartitionKeyHeaderForConflict = (conflictId: ConflictId): unknown => {
if (!conflictId.partitionKey) {
return undefined;
}
return conflictId.partitionKeyValue === undefined ? [{}] : [conflictId.partitionKeyValue];
};

View File

@@ -13,9 +13,9 @@ describe("deleteDatabase", () => {
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
name: "test" name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB,
}); });
}); });
@@ -30,9 +30,9 @@ describe("deleteDatabase", () => {
(client as jest.Mock).mockReturnValue({ (client as jest.Mock).mockReturnValue({
database: () => { database: () => {
return { return {
delete: (): unknown => undefined delete: (): unknown => undefined,
}; };
} },
}); });
await deleteDatabase("database"); await deleteDatabase("database");
expect(client).toHaveBeenCalled(); expect(client).toHaveBeenCalled();

View File

@@ -19,9 +19,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteDatabaseWithARM(databaseId); await deleteDatabaseWithARM(databaseId);
} else { } else {
await client() await client().database(databaseId).delete();
.database(databaseId)
.delete();
} }
logConsoleInfo(`Successfully deleted database ${databaseId}`); logConsoleInfo(`Successfully deleted database ${databaseId}`);
} catch (error) { } catch (error) {

View File

@@ -0,0 +1,25 @@
import { CollectionBase } from "../../Contracts/ViewModels";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import DocumentId from "../../Explorer/Tree/DocumentId";
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
const entityName: string = getEntityName();
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
try {
await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), documentId.partitionKeyValue)
.delete();
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
} catch (error) {
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -27,11 +27,7 @@ export async function deleteStoredProcedure(
storedProcedureId storedProcedureId
); );
} else { } else {
await client() await client().database(databaseId).container(collectionId).scripts.storedProcedure(storedProcedureId).delete();
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedureId)
.delete();
} }
} catch (error) { } catch (error) {
handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`); handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);

View File

@@ -23,11 +23,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr
triggerId triggerId
); );
} else { } else {
await client() await client().database(databaseId).container(collectionId).scripts.trigger(triggerId).delete();
.database(databaseId)
.container(collectionId)
.scripts.trigger(triggerId)
.delete();
} }
} catch (error) { } catch (error) {
handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`); handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);

View File

@@ -23,11 +23,7 @@ export async function deleteUserDefinedFunction(databaseId: string, collectionId
id id
); );
} else { } else {
await client() await client().database(databaseId).container(collectionId).scripts.userDefinedFunction(id).delete();
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(id)
.delete();
} }
} catch (error) { } catch (error) {
handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`); handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);

View File

@@ -0,0 +1,48 @@
import { Collection } from "../../Contracts/ViewModels";
import { ClientDefaults, HttpHeaders } from "../Constants";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import StoredProcedure from "../../Explorer/Tree/StoredProcedure";
export interface ExecuteSprocResult {
result: StoredProcedure;
scriptLogs: string;
}
export const executeStoredProcedure = async (
collection: Collection,
storedProcedure: StoredProcedure,
partitionKeyValue: string,
params: string[]
): Promise<ExecuteSprocResult> => {
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
const timeout = setTimeout(() => {
throw Error(`Request timed out while executing stored procedure ${storedProcedure.id()}`);
}, ClientDefaults.requestTimeoutMs);
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id())
.execute(partitionKeyValue, params, { enableScriptLogging: true });
clearTimeout(timeout);
logConsoleInfo(
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
);
return {
result: response.resource,
scriptLogs: response.headers[HttpHeaders.scriptLogResults] as string,
};
} catch (error) {
handleError(
error,
"ExecuteStoredProcedure",
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -0,0 +1,92 @@
import { AuthType } from "../../AuthType";
import { armRequest } from "../../Utils/arm/request";
import { configContext } from "../../ConfigContext";
import { handleError } from "../ErrorHandlingUtils";
import { userContext } from "../../UserContext";
interface TimeSeriesData {
data: {
timeStamp: string;
total: number;
}[];
metadatavalues: {
name: {
localizedValue: string;
value: string;
};
value: string;
};
}
interface MetricsData {
displayDescription: string;
errorCode: string;
id: string;
name: {
value: string;
localizedValue: string;
};
timeseries: TimeSeriesData[];
type: string;
unit: string;
}
interface MetricsResponse {
cost: number;
interval: string;
namespace: string;
resourceregion: string;
timespan: string;
value: MetricsData[];
}
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
if (window.authType !== AuthType.AAD) {
return undefined;
}
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const filter = `DatabaseName eq '${databaseName}' and CollectionName eq '${containerName}'`;
const metricNames = "DataUsage,IndexUsage";
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/providers/microsoft.insights/metrics`;
try {
const metricsResponse: MetricsResponse = await armRequest({
host: configContext.ARM_ENDPOINT,
path,
method: "GET",
apiVersion: "2018-01-01",
queryParams: {
filter,
metricNames,
},
});
if (metricsResponse?.value?.length !== 2) {
return undefined;
}
const dataUsageData: MetricsData = metricsResponse.value[0];
const indexUsagedata: MetricsData = metricsResponse.value[1];
const dataUsageSizeInKb: number = getUsageSizeInKb(dataUsageData);
const indexUsageSizeInKb: number = getUsageSizeInKb(indexUsagedata);
return dataUsageSizeInKb + indexUsageSizeInKb;
} catch (error) {
handleError(error, "getCollectionUsageSize");
throw error;
}
};
const getUsageSizeInKb = (metricsData: MetricsData): number => {
if (metricsData?.errorCode !== "Success") {
throw Error(`Get collection usage size failed: ${metricsData.errorCode}`);
}
const timeSeriesData: TimeSeriesData = metricsData?.timeseries?.[0];
const usageSizeInBytes: number = timeSeriesData?.data?.[0]?.total;
return usageSizeInBytes ? usageSizeInBytes / 1024 : 0;
};

View File

@@ -11,10 +11,7 @@ export async function getIndexTransformationProgress(databaseId: string, collect
let indexTransformationPercentage: number; let indexTransformationPercentage: number;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`); const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try { try {
const response = await client() const response = await client().database(databaseId).container(collectionId).read({ populateQuotaInfo: true });
.database(databaseId)
.container(collectionId)
.read({ populateQuotaInfo: true });
indexTransformationPercentage = parseInt( indexTransformationPercentage = parseInt(
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string

View File

@@ -0,0 +1,11 @@
import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos";
import { client } from "../CosmosClient";
export const queryConflicts = (
databaseId: string,
containerId: string,
query: string,
options: FeedOptions
): QueryIterator<ConflictDefinition & Resource> => {
return client().database(databaseId).container(containerId).conflicts.query(query, options);
};

View File

@@ -1,13 +1,13 @@
import { getCommonQueryOptions } from "./DataAccessUtilityBase"; import { getCommonQueryOptions } from "./queryDocuments";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
describe("getCommonQueryOptions", () => { describe("getCommonQueryOptions", () => {
it("builds the correct default options objects", () => { it("builds the correct default options objects", () => {
expect(getCommonQueryOptions({})).toMatchSnapshot(); expect(getCommonQueryOptions({})).toMatchSnapshot();
}); });
it("reads from localStorage", () => { it("reads from localStorage", () => {
LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, 37); LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, 37);
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, 17); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, 17);
expect(getCommonQueryOptions({})).toMatchSnapshot(); expect(getCommonQueryOptions({})).toMatchSnapshot();
}); });
}); });

View File

@@ -0,0 +1,31 @@
import { Queries } from "../Constants";
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { client } from "../CosmosClient";
export const queryDocuments = (
databaseId: string,
containerId: string,
query: string,
options: FeedOptions
): QueryIterator<ItemDefinition & Resource> => {
options = getCommonQueryOptions(options);
return client().database(databaseId).container(containerId).items.query(query, options);
};
export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
options = options || {};
options.populateQueryMetrics = true;
options.enableScanInQuery = options.enableScanInQuery || true;
if (!options.partitionKey) {
options.forceQueryPlan = true;
}
options.maxItemCount =
options.maxItemCount ||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
Queries.itemsPerPage;
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
return options;
};

View File

@@ -0,0 +1,26 @@
import { QueryResults } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
import { handleError } from "../ErrorHandlingUtils";
import { getEntityName } from "../DocumentUtility";
export const queryDocumentsPage = async (
resourceName: string,
documentsIterator: MinimalQueryIterator,
firstItemIndex: number
): Promise<QueryResults> => {
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
try {
const result: QueryResults = await nextPage(documentsIterator, firstItemIndex);
const itemCount = (result.documents && result.documents.length) || 0;
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
return result;
} catch (error) {
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -10,9 +10,9 @@ describe("readCollection", () => {
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
name: "test" name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB,
}); });
}); });
@@ -23,11 +23,11 @@ describe("readCollection", () => {
return { return {
container: () => { container: () => {
return { return {
read: (): unknown => ({}) read: (): unknown => ({}),
}; };
} },
}; };
} },
}); });
await readCollection("database", "collection"); await readCollection("database", "collection");
expect(client).toHaveBeenCalled(); expect(client).toHaveBeenCalled();

View File

@@ -7,10 +7,7 @@ export async function readCollection(databaseId: string, collectionId: string):
let collection: DataModels.Collection; let collection: DataModels.Collection;
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
try { try {
const response = await client() const response = await client().database(databaseId).container(collectionId).read();
.database(databaseId)
.container(collectionId)
.read();
collection = response.resource as DataModels.Collection; collection = response.resource as DataModels.Collection;
} catch (error) { } catch (error) {
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`); handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);

View File

@@ -1,9 +1,6 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants"; import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
@@ -11,50 +8,22 @@ import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/20
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
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 { readOffers } from "./readOffers"; import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export const readCollectionOffer = async ( export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => {
params: DataModels.ReadCollectionOfferParams
): Promise<DataModels.OfferWithHeaders> => {
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`); const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
let offerId = params.offerId;
if (!offerId) {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
try {
offerId = await getCollectionOfferIdWithARM(params.databaseId, params.collectionId);
} catch (error) {
clearMessage();
if (error.code !== "NotFound") {
throw error;
}
return undefined;
}
} else {
offerId = await getCollectionOfferIdWithSDK(params.collectionResourceId);
if (!offerId) {
clearMessage();
return undefined;
}
}
}
const options: RequestOptions = {
initialHeaders: {
[HttpHeaders.populateCollectionThroughputInfo]: true
}
};
try { try {
const response = await client() if (
.offer(offerId) window.authType === AuthType.AAD &&
.read(options); !userContext.useSDKOperations &&
return ( userContext.defaultExperience !== DefaultAccountExperienceType.Table
response && { ) {
...response.resource, return await readCollectionOfferWithARM(params.databaseId, params.collectionId);
headers: response.headers }
}
); return await readOfferWithSDK(params.offerId, params.collectionResourceId);
} catch (error) { } catch (error) {
handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`); handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`);
throw error; throw error;
@@ -63,61 +32,92 @@ export const readCollectionOffer = async (
} }
}; };
const getCollectionOfferIdWithARM = async (databaseId: string, collectionId: string): Promise<string> => { const readCollectionOfferWithARM = async (databaseId: string, collectionId: string): Promise<Offer> => {
let rpResponse;
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.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: let rpResponse;
rpResponse = await getSqlContainerThroughput( try {
subscriptionId, switch (defaultExperience) {
resourceGroup, case DefaultAccountExperienceType.DocumentDB:
accountName, rpResponse = await getSqlContainerThroughput(
databaseId, subscriptionId,
collectionId resourceGroup,
); accountName,
break; databaseId,
case DefaultAccountExperienceType.MongoDB: collectionId
rpResponse = await getMongoDBCollectionThroughput( );
subscriptionId, break;
resourceGroup, case DefaultAccountExperienceType.MongoDB:
accountName, rpResponse = await getMongoDBCollectionThroughput(
databaseId, subscriptionId,
collectionId resourceGroup,
); accountName,
break; databaseId,
case DefaultAccountExperienceType.Cassandra: collectionId
rpResponse = await getCassandraTableThroughput( );
subscriptionId, break;
resourceGroup, case DefaultAccountExperienceType.Cassandra:
accountName, rpResponse = await getCassandraTableThroughput(
databaseId, subscriptionId,
collectionId resourceGroup,
); accountName,
break; databaseId,
case DefaultAccountExperienceType.Graph: collectionId
rpResponse = await getGremlinGraphThroughput( );
subscriptionId, break;
resourceGroup, case DefaultAccountExperienceType.Graph:
accountName, rpResponse = await getGremlinGraphThroughput(
databaseId, subscriptionId,
collectionId resourceGroup,
); accountName,
break; databaseId,
case DefaultAccountExperienceType.Table: collectionId
rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId); );
break; break;
default: case DefaultAccountExperienceType.Table:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
return undefined;
} }
return rpResponse?.name; const resource = rpResponse?.properties?.resource;
}; if (resource) {
const offerId: string = rpResponse.name;
const minimumThroughput: number =
typeof resource.minimumThroughput === "string"
? parseInt(resource.minimumThroughput)
: resource.minimumThroughput;
const autoscaleSettings = resource.autoscaleSettings;
const getCollectionOfferIdWithSDK = async (collectionResourceId: string): Promise<string> => { if (autoscaleSettings) {
const offers = await readOffers(); return {
const offer = offers.find(offer => offer.resource === collectionResourceId); id: offerId,
return offer?.id; autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
manualThroughput: undefined,
minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true",
};
}
return {
id: offerId,
autoscaleMaxThroughput: undefined,
manualThroughput: resource.throughput,
minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true",
};
}
return undefined;
}; };

View File

@@ -1,45 +0,0 @@
import * as DataModels from "../../Contracts/DataModels";
import * as HeadersUtility from "../HeadersUtility";
import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerDefinition, Resource } from "@azure/cosmos";
import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
interface ResourceWithStatistics {
statistics: DataModels.Statistic[];
}
export const readCollectionQuotaInfo = async (
collection: ViewModels.Collection
): Promise<DataModels.CollectionQuotaInfo> => {
const clearMessage = logConsoleProgress(`Querying containers for database ${collection.id}`);
const options: RequestOptions = {};
options.populateQuotaInfo = true;
options.initialHeaders = options.initialHeaders || {};
options.initialHeaders[HttpHeaders.populatePartitionStatistics] = true;
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.read(options);
const quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
const resource = response.resource as ContainerDefinition & Resource & ResourceWithStatistics;
quota["usageSizeInKB"] = resource.statistics.reduce(
(previousValue: number, currentValue: DataModels.Statistic) => previousValue + currentValue.sizeInKB,
0
);
quota["numPartitions"] = resource.statistics.length;
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
return quota;
} catch (error) {
handleError(error, "ReadCollectionQuotaInfo", `Error while querying quota info for container ${collection.id}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -12,9 +12,9 @@ describe("readCollections", () => {
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
name: "test" name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB,
}); });
}); });
@@ -32,12 +32,12 @@ describe("readCollections", () => {
containers: { containers: {
readAll: () => { readAll: () => {
return { return {
fetchAll: (): unknown => [] fetchAll: (): unknown => [],
}; };
} },
} },
}; };
} },
}); });
await readCollections("database"); await readCollections("database");
expect(client).toHaveBeenCalled(); expect(client).toHaveBeenCalled();

View File

@@ -23,10 +23,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
return await readCollectionsWithARM(databaseId); return await readCollectionsWithARM(databaseId);
} }
const sdkResponse = await client() const sdkResponse = await client().database(databaseId).containers.readAll().fetchAll();
.database(databaseId)
.containers.readAll()
.fetchAll();
return sdkResponse.resources as DataModels.Collection[]; return sdkResponse.resources as DataModels.Collection[];
} catch (error) { } catch (error) {
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`); handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
@@ -63,5 +60,5 @@ async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Co
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);
} }
return rpResponse?.value?.map(collection => collection.properties?.resource as DataModels.Collection); return rpResponse?.value?.map((collection) => collection.properties?.resource as DataModels.Collection);
} }

View File

@@ -1,51 +1,28 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants"; import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; 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 { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { readOffers } from "./readOffers"; import { readOfferWithSDK } from "./readOfferWithSDK";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export const readDatabaseOffer = async ( export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
params: DataModels.ReadDatabaseOfferParams
): Promise<DataModels.OfferWithHeaders> => {
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
let offerId = params.offerId;
if (!offerId) {
offerId = await (window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
? getDatabaseOfferIdWithARM(params.databaseId)
: getDatabaseOfferIdWithSDK(params.databaseResourceId));
if (!offerId) {
clearMessage();
return undefined;
}
}
const options: RequestOptions = {
initialHeaders: {
[HttpHeaders.populateCollectionThroughputInfo]: true
}
};
try { try {
const response = await client() if (
.offer(offerId) window.authType === AuthType.AAD &&
.read(options); !userContext.useSDKOperations &&
return ( userContext.defaultExperience !== DefaultAccountExperienceType.Table
response && { ) {
...response.resource, return await readDatabaseOfferWithARM(params.databaseId);
headers: response.headers }
}
); return await readOfferWithSDK(params.offerId, params.databaseResourceId);
} catch (error) { } catch (error) {
handleError(error, "ReadDatabaseOffer", `Error while querying offer for database ${params.databaseId}`); handleError(error, "ReadDatabaseOffer", `Error while querying offer for database ${params.databaseId}`);
throw error; throw error;
@@ -54,13 +31,13 @@ export const readDatabaseOffer = async (
} }
}; };
const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> => { const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
let rpResponse;
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.defaultExperience;
let rpResponse;
try { try {
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case DefaultAccountExperienceType.DocumentDB:
@@ -78,18 +55,41 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
default: default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);
} }
return rpResponse?.name;
} catch (error) { } catch (error) {
if (error.code !== "NotFound") { if (error.code !== "NotFound") {
throw error; throw error;
} }
return undefined; return undefined;
} }
};
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => { const resource = rpResponse?.properties?.resource;
const offers = await readOffers(); if (resource) {
const offer = offers.find(offer => offer.resource === databaseResourceId); const offerId: string = rpResponse.name;
return offer?.id; const minimumThroughput: number =
typeof resource.minimumThroughput === "string"
? parseInt(resource.minimumThroughput)
: resource.minimumThroughput;
const autoscaleSettings = resource.autoscaleSettings;
if (autoscaleSettings) {
return {
id: offerId,
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
manualThroughput: undefined,
minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true",
};
}
return {
id: offerId,
autoscaleMaxThroughput: undefined,
manualThroughput: resource.throughput,
minimumThroughput,
offerReplacePending: resource.offerReplacePending === "true",
};
}
return undefined;
}; };

View File

@@ -12,9 +12,9 @@ describe("readDatabases", () => {
beforeAll(() => { beforeAll(() => {
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
name: "test" name: "test",
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB,
}); });
}); });
@@ -30,10 +30,10 @@ describe("readDatabases", () => {
databases: { databases: {
readAll: () => { readAll: () => {
return { return {
fetchAll: (): unknown => [] fetchAll: (): unknown => [],
}; };
} },
} },
}); });
await readDatabases(); await readDatabases();
expect(client).toHaveBeenCalled(); expect(client).toHaveBeenCalled();

View File

@@ -21,9 +21,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
) { ) {
databases = await readDatabasesWithARM(); databases = await readDatabasesWithARM();
} else { } else {
const sdkResponse = await client() const sdkResponse = await client().databases.readAll().fetchAll();
.databases.readAll()
.fetchAll();
databases = sdkResponse.resources as DataModels.Database[]; databases = sdkResponse.resources as DataModels.Database[];
} }
} catch (error) { } catch (error) {
@@ -58,5 +56,5 @@ async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
throw new Error(`Unsupported default experience type: ${defaultExperience}`); throw new Error(`Unsupported default experience type: ${defaultExperience}`);
} }
return rpResponse?.value?.map(database => database.properties?.resource as DataModels.Database); return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database);
} }

View File

@@ -0,0 +1,27 @@
import { Item } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import DocumentId from "../../Explorer/Tree/DocumentId";
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), documentId.partitionKeyValue)
.read();
return response?.resource;
} catch (error) {
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -0,0 +1,27 @@
import { HttpHeaders } from "../Constants";
import { Offer } from "../../Contracts/DataModels";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readOffers } from "./readOffers";
export const readOfferWithSDK = async (offerId: string, resourceId: string): Promise<Offer> => {
if (!offerId) {
const offers = await readOffers();
const offer = offers.find((offer) => offer.resource === resourceId);
if (!offer) {
return undefined;
}
offerId = offer.id;
}
const options: RequestOptions = {
initialHeaders: {
[HttpHeaders.populateCollectionThroughputInfo]: true,
},
};
const response = await client().offer(offerId).read(options);
return parseSDKOfferResponse(response);
};

View File

@@ -1,15 +1,13 @@
import { Offer } from "../../Contracts/DataModels"; import { SDKOfferDefinition } from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError, getErrorMessage } from "../ErrorHandlingUtils"; import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
export const readOffers = async (): Promise<Offer[]> => { export const readOffers = async (): Promise<SDKOfferDefinition[]> => {
const clearMessage = logConsoleProgress(`Querying offers`); const clearMessage = logConsoleProgress(`Querying offers`);
try { try {
const response = await client() const response = await client().offers.readAll().fetchAll();
.offers.readAll()
.fetchAll();
return response?.resources; return response?.resources;
} catch (error) { } catch (error) {
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too. // This should be removed when we can correctly identify if an account is serverless when connected using connection string too.

View File

@@ -25,7 +25,7 @@ export async function readStoredProcedures(
databaseId, databaseId,
collectionId collectionId
); );
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource); return rpResponse?.value?.map((sproc) => sproc.properties?.resource as StoredProcedureDefinition & Resource);
} }
const response = await client() const response = await client()

View File

@@ -25,14 +25,10 @@ export async function readTriggers(
databaseId, databaseId,
collectionId collectionId
); );
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource); return rpResponse?.value?.map((trigger) => trigger.properties?.resource as TriggerDefinition & Resource);
} }
const response = await client() const response = await client().database(databaseId).container(collectionId).scripts.triggers.readAll().fetchAll();
.database(databaseId)
.container(collectionId)
.scripts.triggers.readAll()
.fetchAll();
return response?.resources; return response?.resources;
} catch (error) { } catch (error) {
handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`); handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);

View File

@@ -25,7 +25,7 @@ export async function readUserDefinedFunctions(
databaseId, databaseId,
collectionId collectionId
); );
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource); return rpResponse?.value?.map((udf) => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
} }
const response = await client() const response = await client()

View File

@@ -8,22 +8,22 @@ import {
MongoDBCollectionCreateUpdateParameters, MongoDBCollectionCreateUpdateParameters,
MongoDBCollectionResource, MongoDBCollectionResource,
SqlContainerCreateUpdateParameters, SqlContainerCreateUpdateParameters,
SqlContainerResource SqlContainerResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; 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 { import {
createUpdateMongoDBCollection, createUpdateMongoDBCollection,
getMongoDBCollection getMongoDBCollection,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; } 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 { 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 { handleError } from "../ErrorHandlingUtils";
@@ -130,8 +130,8 @@ export async function updateMongoDBCollectionThroughRP(
const updateParams: MongoDBCollectionCreateUpdateParameters = { const updateParams: MongoDBCollectionCreateUpdateParameters = {
properties: { properties: {
resource: newCollection, resource: newCollection,
options: updateOptions options: updateOptions,
} },
}; };
const updateResponse = await createUpdateMongoDBCollection( const updateResponse = await createUpdateMongoDBCollection(

View File

@@ -0,0 +1,32 @@
import { CollectionBase } from "../../Contracts/ViewModels";
import { Item } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import DocumentId from "../../Explorer/Tree/DocumentId";
export const updateDocument = async (
collection: CollectionBase,
documentId: DocumentId,
newDocument: Item
): Promise<Item> => {
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), documentId.partitionKeyValue)
.replace(newDocument);
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
return response?.resource;
} catch (error) {
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -1,13 +1,14 @@
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants"; import { HttpHeaders } from "../Constants";
import { Offer, UpdateOfferParams } from "../../Contracts/DataModels"; 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 { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readCollectionOffer } from "./readCollectionOffer"; import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer"; import { readDatabaseOffer } from "./readDatabaseOffer";
import { import {
@@ -16,7 +17,7 @@ import {
migrateSqlDatabaseToManualThroughput, migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale, migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput, migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput updateSqlContainerThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { import {
updateCassandraKeyspaceThroughput, updateCassandraKeyspaceThroughput,
@@ -24,7 +25,7 @@ import {
migrateCassandraKeyspaceToManualThroughput, migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale, migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput, migrateCassandraTableToManualThroughput,
updateCassandraTableThroughput updateCassandraTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { import {
updateMongoDBDatabaseThroughput, updateMongoDBDatabaseThroughput,
@@ -32,7 +33,7 @@ import {
migrateMongoDBDatabaseToManualThroughput, migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale, migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput, migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput updateMongoDBCollectionThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { import {
updateGremlinDatabaseThroughput, updateGremlinDatabaseThroughput,
@@ -40,13 +41,13 @@ import {
migrateGremlinDatabaseToManualThroughput, migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale, migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput, migrateGremlinGraphToManualThroughput,
updateGremlinGraphThroughput updateGremlinGraphThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { import {
migrateTableToAutoscale, migrateTableToAutoscale,
migrateTableToManualThroughput, migrateTableToManualThroughput,
updateTableThroughput updateTableThroughput,
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => { export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
@@ -109,7 +110,7 @@ const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<
return await readCollectionOffer({ return await readCollectionOffer({
collectionId: params.collectionId, collectionId: params.collectionId,
databaseId: params.databaseId, databaseId: params.databaseId,
offerId: params.currentOffer.id offerId: params.currentOffer.id,
}); });
}; };
@@ -139,7 +140,7 @@ const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Of
return await readDatabaseOffer({ return await readDatabaseOffer({
databaseId: params.databaseId, databaseId: params.databaseId,
offerId: params.currentOffer.id offerId: params.currentOffer.id,
}); });
}; };
@@ -357,13 +358,13 @@ const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<vo
const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpdateParameters => { const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpdateParameters => {
const body: ThroughputSettingsUpdateParameters = { const body: ThroughputSettingsUpdateParameters = {
properties: { properties: {
resource: {} resource: {},
} },
}; };
if (params.autopilotThroughput) { if (params.autopilotThroughput) {
body.properties.resource.autoscaleSettings = { body.properties.resource.autoscaleSettings = {
maxThroughput: params.autopilotThroughput maxThroughput: params.autopilotThroughput,
}; };
} else { } else {
body.properties.resource.throughput = params.manualThroughput; body.properties.resource.throughput = params.manualThroughput;
@@ -373,26 +374,26 @@ const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpd
}; };
const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> => { const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> => {
const currentOffer = params.currentOffer; const sdkOfferDefinition = params.currentOffer.offerDefinition;
const newOffer: Offer = { const newOffer: SDKOfferDefinition = {
content: { content: {
offerThroughput: undefined, offerThroughput: undefined,
offerIsRUPerMinuteThroughputEnabled: false offerIsRUPerMinuteThroughputEnabled: false,
}, },
_etag: undefined, _etag: undefined,
_ts: undefined, _ts: undefined,
_rid: currentOffer._rid, _rid: sdkOfferDefinition._rid,
_self: currentOffer._self, _self: sdkOfferDefinition._self,
id: currentOffer.id, id: sdkOfferDefinition.id,
offerResourceId: currentOffer.offerResourceId, offerResourceId: sdkOfferDefinition.offerResourceId,
offerVersion: currentOffer.offerVersion, offerVersion: sdkOfferDefinition.offerVersion,
offerType: currentOffer.offerType, offerType: sdkOfferDefinition.offerType,
resource: currentOffer.resource resource: sdkOfferDefinition.resource,
}; };
if (params.autopilotThroughput) { if (params.autopilotThroughput) {
newOffer.content.offerAutopilotSettings = { newOffer.content.offerAutopilotSettings = {
maxThroughput: params.autopilotThroughput maxThroughput: params.autopilotThroughput,
}; };
} else { } else {
newOffer.content.offerThroughput = params.manualThroughput; newOffer.content.offerThroughput = params.manualThroughput;
@@ -401,12 +402,12 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
const options: RequestOptions = {}; const options: RequestOptions = {};
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
options.initialHeaders = { options.initialHeaders = {
[HttpHeaders.migrateOfferToAutopilot]: "true" [HttpHeaders.migrateOfferToAutopilot]: "true",
}; };
delete newOffer.content.offerAutopilotSettings; delete newOffer.content.offerAutopilotSettings;
} else if (params.migrateToManual) { } else if (params.migrateToManual) {
options.initialHeaders = { options.initialHeaders = {
[HttpHeaders.migrateOfferToManualThroughput]: "true" [HttpHeaders.migrateOfferToManualThroughput]: "true",
}; };
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 }; newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
} }
@@ -415,5 +416,6 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
.offer(params.currentOffer.id) .offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660) // TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options); .replace((newOffer as unknown) as OfferDefinition, options);
return sdkResponse?.resource;
return parseSDKOfferResponse(sdkResponse);
}; };

View File

@@ -1,25 +0,0 @@
import { updateOfferThroughputBeyondLimit } from "./updateOfferThroughputBeyondLimit";
describe("updateOfferThroughputBeyondLimit", () => {
it("should call fetch", async () => {
window.fetch = jest.fn(() => {
return {
ok: true
};
});
window.dataExplorer = {
logConsoleData: jest.fn(),
deleteInProgressConsoleDataWithId: jest.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
await updateOfferThroughputBeyondLimit({
subscriptionId: "foo",
resourceGroup: "foo",
databaseAccountName: "foo",
databaseName: "foo",
throughput: 1000000000,
offerIsRUPerMinuteThroughputEnabled: false
});
expect(window.fetch).toHaveBeenCalled();
});
});

View File

@@ -1,57 +0,0 @@
import { Platform, configContext } from "../../ConfigContext";
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { handleError } from "../ErrorHandlingUtils";
interface UpdateOfferThroughputRequest {
subscriptionId: string;
resourceGroup: string;
databaseAccountName: string;
databaseName: string;
collectionName?: string;
throughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
offerAutopilotSettings?: AutoPilotOfferSettings;
}
export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThroughputRequest): Promise<void> {
if (configContext.platform !== Platform.Portal) {
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
}
const resourceDescriptionInfo = request.collectionName
? `database ${request.databaseName} and container ${request.collectionName}`
: `database ${request.databaseName}`;
const clearMessage = logConsoleProgress(
`Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
);
const url = `${configContext.BACKEND_ENDPOINT}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
const authorizationHeader = getAuthorizationHeader();
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(request),
headers: { [authorizationHeader.header]: authorizationHeader.token, [HttpHeaders.contentType]: "application/json" }
});
if (response.ok) {
logConsoleInfo(
`Successfully requested an increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
);
clearMessage();
return undefined;
}
const error = await response.json();
handleError(
error,
"updateOfferThroughputBeyondLimit",
`Failed to request an increase in throughput for ${request.throughput}`
);
clearMessage();
throw error;
}

View File

@@ -3,12 +3,12 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import {
SqlStoredProcedureCreateUpdateParameters, SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource SqlStoredProcedureResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; 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 { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@@ -39,8 +39,8 @@ export async function updateStoredProcedure(
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = { const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: { properties: {
resource: storedProcedure as SqlStoredProcedureResource, resource: storedProcedure as SqlStoredProcedureResource,
options: {} options: {},
} },
}; };
const rpResponse = await createUpdateSqlStoredProcedure( const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -2,7 +2,7 @@ import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
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 { TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
@@ -36,8 +36,8 @@ export async function updateTrigger(
const createTriggerParams: SqlTriggerCreateUpdateParameters = { const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: { properties: {
resource: trigger as SqlTriggerResource, resource: trigger as SqlTriggerResource,
options: {} options: {},
} },
}; };
const rpResponse = await createUpdateSqlTrigger( const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -3,12 +3,12 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import {
SqlUserDefinedFunctionCreateUpdateParameters, SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource SqlUserDefinedFunctionResource,
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; 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 { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@@ -39,8 +39,8 @@ export async function updateUserDefinedFunction(
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = { const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: { properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource, resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {} options: {},
} },
}; };
const rpResponse = await createUpdateSqlUserDefinedFunction( const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId, userContext.subscriptionId,

View File

@@ -1,10 +1,10 @@
export enum Platform { export enum Platform {
Portal = "Portal", Portal = "Portal",
Hosted = "Hosted", Hosted = "Hosted",
Emulator = "Emulator" Emulator = "Emulator",
} }
interface ConfigContext { export interface ConfigContext {
platform: Platform; platform: Platform;
allowedParentFrameOrigins: string[]; allowedParentFrameOrigins: string[];
gitSha?: string; gitSha?: string;
@@ -37,7 +37,7 @@ let configContext: Readonly<ConfigContext> = {
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`, `^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`, `^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`, `^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$` `^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`,
], ],
// Webpack injects this at build time // Webpack injects this at build time
gitSha: process.env.GIT_SHA, gitSha: process.env.GIT_SHA,
@@ -52,7 +52,7 @@ let configContext: Readonly<ConfigContext> = {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
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",
}; };
export function resetConfigContext(): void { export function resetConfigContext(): void {
@@ -73,7 +73,7 @@ if (process.env.NODE_ENV === "development") {
BACKEND_ENDPOINT: "https://localhost:" + port, BACKEND_ENDPOINT: "https://localhost:" + port,
MONGO_BACKEND_ENDPOINT: "https://localhost:" + port, MONGO_BACKEND_ENDPOINT: "https://localhost:" + port,
PROXY_PATH: "/proxy", PROXY_PATH: "/proxy",
EMULATOR_ENDPOINT: "https://localhost:8081" EMULATOR_ENDPOINT: "https://localhost:8081",
}); });
} }
@@ -86,7 +86,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
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],
}); });
} }
} catch (error) { } catch (error) {
@@ -104,7 +104,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
const platform = params.get("platform"); const platform = params.get("platform");
switch (platform) { switch (platform) {
default: default:
console.log("Invalid platform query parameter given, ignoring"); console.error(`Invalid platform query parameter: ${platform}`);
break; break;
case Platform.Portal: case Platform.Portal:
case Platform.Hosted: case Platform.Hosted:
@@ -113,7 +113,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
} }
} }
} catch (error) { } catch (error) {
console.log("No configuration file found using defaults"); console.error("No configuration file found using defaults");
} }
return configContext; return configContext;
} }

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