mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-28 21:32:05 +00:00
Compare commits
61 Commits
remove-red
...
users/srna
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e8793d1b1 | ||
|
|
f41de718a0 | ||
|
|
5820afc25a | ||
|
|
6d94f18f9a | ||
|
|
a133134b8b | ||
|
|
79dec6a8a8 | ||
|
|
53a8cea95e | ||
|
|
5f1f7a8266 | ||
|
|
a009a8ba5f | ||
|
|
3e782527d0 | ||
|
|
e6ca1d25c9 | ||
|
|
473f722dcc | ||
|
|
5741802c25 | ||
|
|
e2e58f73b1 | ||
|
|
79769e9689 | ||
|
|
542abf4d3a | ||
|
|
4bee46ce55 | ||
|
|
fe58722002 | ||
|
|
94ff6b3e81 | ||
|
|
b4219e2994 | ||
|
|
294270b6aa | ||
|
|
e4bab1de4b | ||
|
|
703ceacd3f | ||
|
|
734df3dd18 | ||
|
|
1e19f02fd7 | ||
|
|
24b5b754ca | ||
|
|
e09730d782 | ||
|
|
09a95fded4 | ||
|
|
7ffa18a190 | ||
|
|
30353c26f3 | ||
|
|
34d8704071 | ||
|
|
23714831bd | ||
|
|
9a5d46b6e0 | ||
|
|
b9245101bc | ||
|
|
274deb13be | ||
|
|
9d50577800 | ||
|
|
bd00e5eb9b | ||
|
|
821f665e78 | ||
|
|
39f7ef331a | ||
|
|
9933a4988a | ||
|
|
d525afa142 | ||
|
|
cfb9a0b321 | ||
|
|
3b64d75322 | ||
|
|
daba1c4ed4 | ||
|
|
a698e08638 | ||
|
|
88d630fef4 | ||
|
|
5ffa746adb | ||
|
|
a9a57f4ba9 | ||
|
|
47cc6fd7a8 | ||
|
|
14cdf19efb | ||
|
|
5191ae3f3a | ||
|
|
ba862a8106 | ||
|
|
fe085b3e5a | ||
|
|
8028734cb0 | ||
|
|
444f663733 | ||
|
|
8c1ca35420 | ||
|
|
b69174788d | ||
|
|
ff03c79399 | ||
|
|
0382628249 | ||
|
|
d346ebe054 | ||
|
|
f5ecb8a04f |
@@ -1,7 +1,10 @@
|
|||||||
# These options are only needed when if running end to end tests locally
|
|
||||||
PORTAL_RUNNER_USERNAME=
|
PORTAL_RUNNER_USERNAME=
|
||||||
PORTAL_RUNNER_PASSWORD=
|
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_CONNECTION_STRING=
|
PORTAL_RUNNER_CONNECTION_STRING=
|
||||||
|
CASSANDRA_CONNECTION_STRING=
|
||||||
|
MONGO_CONNECTION_STRING=
|
||||||
|
TABLES_CONNECTION_STRING=
|
||||||
|
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
||||||
@@ -15,8 +15,6 @@ 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/EnvironmentUtility.ts
|
||||||
src/Common/ErrorParserUtility.test.ts
|
|
||||||
src/Common/ErrorParserUtility.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
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ module.exports = {
|
|||||||
"no-null/no-null": "error",
|
"no-null/no-null": "error",
|
||||||
"@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",
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
|
||||||
|
message: "Do not use JSON.stringify(error). It will print '{}'"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
29
.github/workflows/automerge.yml
vendored
29
.github/workflows/automerge.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
name: automerge
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- labeled
|
|
||||||
- unlabeled
|
|
||||||
- synchronize
|
|
||||||
- opened
|
|
||||||
- edited
|
|
||||||
- ready_for_review
|
|
||||||
- reopened
|
|
||||||
- unlocked
|
|
||||||
pull_request_review:
|
|
||||||
types:
|
|
||||||
- submitted
|
|
||||||
check_suite:
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
status: {}
|
|
||||||
jobs:
|
|
||||||
automerge:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: automerge
|
|
||||||
uses: "pascalgn/automerge-action@v0.11.0"
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: "${{ secrets.AUTOMERGE_GITHUB_PAT }}"
|
|
||||||
MERGE_METHOD: "squash"
|
|
||||||
MERGE_COMMIT_MESSAGE: "pull-request-title"
|
|
||||||
78
.github/workflows/ci.yml
vendored
78
.github/workflows/ci.yml
vendored
@@ -105,74 +105,6 @@ jobs:
|
|||||||
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
|
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
||||||
endtoendsql:
|
|
||||||
name: "End To End Tests | SQL"
|
|
||||||
needs: [lint, format, compile, unittest]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js 12.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 12.x
|
|
||||||
- name: Restore Cypress Binary Cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/.cache/Cypress
|
|
||||||
key: ${{ runner.os }}-cypress-binary-cache
|
|
||||||
- run: npm ci
|
|
||||||
- name: End to End Tests
|
|
||||||
run: |
|
|
||||||
npm start &
|
|
||||||
cd cypress
|
|
||||||
npm ci
|
|
||||||
node cleanup.js
|
|
||||||
npm run wait-for-server
|
|
||||||
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/SQL/*"
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
|
||||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
|
||||||
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
name: videos
|
|
||||||
if: ${{ failure() }}
|
|
||||||
with:
|
|
||||||
path: "**/*.mp4"
|
|
||||||
endtoendmongo:
|
|
||||||
name: "End To End Tests | Mongo"
|
|
||||||
needs: [lint, format, compile, unittest]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js 12.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 12.x
|
|
||||||
- name: Restore Cypress Binary Cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/.cache/Cypress
|
|
||||||
key: ${{ runner.os }}-cypress-binary-cache
|
|
||||||
- name: End to End Tests
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npm start &
|
|
||||||
cd cypress
|
|
||||||
npm ci
|
|
||||||
node cleanup.js
|
|
||||||
npm run wait-for-server
|
|
||||||
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/MONGO/*"
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
|
||||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
|
||||||
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ failure() }}
|
|
||||||
name: videos
|
|
||||||
with:
|
|
||||||
path: "**/*.mp4"
|
|
||||||
accessibility:
|
accessibility:
|
||||||
name: "Accessibility | Hosted"
|
name: "Accessibility | Hosted"
|
||||||
needs: [lint, format, compile, unittest]
|
needs: [lint, format, compile, unittest]
|
||||||
@@ -218,10 +150,16 @@ jobs:
|
|||||||
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 }}
|
||||||
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
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, endtoendsql, endtoendmongo]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -245,7 +183,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, endtoendsql, endtoendmongo]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -1,4 +1,4 @@
|
|||||||
# CosmosDB Explorer
|
# Cosmos DB Explorer
|
||||||
|
|
||||||
UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator)
|
UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin t
|
|||||||
|
|
||||||
### 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 enironment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
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.
|
||||||
|
|
||||||
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ The Cosmos Portal that consumes this repo is not currently open source. If you h
|
|||||||
You can however load a local running instance of data explorer in the production portal.
|
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)
|
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. Whitelist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
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`
|
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
|
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
|
||||||
@@ -84,16 +84,19 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
|
|||||||
4. Install dependencies: `npm install`
|
4. Install dependencies: `npm install`
|
||||||
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
|
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
|
||||||
|
|
||||||
#### End to End Production Runners
|
#### End to End Production Tests
|
||||||
|
|
||||||
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
|
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
|
||||||
|
|
||||||
1. Copy .env.example to .env and fill in all variables
|
1. Copy .env.example to .env
|
||||||
2. Run `npm run test:e2e`
|
2. Update the values in .env including your local data explorer endpoint (ask a teammate/codeowner for help with .env values)
|
||||||
|
3. Make sure all packages are installed `npm install`
|
||||||
|
4. Run the server `npm run start` and wait for it to start
|
||||||
|
5. Run `npm run test:e2e`
|
||||||
|
|
||||||
### Releasing
|
### Releasing
|
||||||
|
|
||||||
We generally adhear 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.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ trigger:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-latest'
|
vmImage: "ubuntu-latest"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: ComponentGovernanceComponentDetection@0
|
- task: ComponentGovernanceComponentDetection@0
|
||||||
|
|||||||
177
externals/jquery.contextMenu.css
vendored
177
externals/jquery.contextMenu.css
vendored
@@ -1,177 +0,0 @@
|
|||||||
/*!
|
|
||||||
* jQuery contextMenu - Plugin for simple contextMenu handling
|
|
||||||
*
|
|
||||||
* Version: 1.6.6
|
|
||||||
*
|
|
||||||
* Authors: Rodney Rehm, Addy Osmani (patches for FF)
|
|
||||||
* Web: http://medialize.github.com/jQuery-contextMenu/
|
|
||||||
*
|
|
||||||
* Licensed under
|
|
||||||
* MIT License http://www.opensource.org/licenses/mit-license
|
|
||||||
* GPL v3 http://opensource.org/licenses/GPL-3.0
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
.context-menu-list {
|
|
||||||
z-index: 1001;
|
|
||||||
position: fixed;
|
|
||||||
background: white;
|
|
||||||
border: solid 1px gainsboro;
|
|
||||||
box-shadow: 4px 4px 4px -2px #888888;
|
|
||||||
padding: 8px 0px 8px 0px;
|
|
||||||
line-height: 25px;
|
|
||||||
width: 254px;
|
|
||||||
list-style: none;
|
|
||||||
margin-left: -10px;
|
|
||||||
outline: 0px #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item {
|
|
||||||
padding: 2px 2px 2px 31px;
|
|
||||||
background-color: #fff;
|
|
||||||
position: relative;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: -moz-none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-separator {
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: 1px solid #DDD;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item>label>input,
|
|
||||||
.context-menu-item>label>textarea {
|
|
||||||
-webkit-user-select: text;
|
|
||||||
-moz-user-select: text;
|
|
||||||
-ms-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
margin-left: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.disabled {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-input.hover,
|
|
||||||
.context-menu-item.disabled.hover {
|
|
||||||
cursor: default;
|
|
||||||
background-color: #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-submenu:after {
|
|
||||||
content: ">";
|
|
||||||
color: #666;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 3px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* icons
|
|
||||||
#protip:
|
|
||||||
In case you want to use sprites for icons (which I would suggest you do) have a look at
|
|
||||||
http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
|
|
||||||
.context-menu-item.icon:before {}
|
|
||||||
*/
|
|
||||||
|
|
||||||
.context-menu-item.icon {
|
|
||||||
min-height: 18px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 10px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon:hover {
|
|
||||||
min-height: 18px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 10px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*.context-menu-item.icon-edit {
|
|
||||||
background-image: url(images/page_white_edit.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon-cut {
|
|
||||||
background-image: url(images/cut.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon-copy {
|
|
||||||
background-image: url(images/page_white_copy.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon-paste {
|
|
||||||
background-image: url(images/page_white_paste.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon-delete {
|
|
||||||
background-image: url(images/page_white_delete.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon-add {
|
|
||||||
background-image: url(images/page_white_add.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item.icon-quit {
|
|
||||||
background-image: url(images/door.png);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
/* vertically align inside labels */
|
|
||||||
|
|
||||||
.context-menu-input>label>* {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* position checkboxes and radios as icons */
|
|
||||||
|
|
||||||
.context-menu-input>label>input[type="checkbox"],
|
|
||||||
.context-menu-input>label>input[type="radio"] {
|
|
||||||
margin-left: -17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-input>label>span {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-input>label,
|
|
||||||
.context-menu-input>label>input[type="text"],
|
|
||||||
.context-menu-input>label>textarea,
|
|
||||||
.context-menu-input>label>select {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
-ms-box-sizing: border-box;
|
|
||||||
-o-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-input>label>textarea {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-item>.context-menu-list {
|
|
||||||
display: none;
|
|
||||||
/* re-positioned by js */
|
|
||||||
right: -5px;
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*.context-menu-item.hover>.context-menu-list {
|
|
||||||
display: block;
|
|
||||||
padding-left: 5px;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
.context-menu-accesskey {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
1686
externals/jquery.contextMenu.js
vendored
1686
externals/jquery.contextMenu.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ const isCI = require("is-ci");
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
launch: {
|
launch: {
|
||||||
headless: isCI,
|
headless: isCI,
|
||||||
slowMo: 30,
|
slowMo: 55,
|
||||||
defaultViewport: null,
|
defaultViewport: null,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
args: ["--disable-web-security"]
|
args: ["--disable-web-security"]
|
||||||
|
|||||||
@@ -2082,7 +2082,8 @@ a:link {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3022,3 +3023,7 @@ settings-pane {
|
|||||||
.infoBoxContent a {
|
.infoBoxContent a {
|
||||||
color: @AccentMediumHigh
|
color: @AccentMediumHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collapsibleSection :hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
250
package-lock.json
generated
250
package-lock.json
generated
@@ -76,7 +76,6 @@
|
|||||||
"version": "7.9.0",
|
"version": "7.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
||||||
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.8.3",
|
"@babel/code-frame": "^7.8.3",
|
||||||
"@babel/generator": "^7.9.0",
|
"@babel/generator": "^7.9.0",
|
||||||
@@ -99,8 +98,7 @@
|
|||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2326,83 +2324,83 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-analytics-js": {
|
"@microsoft/applicationinsights-analytics-js": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.5.9.tgz",
|
||||||
"integrity": "sha512-2RsftiXa5ojNXuHRIC7RybWbN+Z7TMrLK2XGTza1Wq/KHRJNB48WmQuxjd6SzsNguqxRoHsH0sUogIwlK+NO8A==",
|
"integrity": "sha512-9L3fb1H1as+J3J2j2EDx1HEMdrucjgR4INqahy+ZAxDPFvR3HCOedYzx645zObBIPu7QkH2LAjPk4fuNGHR1rg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.8",
|
"@microsoft/applicationinsights-common": "2.5.9",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.8",
|
"@microsoft/applicationinsights-core-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1",
|
"@microsoft/applicationinsights-shims": "1.0.3",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-channel-js": {
|
"@microsoft/applicationinsights-channel-js": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.5.9.tgz",
|
||||||
"integrity": "sha512-etVGzhNluflTikOpW9dlDxdg+B+8gt/nxmGeXqti9Hsqq0fTxcY9bmNklFIEKhOIwai4DodgJjflaDdgR0ObUQ==",
|
"integrity": "sha512-NAQ/2wWmD+gaIZDCMzzwxm8RcbswDvUO5BYeuW9UHJaFuEZ9o9xpztKVz32u4CMv7OI/mLOqnmR4rb0d+kUMwQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.8",
|
"@microsoft/applicationinsights-common": "2.5.9",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.8",
|
"@microsoft/applicationinsights-core-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1",
|
"@microsoft/applicationinsights-shims": "1.0.3",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-common": {
|
"@microsoft/applicationinsights-common": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.5.9.tgz",
|
||||||
"integrity": "sha512-RIMAJTsF0H5wiRZxYIF9H8sbMe2W5Ig4yMuH0/lho69DcNZpHf8p6PSa4Qhhli0AnoWYfLE7/WlWO1eR5SkByw==",
|
"integrity": "sha512-dKmXO9m55uRDhpoa0P7l+BApf+lsrqjgoLeKv+ABM8ygIyd9JH6CDcdaT3af+kUFtt9Oj3ChyfueKr1EVOdGkQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.8",
|
"@microsoft/applicationinsights-core-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1"
|
"@microsoft/applicationinsights-shims": "1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-core-js": {
|
"@microsoft/applicationinsights-core-js": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.5.9.tgz",
|
||||||
"integrity": "sha512-NxxIViHKuqgpla+KdQk7Qy48Dm8lN4oy2mMEpv9kM5GW5MBJ8nZ4A5RV1kokF3kXuDmTUTHlWBXeLR8hauA3qQ==",
|
"integrity": "sha512-KE9h1wmC/Ckm7jYjsMF1SEWQnk0v0CRzZq1upSARgPH7BgmyClXz1kdnLtuTWz8Aha8IIH9dW2hUOfPCdR+BpQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1",
|
"@microsoft/applicationinsights-shims": "1.0.3",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-dependencies-js": {
|
"@microsoft/applicationinsights-dependencies-js": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.5.9.tgz",
|
||||||
"integrity": "sha512-jnXpjE/bnlLeew78OsN8aPTLOPZQJ4y1MOO8R3E+eUXdproD2TemynSk5kUfrMdry91DZOBZnrmJ2NCB+g5ArQ==",
|
"integrity": "sha512-pNM/dkUOscV0ul/YJe928+77EBtRkRXO/le/VWzlunoUFaEEo4pirc7NycvPx9w/KxA62JMEogbQsWE6nAmqPg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.8",
|
"@microsoft/applicationinsights-common": "2.5.9",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.8",
|
"@microsoft/applicationinsights-core-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1",
|
"@microsoft/applicationinsights-shims": "1.0.3",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-properties-js": {
|
"@microsoft/applicationinsights-properties-js": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.5.9.tgz",
|
||||||
"integrity": "sha512-BAOSguXe07ua6SqYmAW+8+vVvBZb4qmmUP6s5zc7kNMMgEoEGsCn2VHmM8wHHxq4J/TmYxIqwoufS+XKbUvCeQ==",
|
"integrity": "sha512-mZxaC8CZsURn38IwsPaUx+o9QXQU2vm81THZL+1Lc+7scPo55ATDTFgZ2awIj7CdTp69oGzUkpB7maOn6+OVOw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.8",
|
"@microsoft/applicationinsights-common": "2.5.9",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.8",
|
"@microsoft/applicationinsights-core-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1"
|
"@microsoft/applicationinsights-shims": "1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-shims": {
|
"@microsoft/applicationinsights-shims": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-1.0.3.tgz",
|
||||||
"integrity": "sha512-nPjUBSpvX5Dnkovp2lZzrg0/uSvYNtbAclWwVP7t8J1hy5OJ3xr3KPNaz79+b84G16Rj861ybau9Gbk7inXkTg=="
|
"integrity": "sha512-+S17aqEkOYpyBpmclhgwcEplwnxSo5AxYBdRg38GBobI1GKPSpZfnLssLzcjJ6XZCS5tqB5xjyTZs6gHj7ZJWQ=="
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-web": {
|
"@microsoft/applicationinsights-web": {
|
||||||
"version": "2.5.8",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-2.5.9.tgz",
|
||||||
"integrity": "sha512-ZxCUkBJrCFNHa0LgWWbtwqu4TxJPlukuSvDrdLv6XV1yX2ETq6Q1kw/IUEtKhbtNbTZQ8aJ+x8Nc/iqbssdXTA==",
|
"integrity": "sha512-dxg5XXbQqjWw9QmGdgbd7knb1qFA58FFYj9ObqRmlqiihk25kper7H15HH8LaV0lV6goClmBWc9KsNGA2veyeA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-analytics-js": "2.5.8",
|
"@microsoft/applicationinsights-analytics-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-channel-js": "2.5.8",
|
"@microsoft/applicationinsights-channel-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-common": "2.5.8",
|
"@microsoft/applicationinsights-common": "2.5.9",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.8",
|
"@microsoft/applicationinsights-core-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-dependencies-js": "2.5.8",
|
"@microsoft/applicationinsights-dependencies-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-properties-js": "2.5.8",
|
"@microsoft/applicationinsights-properties-js": "2.5.9",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.1",
|
"@microsoft/applicationinsights-shims": "1.0.3",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2805,12 +2803,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nteract/monaco-editor": {
|
"@nteract/monaco-editor": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.2.tgz",
|
||||||
"integrity": "sha512-PGEUvy/GTBMECy4RUfh4wxO7GfA9YDBSV3hGt8MyrVz/GxUDtjB7FqrYS0ZhmVQPYl8hnV2i48F3YlypC+xIXA==",
|
"integrity": "sha512-51Pxt6v6qaAlbDY0BgEydk/Jxuu93t+uB8Geg3vJfE6VDphTEakB0wocBIfvcTKVV55Lx53/rTSp6QHqtaHiGg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nteract/core": "^14.0.0",
|
"@nteract/core": "^14.0.0",
|
||||||
"@nteract/messaging": "^7.0.10",
|
"@nteract/messaging": "^7.0.12",
|
||||||
|
"lodash.debounce": "^4.0.6",
|
||||||
"monaco-editor": "0.18.1",
|
"monaco-editor": "0.18.1",
|
||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
},
|
},
|
||||||
@@ -2859,6 +2858,40 @@
|
|||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nteract/messaging": {
|
||||||
|
"version": "7.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.12.tgz",
|
||||||
|
"integrity": "sha512-5z2Ffd1hj7AsGBJTAoqJshLlUZ+ISJBjiZAdNDjb70PNEv0x8UOMk/di80RI3WBLK5MKxSJkGXfs4jfzfdW6bA==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/types": "^7.1.2",
|
||||||
|
"@types/uuid": "^8.0.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nteract/commutable": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-Z6aUtIZN0CKUMJwbZjUUqaaBhT6P0RiEG5nHso+oG/FOXF20Qv+hf/TyvYhw9SXQVmmacaMk4zj0iOID20pIng==",
|
||||||
|
"requires": {
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nteract/types": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/types/-/types-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-I/1TvaUC/m9I/LFk1HemsOUqB0eNdagu0KRLA1YEtChPh9pk5F9flglA7m5+0/j31gLXBISj5+6tL8ikA8BxOQ==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/commutable": "^7.3.4",
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nteract/mythic-notifications": {
|
"@nteract/mythic-notifications": {
|
||||||
"version": "0.1.9",
|
"version": "0.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
||||||
@@ -7203,11 +7236,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create-react-class": {
|
"create-react-class": {
|
||||||
"version": "15.6.3",
|
"version": "15.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||||
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
|
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fbjs": "^0.8.9",
|
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
@@ -8504,24 +8536,6 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"encoding": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
|
||||||
"requires": {
|
|
||||||
"iconv-lite": "^0.6.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
|
||||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
@@ -9667,27 +9681,6 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fbjs": {
|
|
||||||
"version": "0.8.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
|
|
||||||
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
|
|
||||||
"requires": {
|
|
||||||
"core-js": "^1.0.0",
|
|
||||||
"isomorphic-fetch": "^2.1.1",
|
|
||||||
"loose-envify": "^1.0.0",
|
|
||||||
"object-assign": "^4.1.0",
|
|
||||||
"promise": "^7.1.1",
|
|
||||||
"setimmediate": "^1.0.5",
|
|
||||||
"ua-parser-js": "^0.7.18"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"core-js": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
|
||||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fd-slicer": {
|
"fd-slicer": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
@@ -11888,26 +11881,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||||
},
|
},
|
||||||
"isomorphic-fetch": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
|
||||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
|
||||||
"requires": {
|
|
||||||
"node-fetch": "^1.0.1",
|
|
||||||
"whatwg-fetch": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"node-fetch": {
|
|
||||||
"version": "1.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
|
||||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
|
||||||
"requires": {
|
|
||||||
"encoding": "^0.1.11",
|
|
||||||
"is-stream": "^1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
@@ -13406,36 +13379,6 @@
|
|||||||
"micromatch": "^3.1.10",
|
"micromatch": "^3.1.10",
|
||||||
"pretty-format": "^24.9.0",
|
"pretty-format": "^24.9.0",
|
||||||
"realpath-native": "^1.1.0"
|
"realpath-native": "^1.1.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/core": {
|
|
||||||
"version": "7.11.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
|
|
||||||
"integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/code-frame": "^7.10.4",
|
|
||||||
"@babel/generator": "^7.11.6",
|
|
||||||
"@babel/helper-module-transforms": "^7.11.0",
|
|
||||||
"@babel/helpers": "^7.10.4",
|
|
||||||
"@babel/parser": "^7.11.5",
|
|
||||||
"@babel/template": "^7.10.4",
|
|
||||||
"@babel/traverse": "^7.11.5",
|
|
||||||
"@babel/types": "^7.11.5",
|
|
||||||
"convert-source-map": "^1.7.0",
|
|
||||||
"debug": "^4.1.0",
|
|
||||||
"gensync": "^1.0.0-beta.1",
|
|
||||||
"json5": "^2.1.2",
|
|
||||||
"lodash": "^4.17.19",
|
|
||||||
"resolve": "^1.3.2",
|
|
||||||
"semver": "^5.4.1",
|
|
||||||
"source-map": "^0.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.5.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-dev-server": {
|
"jest-dev-server": {
|
||||||
@@ -14514,6 +14457,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||||
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
||||||
},
|
},
|
||||||
|
"lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||||
|
},
|
||||||
"lodash.defaults": {
|
"lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
@@ -16619,6 +16567,8 @@
|
|||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "~2.0.3"
|
"asap": "~2.0.3"
|
||||||
}
|
}
|
||||||
@@ -18246,7 +18196,8 @@
|
|||||||
"setimmediate": {
|
"setimmediate": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"setprototypeof": {
|
"setprototypeof": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -20006,11 +19957,6 @@
|
|||||||
"free-style": "3.1.0"
|
"free-style": "3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
|
||||||
"version": "0.7.22",
|
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
|
||||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
|
||||||
},
|
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"@azure/cosmos-language-service": "0.0.4",
|
"@azure/cosmos-language-service": "0.0.4",
|
||||||
"@jupyterlab/services": "6.0.0-rc.2",
|
"@jupyterlab/services": "6.0.0-rc.2",
|
||||||
"@jupyterlab/terminal": "3.0.0-rc.2",
|
"@jupyterlab/terminal": "3.0.0-rc.2",
|
||||||
"@microsoft/applicationinsights-web": "2.5.8",
|
"@microsoft/applicationinsights-web": "2.5.9",
|
||||||
"@nteract/commutable": "7.3.2",
|
"@nteract/commutable": "7.3.2",
|
||||||
"@nteract/connected-components": "6.8.2",
|
"@nteract/connected-components": "6.8.2",
|
||||||
"@nteract/core": "15.1.0",
|
"@nteract/core": "15.1.0",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"@nteract/jupyter-widgets": "2.0.0",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
"@nteract/logos": "1.0.0",
|
"@nteract/logos": "1.0.0",
|
||||||
"@nteract/markdown": "4.4.0",
|
"@nteract/markdown": "4.4.0",
|
||||||
"@nteract/monaco-editor": "3.2.0",
|
"@nteract/monaco-editor": "3.2.2",
|
||||||
"@nteract/octicons": "2.0.0",
|
"@nteract/octicons": "2.0.0",
|
||||||
"@nteract/outputs": "3.0.9",
|
"@nteract/outputs": "3.0.9",
|
||||||
"@nteract/presentational-components": "3.0.7",
|
"@nteract/presentational-components": "3.0.7",
|
||||||
@@ -88,8 +88,8 @@
|
|||||||
"styled-components": "4.3.2",
|
"styled-components": "4.3.2",
|
||||||
"text-encoding": "0.7.0",
|
"text-encoding": "0.7.0",
|
||||||
"underscore": "1.9.1",
|
"underscore": "1.9.1",
|
||||||
"utility-types": "3.10.0",
|
|
||||||
"url-polyfill": "1.1.7",
|
"url-polyfill": "1.1.7",
|
||||||
|
"utility-types": "3.10.0",
|
||||||
"webcrypto-liner": "1.1.4",
|
"webcrypto-liner": "1.1.4",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"whatwg-fetch": "3.0.0"
|
"whatwg-fetch": "3.0.0"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AutopilotTier } from "../Contracts/DataModels";
|
|
||||||
import { HashMap } from "./HashMap";
|
import { HashMap } from "./HashMap";
|
||||||
|
|
||||||
export class AuthorizationEndpoints {
|
export class AuthorizationEndpoints {
|
||||||
@@ -117,7 +116,6 @@ export class Features {
|
|||||||
public static readonly enableGalleryPublish = "enablegallerypublish";
|
public static readonly enableGalleryPublish = "enablegallerypublish";
|
||||||
public static readonly enableCodeOfConduct = "enablecodeofconduct";
|
public static readonly enableCodeOfConduct = "enablecodeofconduct";
|
||||||
public static readonly enableLinkInjection = "enablelinkinjection";
|
public static readonly enableLinkInjection = "enablelinkinjection";
|
||||||
public static readonly enableSettingsV2 = "enablesettingsv2";
|
|
||||||
public static readonly enableSpark = "enablespark";
|
public static readonly enableSpark = "enablespark";
|
||||||
public static readonly livyEndpoint = "livyendpoint";
|
public static readonly livyEndpoint = "livyendpoint";
|
||||||
public static readonly notebookServerUrl = "notebookserverurl";
|
public static readonly notebookServerUrl = "notebookserverurl";
|
||||||
@@ -125,12 +123,17 @@ export class Features {
|
|||||||
public static readonly notebookBasePath = "notebookbasepath";
|
public static readonly notebookBasePath = "notebookbasepath";
|
||||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
||||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
||||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
|
||||||
public static readonly ttl90Days = "ttl90days";
|
public static readonly ttl90Days = "ttl90days";
|
||||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flight names returned from the portal are always lowercase
|
||||||
|
export class Flights {
|
||||||
|
public static readonly SettingsV2 = "settingsv2";
|
||||||
|
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||||
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
public static readonly Spark = "spark-public-preview";
|
public static readonly Spark = "spark-public-preview";
|
||||||
public static readonly Notebooks = "sparknotebooks-public-preview";
|
public static readonly Notebooks = "sparknotebooks-public-preview";
|
||||||
@@ -257,7 +260,6 @@ export class HttpHeaders {
|
|||||||
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
||||||
public static autoPilotThroughput = "autoscaleSettings";
|
public static autoPilotThroughput = "autoscaleSettings";
|
||||||
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
||||||
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
|
|
||||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
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 migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||||
@@ -402,54 +404,6 @@ export enum ConflictOperationType {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AutoPilot {
|
|
||||||
public static tier1Text: string = "4,000 RU/s";
|
|
||||||
public static tier2Text: string = "20,000 RU/s";
|
|
||||||
public static tier3Text: string = "100,000 RU/s";
|
|
||||||
public static tier4Text: string = "500,000 RU/s";
|
|
||||||
|
|
||||||
public static tierText = {
|
|
||||||
[AutopilotTier.Tier1]: "Tier 1",
|
|
||||||
[AutopilotTier.Tier2]: "Tier 2",
|
|
||||||
[AutopilotTier.Tier3]: "Tier 3",
|
|
||||||
[AutopilotTier.Tier4]: "Tier 4"
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierMaxRus = {
|
|
||||||
[AutopilotTier.Tier1]: 2000,
|
|
||||||
[AutopilotTier.Tier2]: 20000,
|
|
||||||
[AutopilotTier.Tier3]: 100000,
|
|
||||||
[AutopilotTier.Tier4]: 500000
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierMinRus = {
|
|
||||||
[AutopilotTier.Tier1]: 0,
|
|
||||||
[AutopilotTier.Tier2]: 0,
|
|
||||||
[AutopilotTier.Tier3]: 0,
|
|
||||||
[AutopilotTier.Tier4]: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierStorageInGB = {
|
|
||||||
[AutopilotTier.Tier1]: 50,
|
|
||||||
[AutopilotTier.Tier2]: 200,
|
|
||||||
[AutopilotTier.Tier3]: 1000,
|
|
||||||
[AutopilotTier.Tier4]: 5000
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataExplorerVersions {
|
|
||||||
public static readonly v_1_0_0: string = "1.0.0";
|
|
||||||
public static readonly v_1_0_1: string = "1.0.1";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataExplorerFeatures {
|
|
||||||
public static offerCache: string = "OfferCache";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DataExplorerFeaturesVersions: any = {
|
|
||||||
OfferCache: DataExplorerVersions.v_1_0_1
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ describe("endpoint", () => {
|
|||||||
|
|
||||||
describe("requestPlugin", () => {
|
describe("requestPlugin", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
delete window.dataExplorerPlatform;
|
|
||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
|
import { getErrorMessage } from "./ErrorHandlingUtils";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -69,7 +70,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
const result = JSON.parse(await response.json());
|
const result = JSON.parse(await response.json());
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`);
|
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,22 +72,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
|
|||||||
return [partitionKeyValue];
|
return [partitionKeyValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOffer(
|
|
||||||
offer: DataModels.Offer,
|
|
||||||
newOffer: DataModels.Offer,
|
|
||||||
options?: RequestOptions
|
|
||||||
): Q.Promise<DataModels.Offer> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.offer(offer.id)
|
|
||||||
// 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)
|
|
||||||
.then(response => {
|
|
||||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
export function updateDocument(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
@@ -184,22 +168,6 @@ export function deleteConflict(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
export function queryConflicts(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
containerId: string,
|
containerId: string,
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
|
||||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||||
import * as Logger from "./Logger";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
|
||||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||||
export function queryDocuments(
|
export function queryDocuments(
|
||||||
@@ -59,13 +56,11 @@ export function executeStoredProcedure(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
error,
|
||||||
error
|
"ExecuteStoredProcedure",
|
||||||
)}`
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
);
|
);
|
||||||
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -93,9 +88,7 @@ export function queryDocumentsPage(
|
|||||||
deferred.resolve(result);
|
deferred.resolve(result);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -116,9 +109,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
|||||||
deferred.resolve(document);
|
deferred.resolve(document);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -144,44 +135,7 @@ export function updateDocument(
|
|||||||
deferred.resolve(updatedDocument);
|
deferred.resolve(updatedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateOffer(
|
|
||||||
offer: DataModels.Offer,
|
|
||||||
newOffer: DataModels.Offer,
|
|
||||||
options: RequestOptions
|
|
||||||
): Q.Promise<DataModels.Offer> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const clearMessage = logConsoleProgress(`Updating offer for resource ${offer.resource}`);
|
|
||||||
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
|
|
||||||
.then(
|
|
||||||
(replacedOffer: DataModels.Offer) => {
|
|
||||||
logConsoleInfo(`Successfully updated offer for resource ${offer.resource}`);
|
|
||||||
deferred.resolve(replacedOffer);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
logConsoleError(`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`);
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
oldOffer: offer,
|
|
||||||
newOffer: newOffer,
|
|
||||||
error: error
|
|
||||||
}),
|
|
||||||
"UpdateOffer",
|
|
||||||
error.code
|
|
||||||
);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -203,11 +157,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
|||||||
deferred.resolve(savedDocument);
|
deferred.resolve(savedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -229,9 +179,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
|
|||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -257,9 +205,7 @@ export function deleteConflict(
|
|||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -269,11 +215,3 @@ export function deleteConflict(
|
|||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedResources(options: any = {}): Q.Promise<void> {
|
|
||||||
return DataAccessUtilityBase.refreshCachedResources(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
|
||||||
return DataAccessUtilityBase.refreshCachedOffers();
|
|
||||||
}
|
|
||||||
|
|||||||
56
src/Common/ErrorHandlingUtils.ts
Normal file
56
src/Common/ErrorHandlingUtils.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { ARMError } from "../Utils/arm/request";
|
||||||
|
import { HttpStatusCodes } from "./Constants";
|
||||||
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
|
import { SubscriptionType } from "../Contracts/ViewModels";
|
||||||
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import { logError } from "./Logger";
|
||||||
|
import { sendMessage } from "./MessageHandler";
|
||||||
|
|
||||||
|
export const handleError = (error: string | ARMError | Error, area: string, consoleErrorPrefix?: string): void => {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
const errorCode = error instanceof ARMError ? error.code : undefined;
|
||||||
|
|
||||||
|
// logs error to data explorer console
|
||||||
|
const consoleErrorMessage = consoleErrorPrefix ? `${consoleErrorPrefix}:\n ${errorMessage}` : errorMessage;
|
||||||
|
logConsoleError(consoleErrorMessage);
|
||||||
|
|
||||||
|
// logs error to both app insight and kusto
|
||||||
|
logError(errorMessage, area, errorCode);
|
||||||
|
|
||||||
|
// checks for errors caused by firewall and sends them to portal to handle
|
||||||
|
sendNotificationForError(errorMessage, errorCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorMessage = (error: string | Error): string => {
|
||||||
|
const errorMessage = typeof error === "string" ? error : error.message;
|
||||||
|
return replaceKnownError(errorMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorStack = (error: string | Error): string => {
|
||||||
|
return typeof error === "string" ? undefined : error.stack;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendNotificationForError = (errorMessage: string, errorCode: number | string): void => {
|
||||||
|
if (errorCode === HttpStatusCodes.Forbidden) {
|
||||||
|
if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMessage({
|
||||||
|
type: MessageTypes.ForbiddenError,
|
||||||
|
reason: errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceKnownError = (errorMessage: string): string => {
|
||||||
|
if (
|
||||||
|
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
||||||
|
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||||
|
) {
|
||||||
|
return "Database throughput is not supported for internal subscriptions.";
|
||||||
|
} 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 errorMessage;
|
||||||
|
};
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
|
||||||
|
|
||||||
describe("Error Parser Utility", () => {
|
|
||||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
|
||||||
it("should parse a backend error correctly", () => {
|
|
||||||
// A fake error matching what is thrown by the SDK on a bad collection create request
|
|
||||||
const innerMessage =
|
|
||||||
"The partition key component definition path '/asdwqr31 @#$#$WRadf' could not be accepted, failed near position '10'. Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
|
||||||
const message = `Message: {\"Errors\":[\"${innerMessage}\"]}\r\nActivityId: 97b2e684-7505-4921-85f6-2513b9b28220, Request URI: /apps/89fdcf25-2a0b-4d2a-aab6-e161e565b26f/services/54911149-7bb1-4e7d-a1fa-22c8b36a4bb9/partitions/cc2a7a04-5f5a-4709-bcf7-8509b264963f/replicas/132304018743619218p, RequestStats: , SDK: Microsoft.Azure.Documents.Common/2.10.0`;
|
|
||||||
const err = new Error(message) as any;
|
|
||||||
err.code = 400;
|
|
||||||
err.body = {
|
|
||||||
code: "BadRequest",
|
|
||||||
message
|
|
||||||
};
|
|
||||||
err.headers = {};
|
|
||||||
err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";
|
|
||||||
|
|
||||||
const parsedError = ErrorParserUtility.parse(err);
|
|
||||||
expect(parsedError.length).toBe(1);
|
|
||||||
expect(parsedError[0].message).toBe(innerMessage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
|
|
||||||
export function replaceKnownError(err: string): string {
|
|
||||||
if (
|
|
||||||
window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
|
|
||||||
err.indexOf("SharedOffer is Disabled for your account") >= 0
|
|
||||||
) {
|
|
||||||
return "Database throughput is not supported for internal subscriptions.";
|
|
||||||
} else if (err.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 err;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(err: any): DataModels.ErrorDataModel[] {
|
|
||||||
try {
|
|
||||||
return _parse(err);
|
|
||||||
} catch (e) {
|
|
||||||
return [<DataModels.ErrorDataModel>{ message: JSON.stringify(err) }];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parse(err: any): DataModels.ErrorDataModel[] {
|
|
||||||
var normalizedErrors: DataModels.ErrorDataModel[] = [];
|
|
||||||
if (err.message && !err.code) {
|
|
||||||
normalizedErrors.push(err);
|
|
||||||
} else {
|
|
||||||
const innerErrors: any[] = _getInnerErrors(err.message);
|
|
||||||
normalizedErrors = innerErrors.map(innerError =>
|
|
||||||
typeof innerError === "string" ? { message: innerError } : innerError
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getInnerErrors(message: string): any[] {
|
|
||||||
/*
|
|
||||||
The backend error message has an inner-message which is a stringified object.
|
|
||||||
|
|
||||||
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
|
|
||||||
Example:
|
|
||||||
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
|
|
||||||
For non-SQL errors the "Errors" propery is an array of string.
|
|
||||||
Example:
|
|
||||||
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
|
|
||||||
*/
|
|
||||||
|
|
||||||
let innerMessage: any = null;
|
|
||||||
|
|
||||||
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
|
|
||||||
try {
|
|
||||||
// Multi-Partition error flavor
|
|
||||||
const regExp = /^(.*)ActivityId: (.*)/g;
|
|
||||||
const regString = regExp.exec(singleLineMessage);
|
|
||||||
const innerMessageString = regString[1];
|
|
||||||
innerMessage = JSON.parse(innerMessageString);
|
|
||||||
} catch (e) {
|
|
||||||
// Single-partition error flavor
|
|
||||||
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
|
|
||||||
const regString = regExp.exec(singleLineMessage);
|
|
||||||
const innerMessageString = regString[1];
|
|
||||||
innerMessage = JSON.parse(innerMessageString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
|
|
||||||
}
|
|
||||||
@@ -21,14 +21,8 @@ export function logWarning(message: string, area: string, code?: number): void {
|
|||||||
return _logEntry(entry);
|
return _logEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logError(message: string | Error, area: string, code?: number): void {
|
export function logError(errorMessage: string, area: string, code?: number | string): void {
|
||||||
let logMessage: string;
|
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, errorMessage, area, code);
|
||||||
if (typeof message === "string") {
|
|
||||||
logMessage = message;
|
|
||||||
} else {
|
|
||||||
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
|
|
||||||
}
|
|
||||||
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, logMessage, area, code);
|
|
||||||
return _logEntry(entry);
|
return _logEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +53,7 @@ function _generateLogEntry(
|
|||||||
level: Diagnostics.LogEntryLevel,
|
level: Diagnostics.LogEntryLevel,
|
||||||
message: string,
|
message: string,
|
||||||
area: string,
|
area: string,
|
||||||
code?: number
|
code?: number | string
|
||||||
): Diagnostics.LogEntry {
|
): Diagnostics.LogEntry {
|
||||||
return {
|
return {
|
||||||
timestamp: new Date().getUTCSeconds(),
|
timestamp: new Date().getUTCSeconds(),
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import "jquery";
|
|
||||||
import * as Q from "q";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
|
|
||||||
export class NotificationsClientBase {
|
|
||||||
private _extensionEndpoint: string;
|
|
||||||
private _notificationsApiSuffix: string;
|
|
||||||
|
|
||||||
protected constructor(notificationsApiSuffix: string) {
|
|
||||||
this._notificationsApiSuffix = notificationsApiSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
|
|
||||||
const databaseAccount = userContext.databaseAccount;
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
|
||||||
const resourceGroup = userContext.resourceGroup;
|
|
||||||
const url = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
|
||||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
|
||||||
const headers: any = {};
|
|
||||||
headers[authorizationHeader.header] = authorizationHeader.token;
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "GET",
|
|
||||||
headers: headers,
|
|
||||||
cache: false
|
|
||||||
}).then(
|
|
||||||
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
|
|
||||||
deferred.resolve(notifications);
|
|
||||||
},
|
|
||||||
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
|
|
||||||
deferred.reject(xhr.responseText);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setExtensionEndpoint(extensionEndpoint: string): void {
|
|
||||||
this._extensionEndpoint = extensionEndpoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
src/Common/PortalNotifications.ts
Normal file
41
src/Common/PortalNotifications.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
|
|
||||||
|
const notificationsPath = () => {
|
||||||
|
switch (configContext.platform) {
|
||||||
|
case Platform.Hosted:
|
||||||
|
return "/api/guest/notifications";
|
||||||
|
case Platform.Portal:
|
||||||
|
return "/api/notifications";
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown platform: ${configContext.platform}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||||
|
if (configContext.platform === Platform.Emulator) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const databaseAccount = userContext.databaseAccount;
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
||||||
|
databaseAccount.name
|
||||||
|
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
||||||
|
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
|
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||||
|
|
||||||
|
const response = await window.fetch(url, {
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await response.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await response.json()) as DataModels.Notification[];
|
||||||
|
};
|
||||||
@@ -12,8 +12,7 @@ import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
import * as Logger from "./Logger";
|
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||||
@@ -53,13 +52,8 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "setupQueriesCollection");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -102,19 +96,11 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
let errorMessage: string;
|
if (error.code === HttpStatusCodes.Conflict.toString()) {
|
||||||
const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0];
|
error = `Query ${query.queryName} already exists`;
|
||||||
if (parsedError.code === HttpStatusCodes.Conflict.toString()) {
|
|
||||||
errorMessage = `Query ${query.queryName} already exists`;
|
|
||||||
} else {
|
|
||||||
errorMessage = parsedError.message;
|
|
||||||
}
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
|
||||||
ConsoleDataType.Error,
|
return Promise.reject(error);
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(parsedError), "saveQuery");
|
|
||||||
return Promise.reject(errorMessage);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -163,25 +149,15 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queries);
|
return Promise.resolve(queries);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "getSavedQueries");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
// should never get into this state but we handle this regardless
|
// should never get into this state but we handle this regardless
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "getSavedQueries");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -232,13 +208,8 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "deleteQuery");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||||
@@ -23,21 +22,19 @@ import {
|
|||||||
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, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { createDatabase } from "./createDatabase";
|
import { createDatabase } from "./createDatabase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
let collection: DataModels.Collection;
|
|
||||||
const clearMessage = logConsoleProgress(
|
const clearMessage = logConsoleProgress(
|
||||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
let collection: DataModels.Collection;
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||||
if (params.createNewDatabase) {
|
if (params.createNewDatabase) {
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
@@ -54,18 +51,15 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
} else {
|
} else {
|
||||||
collection = await createCollectionWithSDK(params);
|
collection = await createCollectionWithSDK(params);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
|
||||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
|
||||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||||
await refreshCachedResources();
|
|
||||||
clearMessage();
|
|
||||||
return collection;
|
return collection;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
|
|||||||
@@ -24,36 +24,28 @@ import {
|
|||||||
createUpdateGremlinDatabase,
|
createUpdateGremlinDatabase,
|
||||||
getGremlinDatabase
|
getGremlinDatabase
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
let database: DataModels.Database;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||||
throw new Error("Creating database resources is not allowed for tables accounts");
|
throw new Error("Creating database resources is not allowed for tables accounts");
|
||||||
}
|
}
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
|
||||||
database = await createDatabaseWithARM(params);
|
? createDatabaseWithARM(params)
|
||||||
} else {
|
: createDatabaseWithSDK(params));
|
||||||
database = await createDatabaseWithSDK(params);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
|
||||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||||
await refreshCachedResources();
|
|
||||||
await refreshCachedOffers();
|
|
||||||
clearMessage();
|
|
||||||
return database;
|
return database;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "CreateDatabase", `Error while creating database ${params.databaseId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
|
|||||||
@@ -1,28 +1,78 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlStoredProcedureCreateUpdateParameters,
|
||||||
|
SqlStoredProcedureResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlStoredProcedure,
|
||||||
|
getSqlStoredProcedure
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createStoredProcedure(
|
export async function createStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
storedProcedure: StoredProcedureDefinition
|
storedProcedure: StoredProcedureDefinition
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
): Promise<StoredProcedureDefinition & Resource> {
|
||||||
let createdStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(
|
||||||
|
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: storedProcedure as SqlStoredProcedureResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id,
|
||||||
|
createSprocParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedures.create(storedProcedure);
|
.scripts.storedProcedures.create(storedProcedure);
|
||||||
createdStoredProcedure = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "CreateStoredProcedure", `Error while creating stored procedure ${storedProcedure.id}`);
|
||||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return createdStoredProcedure;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,73 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlTriggerCreateUpdateParameters,
|
||||||
|
SqlTriggerResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createTrigger(
|
export async function createTrigger(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
trigger: TriggerDefinition
|
trigger: TriggerDefinition
|
||||||
): Promise<TriggerDefinition & Resource> {
|
): Promise<TriggerDefinition & Resource> {
|
||||||
let createdTrigger: TriggerDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: trigger as SqlTriggerResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id,
|
||||||
|
createTriggerParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.triggers.create(trigger);
|
.scripts.triggers.create(trigger);
|
||||||
createdTrigger = response.resource;
|
return response.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);
|
||||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return createdTrigger;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,82 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||||
|
SqlUserDefinedFunctionResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlUserDefinedFunction,
|
||||||
|
getSqlUserDefinedFunction
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createUserDefinedFunction(
|
export async function createUserDefinedFunction(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
userDefinedFunction: UserDefinedFunctionDefinition
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||||
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(
|
||||||
|
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id,
|
||||||
|
createUDFParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
||||||
createdUserDefinedFunction = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"CreateUserupdateUserDefinedFunction",
|
||||||
}
|
`Error while creating user defined function ${userDefinedFunction.id}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return createdUserDefinedFunction;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|
||||||
describe("deleteCollection", () => {
|
describe("deleteCollection", () => {
|
||||||
@@ -18,7 +17,6 @@ describe("deleteCollection", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||||
});
|
});
|
||||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-0
|
|||||||
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
|
|
||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
@@ -23,15 +21,13 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
|||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
await refreshCachedResources();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|
||||||
describe("deleteDatabase", () => {
|
describe("deleteDatabase", () => {
|
||||||
@@ -18,7 +17,6 @@ describe("deleteDatabase", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||||
});
|
});
|
||||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/s
|
|||||||
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function deleteDatabase(databaseId: string): Promise<void> {
|
export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||||
@@ -25,15 +23,13 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteDatabase", `Error while deleting database ${databaseId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
await refreshCachedResources();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteStoredProcedure(
|
export async function deleteStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
@@ -10,17 +13,30 @@ export async function deleteStoredProcedure(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
await deleteSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedureId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
await client()
|
await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedure(storedProcedureId)
|
.scripts.storedProcedure(storedProcedureId)
|
||||||
.delete();
|
.delete();
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return undefined;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
await deleteSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
triggerId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
await client()
|
await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.trigger(triggerId)
|
.scripts.trigger(triggerId)
|
||||||
.delete();
|
.delete();
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return undefined;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
await deleteSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
await client()
|
await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunction(id)
|
.scripts.userDefinedFunction(id)
|
||||||
.delete();
|
.delete();
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return undefined;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import * as Constants from "../Constants";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
|
||||||
|
export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let indexTransformationPercentage: number;
|
||||||
|
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.read({ populateQuotaInfo: true });
|
||||||
|
|
||||||
|
indexTransformationPercentage = parseInt(
|
||||||
|
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return indexTransformationPercentage;
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
||||||
let collection: DataModels.Collection;
|
let collection: DataModels.Collection;
|
||||||
@@ -14,9 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
|
|||||||
.read();
|
.read();
|
||||||
collection = response.resource as DataModels.Collection;
|
collection = response.resource as DataModels.Collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
|
|||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
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";
|
||||||
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readCollectionOffer = async (
|
export const readCollectionOffer = async (
|
||||||
@@ -57,9 +56,7 @@ export const readCollectionOffer = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying offer for collection ${params.collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollectionOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { ContainerDefinition, Resource } from "@azure/cosmos";
|
|||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
interface ResourceWithStatistics {
|
interface ResourceWithStatistics {
|
||||||
statistics: DataModels.Statistic[];
|
statistics: DataModels.Statistic[];
|
||||||
@@ -38,9 +37,7 @@ export const readCollectionQuotaInfo = async (
|
|||||||
|
|
||||||
return quota;
|
return quota;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollectionQuotaInfo", `Error while querying quota info for container ${collection.id}`);
|
||||||
logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -2,18 +2,16 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
let collections: DataModels.Collection[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
@@ -22,22 +20,20 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
) {
|
||||||
collections = await readCollectionsWithARM(databaseId);
|
return await readCollectionsWithARM(databaseId);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.containers.readAll()
|
.containers.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
collections = sdkResponse.resources as DataModels.Collection[];
|
return sdkResponse.resources as DataModels.Collection[];
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollections", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return collections;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-
|
|||||||
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 { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readDatabaseOffer = async (
|
export const readDatabaseOffer = async (
|
||||||
@@ -20,28 +19,16 @@ export const readDatabaseOffer = async (
|
|||||||
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
|
||||||
let offerId = params.offerId;
|
let offerId = params.offerId;
|
||||||
if (!offerId) {
|
if (!offerId) {
|
||||||
if (
|
offerId = await (window.authType === AuthType.AAD &&
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
!userContext.useSDKOperations &&
|
!userContext.useSDKOperations &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
? getDatabaseOfferIdWithARM(params.databaseId)
|
||||||
try {
|
: getDatabaseOfferIdWithSDK(params.databaseResourceId));
|
||||||
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
|
||||||
} catch (error) {
|
|
||||||
clearMessage();
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw new error();
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
|
|
||||||
if (!offerId) {
|
if (!offerId) {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const options: RequestOptions = {
|
const options: RequestOptions = {
|
||||||
initialHeaders: {
|
initialHeaders: {
|
||||||
@@ -60,9 +47,7 @@ export const readDatabaseOffer = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying offer for database ${params.databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadDatabaseOffer", `Error while querying offer for database ${params.databaseId}`);
|
||||||
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -75,6 +60,8 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
|
|||||||
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;
|
||||||
|
|
||||||
|
try {
|
||||||
switch (defaultExperience) {
|
switch (defaultExperience) {
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
@@ -93,6 +80,12 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return rpResponse?.name;
|
return rpResponse?.name;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readDatabases(): Promise<DataModels.Database[]> {
|
export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||||
@@ -18,9 +17,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
if (
|
if (
|
||||||
window.authType === AuthType.AAD &&
|
window.authType === AuthType.AAD &&
|
||||||
!userContext.useSDKOperations &&
|
!userContext.useSDKOperations &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
|
|
||||||
) {
|
) {
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
@@ -30,9 +27,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
databases = sdkResponse.resources as DataModels.Database[];
|
databases = sdkResponse.resources as DataModels.Database[];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadDatabases", `Error while querying databases`);
|
||||||
logError(JSON.stringify(error), "ReadDatabases", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
30
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
30
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
|
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
|
||||||
|
export async function readMongoDBCollectionThroughRP(
|
||||||
|
databaseId: string,
|
||||||
|
collectionId: string
|
||||||
|
): Promise<MongoDBCollectionResource> {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let collection: MongoDBCollectionResource;
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||||
|
try {
|
||||||
|
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
|
collection = response.properties.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
@@ -1,26 +1,10 @@
|
|||||||
import { Offer } from "../../Contracts/DataModels";
|
import { Offer } from "../../Contracts/DataModels";
|
||||||
import { ClientDefaults } from "../Constants";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
import { Platform, configContext } from "../../ConfigContext";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export const readOffers = async (): Promise<Offer[]> => {
|
export const readOffers = async (): Promise<Offer[]> => {
|
||||||
const clearMessage = logConsoleProgress(`Querying offers`);
|
const clearMessage = logConsoleProgress(`Querying offers`);
|
||||||
try {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
|
||||||
userContext.databaseAccount.id,
|
|
||||||
ClientDefaults.portalCacheTimeoutMs
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If error getting cached Offers, continue on and read via SDK
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await client()
|
const response = await client()
|
||||||
@@ -29,13 +13,11 @@ export const readOffers = async (): Promise<Offer[]> => {
|
|||||||
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.
|
||||||
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
if (getErrorMessage(error)?.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadOffers", `Error while querying offers`);
|
||||||
logError(JSON.stringify(error), "ReadOffers", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readStoredProcedures(
|
export async function readStoredProcedures(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
||||||
let sprocs: (StoredProcedureDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlStoredProcedures(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedures.readAll()
|
.scripts.storedProcedures.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
sprocs = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadStoredProcedures", `Failed to query stored procedures for container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
}
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return sprocs;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export async function readTriggers(
|
export async function readTriggers(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(TriggerDefinition & Resource)[]> {
|
): Promise<(TriggerDefinition & Resource)[]> {
|
||||||
let triggers: (TriggerDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlTriggers(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.triggers.readAll()
|
.scripts.triggers.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
triggers = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
}
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return triggers;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readUserDefinedFunctions(
|
export async function readUserDefinedFunctions(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
||||||
let udfs: (UserDefinedFunctionDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlUserDefinedFunctions(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunctions.readAll()
|
.scripts.userDefinedFunctions.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
udfs = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"ReadUserDefinedFunctions",
|
||||||
}
|
`Failed to query user defined functions for container ${collectionId}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return udfs;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import * as Constants from "../Constants";
|
|
||||||
import { sendMessage } from "../MessageHandler";
|
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
|
|
||||||
interface CosmosError {
|
|
||||||
code: number;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendNotificationForError(error: CosmosError): void {
|
|
||||||
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
|
|
||||||
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMessage({
|
|
||||||
type: MessageTypes.ForbiddenError,
|
|
||||||
reason: error && error.message ? error.message : error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,10 @@ import { Collection } from "../../Contracts/DataModels";
|
|||||||
import { ContainerDefinition } from "@azure/cosmos";
|
import { ContainerDefinition } from "@azure/cosmos";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import {
|
import {
|
||||||
|
CreateUpdateOptions,
|
||||||
ExtendedResourceProperties,
|
ExtendedResourceProperties,
|
||||||
|
MongoDBCollectionCreateUpdateParameters,
|
||||||
|
MongoDBCollectionResource,
|
||||||
SqlContainerCreateUpdateParameters,
|
SqlContainerCreateUpdateParameters,
|
||||||
SqlContainerResource
|
SqlContainerResource
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
@@ -23,10 +26,8 @@ import {
|
|||||||
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 { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateCollection(
|
export async function updateCollection(
|
||||||
@@ -41,6 +42,7 @@ export async function updateCollection(
|
|||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
window.authType === AuthType.AAD &&
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
) {
|
||||||
@@ -50,18 +52,18 @@ export async function updateCollection(
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.replace(newCollection as ContainerDefinition, options);
|
.replace(newCollection as ContainerDefinition, options);
|
||||||
|
|
||||||
collection = sdkResponse.resource as Collection;
|
collection = sdkResponse.resource as Collection;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
return collection;
|
return collection;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "UpdateCollection", `Failed to update container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCollectionWithARM(
|
async function updateCollectionWithARM(
|
||||||
@@ -77,15 +79,6 @@ async function updateCollectionWithARM(
|
|||||||
switch (defaultExperience) {
|
switch (defaultExperience) {
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
return updateMongoDBCollection(
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
newCollection
|
|
||||||
);
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.Graph:
|
case DefaultAccountExperienceType.Graph:
|
||||||
@@ -122,26 +115,35 @@ async function updateSqlContainer(
|
|||||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateMongoDBCollection(
|
export async function updateMongoDBCollectionThroughRP(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
subscriptionId: string,
|
newCollection: MongoDBCollectionResource,
|
||||||
resourceGroup: string,
|
updateOptions?: CreateUpdateOptions
|
||||||
accountName: string,
|
): Promise<MongoDBCollectionResource> {
|
||||||
newCollection: Collection
|
const subscriptionId = userContext.subscriptionId;
|
||||||
): Promise<Collection> {
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
const updateParams: MongoDBCollectionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: newCollection,
|
||||||
|
options: updateOptions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateResponse = await createUpdateMongoDBCollection(
|
const updateResponse = await createUpdateMongoDBCollection(
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
accountName,
|
accountName,
|
||||||
databaseId,
|
databaseId,
|
||||||
collectionId,
|
collectionId,
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
updateParams
|
||||||
);
|
);
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
|
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
419
src/Common/dataAccess/updateOffer.ts
Normal file
419
src/Common/dataAccess/updateOffer.ts
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
import { HttpHeaders } from "../Constants";
|
||||||
|
import { Offer, UpdateOfferParams } from "../../Contracts/DataModels";
|
||||||
|
import { OfferDefinition } from "@azure/cosmos";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
|
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { readCollectionOffer } from "./readCollectionOffer";
|
||||||
|
import { readDatabaseOffer } from "./readDatabaseOffer";
|
||||||
|
import {
|
||||||
|
updateSqlDatabaseThroughput,
|
||||||
|
migrateSqlDatabaseToAutoscale,
|
||||||
|
migrateSqlDatabaseToManualThroughput,
|
||||||
|
migrateSqlContainerToAutoscale,
|
||||||
|
migrateSqlContainerToManualThroughput,
|
||||||
|
updateSqlContainerThroughput
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import {
|
||||||
|
updateCassandraKeyspaceThroughput,
|
||||||
|
migrateCassandraKeyspaceToAutoscale,
|
||||||
|
migrateCassandraKeyspaceToManualThroughput,
|
||||||
|
migrateCassandraTableToAutoscale,
|
||||||
|
migrateCassandraTableToManualThroughput,
|
||||||
|
updateCassandraTableThroughput
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
|
import {
|
||||||
|
updateMongoDBDatabaseThroughput,
|
||||||
|
migrateMongoDBDatabaseToAutoscale,
|
||||||
|
migrateMongoDBDatabaseToManualThroughput,
|
||||||
|
migrateMongoDBCollectionToAutoscale,
|
||||||
|
migrateMongoDBCollectionToManualThroughput,
|
||||||
|
updateMongoDBCollectionThroughput
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
|
import {
|
||||||
|
updateGremlinDatabaseThroughput,
|
||||||
|
migrateGremlinDatabaseToAutoscale,
|
||||||
|
migrateGremlinDatabaseToManualThroughput,
|
||||||
|
migrateGremlinGraphToAutoscale,
|
||||||
|
migrateGremlinGraphToManualThroughput,
|
||||||
|
updateGremlinGraphThroughput
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import {
|
||||||
|
migrateTableToAutoscale,
|
||||||
|
migrateTableToManualThroughput,
|
||||||
|
updateTableThroughput
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
|
|
||||||
|
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
|
||||||
|
let updatedOffer: Offer;
|
||||||
|
const offerResourceText: string = params.collectionId
|
||||||
|
? `collection ${params.collectionId}`
|
||||||
|
: `database ${params.databaseId}`;
|
||||||
|
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||||
|
if (params.collectionId) {
|
||||||
|
updatedOffer = await updateCollectionOfferWithARM(params);
|
||||||
|
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||||
|
// update table's database offer with SDK since RP doesn't support it
|
||||||
|
updatedOffer = await updateOfferWithSDK(params);
|
||||||
|
} else {
|
||||||
|
updatedOffer = await updateDatabaseOfferWithARM(params);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedOffer = await updateOfferWithSDK(params);
|
||||||
|
}
|
||||||
|
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||||
|
return updatedOffer;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "UpdateCollection", `Error updating offer for ${offerResourceText}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
|
||||||
|
try {
|
||||||
|
switch (userContext.defaultExperience) {
|
||||||
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
|
await updateSqlContainerOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.MongoDB:
|
||||||
|
await updateMongoCollectionOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
|
await updateCassandraTableOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Graph:
|
||||||
|
await updateGremlinGraphOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Table:
|
||||||
|
await updateTableOffer(params);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "MethodNotAllowed") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await readCollectionOffer({
|
||||||
|
collectionId: params.collectionId,
|
||||||
|
databaseId: params.databaseId,
|
||||||
|
offerId: params.currentOffer.id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
|
||||||
|
try {
|
||||||
|
switch (userContext.defaultExperience) {
|
||||||
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
|
await updateSqlDatabaseOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.MongoDB:
|
||||||
|
await updateMongoDatabaseOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
|
await updateCassandraKeyspaceOffer(params);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Graph:
|
||||||
|
await updateGremlinDatabaseOffer(params);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "MethodNotAllowed") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await readDatabaseOffer({
|
||||||
|
databaseId: params.databaseId,
|
||||||
|
offerId: params.currentOffer.id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateSqlContainerToAutoscale(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateSqlContainerToManualThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateSqlContainerThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateMongoDBCollectionToAutoscale(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateMongoDBCollectionToManualThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateMongoDBCollectionThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateCassandraTableToAutoscale(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateCassandraTableToManualThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateCassandraTableThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateGremlinGraphToAutoscale(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateGremlinGraphToManualThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateGremlinGraphThroughput(
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
params.databaseId,
|
||||||
|
params.collectionId,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateTableToManualThroughput(subscriptionId, resourceGroup, accountName, params.collectionId);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateTableThroughput(subscriptionId, resourceGroup, accountName, params.collectionId, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateSqlDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateMongoDBDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateCassandraKeyspaceToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
await migrateGremlinDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
|
||||||
|
} else {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
|
||||||
|
await updateGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpdateParameters => {
|
||||||
|
const body: ThroughputSettingsUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.autopilotThroughput) {
|
||||||
|
body.properties.resource.autoscaleSettings = {
|
||||||
|
maxThroughput: params.autopilotThroughput
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
body.properties.resource.throughput = params.manualThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> => {
|
||||||
|
const currentOffer = params.currentOffer;
|
||||||
|
const newOffer: Offer = {
|
||||||
|
content: {
|
||||||
|
offerThroughput: undefined,
|
||||||
|
offerIsRUPerMinuteThroughputEnabled: false
|
||||||
|
},
|
||||||
|
_etag: undefined,
|
||||||
|
_ts: undefined,
|
||||||
|
_rid: currentOffer._rid,
|
||||||
|
_self: currentOffer._self,
|
||||||
|
id: currentOffer.id,
|
||||||
|
offerResourceId: currentOffer.offerResourceId,
|
||||||
|
offerVersion: currentOffer.offerVersion,
|
||||||
|
offerType: currentOffer.offerType,
|
||||||
|
resource: currentOffer.resource
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.autopilotThroughput) {
|
||||||
|
newOffer.content.offerAutopilotSettings = {
|
||||||
|
maxThroughput: params.autopilotThroughput
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newOffer.content.offerThroughput = params.manualThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: RequestOptions = {};
|
||||||
|
if (params.migrateToAutoPilot) {
|
||||||
|
options.initialHeaders = {
|
||||||
|
[HttpHeaders.migrateOfferToAutopilot]: "true"
|
||||||
|
};
|
||||||
|
delete newOffer.content.offerAutopilotSettings;
|
||||||
|
} else if (params.migrateToManual) {
|
||||||
|
options.initialHeaders = {
|
||||||
|
[HttpHeaders.migrateOfferToManualThroughput]: "true"
|
||||||
|
};
|
||||||
|
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdkResponse = await client()
|
||||||
|
.offer(params.currentOffer.id)
|
||||||
|
// 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);
|
||||||
|
return sdkResponse?.resource;
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Platform, configContext } from "../../ConfigContext";
|
import { Platform, configContext } from "../../ConfigContext";
|
||||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
||||||
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
interface UpdateOfferThroughputRequest {
|
interface UpdateOfferThroughputRequest {
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
@@ -44,8 +45,13 @@ export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThrou
|
|||||||
clearMessage();
|
clearMessage();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
|
handleError(
|
||||||
|
error,
|
||||||
|
"updateOfferThroughputBeyondLimit",
|
||||||
|
`Failed to request an increase in throughput for ${request.throughput}`
|
||||||
|
);
|
||||||
clearMessage();
|
clearMessage();
|
||||||
throw new Error(error.message);
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,72 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlStoredProcedureCreateUpdateParameters,
|
||||||
|
SqlStoredProcedureResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlStoredProcedure,
|
||||||
|
getSqlStoredProcedure
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateStoredProcedure(
|
export async function updateStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
storedProcedure: StoredProcedureDefinition
|
storedProcedure: StoredProcedureDefinition
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
): Promise<StoredProcedureDefinition & Resource> {
|
||||||
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: storedProcedure as SqlStoredProcedureResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id,
|
||||||
|
createSprocParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedure(storedProcedure.id)
|
.scripts.storedProcedure(storedProcedure.id)
|
||||||
.replace(storedProcedure);
|
.replace(storedProcedure);
|
||||||
updatedStoredProcedure = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "UpdateStoredProcedure", `Error while updating stored procedure ${storedProcedure.id}`);
|
||||||
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return updatedStoredProcedure;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,69 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
import {
|
||||||
|
SqlTriggerCreateUpdateParameters,
|
||||||
|
SqlTriggerResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { TriggerDefinition } from "@azure/cosmos";
|
import { TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateTrigger(
|
export async function updateTrigger(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
trigger: TriggerDefinition
|
trigger: TriggerDefinition
|
||||||
): Promise<TriggerDefinition> {
|
): Promise<TriggerDefinition> {
|
||||||
let updatedTrigger: TriggerDefinition;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: trigger as SqlTriggerResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id,
|
||||||
|
createTriggerParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.trigger(trigger.id)
|
.scripts.trigger(trigger.id)
|
||||||
.replace(trigger);
|
.replace(trigger);
|
||||||
updatedTrigger = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);
|
||||||
logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return updatedTrigger;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,76 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||||
|
SqlUserDefinedFunctionResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlUserDefinedFunction,
|
||||||
|
getSqlUserDefinedFunction
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateUserDefinedFunction(
|
export async function updateUserDefinedFunction(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
userDefinedFunction: UserDefinedFunctionDefinition
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||||
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id,
|
||||||
|
createUDFParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||||
.replace(userDefinedFunction);
|
.replace(userDefinedFunction);
|
||||||
updatedUserDefinedFunction = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"UpdateUserupdateUserDefinedFunction",
|
||||||
}
|
`Error while updating user defined function ${userDefinedFunction.id}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return updatedUserDefinedFunction;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
allowedParentFrameOrigins: [
|
allowedParentFrameOrigins: [
|
||||||
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
|
||||||
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
|
||||||
|
`^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$`
|
||||||
@@ -50,7 +51,8 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||||
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"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
|
|||||||
@@ -216,18 +216,6 @@ export interface UniqueKey {
|
|||||||
paths: string[];
|
paths: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned by DocumentDb client proxy
|
|
||||||
// Inner errors in BackendErrorDataModel when error is in SQL syntax
|
|
||||||
export interface ErrorDataModel {
|
|
||||||
message: string;
|
|
||||||
severity?: string;
|
|
||||||
location?: {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
};
|
|
||||||
code?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateDatabaseAndCollectionRequest {
|
export interface CreateDatabaseAndCollectionRequest {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -239,21 +227,12 @@ export interface CreateDatabaseAndCollectionRequest {
|
|||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
autoPilot?: AutoPilotCreationSettings;
|
autoPilot?: AutoPilotCreationSettings;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
hasAutoPilotV2FeatureFlag?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoPilotCreationSettings {
|
export interface AutoPilotCreationSettings {
|
||||||
autopilotTier?: AutopilotTier;
|
|
||||||
maxThroughput?: number;
|
maxThroughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutopilotTier {
|
|
||||||
Tier1 = 1,
|
|
||||||
Tier2 = 2,
|
|
||||||
Tier3 = 3,
|
|
||||||
Tier4 = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
id: string;
|
id: string;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
@@ -262,9 +241,7 @@ export interface Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoPilotOfferSettings {
|
export interface AutoPilotOfferSettings {
|
||||||
tier?: AutopilotTier;
|
|
||||||
maximumTierThroughput?: number;
|
maximumTierThroughput?: number;
|
||||||
targetTier?: AutopilotTier;
|
|
||||||
maxThroughput?: number;
|
maxThroughput?: number;
|
||||||
targetMaxThroughput?: number;
|
targetMaxThroughput?: number;
|
||||||
}
|
}
|
||||||
@@ -303,6 +280,16 @@ export interface ReadCollectionOfferParams {
|
|||||||
offerId?: string;
|
offerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateOfferParams {
|
||||||
|
currentOffer: Offer;
|
||||||
|
databaseId: string;
|
||||||
|
autopilotThroughput: number;
|
||||||
|
manualThroughput: number;
|
||||||
|
collectionId?: string;
|
||||||
|
migrateToAutoPilot?: boolean;
|
||||||
|
migrateToManual?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
id: string;
|
id: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
@@ -481,7 +468,6 @@ export interface MongoParameters extends RpParameters {
|
|||||||
rid?: string;
|
rid?: string;
|
||||||
rtype?: string;
|
rtype?: string;
|
||||||
isAutoPilot?: Boolean;
|
isAutoPilot?: Boolean;
|
||||||
autoPilotTier?: string;
|
|
||||||
autoPilotThroughput?: string;
|
autoPilotThroughput?: string;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export interface LogEntry {
|
|||||||
/**
|
/**
|
||||||
* The message code.
|
* The message code.
|
||||||
*/
|
*/
|
||||||
code?: number;
|
code?: number | string;
|
||||||
/**
|
/**
|
||||||
* Any additional data to be logged.
|
* Any additional data to be logged.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export interface Database extends TreeNode {
|
|||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
loadCollections(): Promise<void>;
|
loadCollections(): Promise<void>;
|
||||||
findCollectionWithId(collectionRid: string): Collection;
|
findCollectionWithId(collectionId: string): Collection;
|
||||||
openAddCollection(database: Database, event: MouseEvent): void;
|
openAddCollection(database: Database, event: MouseEvent): void;
|
||||||
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
@@ -162,6 +162,7 @@ export interface Collection extends CollectionBase {
|
|||||||
loadStoredProcedures(): Promise<any>;
|
loadStoredProcedures(): Promise<any>;
|
||||||
loadTriggers(): Promise<any>;
|
loadTriggers(): Promise<any>;
|
||||||
loadOffer(): Promise<void>;
|
loadOffer(): Promise<void>;
|
||||||
|
loadAutopilotOfferWithRetry(): Promise<DataModels.Offer>;
|
||||||
|
|
||||||
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
||||||
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
||||||
@@ -266,7 +267,6 @@ export interface TabOptions {
|
|||||||
tabKind: CollectionTabKind;
|
tabKind: CollectionTabKind;
|
||||||
title: string;
|
title: string;
|
||||||
tabPath: string;
|
tabPath: string;
|
||||||
selfLink: string;
|
|
||||||
isActive: ko.Observable<boolean>;
|
isActive: ko.Observable<boolean>;
|
||||||
hashLocation: string;
|
hashLocation: string;
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
||||||
@@ -290,6 +290,10 @@ export interface DocumentsTabOptions extends TabOptions {
|
|||||||
resourceTokenPartitionKey?: string;
|
resourceTokenPartitionKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SettingsTabV2Options extends TabOptions {
|
||||||
|
getPendingNotification: Q.Promise<DataModels.Notification>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConflictsTabOptions extends TabOptions {
|
export interface ConflictsTabOptions extends TabOptions {
|
||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
conflictIds: ko.ObservableArray<ConflictId>;
|
conflictIds: ko.ObservableArray<ConflictId>;
|
||||||
@@ -305,7 +309,6 @@ export interface QueryTabOptions extends TabOptions {
|
|||||||
export interface ScriptTabOption extends TabOptions {
|
export interface ScriptTabOption extends TabOptions {
|
||||||
resource: any;
|
resource: any;
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
collectionSelfLink?: string;
|
|
||||||
partitionKey?: DataModels.PartitionKey;
|
partitionKey?: DataModels.PartitionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +391,7 @@ export interface DataExplorerInputsFrame {
|
|||||||
dataExplorerVersion?: string;
|
dataExplorerVersion?: string;
|
||||||
isAuthWithresourceToken?: boolean;
|
isAuthWithresourceToken?: boolean;
|
||||||
defaultCollectionThroughput?: CollectionCreationDefaults;
|
defaultCollectionThroughput?: CollectionCreationDefaults;
|
||||||
|
flights?: readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCreationDefaults {
|
export interface CollectionCreationDefaults {
|
||||||
|
|||||||
42
src/Definitions/jquery.contextmenu.d.ts
vendored
42
src/Definitions/jquery.contextmenu.d.ts
vendored
@@ -1,42 +0,0 @@
|
|||||||
// Type definitions for jQuery contextMenu 1.7.0
|
|
||||||
// Project: http://medialize.github.com/jQuery-contextMenu/
|
|
||||||
// Definitions by: Natan Vivo <https://github.com/nvivo/>
|
|
||||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
||||||
|
|
||||||
/// <reference path="jquery.d.ts" />
|
|
||||||
|
|
||||||
interface JQueryContextMenuOptions {
|
|
||||||
selector: string;
|
|
||||||
appendTo?: string;
|
|
||||||
trigger?: string;
|
|
||||||
autoHide?: boolean;
|
|
||||||
delay?: number;
|
|
||||||
determinePosition?: (menu: JQuery) => void;
|
|
||||||
position?: (opt: JQuery, x: number, y: number) => void;
|
|
||||||
positionSubmenu?: (menu: JQuery) => void;
|
|
||||||
zIndex?: number;
|
|
||||||
animation?: {
|
|
||||||
duration?: number;
|
|
||||||
show?: string;
|
|
||||||
hide?: string;
|
|
||||||
};
|
|
||||||
events?: {
|
|
||||||
show?: () => void;
|
|
||||||
hide?: () => void;
|
|
||||||
};
|
|
||||||
callback?: (key: any, options: any) => any;
|
|
||||||
items?: any;
|
|
||||||
build?: (triggerElement: JQuery, e: Event) => any;
|
|
||||||
reposition?: boolean;
|
|
||||||
className?: string;
|
|
||||||
itemClickEvent?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface JQueryStatic {
|
|
||||||
contextMenu(options?: JQueryContextMenuOptions): JQuery;
|
|
||||||
contextMenu(type: string, selector?: any): JQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface JQuery {
|
|
||||||
contextMenu(options?: any): JQuery;
|
|
||||||
}
|
|
||||||
@@ -123,8 +123,4 @@ describe("Component Registerer", () => {
|
|||||||
it("should register dynamic-list component", () => {
|
it("should register dynamic-list component", () => {
|
||||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register throughput-input component", () => {
|
|
||||||
expect(ko.components.isRegistered("throughput-input")).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
|
|||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
||||||
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
||||||
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
|
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||||
|
|
||||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||||
@@ -23,7 +22,6 @@ ko.components.register("editor", new EditorComponent());
|
|||||||
ko.components.register("json-editor", new JsonEditorComponent());
|
ko.components.register("json-editor", new JsonEditorComponent());
|
||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
ko.components.register("dynamic-list", DynamicListComponent);
|
||||||
ko.components.register("throughput-input", ThroughputInputComponent);
|
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
||||||
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
||||||
import {
|
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
IContextualMenuItem,
|
|
||||||
IContextualMenuProps,
|
|
||||||
ContextualMenuItemType
|
|
||||||
} from "office-ui-fabric-react/lib/ContextualMenu";
|
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface ArcadiaMenuPickerProps {
|
export interface ArcadiaMenuPickerProps {
|
||||||
selectText?: string;
|
selectText?: string;
|
||||||
@@ -47,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
|
|||||||
selectedSparkPool: item.text
|
selectedSparkPool: item.text
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked");
|
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./CollapsibleSectionComponent";
|
||||||
|
|
||||||
|
describe("CollapsibleSectionComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: CollapsibleSectionProps = {
|
||||||
|
title: "Sample title"
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
|
export interface CollapsibleSectionProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollapsibleSectionState {
|
||||||
|
isExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CollapsibleSectionComponent extends React.Component<CollapsibleSectionProps, CollapsibleSectionState> {
|
||||||
|
constructor(props: CollapsibleSectionProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isExpanded: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleCollapsed = (): void => {
|
||||||
|
this.setState({ isExpanded: !this.state.isExpanded });
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
|
||||||
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
|
||||||
|
<Label>{this.props.title}</Label>
|
||||||
|
</Stack>
|
||||||
|
{this.state.isExpanded && this.props.children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||||
|
<Fragment>
|
||||||
|
<Stack
|
||||||
|
className="collapsibleSection"
|
||||||
|
horizontal={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledIconBase
|
||||||
|
iconName="ChevronDown"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingTop": 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledLabelBase>
|
||||||
|
Sample title
|
||||||
|
</StyledLabelBase>
|
||||||
|
</Stack>
|
||||||
|
</Fragment>
|
||||||
|
`;
|
||||||
@@ -3,7 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
|
|||||||
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
||||||
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
import { Link } from "office-ui-fabric-react/lib/Link";
|
import { Link } from "office-ui-fabric-react/lib/Link";
|
||||||
import { FontIcon } from "office-ui-fabric-react";
|
import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
|
|
||||||
export interface TextFieldProps extends ITextFieldProps {
|
export interface TextFieldProps extends ITextFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -24,6 +24,7 @@ export interface DialogProps {
|
|||||||
subText: string;
|
subText: string;
|
||||||
isModal: boolean;
|
isModal: boolean;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
choiceGroupProps?: IChoiceGroupProps;
|
||||||
textFieldProps?: TextFieldProps;
|
textFieldProps?: TextFieldProps;
|
||||||
linkProps?: LinkProps;
|
linkProps?: LinkProps;
|
||||||
primaryButtonText: string;
|
primaryButtonText: string;
|
||||||
@@ -65,6 +66,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
|||||||
minWidth: DIALOG_MIN_WIDTH,
|
minWidth: DIALOG_MIN_WIDTH,
|
||||||
maxWidth: DIALOG_MAX_WIDTH
|
maxWidth: DIALOG_MAX_WIDTH
|
||||||
};
|
};
|
||||||
|
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
|
||||||
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
||||||
const linkProps: LinkProps = this.props.linkProps;
|
const linkProps: LinkProps = this.props.linkProps;
|
||||||
const primaryButtonProps: IButtonProps = {
|
const primaryButtonProps: IButtonProps = {
|
||||||
@@ -82,6 +84,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...dialogProps}>
|
<Dialog {...dialogProps}>
|
||||||
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
{textFieldProps && <TextField {...textFieldProps} />}
|
{textFieldProps && <TextField {...textFieldProps} />}
|
||||||
{linkProps && (
|
{linkProps && (
|
||||||
<Link href={linkProps.linkUrl} target="_blank">
|
<Link href={linkProps.linkUrl} target="_blank">
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
|||||||
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
||||||
value: "true"
|
value: "true"
|
||||||
},
|
},
|
||||||
{ key: "feature.enablesettingsv2", label: "Enable SettingsV2 Tab", value: "true" },
|
|
||||||
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
||||||
{
|
{
|
||||||
key: "feature.enablefixedcollectionwithsharedthroughput",
|
key: "feature.enablefixedcollectionwithsharedthroughput",
|
||||||
|
|||||||
@@ -178,12 +178,6 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
className="checkboxRow"
|
className="checkboxRow"
|
||||||
horizontalAlign="space-between"
|
horizontalAlign="space-between"
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
|
||||||
checked={false}
|
|
||||||
key="feature.enablesettingsv2"
|
|
||||||
label="Enable SettingsV2 Tab"
|
|
||||||
onChange={[Function]}
|
|
||||||
/>
|
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.canexceedmaximumvalue"
|
key="feature.canexceedmaximumvalue"
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { StringUtils } from "../../../Utils/StringUtils";
|
import { StringUtils } from "../../../Utils/StringUtils";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -71,9 +69,10 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
params: Map<string, string>
|
params: Map<string, string>
|
||||||
): string {
|
): string {
|
||||||
if (!serverInfo.notebookServerEndpoint) {
|
if (!serverInfo.notebookServerEndpoint) {
|
||||||
const error = "Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.";
|
handleError(
|
||||||
Logger.logError(error, "NotebookTerminalComponent/createNotebookAppSrc");
|
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
"NotebookTerminalComponent/createNotebookAppSrc"
|
||||||
|
);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ describe("GalleryCardComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
showDownload: true,
|
showDownload: true,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
import { JunoClient } from "../../../Juno/JunoClient";
|
||||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface CodeOfConductComponentProps {
|
export interface CodeOfConductComponentProps {
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
@@ -45,9 +44,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
|||||||
|
|
||||||
this.props.onAcceptCodeOfConduct(response.data);
|
this.props.onAcceptCodeOfConduct(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to accept code of conduct: ${error}`;
|
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||||
Logger.logError(message, "CodeOfConductComponent/acceptCodeOfConduct");
|
|
||||||
logConsoleError(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
IPivotItemProps,
|
IPivotItemProps,
|
||||||
@@ -11,14 +12,12 @@ import {
|
|||||||
Pivot,
|
Pivot,
|
||||||
PivotItem,
|
PivotItem,
|
||||||
SearchBox,
|
SearchBox,
|
||||||
Stack
|
Stack,
|
||||||
|
Text
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
import "./GalleryViewerComponent.less";
|
import "./GalleryViewerComponent.less";
|
||||||
@@ -26,6 +25,7 @@ import { HttpStatusCodes } from "../../../Common/Constants";
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface GalleryViewerComponentProps {
|
export interface GalleryViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -151,7 +151,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
||||||
// Displaying code of conduct component on gallery load should not be the default behavior.
|
// Displaying code of conduct component on gallery load should not be the default behavior.
|
||||||
if (this.state.isCodeOfConductAccepted !== false) {
|
if (this.state.isCodeOfConductAccepted !== false) {
|
||||||
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +197,59 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.createTabContent(data)
|
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
|
return {
|
||||||
|
tab,
|
||||||
|
content: this.createPublishedNotebooksTabContent(data)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => {
|
||||||
|
const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data);
|
||||||
|
const content = (
|
||||||
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
|
{published?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
undefined,
|
||||||
|
"You have successfully published the following notebook(s) to public gallery and shared with other Azure Cosmos DB users.",
|
||||||
|
this.createCardsTabContent(published)
|
||||||
|
)}
|
||||||
|
{underReview?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
"Under Review",
|
||||||
|
"Content of a notebook you published is currently being scanned for illegal content. It will not be available to public gallery until the review is completed (may take a few days)",
|
||||||
|
this.createCardsTabContent(underReview)
|
||||||
|
)}
|
||||||
|
{removed?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
"Removed",
|
||||||
|
"These notebooks were found to contain illegal content and has been taken down.",
|
||||||
|
this.createPolicyViolationsListContent(removed)
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.createSearchBarHeader(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksSectionContent = (
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
content: JSX.Element
|
||||||
|
): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
|
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
|
||||||
|
{description && <Text>{description}</Text>}
|
||||||
|
{content}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
||||||
return acceptedCodeOfConduct === false ? (
|
return acceptedCodeOfConduct === false ? (
|
||||||
<CodeOfConductComponent
|
<CodeOfConductComponent
|
||||||
@@ -210,11 +259,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
this.createTabContent(data)
|
this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTabContent(data: IGalleryItem[]): JSX.Element {
|
private createSearchBarHeader(content: JSX.Element): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
||||||
@@ -227,13 +276,13 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
<Stack.Item styles={{ root: { minWidth: 200 } }}>
|
<Stack.Item styles={{ root: { minWidth: 200 } }}>
|
||||||
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
|
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
{this.props.container?.isGalleryPublishEnabled() && (
|
{(!this.props.container || this.props.container.isGalleryPublishEnabled()) && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<InfoComponent />
|
<InfoComponent />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{data && this.createCardsTabContent(data)}
|
<Stack.Item>{content}</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -251,6 +300,25 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Policy violations</th>
|
||||||
|
</tr>
|
||||||
|
{data.map(item => (
|
||||||
|
<tr key={`policy-violations-tr-${item.id}`}>
|
||||||
|
<td>{item.name}</td>
|
||||||
|
<td>{item.policyViolations.join(", ")}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case GalleryTab.OfficialSamples:
|
case GalleryTab.OfficialSamples:
|
||||||
@@ -284,9 +352,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.sampleNotebooks = response.data;
|
this.sampleNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load sample notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadSampleNotebooks", "Failed to load sample notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadSampleNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,9 +378,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load public notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadPublicNotebooks", "Failed to load public notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadPublicNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +398,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.favoriteNotebooks = response.data;
|
this.favoriteNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load favorite notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadFavoriteNotebooks", "Failed to load favorite notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadFavoriteNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,9 +424,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.publishedNotebooks = response.data;
|
this.publishedNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load published notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadPublishedNotebooks", "Failed to load published notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadPublishedNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "office-ui-fa
|
|||||||
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
|
import { CodeOfConductEndpoints } from "../../../../Common/Constants";
|
||||||
import "./InfoComponent.less";
|
import "./InfoComponent.less";
|
||||||
|
|
||||||
export class InfoComponent extends React.Component {
|
export interface InfoComponentProps {
|
||||||
private getInfoPanel = (iconName: string, labelText: string, url: string): JSX.Element => {
|
onReportAbuseClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InfoComponent extends React.Component<InfoComponentProps> {
|
||||||
|
private getInfoPanel = (iconName: string, labelText: string, url?: string, onClick?: () => void): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Link href={url} target="_blank">
|
<Link href={url} target={url && "_blank"} onClick={onClick}>
|
||||||
<div className="infoPanel">
|
<div className="infoPanel">
|
||||||
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} />
|
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} />
|
||||||
<Label className="infoLabel">{labelText}</Label>
|
<Label className="infoLabel">{labelText}</Label>
|
||||||
@@ -25,6 +29,11 @@ export class InfoComponent extends React.Component {
|
|||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
{this.props.onReportAbuseClick && (
|
||||||
|
<Stack.Item>
|
||||||
|
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
||||||
|
</Stack.Item>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,7 +77,25 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
selectedKey={0}
|
selectedKey={0}
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<StackItem>
|
||||||
|
<FocusZone
|
||||||
|
direction={2}
|
||||||
|
isCircularNavigation={false}
|
||||||
|
shouldRaiseClicks={true}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
getPageSpecification={[Function]}
|
||||||
|
onRenderCell={[Function]}
|
||||||
|
renderedWindowsAhead={3}
|
||||||
|
renderedWindowsBehind={2}
|
||||||
|
startIndex={0}
|
||||||
|
/>
|
||||||
|
</FocusZone>
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</StyledPivotBase>
|
</StyledPivotBase>
|
||||||
|
|||||||
@@ -18,14 +18,17 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
onTagClick: undefined,
|
onTagClick: undefined,
|
||||||
onDownloadClick: undefined,
|
onDownloadClick: undefined,
|
||||||
onFavoriteClick: undefined,
|
onFavoriteClick: undefined,
|
||||||
onUnfavoriteClick: undefined
|
onUnfavoriteClick: undefined,
|
||||||
|
onReportAbuseClick: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
||||||
@@ -47,14 +50,17 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
onTagClick: undefined,
|
onTagClick: undefined,
|
||||||
onDownloadClick: undefined,
|
onDownloadClick: undefined,
|
||||||
onFavoriteClick: undefined,
|
onFavoriteClick: undefined,
|
||||||
onUnfavoriteClick: undefined
|
onUnfavoriteClick: undefined,
|
||||||
|
onReportAbuseClick: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { IGalleryItem } from "../../../Juno/JunoClient";
|
|||||||
import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
|
import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
|
||||||
import "./NotebookViewerComponent.less";
|
import "./NotebookViewerComponent.less";
|
||||||
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
|
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
|
||||||
|
import { InfoComponent } from "../NotebookGallery/InfoComponent/InfoComponent";
|
||||||
|
|
||||||
export interface NotebookMetadataComponentProps {
|
export interface NotebookMetadataComponentProps {
|
||||||
data: IGalleryItem;
|
data: IGalleryItem;
|
||||||
@@ -26,6 +27,7 @@ export interface NotebookMetadataComponentProps {
|
|||||||
onFavoriteClick: () => void;
|
onFavoriteClick: () => void;
|
||||||
onUnfavoriteClick: () => void;
|
onUnfavoriteClick: () => void;
|
||||||
onDownloadClick: () => void;
|
onDownloadClick: () => void;
|
||||||
|
onReportAbuseClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
|
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
|
||||||
@@ -41,9 +43,13 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
|
|||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
|
||||||
|
<Stack.Item>
|
||||||
<Text variant="xxLarge" nowrap>
|
<Text variant="xxLarge" nowrap>
|
||||||
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
<Text>
|
<Text>
|
||||||
{this.props.isFavorite !== undefined && (
|
{this.props.isFavorite !== undefined && (
|
||||||
<>
|
<>
|
||||||
@@ -55,10 +61,21 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
{this.props.downloadButtonText && (
|
{this.props.downloadButtonText && (
|
||||||
|
<Stack.Item>
|
||||||
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
||||||
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Stack.Item grow>
|
||||||
|
<></>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Stack.Item>
|
||||||
|
<InfoComponent onReportAbuseClick={this.props.onReportAbuseClick} />
|
||||||
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
import { Notebook } from "@nteract/commutable";
|
import { Notebook } from "@nteract/commutable";
|
||||||
import { createContentRef } from "@nteract/core";
|
import { createContentRef } from "@nteract/core";
|
||||||
import { Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
|
import { IChoiceGroupProps, Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { contents } from "rx-jupyter";
|
import { contents } from "rx-jupyter";
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
@@ -15,12 +14,14 @@ import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationCon
|
|||||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps, TextFieldProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||||
import "./NotebookViewerComponent.less";
|
import "./NotebookViewerComponent.less";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||||
|
import { DialogHost } from "../../../Utils/GalleryUtils";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface NotebookViewerComponentProps {
|
export interface NotebookViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -43,10 +44,8 @@ interface NotebookViewerComponentState {
|
|||||||
showProgressBar: boolean;
|
showProgressBar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookViewerComponent extends React.Component<
|
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
|
||||||
NotebookViewerComponentProps,
|
implements DialogHost {
|
||||||
NotebookViewerComponentState
|
|
||||||
> {
|
|
||||||
private clientManager: NotebookClientV2;
|
private clientManager: NotebookClientV2;
|
||||||
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
|
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
|
||||||
|
|
||||||
@@ -102,9 +101,7 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ showProgressBar: false });
|
this.setState({ showProgressBar: false });
|
||||||
const message = `Failed to load notebook content: ${error}`;
|
handleError(error, "NotebookViewerComponent/loadNotebookContent", "Failed to load notebook content");
|
||||||
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +137,7 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
onFavoriteClick={this.favoriteItem}
|
onFavoriteClick={this.favoriteItem}
|
||||||
onUnfavoriteClick={this.unfavoriteItem}
|
onUnfavoriteClick={this.unfavoriteItem}
|
||||||
onDownloadClick={this.downloadItem}
|
onDownloadClick={this.downloadItem}
|
||||||
|
onReportAbuseClick={this.state.galleryItem.isSample ? undefined : this.reportAbuse}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -179,6 +177,39 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialogHost
|
||||||
|
showOkCancelModalDialog(
|
||||||
|
title: string,
|
||||||
|
msg: string,
|
||||||
|
okLabel: string,
|
||||||
|
onOk: () => void,
|
||||||
|
cancelLabel: string,
|
||||||
|
onCancel: () => void,
|
||||||
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps
|
||||||
|
): void {
|
||||||
|
this.setState({
|
||||||
|
dialogProps: {
|
||||||
|
isModal: true,
|
||||||
|
visible: true,
|
||||||
|
title,
|
||||||
|
subText: msg,
|
||||||
|
primaryButtonText: okLabel,
|
||||||
|
secondaryButtonText: cancelLabel,
|
||||||
|
onPrimaryButtonClick: () => {
|
||||||
|
this.setState({ dialogProps: undefined });
|
||||||
|
onOk && onOk();
|
||||||
|
},
|
||||||
|
onSecondaryButtonClick: () => {
|
||||||
|
this.setState({ dialogProps: undefined });
|
||||||
|
onCancel && onCancel();
|
||||||
|
},
|
||||||
|
choiceGroupProps,
|
||||||
|
textFieldProps
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private favoriteItem = async (): Promise<void> => {
|
private favoriteItem = async (): Promise<void> => {
|
||||||
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
|
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
|
||||||
this.setState({ galleryItem: item, isFavorite: true })
|
this.setState({ galleryItem: item, isFavorite: true })
|
||||||
@@ -196,4 +227,8 @@ export class NotebookViewerComponent extends React.Component<
|
|||||||
this.setState({ galleryItem: item })
|
this.setState({ galleryItem: item })
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private reportAbuse = (): void => {
|
||||||
|
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,15 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
|
<StackItem>
|
||||||
<Text
|
<Text
|
||||||
nowrap={true}
|
nowrap={true}
|
||||||
variant="xxLarge"
|
variant="xxLarge"
|
||||||
>
|
>
|
||||||
name
|
name
|
||||||
</Text>
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
<Text>
|
<Text>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
iconProps={
|
iconProps={
|
||||||
@@ -34,9 +37,18 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
|||||||
0
|
0
|
||||||
likes
|
likes
|
||||||
</Text>
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
text="Download"
|
text="Download"
|
||||||
/>
|
/>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem
|
||||||
|
grow={true}
|
||||||
|
/>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
@@ -117,12 +129,15 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
|
<StackItem>
|
||||||
<Text
|
<Text
|
||||||
nowrap={true}
|
nowrap={true}
|
||||||
variant="xxLarge"
|
variant="xxLarge"
|
||||||
>
|
>
|
||||||
name
|
name
|
||||||
</Text>
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
<Text>
|
<Text>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
iconProps={
|
iconProps={
|
||||||
@@ -134,9 +149,18 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
|||||||
0
|
0
|
||||||
likes
|
likes
|
||||||
</Text>
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
text="Download"
|
text="Download"
|
||||||
/>
|
/>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem
|
||||||
|
grow={true}
|
||||||
|
/>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcesso
|
|||||||
|
|
||||||
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface QueriesGridComponentProps {
|
export interface QueriesGridComponentProps {
|
||||||
queriesClient: QueriesClient;
|
queriesClient: QueriesClient;
|
||||||
@@ -244,7 +245,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
|||||||
databaseAccountName: container && container.databaseAccount().name,
|
databaseAccountName: container && container.databaseAccount().name,
|
||||||
defaultExperience: container && container.defaultExperience(),
|
defaultExperience: container && container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
paneTitle: container && container.browseQueriesPane.title()
|
paneTitle: container && container.browseQueriesPane.title(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import SettingsTabV2 from "../../Tabs/SettingsTabV2";
|
|||||||
import { collection } from "./TestUtils";
|
import { collection } from "./TestUtils";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { TtlType, isDirty, TtlOnNoDefault, TtlOn, TtlOff } from "./SettingsUtils";
|
import { TtlType, isDirty } from "./SettingsUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
|
||||||
|
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||||
|
}));
|
||||||
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||||
updateCollection: jest.fn().mockReturnValue({
|
updateCollection: jest.fn().mockReturnValue({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -18,10 +21,18 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
|||||||
changeFeedPolicy: undefined,
|
changeFeedPolicy: undefined,
|
||||||
analyticalStorageTtl: undefined,
|
analyticalStorageTtl: undefined,
|
||||||
geospatialConfig: undefined
|
geospatialConfig: undefined
|
||||||
} as DataModels.Collection)
|
} as DataModels.Collection),
|
||||||
|
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
|
||||||
|
id: undefined,
|
||||||
|
shardKey: undefined,
|
||||||
|
indexes: [],
|
||||||
|
analyticalStorageTtl: undefined
|
||||||
|
} as MongoDBCollectionResource)
|
||||||
}));
|
}));
|
||||||
import { updateOffer } from "../../../Common/DocumentClientUtilityBase";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
jest.mock("../../../Common/DocumentClientUtilityBase", () => ({
|
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import Q from "q";
|
||||||
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -33,10 +44,12 @@ describe("SettingsComponent", () => {
|
|||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
selfLink: undefined,
|
|
||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: undefined
|
onUpdateTabsButtons: undefined,
|
||||||
|
getPendingNotification: Q.Promise<DataModels.Notification>(() => {
|
||||||
|
return;
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,10 +116,7 @@ describe("SettingsComponent", () => {
|
|||||||
let settingsComponentInstance = new SettingsComponent(baseProps);
|
let settingsComponentInstance = new SettingsComponent(baseProps);
|
||||||
expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false);
|
expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false);
|
||||||
|
|
||||||
const newContainer = new Explorer({
|
const newContainer = new Explorer();
|
||||||
notificationsClient: undefined,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
newContainer.isPreferredApiCassandra = ko.computed(() => true);
|
newContainer.isPreferredApiCassandra = ko.computed(() => true);
|
||||||
|
|
||||||
const newCollection = { ...collection };
|
const newCollection = { ...collection };
|
||||||
@@ -147,10 +157,7 @@ describe("SettingsComponent", () => {
|
|||||||
let settingsComponentInstance = new SettingsComponent(baseProps);
|
let settingsComponentInstance = new SettingsComponent(baseProps);
|
||||||
expect(settingsComponentInstance.hasConflictResolution()).toEqual(undefined);
|
expect(settingsComponentInstance.hasConflictResolution()).toEqual(undefined);
|
||||||
|
|
||||||
const newContainer = new Explorer({
|
const newContainer = new Explorer();
|
||||||
notificationsClient: undefined,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
newContainer.databaseAccount = ko.observable({
|
newContainer.databaseAccount = ko.observable({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -195,13 +202,17 @@ describe("SettingsComponent", () => {
|
|||||||
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("save calls updateCollection and updateOffer", async () => {
|
it("save calls updateCollection, updateMongoDBCollectionThroughRP and updateOffer", async () => {
|
||||||
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true });
|
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true, isMongoIndexingPolicySaveable: true });
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
||||||
|
settingsComponentInstance.mongoDBCollectionResource = {
|
||||||
|
id: "id"
|
||||||
|
};
|
||||||
await settingsComponentInstance.onSaveClick();
|
await settingsComponentInstance.onSaveClick();
|
||||||
expect(updateCollection).toBeCalled();
|
expect(updateCollection).toBeCalled();
|
||||||
|
expect(updateMongoDBCollectionThroughRP).toBeCalled();
|
||||||
expect(updateOffer).toBeCalled();
|
expect(updateOffer).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -220,13 +231,6 @@ describe("SettingsComponent", () => {
|
|||||||
expect(isDirty(state.throughput, state.throughputBaseline)).toEqual(false);
|
expect(isDirty(state.throughput, state.throughputBaseline)).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getTtlValue", async () => {
|
|
||||||
const settingsComponentInstance = new SettingsComponent(baseProps);
|
|
||||||
expect(settingsComponentInstance.getTtlValue(TtlType.OnNoDefault)).toEqual(TtlOnNoDefault);
|
|
||||||
expect(settingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
|
|
||||||
expect(settingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getAnalyticalStorageTtl", () => {
|
it("getAnalyticalStorageTtl", () => {
|
||||||
const newCollection = { ...collection };
|
const newCollection = { ...collection };
|
||||||
newCollection.analyticalStorageTtl = ko.observable(10);
|
newCollection.analyticalStorageTtl = ko.observable(10);
|
||||||
|
|||||||
@@ -6,18 +6,22 @@ import * as SharedConstants from "../../../Shared/Constants";
|
|||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||||
import { traceStart, traceFailure, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceStart, traceFailure, traceSuccess, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { updateOffer } from "../../../Common/DocumentClientUtilityBase";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||||
import { throughputUnit } from "./SettingsRenderUtils";
|
import { throughputUnit } from "./SettingsRenderUtils";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||||
|
import {
|
||||||
|
MongoIndexingPolicyComponent,
|
||||||
|
MongoIndexingPolicyComponentProps
|
||||||
|
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||||
import {
|
import {
|
||||||
getMaxRUs,
|
getMaxRUs,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
@@ -27,20 +31,24 @@ import {
|
|||||||
SettingsV2TabTypes,
|
SettingsV2TabTypes,
|
||||||
getTabTitle,
|
getTabTitle,
|
||||||
isDirty,
|
isDirty,
|
||||||
TtlOff,
|
AddMongoIndexProps,
|
||||||
TtlOn,
|
MongoIndexTypes,
|
||||||
TtlOnNoDefault,
|
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure
|
parseConflictResolutionProcedure,
|
||||||
|
getMongoNotification
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
import {
|
import {
|
||||||
ConflictResolutionComponent,
|
ConflictResolutionComponent,
|
||||||
ConflictResolutionComponentProps
|
ConflictResolutionComponentProps
|
||||||
} from "./SettingsSubComponents/ConflictResolutionComponent";
|
} from "./SettingsSubComponents/ConflictResolutionComponent";
|
||||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
|
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
|
||||||
import { Pivot, PivotItem, IPivotProps, IPivotItemProps, IChoiceGroupOption } from "office-ui-fabric-react";
|
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||||
|
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||||
|
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
interface SettingsV2TabInfo {
|
interface SettingsV2TabInfo {
|
||||||
tab: SettingsV2TabTypes;
|
tab: SettingsV2TabTypes;
|
||||||
@@ -85,9 +93,15 @@ export interface SettingsComponentState {
|
|||||||
indexingPolicyContent: DataModels.IndexingPolicy;
|
indexingPolicyContent: DataModels.IndexingPolicy;
|
||||||
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
indexingPolicyElementFocussed: boolean;
|
|
||||||
isIndexingPolicyDirty: boolean;
|
isIndexingPolicyDirty: boolean;
|
||||||
|
|
||||||
|
isMongoIndexingPolicySaveable: boolean;
|
||||||
|
isMongoIndexingPolicyDiscardable: boolean;
|
||||||
|
currentMongoIndexes: MongoIndex[];
|
||||||
|
indexesToDrop: number[];
|
||||||
|
indexesToAdd: AddMongoIndexProps[];
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyPath: string;
|
conflictResolutionPolicyPath: string;
|
||||||
@@ -102,18 +116,16 @@ export interface SettingsComponentState {
|
|||||||
|
|
||||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||||
private static readonly sixMonthsInSeconds = 15768000;
|
private static readonly sixMonthsInSeconds = 15768000;
|
||||||
private static readonly zeroSeconds = 0;
|
private saveSettingsButton: ButtonV2;
|
||||||
|
private discardSettingsChangesButton: ButtonV2;
|
||||||
|
|
||||||
public saveSettingsButton: ButtonV2;
|
private isAnalyticalStorageEnabled: boolean;
|
||||||
public discardSettingsChangesButton: ButtonV2;
|
|
||||||
|
|
||||||
public isAnalyticalStorageEnabled: boolean;
|
|
||||||
private collection: ViewModels.Collection;
|
private collection: ViewModels.Collection;
|
||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -127,8 +139,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.changeFeedPolicyVisible = this.collection?.container.isFeatureEnabled(
|
this.changeFeedPolicyVisible = this.collection?.container.isFeatureEnabled(
|
||||||
Constants.Features.enableChangeFeedPolicy
|
Constants.Features.enableChangeFeedPolicy
|
||||||
);
|
);
|
||||||
// Mongo container with system partition key still treat as "Fixed"
|
|
||||||
|
|
||||||
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
!this.collection.partitionKey ||
|
!this.collection.partitionKey ||
|
||||||
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
|
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
|
||||||
@@ -160,10 +172,16 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
indexingPolicyContent: undefined,
|
indexingPolicyContent: undefined,
|
||||||
indexingPolicyContentBaseline: undefined,
|
indexingPolicyContentBaseline: undefined,
|
||||||
indexingPolicyElementFocussed: false,
|
|
||||||
shouldDiscardIndexingPolicy: false,
|
shouldDiscardIndexingPolicy: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
|
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: undefined,
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
|
||||||
conflictResolutionPolicyMode: undefined,
|
conflictResolutionPolicyMode: undefined,
|
||||||
conflictResolutionPolicyModeBaseline: undefined,
|
conflictResolutionPolicyModeBaseline: undefined,
|
||||||
conflictResolutionPolicyPath: undefined,
|
conflictResolutionPolicyPath: undefined,
|
||||||
@@ -192,6 +210,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
this.refreshIndexTransformationProgress();
|
||||||
|
this.loadMongoIndexes();
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
@@ -205,6 +225,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadMongoIndexes = async (): Promise<void> => {
|
||||||
|
if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.isEnableMongoCapabilityPresent() &&
|
||||||
|
this.container.databaseAccount()
|
||||||
|
) {
|
||||||
|
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.mongoDBCollectionResource) {
|
||||||
|
this.setState({
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public refreshIndexTransformationProgress = async (): Promise<void> => {
|
||||||
|
const currentProgress = await getIndexTransformationProgress(this.collection.databaseId, this.collection.id());
|
||||||
|
this.setState({ indexTransformationProgress: currentProgress });
|
||||||
|
};
|
||||||
|
|
||||||
public isSaveSettingsButtonEnabled = (): boolean => {
|
public isSaveSettingsButtonEnabled = (): boolean => {
|
||||||
if (this.isOfferReplacePending()) {
|
if (this.isOfferReplacePending()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -214,7 +259,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isScaleSaveable ||
|
this.state.isScaleSaveable ||
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty
|
this.state.isConflictResolutionDirty ||
|
||||||
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -223,7 +269,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isScaleDiscardable ||
|
this.state.isScaleDiscardable ||
|
||||||
this.state.isSubSettingsDiscardable ||
|
this.state.isSubSettingsDiscardable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty
|
this.state.isConflictResolutionDirty ||
|
||||||
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -270,7 +317,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.props.settingsTab.isExecutionError(false);
|
this.props.settingsTab.isExecutionError(false);
|
||||||
|
|
||||||
this.props.settingsTab.isExecuting(true);
|
this.props.settingsTab.isExecuting(true);
|
||||||
const startKey: number = traceStart(Action.UpdateSettings, {
|
const startKey: number = traceStart(Action.SettingsV2Updated, {
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
@@ -299,6 +346,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
||||||
newCollection.defaultTtl = defaultTtl;
|
newCollection.defaultTtl = defaultTtl;
|
||||||
|
|
||||||
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
||||||
@@ -334,6 +382,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
||||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||||
|
|
||||||
|
if (wasIndexingPolicyModified) {
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
@@ -342,6 +395,58 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||||
|
try {
|
||||||
|
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||||
|
const newMongoCollection: MongoDBCollectionResource = {
|
||||||
|
...this.mongoDBCollectionResource,
|
||||||
|
indexes: newMongoIndexes
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id(),
|
||||||
|
newMongoCollection
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
this.setState({
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||||
|
});
|
||||||
|
traceSuccess(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle()
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
traceFailure(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.isScaleSaveable) {
|
if (this.state.isScaleSaveable) {
|
||||||
const newThroughput = this.state.throughput;
|
const newThroughput = this.state.throughput;
|
||||||
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
||||||
@@ -398,41 +503,41 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
throughput: newThroughput,
|
throughput: newThroughput,
|
||||||
offerIsRUPerMinuteThroughputEnabled: false
|
offerIsRUPerMinuteThroughputEnabled: false
|
||||||
};
|
};
|
||||||
try {
|
|
||||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isScaleSaveable: false,
|
||||||
|
isScaleDiscardable: false,
|
||||||
throughput: originalThroughputValue,
|
throughput: originalThroughputValue,
|
||||||
throughputBaseline: originalThroughputValue,
|
throughputBaseline: originalThroughputValue,
|
||||||
initialNotification: {
|
initialNotification: {
|
||||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||||
} as DataModels.Notification
|
} as DataModels.Notification
|
||||||
});
|
});
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
|
||||||
} catch (error) {
|
|
||||||
traceFailure(
|
|
||||||
Action.UpdateSettings,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
|
||||||
collectionName: this.collection && this.collection.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
|
||||||
error: error
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions);
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
this.collection.offer(updatedOffer);
|
databaseId: this.collection.databaseId,
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
collectionId: this.collection.id(),
|
||||||
|
currentOffer: this.collection.offer(),
|
||||||
|
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
||||||
|
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
|
||||||
|
};
|
||||||
|
if (this.hasProvisioningTypeChanged()) {
|
||||||
if (this.state.isAutoPilotSelected) {
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
updateOfferParams.migrateToAutoPilot = true;
|
||||||
|
} else {
|
||||||
|
updateOfferParams.migrateToManual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
|
this.collection.offer(updatedOffer);
|
||||||
|
|
||||||
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
const autoPilotOffer = await this.collection.loadAutopilotOfferWithRetry();
|
||||||
this.setState({
|
this.setState({
|
||||||
autoPilotThroughput: updatedOffer.content.offerAutopilotSettings.maxThroughput,
|
autoPilotThroughput: autoPilotOffer.content.offerAutopilotSettings.maxThroughput,
|
||||||
autoPilotThroughputBaseline: updatedOffer.content.offerAutopilotSettings.maxThroughput
|
autoPilotThroughputBaseline: autoPilotOffer.content.offerAutopilotSettings.maxThroughput
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -440,32 +545,40 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
throughputBaseline: updatedOffer.content.offerThroughput
|
throughputBaseline: updatedOffer.content.offerThroughput
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
|
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
|
||||||
traceSuccess(
|
traceSuccess(
|
||||||
Action.UpdateSettings,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.props.settingsTab.tabTitle()
|
tabTitle: this.props.settingsTab.tabTitle()
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
} catch (reason) {
|
} catch (error) {
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
this.props.settingsTab.isExecutionError(true);
|
this.props.settingsTab.isExecutionError(true);
|
||||||
console.error(reason);
|
console.error(error);
|
||||||
traceFailure(
|
traceFailure(
|
||||||
Action.UpdateSettings,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.props.settingsTab.tabTitle()
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
@@ -474,12 +587,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onRevertClick = (): void => {
|
public onRevertClick = (): void => {
|
||||||
|
trace(Action.SettingsV2Discarded, ActionModifiers.Mark, {
|
||||||
|
message: "Settings Discarded"
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
throughput: this.state.throughputBaseline,
|
throughput: this.state.throughputBaseline,
|
||||||
timeToLive: this.state.timeToLiveBaseline,
|
timeToLive: this.state.timeToLiveBaseline,
|
||||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||||
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
||||||
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
||||||
|
indexesToAdd: [],
|
||||||
|
indexesToDrop: [],
|
||||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
||||||
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
||||||
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
||||||
@@ -494,19 +613,29 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
isConflictResolutionDirty: false
|
isConflictResolutionDirty: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||||
|
let finalIndexes: MongoIndex[] = [];
|
||||||
|
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||||
|
if (!this.state.indexesToDrop.includes(index)) {
|
||||||
|
finalIndexes.push(mongoIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
finalIndexes = finalIndexes.concat(this.state.indexesToAdd.map((m: AddMongoIndexProps) => m.mongoIndex));
|
||||||
|
return finalIndexes;
|
||||||
|
};
|
||||||
|
|
||||||
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
||||||
this.setState({ isScaleSaveable: isScaleSaveable });
|
this.setState({ isScaleSaveable: isScaleSaveable });
|
||||||
|
|
||||||
private onScaleDiscardableChange = (isScaleDiscardable: boolean): void =>
|
private onScaleDiscardableChange = (isScaleDiscardable: boolean): void =>
|
||||||
this.setState({ isScaleDiscardable: isScaleDiscardable });
|
this.setState({ isScaleDiscardable: isScaleDiscardable });
|
||||||
|
|
||||||
private onIndexingPolicyElementFocusChange = (indexingPolicyElementFocussed: boolean): void =>
|
|
||||||
this.setState({ indexingPolicyElementFocussed: indexingPolicyElementFocussed });
|
|
||||||
|
|
||||||
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
|
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
|
||||||
this.setState({ indexingPolicyContent: newIndexingPolicy });
|
this.setState({ indexingPolicyContent: newIndexingPolicy });
|
||||||
|
|
||||||
@@ -530,79 +659,64 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onConflictResolutionPolicyModeChange = (
|
private onIndexDrop = (index: number): void => this.setState({ indexesToDrop: [...this.state.indexesToDrop, index] });
|
||||||
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
|
||||||
option?: IChoiceGroupOption
|
|
||||||
): void =>
|
|
||||||
this.setState({
|
|
||||||
conflictResolutionPolicyMode:
|
|
||||||
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
|
|
||||||
});
|
|
||||||
|
|
||||||
private onConflictResolutionPolicyPathChange = (
|
private onRevertIndexDrop = (index: number): void => {
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
const indexesToDrop = [...this.state.indexesToDrop];
|
||||||
newValue?: string
|
indexesToDrop.splice(index, 1);
|
||||||
): void => this.setState({ conflictResolutionPolicyPath: newValue });
|
this.setState({ indexesToDrop });
|
||||||
|
};
|
||||||
|
|
||||||
private onConflictResolutionPolicyProcedureChange = (
|
private onRevertIndexAdd = (index: number): void => {
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
const indexesToAdd = [...this.state.indexesToAdd];
|
||||||
newValue?: string
|
indexesToAdd.splice(index, 1);
|
||||||
): void => this.setState({ conflictResolutionPolicyProcedure: newValue });
|
this.setState({ indexesToAdd });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onIndexAddOrChange = (index: number, description: string, type: MongoIndexTypes): void => {
|
||||||
|
const newIndexesToAdd = [...this.state.indexesToAdd];
|
||||||
|
const notification = getMongoNotification(description, type);
|
||||||
|
const newMongoIndexWithType: AddMongoIndexProps = {
|
||||||
|
mongoIndex: { key: { keys: [description] } } as MongoIndex,
|
||||||
|
type: type,
|
||||||
|
notification: notification
|
||||||
|
};
|
||||||
|
if (index === newIndexesToAdd.length) {
|
||||||
|
newIndexesToAdd.push(newMongoIndexWithType);
|
||||||
|
} else {
|
||||||
|
newIndexesToAdd[index] = newMongoIndexWithType;
|
||||||
|
}
|
||||||
|
this.setState({ indexesToAdd: newIndexesToAdd });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
||||||
|
this.setState({ conflictResolutionPolicyMode: newMode });
|
||||||
|
|
||||||
|
private onConflictResolutionPolicyPathChange = (newPath: string): void =>
|
||||||
|
this.setState({ conflictResolutionPolicyPath: newPath });
|
||||||
|
|
||||||
|
private onConflictResolutionPolicyProcedureChange = (newProcedure: string): void =>
|
||||||
|
this.setState({ conflictResolutionPolicyProcedure: newProcedure });
|
||||||
|
|
||||||
private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void =>
|
private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void =>
|
||||||
this.setState({ isConflictResolutionDirty: isConflictResolutionDirty });
|
this.setState({ isConflictResolutionDirty: isConflictResolutionDirty });
|
||||||
|
|
||||||
public getTtlValue = (value: string): TtlType => {
|
private onTtlChange = (newTtl: TtlType): void => this.setState({ timeToLive: newTtl });
|
||||||
switch (value) {
|
|
||||||
case TtlOn:
|
|
||||||
return TtlType.On;
|
|
||||||
case TtlOff:
|
|
||||||
return TtlType.Off;
|
|
||||||
case TtlOnNoDefault:
|
|
||||||
return TtlType.OnNoDefault;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
private onTtlChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption): void =>
|
private onTimeToLiveSecondsChange = (newTimeToLiveSeconds: number): void =>
|
||||||
this.setState({ timeToLive: this.getTtlValue(option.key) });
|
|
||||||
|
|
||||||
private onTimeToLiveSecondsChange = (
|
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
): void => {
|
|
||||||
let newTimeToLiveSeconds = parseInt(newValue);
|
|
||||||
newTimeToLiveSeconds = isNaN(newTimeToLiveSeconds) ? SettingsComponent.zeroSeconds : newTimeToLiveSeconds;
|
|
||||||
this.setState({ timeToLiveSeconds: newTimeToLiveSeconds });
|
this.setState({ timeToLiveSeconds: newTimeToLiveSeconds });
|
||||||
};
|
|
||||||
|
|
||||||
private onGeoSpatialConfigTypeChange = (
|
private onGeoSpatialConfigTypeChange = (newGeoSpatialConfigType: GeospatialConfigType): void =>
|
||||||
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
this.setState({ geospatialConfigType: newGeoSpatialConfigType });
|
||||||
option?: IChoiceGroupOption
|
|
||||||
): void =>
|
|
||||||
this.setState({ geospatialConfigType: GeospatialConfigType[option.key as keyof typeof GeospatialConfigType] });
|
|
||||||
|
|
||||||
private onAnalyticalStorageTtlSelectionChange = (
|
private onAnalyticalStorageTtlSelectionChange = (newAnalyticalStorageTtlSelection: TtlType): void =>
|
||||||
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
this.setState({ analyticalStorageTtlSelection: newAnalyticalStorageTtlSelection });
|
||||||
option?: IChoiceGroupOption
|
|
||||||
): void => this.setState({ analyticalStorageTtlSelection: this.getTtlValue(option.key) });
|
|
||||||
|
|
||||||
private onAnalyticalStorageTtlSecondsChange = (
|
private onAnalyticalStorageTtlSecondsChange = (newAnalyticalStorageTtlSeconds: number): void =>
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
): void => {
|
|
||||||
let newAnalyticalStorageTtlSeconds = parseInt(newValue);
|
|
||||||
newAnalyticalStorageTtlSeconds = isNaN(newAnalyticalStorageTtlSeconds)
|
|
||||||
? SettingsComponent.zeroSeconds
|
|
||||||
: newAnalyticalStorageTtlSeconds;
|
|
||||||
this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds });
|
this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds });
|
||||||
};
|
|
||||||
|
|
||||||
private onChangeFeedPolicyChange = (
|
private onChangeFeedPolicyChange = (newChangeFeedPolicy: ChangeFeedPolicyState): void =>
|
||||||
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
this.setState({ changeFeedPolicy: newChangeFeedPolicy });
|
||||||
option?: IChoiceGroupOption
|
|
||||||
): void =>
|
|
||||||
this.setState({ changeFeedPolicy: ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState] });
|
|
||||||
|
|
||||||
private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void =>
|
private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void =>
|
||||||
this.setState({ isSubSettingsSaveable: isSubSettingsSaveable });
|
this.setState({ isSubSettingsSaveable: isSubSettingsSaveable });
|
||||||
@@ -613,6 +727,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
||||||
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
||||||
|
|
||||||
|
private onMongoIndexingPolicySaveableChange = (isMongoIndexingPolicySaveable: boolean): void =>
|
||||||
|
this.setState({ isMongoIndexingPolicySaveable });
|
||||||
|
|
||||||
|
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||||
|
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||||
|
|
||||||
public getAnalyticalStorageTtl = (): number => {
|
public getAnalyticalStorageTtl = (): number => {
|
||||||
if (this.isAnalyticalStorageEnabled) {
|
if (this.isAnalyticalStorageEnabled) {
|
||||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||||
@@ -783,7 +903,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
isFixedContainer: this.isFixedContainer,
|
isFixedContainer: this.isFixedContainer,
|
||||||
autoPilotTiersList: this.autoPilotTiersList,
|
|
||||||
onThroughputChange: this.onThroughputChange,
|
onThroughputChange: this.onThroughputChange,
|
||||||
throughput: this.state.throughput,
|
throughput: this.state.throughput,
|
||||||
throughputBaseline: this.state.throughputBaseline,
|
throughputBaseline: this.state.throughputBaseline,
|
||||||
@@ -830,12 +949,27 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy,
|
resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy,
|
||||||
indexingPolicyContent: this.state.indexingPolicyContent,
|
indexingPolicyContent: this.state.indexingPolicyContent,
|
||||||
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
||||||
onIndexingPolicyElementFocusChange: this.onIndexingPolicyElementFocusChange,
|
|
||||||
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
||||||
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
||||||
|
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||||
|
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||||
|
mongoIndexes: this.state.currentMongoIndexes,
|
||||||
|
onIndexDrop: this.onIndexDrop,
|
||||||
|
indexesToDrop: this.state.indexesToDrop,
|
||||||
|
onRevertIndexDrop: this.onRevertIndexDrop,
|
||||||
|
indexesToAdd: this.state.indexesToAdd,
|
||||||
|
onRevertIndexAdd: this.onRevertIndexAdd,
|
||||||
|
onIndexAddOrChange: this.onIndexAddOrChange,
|
||||||
|
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||||
|
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||||
|
onMongoIndexingPolicySaveableChange: this.onMongoIndexingPolicySaveableChange,
|
||||||
|
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange
|
||||||
|
};
|
||||||
|
|
||||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
@@ -852,7 +986,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabs: SettingsV2TabInfo[] = [];
|
const tabs: SettingsV2TabInfo[] = [];
|
||||||
if (!hasDatabaseSharedThroughput(this.collection)) {
|
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ScaleTab,
|
tab: SettingsV2TabTypes.ScaleTab,
|
||||||
content: <ScaleComponent {...scaleComponentProps} />
|
content: <ScaleComponent {...scaleComponentProps} />
|
||||||
@@ -869,6 +1003,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||||
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.isEnableMongoCapabilityPresent()
|
||||||
|
) {
|
||||||
|
tabs.push({
|
||||||
|
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||||
|
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasConflictResolution()) {
|
if (this.hasConflictResolution()) {
|
||||||
|
|||||||
@@ -4,17 +4,11 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|||||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
||||||
|
|
||||||
export class SettingsComponentAdapter implements ReactAdapter {
|
export class SettingsComponentAdapter implements ReactAdapter {
|
||||||
public parameters: ko.Observable<number>;
|
public parameters: ko.Computed<boolean>;
|
||||||
|
|
||||||
constructor(private props: SettingsComponentProps) {
|
constructor(private props: SettingsComponentProps) {}
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
return <SettingsComponent {...this.props} />;
|
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getEstimatedAutoscaleSpendElement,
|
getEstimatedAutoscaleSpendElement,
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
indexingPolicyTTLWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
updateThroughputBeyondLimitWarningMessage,
|
updateThroughputBeyondLimitWarningMessage,
|
||||||
updateThroughputDelayedApplyWarningMessage,
|
updateThroughputDelayedApplyWarningMessage,
|
||||||
getThroughputApplyDelayedMessage,
|
getThroughputApplyDelayedMessage,
|
||||||
@@ -15,7 +15,11 @@ import {
|
|||||||
getToolTipContainer,
|
getToolTipContainer,
|
||||||
conflictResolutionCustomToolTip,
|
conflictResolutionCustomToolTip,
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
conflictResolutionLwwTooltip
|
conflictResolutionLwwTooltip,
|
||||||
|
mongoIndexingPolicyDisclaimer,
|
||||||
|
mongoIndexingPolicyAADError,
|
||||||
|
mongoIndexTransformationRefreshingMessage,
|
||||||
|
renderMongoIndexTransformationRefreshMessage
|
||||||
} from "./SettingsRenderUtils";
|
} from "./SettingsRenderUtils";
|
||||||
|
|
||||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||||
@@ -33,7 +37,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
{indexingPolicyTTLWarningMessage}
|
{indexingPolicynUnsavedWarningMessage}
|
||||||
{updateThroughputBeyondLimitWarningMessage}
|
{updateThroughputBeyondLimitWarningMessage}
|
||||||
{updateThroughputDelayedApplyWarningMessage}
|
{updateThroughputDelayedApplyWarningMessage}
|
||||||
|
|
||||||
@@ -45,6 +49,16 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{conflictResolutionLwwTooltip}
|
{conflictResolutionLwwTooltip}
|
||||||
{conflictResolutionCustomToolTip}
|
{conflictResolutionCustomToolTip}
|
||||||
{changeFeedPolicyToolTip}
|
{changeFeedPolicyToolTip}
|
||||||
|
|
||||||
|
{mongoIndexingPolicyDisclaimer}
|
||||||
|
{mongoIndexingPolicyAADError}
|
||||||
|
{mongoIndexTransformationRefreshingMessage}
|
||||||
|
{renderMongoIndexTransformationRefreshMessage(0, () => {
|
||||||
|
return;
|
||||||
|
})}
|
||||||
|
{renderMongoIndexTransformationRefreshMessage(90, () => {
|
||||||
|
return;
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,24 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
IMessageBarStyles,
|
IMessageBarStyles,
|
||||||
ITextStyles
|
ITextStyles,
|
||||||
|
IDetailsRowStyles,
|
||||||
|
IStackStyles,
|
||||||
|
IIconStyles,
|
||||||
|
IDetailsListStyles,
|
||||||
|
IDropdownStyles,
|
||||||
|
ISeparatorStyles,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Stack,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
|
|
||||||
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||||
|
|
||||||
export const spendAckCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: "2 0 2 0"
|
padding: "2 0 2 0"
|
||||||
@@ -45,6 +56,20 @@ export const titleAndInputStackProps: Partial<IStackProps> = {
|
|||||||
tokens: { childrenGap: 5 }
|
tokens: { childrenGap: 5 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mongoWarningStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mongoErrorMessageStyles: Partial<IMessageBarStyles> = { root: { marginLeft: 10 } };
|
||||||
|
|
||||||
|
export const createAndAddMongoIndexStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMongoIndexStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 10 }
|
||||||
|
};
|
||||||
|
|
||||||
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
||||||
tokens: { childrenGap: 10 }
|
tokens: { childrenGap: 10 }
|
||||||
};
|
};
|
||||||
@@ -53,6 +78,54 @@ export const toolTipLabelStackTokens: IStackTokens = {
|
|||||||
childrenGap: 6
|
childrenGap: 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const accordionStackTokens: IStackTokens = {
|
||||||
|
childrenGap: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMongoIndexSubElementsTokens: IStackTokens = {
|
||||||
|
childrenGap: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
|
||||||
|
|
||||||
|
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||||
|
|
||||||
|
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
||||||
|
|
||||||
|
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = { dropdown: { paddingleft: 10, width: 202 } };
|
||||||
|
|
||||||
|
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
background: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
".ms-FocusZone": {
|
||||||
|
paddingTop: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const separatorStyles: Partial<ISeparatorStyles> = {
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
selectors: {
|
||||||
|
"::before": {
|
||||||
|
background: StyleConstants.BaseMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
||||||
|
|
||||||
export const throughputUnit = "RU/s";
|
export const throughputUnit = "RU/s";
|
||||||
@@ -172,15 +245,9 @@ export const ttlWarning: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const indexingPolicyTTLWarningMessage: JSX.Element = (
|
export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
|
||||||
<Text styles={infoAndToolTipTextStyle}>
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and
|
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
||||||
the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For
|
|
||||||
more information see,{" "}
|
|
||||||
<Link target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">
|
|
||||||
Modifying Indexing Policies
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -313,6 +380,56 @@ export const changeFeedPolicyToolTip: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||||
|
<Text>
|
||||||
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
|
<Link href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" target="_blank">
|
||||||
|
{` Compound indexes `}
|
||||||
|
</Link>
|
||||||
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||||
|
shell.
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
<Text>
|
||||||
|
To use the indexing policy editor, please login to the
|
||||||
|
<Link target="_blank" href="https://portal.azure.com">
|
||||||
|
{"azure portal."}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||||
|
<Stack horizontal {...mongoWarningStackProps}>
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>Refreshing index transformation progress</Text>
|
||||||
|
<Spinner size={SpinnerSize.small} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const renderMongoIndexTransformationRefreshMessage = (
|
||||||
|
progress: number,
|
||||||
|
performRefresh: () => void
|
||||||
|
): JSX.Element => {
|
||||||
|
if (progress === 0) {
|
||||||
|
return (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
{"You can make more indexing changes once the current index transformation is complete. "}
|
||||||
|
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
|
||||||
|
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
||||||
fieldGroup: {
|
fieldGroup: {
|
||||||
height: 25,
|
height: 25,
|
||||||
|
|||||||
@@ -18,24 +18,15 @@ export interface ConflictResolutionComponentProps {
|
|||||||
container: Explorer;
|
container: Explorer;
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
onConflictResolutionPolicyModeChange: (
|
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
||||||
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
|
||||||
option?: IChoiceGroupOption
|
|
||||||
) => void;
|
|
||||||
conflictResolutionPolicyPath: string;
|
conflictResolutionPolicyPath: string;
|
||||||
conflictResolutionPolicyPathBaseline: string;
|
conflictResolutionPolicyPathBaseline: string;
|
||||||
|
|
||||||
onConflictResolutionPolicyPathChange: (
|
onConflictResolutionPolicyPathChange: (newPath: string) => void;
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
) => void;
|
|
||||||
conflictResolutionPolicyProcedure: string;
|
conflictResolutionPolicyProcedure: string;
|
||||||
conflictResolutionPolicyProcedureBaseline: string;
|
conflictResolutionPolicyProcedureBaseline: string;
|
||||||
|
|
||||||
onConflictResolutionPolicyProcedureChange: (
|
onConflictResolutionPolicyProcedureChange: (newProcedure: string) => void;
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
) => void;
|
|
||||||
onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void;
|
onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,12 +68,30 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onConflictResolutionPolicyModeChange = (
|
||||||
|
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void =>
|
||||||
|
this.props.onConflictResolutionPolicyModeChange(
|
||||||
|
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
private onConflictResolutionPolicyPathChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => this.props.onConflictResolutionPolicyPathChange(newValue);
|
||||||
|
|
||||||
|
private onConflictResolutionPolicyProcedureChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => this.props.onConflictResolutionPolicyProcedureChange(newValue);
|
||||||
|
|
||||||
private getConflictResolutionModeComponent = (): JSX.Element => (
|
private getConflictResolutionModeComponent = (): JSX.Element => (
|
||||||
<ChoiceGroup
|
<ChoiceGroup
|
||||||
label="Mode"
|
label="Mode"
|
||||||
selectedKey={this.props.conflictResolutionPolicyMode}
|
selectedKey={this.props.conflictResolutionPolicyMode}
|
||||||
options={this.conflictResolutionChoiceGroupOptions}
|
options={this.conflictResolutionChoiceGroupOptions}
|
||||||
onChange={this.props.onConflictResolutionPolicyModeChange}
|
onChange={this.onConflictResolutionPolicyModeChange}
|
||||||
styles={getChoiceGroupStyles(
|
styles={getChoiceGroupStyles(
|
||||||
this.props.conflictResolutionPolicyMode,
|
this.props.conflictResolutionPolicyMode,
|
||||||
this.props.conflictResolutionPolicyModeBaseline
|
this.props.conflictResolutionPolicyModeBaseline
|
||||||
@@ -104,7 +113,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
this.props.conflictResolutionPolicyPathBaseline
|
this.props.conflictResolutionPolicyPathBaseline
|
||||||
)}
|
)}
|
||||||
value={this.props.conflictResolutionPolicyPath}
|
value={this.props.conflictResolutionPolicyPath}
|
||||||
onChange={this.props.onConflictResolutionPolicyPathChange}
|
onChange={this.onConflictResolutionPolicyPathChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -122,7 +131,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
this.props.conflictResolutionPolicyProcedureBaseline
|
this.props.conflictResolutionPolicyProcedureBaseline
|
||||||
)}
|
)}
|
||||||
value={this.props.conflictResolutionPolicyProcedure}
|
value={this.props.conflictResolutionPolicyProcedure}
|
||||||
onChange={this.props.onConflictResolutionPolicyProcedureChange}
|
onChange={this.onConflictResolutionPolicyProcedureChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ describe("IndexingPolicyComponent", () => {
|
|||||||
},
|
},
|
||||||
indexingPolicyContent: initialIndexingPolicyContent,
|
indexingPolicyContent: initialIndexingPolicyContent,
|
||||||
indexingPolicyContentBaseline: initialIndexingPolicyContent,
|
indexingPolicyContentBaseline: initialIndexingPolicyContent,
|
||||||
onIndexingPolicyElementFocusChange: () => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
onIndexingPolicyContentChange: () => {
|
onIndexingPolicyContentChange: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
@@ -28,7 +25,9 @@ describe("IndexingPolicyComponent", () => {
|
|||||||
},
|
},
|
||||||
onIndexingPolicyDirtyChange: () => {
|
onIndexingPolicyDirtyChange: () => {
|
||||||
return;
|
return;
|
||||||
}
|
},
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import { isDirty } from "../SettingsUtils";
|
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
||||||
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
|
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
|
||||||
import { indexingPolicyTTLWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
||||||
|
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
export interface IndexingPolicyComponentProps {
|
export interface IndexingPolicyComponentProps {
|
||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
resetShouldDiscardIndexingPolicy: () => void;
|
resetShouldDiscardIndexingPolicy: () => void;
|
||||||
indexingPolicyContent: DataModels.IndexingPolicy;
|
indexingPolicyContent: DataModels.IndexingPolicy;
|
||||||
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
||||||
onIndexingPolicyElementFocusChange: (indexingPolicyContentFocussed: boolean) => void;
|
|
||||||
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
|
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
|
||||||
logIndexingPolicySuccessMessage: () => void;
|
logIndexingPolicySuccessMessage: () => void;
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
refreshIndexTransformationProgress: () => Promise<void>;
|
||||||
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +54,9 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
if (!this.indexingPolicyEditor) {
|
if (!this.indexingPolicyEditor) {
|
||||||
this.createIndexingPolicyEditor();
|
this.createIndexingPolicyEditor();
|
||||||
} else {
|
} else {
|
||||||
|
this.indexingPolicyEditor.updateOptions({
|
||||||
|
readOnly: isIndexTransforming(this.props.indexTransformationProgress)
|
||||||
|
});
|
||||||
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
||||||
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
||||||
indexingPolicyEditorModel.setValue(value);
|
indexingPolicyEditorModel.setValue(value);
|
||||||
@@ -85,12 +90,10 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
||||||
value: value,
|
value: value,
|
||||||
language: "json",
|
language: "json",
|
||||||
readOnly: false,
|
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
|
||||||
ariaLabel: "Indexing Policy"
|
ariaLabel: "Indexing Policy"
|
||||||
});
|
});
|
||||||
if (this.indexingPolicyEditor) {
|
if (this.indexingPolicyEditor) {
|
||||||
this.indexingPolicyEditor.onDidFocusEditorText(() => this.props.onIndexingPolicyElementFocusChange(true));
|
|
||||||
this.indexingPolicyEditor.onDidBlurEditorText(() => this.props.onIndexingPolicyElementFocusChange(false));
|
|
||||||
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
||||||
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
||||||
this.props.logIndexingPolicySuccessMessage();
|
this.props.logIndexingPolicySuccessMessage();
|
||||||
@@ -111,8 +114,12 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||||
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
|
/>
|
||||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicyTTLWarningMessage}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
||||||
)}
|
)}
|
||||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { IndexingPolicyRefreshComponentProps, IndexingPolicyRefreshComponent } from "./IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
|
describe("IndexingPolicyRefreshComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: IndexingPolicyRefreshComponentProps = {
|
||||||
|
indexTransformationProgress: 90,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<IndexingPolicyRefreshComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||||
|
import {
|
||||||
|
mongoIndexTransformationRefreshingMessage,
|
||||||
|
renderMongoIndexTransformationRefreshMessage
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||||
|
import { isIndexTransforming } from "../../SettingsUtils";
|
||||||
|
|
||||||
|
export interface IndexingPolicyRefreshComponentProps {
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
refreshIndexTransformationProgress: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IndexingPolicyRefreshComponentState {
|
||||||
|
isRefreshing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IndexingPolicyRefreshComponent extends React.Component<
|
||||||
|
IndexingPolicyRefreshComponentProps,
|
||||||
|
IndexingPolicyRefreshComponentState
|
||||||
|
> {
|
||||||
|
constructor(props: IndexingPolicyRefreshComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isRefreshing: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
|
||||||
|
|
||||||
|
private renderIndexTransformationWarning = (): JSX.Element => {
|
||||||
|
if (this.state.isRefreshing) {
|
||||||
|
return mongoIndexTransformationRefreshingMessage;
|
||||||
|
} else if (isIndexTransforming(this.props.indexTransformationProgress)) {
|
||||||
|
return renderMongoIndexTransformationRefreshMessage(
|
||||||
|
this.props.indexTransformationProgress,
|
||||||
|
this.onClickRefreshIndexingTransformationLink
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
private refreshIndexTransformationProgress = async () => {
|
||||||
|
this.setState({ isRefreshing: true });
|
||||||
|
try {
|
||||||
|
await this.props.refreshIndexTransformationProgress();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "RefreshIndexTransformationProgress", "Refreshing index transformation progress failed");
|
||||||
|
} finally {
|
||||||
|
this.setState({ isRefreshing: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return this.renderIndexTransformationWarning() ? (
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={5}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||||
|
<StyledLinkBase
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Refresh to check the progress.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { MongoIndexTypes, MongoNotificationType } from "../../SettingsUtils";
|
||||||
|
import { AddMongoIndexComponent, AddMongoIndexComponentProps } from "./AddMongoIndexComponent";
|
||||||
|
|
||||||
|
describe("AddMongoIndexComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: AddMongoIndexComponentProps = {
|
||||||
|
position: 1,
|
||||||
|
description: "sample_key",
|
||||||
|
type: MongoIndexTypes.Single,
|
||||||
|
notification: { type: MongoNotificationType.Error, message: "sample error" },
|
||||||
|
onIndexAddOrChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onDiscard: () => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<AddMongoIndexComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
TextField,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
ITextField
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
|
import {
|
||||||
|
addMongoIndexSubElementsTokens,
|
||||||
|
mongoErrorMessageStyles,
|
||||||
|
mongoWarningStackProps,
|
||||||
|
shortWidthDropDownStyles,
|
||||||
|
shortWidthTextFieldStyles
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import {
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
MongoIndexTypes,
|
||||||
|
MongoNotificationMessage,
|
||||||
|
MongoNotificationType,
|
||||||
|
MongoWildcardPlaceHolder
|
||||||
|
} from "../../SettingsUtils";
|
||||||
|
|
||||||
|
export interface AddMongoIndexComponentProps {
|
||||||
|
position: number;
|
||||||
|
description: string;
|
||||||
|
type: MongoIndexTypes;
|
||||||
|
notification: MongoNotificationMessage;
|
||||||
|
onIndexAddOrChange: (description: string, type: MongoIndexTypes) => void;
|
||||||
|
onDiscard: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddMongoIndexComponent extends React.Component<AddMongoIndexComponentProps> {
|
||||||
|
private descriptionTextField: ITextField;
|
||||||
|
private indexTypes: IDropdownOption[] = [MongoIndexTypes.Single, MongoIndexTypes.Wildcard].map(
|
||||||
|
(value: MongoIndexTypes) => ({
|
||||||
|
text: getMongoIndexTypeText(value),
|
||||||
|
key: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
private onDescriptionChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => {
|
||||||
|
this.props.onIndexAddOrChange(newValue, this.props.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onTypeChange = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||||
|
const newType = MongoIndexTypes[option.key as keyof typeof MongoIndexTypes];
|
||||||
|
this.props.onIndexAddOrChange(this.props.description, newType);
|
||||||
|
};
|
||||||
|
|
||||||
|
private setRef = (textField: ITextField) => (this.descriptionTextField = textField);
|
||||||
|
|
||||||
|
public focus = (): void => {
|
||||||
|
this.descriptionTextField.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Stack {...mongoWarningStackProps}>
|
||||||
|
<Stack horizontal tokens={addMongoIndexSubElementsTokens}>
|
||||||
|
<TextField
|
||||||
|
ariaLabel={"Index Field Name " + this.props.position}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
styles={shortWidthTextFieldStyles}
|
||||||
|
componentRef={this.setRef}
|
||||||
|
value={this.props.description}
|
||||||
|
placeholder={this.props.type === MongoIndexTypes.Wildcard ? MongoWildcardPlaceHolder : undefined}
|
||||||
|
onChange={this.onDescriptionChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
ariaLabel={"Index Type " + this.props.position}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
styles={shortWidthDropDownStyles}
|
||||||
|
placeholder="Select an index type"
|
||||||
|
selectedKey={this.props.type}
|
||||||
|
options={this.indexTypes}
|
||||||
|
onChange={this.onTypeChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
ariaLabel={"Undo Button " + this.props.position}
|
||||||
|
iconProps={{ iconName: "Undo" }}
|
||||||
|
disabled={!this.props.description && !this.props.type}
|
||||||
|
onClick={() => this.props.onDiscard()}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
{this.props.notification?.type === MongoNotificationType.Error && (
|
||||||
|
<MessageBar styles={mongoErrorMessageStyles} messageBarType={MessageBarType.error}>
|
||||||
|
{this.props.notification.message}
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
|
||||||
|
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
|
||||||
|
import { renderToString } from "react-dom/server";
|
||||||
|
|
||||||
|
describe("MongoIndexingPolicyComponent", () => {
|
||||||
|
const baseProps: MongoIndexingPolicyComponentProps = {
|
||||||
|
mongoIndexes: [],
|
||||||
|
onIndexDrop: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexesToDrop: [],
|
||||||
|
onRevertIndexDrop: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexesToAdd: [],
|
||||||
|
onRevertIndexAdd: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onIndexAddOrChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn()),
|
||||||
|
onMongoIndexingPolicySaveableChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onMongoIndexingPolicyDiscardableChange: () => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AddMongoIndexProps test", () => {
|
||||||
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
|
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||||
|
|
||||||
|
it("defaults", () => {
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
|
||||||
|
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
const sampleWarning = "sampleWarning";
|
||||||
|
const sampleError = "sampleError";
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
[
|
||||||
|
{ type: MongoNotificationType.Warning, message: sampleWarning } as MongoNotificationMessage,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
sampleWarning
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[undefined, false, true, true, undefined],
|
||||||
|
[undefined, true, true, true, undefined]
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(cases)(
|
||||||
|
"",
|
||||||
|
(
|
||||||
|
notification: MongoNotificationMessage,
|
||||||
|
indexToDropIsPresent: boolean,
|
||||||
|
isMongoIndexingPolicySaveable: boolean,
|
||||||
|
isMongoIndexingPolicyDiscardable: boolean,
|
||||||
|
mongoWarningNotificationMessage: string
|
||||||
|
) => {
|
||||||
|
const addMongoIndexProps = {
|
||||||
|
mongoIndex: { key: { keys: ["sampleKey"] } },
|
||||||
|
type: MongoIndexTypes.Single,
|
||||||
|
notification: notification
|
||||||
|
};
|
||||||
|
|
||||||
|
let indexesToDrop: number[] = [];
|
||||||
|
if (indexToDropIsPresent) {
|
||||||
|
indexesToDrop = [0];
|
||||||
|
}
|
||||||
|
wrapper.setProps({ indexesToAdd: [addMongoIndexProps], indexesToDrop: indexesToDrop });
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(isMongoIndexingPolicySaveable);
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
|
||||||
|
isMongoIndexingPolicyDiscardable
|
||||||
|
);
|
||||||
|
if (mongoWarningNotificationMessage) {
|
||||||
|
const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
|
||||||
|
expect(elementAsString).toContain(mongoWarningNotificationMessage);
|
||||||
|
} else {
|
||||||
|
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
DetailsList,
|
||||||
|
DetailsListLayoutMode,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
Text,
|
||||||
|
SelectionMode,
|
||||||
|
IDetailsRowProps,
|
||||||
|
DetailsRow,
|
||||||
|
IColumn,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize,
|
||||||
|
Separator
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
|
import {
|
||||||
|
addMongoIndexStackProps,
|
||||||
|
customDetailsListStyles,
|
||||||
|
mongoIndexingPolicyDisclaimer,
|
||||||
|
mediumWidthStackStyles,
|
||||||
|
subComponentStackProps,
|
||||||
|
transparentDetailsRowStyles,
|
||||||
|
createAndAddMongoIndexStackProps,
|
||||||
|
separatorStyles,
|
||||||
|
mongoIndexingPolicyAADError,
|
||||||
|
indexingPolicynUnsavedWarningMessage,
|
||||||
|
infoAndToolTipTextStyle
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import {
|
||||||
|
MongoIndexTypes,
|
||||||
|
AddMongoIndexProps,
|
||||||
|
MongoIndexIdField,
|
||||||
|
MongoNotificationType,
|
||||||
|
getMongoIndexType,
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
isIndexTransforming
|
||||||
|
} from "../../SettingsUtils";
|
||||||
|
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||||
|
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
|
import { AuthType } from "../../../../../AuthType";
|
||||||
|
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
|
export interface MongoIndexingPolicyComponentProps {
|
||||||
|
mongoIndexes: MongoIndex[];
|
||||||
|
onIndexDrop: (index: number) => void;
|
||||||
|
indexesToDrop: number[];
|
||||||
|
onRevertIndexDrop: (index: number) => void;
|
||||||
|
indexesToAdd: AddMongoIndexProps[];
|
||||||
|
onRevertIndexAdd: (index: number) => void;
|
||||||
|
onIndexAddOrChange: (index: number, description: string, type: MongoIndexTypes) => void;
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
refreshIndexTransformationProgress: () => Promise<void>;
|
||||||
|
onMongoIndexingPolicySaveableChange: (isMongoIndexingPolicySaveable: boolean) => void;
|
||||||
|
onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoIndexDisplayProps {
|
||||||
|
definition: JSX.Element;
|
||||||
|
type: JSX.Element;
|
||||||
|
actionButton: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingPolicyComponentProps> {
|
||||||
|
private shouldCheckComponentIsDirty = true;
|
||||||
|
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
||||||
|
private initialIndexesColumns: IColumn[] = [
|
||||||
|
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{
|
||||||
|
key: "actionButton",
|
||||||
|
name: "Drop Index",
|
||||||
|
fieldName: "actionButton",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
private indexesToBeDroppedColumns: IColumn[] = [
|
||||||
|
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{
|
||||||
|
key: "actionButton",
|
||||||
|
name: "Add index back",
|
||||||
|
fieldName: "actionButton",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
|
||||||
|
if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) {
|
||||||
|
this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus();
|
||||||
|
}
|
||||||
|
this.onComponentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.onComponentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onComponentUpdate = (): void => {
|
||||||
|
if (!this.shouldCheckComponentIsDirty) {
|
||||||
|
this.shouldCheckComponentIsDirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onMongoIndexingPolicySaveableChange(this.isMongoIndexingPolicySaveable());
|
||||||
|
this.props.onMongoIndexingPolicyDiscardableChange(this.isMongoIndexingPolicyDiscardable());
|
||||||
|
this.shouldCheckComponentIsDirty = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
public isMongoIndexingPolicySaveable = (): boolean => {
|
||||||
|
if (this.props.indexesToAdd.length === 0 && this.props.indexesToDrop.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addErrorsExist = !!this.props.indexesToAdd.find(addMongoIndexProps => addMongoIndexProps.notification);
|
||||||
|
|
||||||
|
if (addErrorsExist) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
public isMongoIndexingPolicyDiscardable = (): boolean => {
|
||||||
|
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getMongoWarningNotificationMessage = (): JSX.Element => {
|
||||||
|
const warningMessage = this.props.indexesToAdd.find(
|
||||||
|
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
|
||||||
|
)?.notification.message;
|
||||||
|
|
||||||
|
if (warningMessage) {
|
||||||
|
return <Text styles={infoAndToolTipTextStyle}>{warningMessage}</Text>;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||||
|
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||||
|
return isCurrentIndex ? (
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Delete index Button"
|
||||||
|
iconProps={{ iconName: "Delete" }}
|
||||||
|
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.onIndexDrop(arrayPosition);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Add back Index Button"
|
||||||
|
iconProps={{ iconName: "Add" }}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.onRevertIndexDrop(arrayPosition);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private getMongoIndexDisplayProps = (
|
||||||
|
mongoIndex: MongoIndex,
|
||||||
|
arrayPosition: number,
|
||||||
|
isCurrentIndex: boolean
|
||||||
|
): MongoIndexDisplayProps => {
|
||||||
|
const keys = mongoIndex?.key?.keys;
|
||||||
|
const type = getMongoIndexType(keys);
|
||||||
|
const definition = keys?.join();
|
||||||
|
let mongoIndexDisplayProps: MongoIndexDisplayProps;
|
||||||
|
if (type) {
|
||||||
|
mongoIndexDisplayProps = {
|
||||||
|
definition: <Text>{definition}</Text>,
|
||||||
|
type: <Text>{getMongoIndexTypeText(type)}</Text>,
|
||||||
|
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return mongoIndexDisplayProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderIndexesToBeAdded = (): JSX.Element => {
|
||||||
|
const indexesToAddLength = this.props.indexesToAdd.length;
|
||||||
|
for (let i = 0; i < indexesToAddLength; i++) {
|
||||||
|
const existingIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||||
|
this.addMongoIndexComponentRefs[i] = existingIndexToAddRef;
|
||||||
|
}
|
||||||
|
const newIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||||
|
this.addMongoIndexComponentRefs[indexesToAddLength] = newIndexToAddRef;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
|
{this.props.indexesToAdd.map((mongoIndexWithType, arrayPosition) => {
|
||||||
|
const keys = mongoIndexWithType.mongoIndex.key.keys;
|
||||||
|
const type = mongoIndexWithType.type;
|
||||||
|
const notification = mongoIndexWithType.notification;
|
||||||
|
return (
|
||||||
|
<AddMongoIndexComponent
|
||||||
|
ref={this.addMongoIndexComponentRefs[arrayPosition]}
|
||||||
|
position={arrayPosition}
|
||||||
|
key={arrayPosition}
|
||||||
|
description={keys.join()}
|
||||||
|
type={type}
|
||||||
|
notification={notification}
|
||||||
|
onIndexAddOrChange={(description, type) =>
|
||||||
|
this.props.onIndexAddOrChange(arrayPosition, description, type)
|
||||||
|
}
|
||||||
|
onDiscard={() => {
|
||||||
|
this.addMongoIndexComponentRefs.splice(arrayPosition, 1);
|
||||||
|
this.props.onRevertIndexAdd(arrayPosition);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<AddMongoIndexComponent
|
||||||
|
ref={this.addMongoIndexComponentRefs[indexesToAddLength]}
|
||||||
|
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||||
|
position={indexesToAddLength}
|
||||||
|
key={indexesToAddLength}
|
||||||
|
description={undefined}
|
||||||
|
type={undefined}
|
||||||
|
notification={undefined}
|
||||||
|
onIndexAddOrChange={(description, type) =>
|
||||||
|
this.props.onIndexAddOrChange(indexesToAddLength, description, type)
|
||||||
|
}
|
||||||
|
onDiscard={() => {
|
||||||
|
this.props.onRevertIndexAdd(indexesToAddLength);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderInitialIndexes = (): JSX.Element => {
|
||||||
|
const initialIndexes = this.props.mongoIndexes
|
||||||
|
.map((mongoIndex, arrayPosition) => this.getMongoIndexDisplayProps(mongoIndex, arrayPosition, true))
|
||||||
|
.filter((value, arrayPosition) => !!value && !this.props.indexesToDrop.includes(arrayPosition));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
|
<CollapsibleSectionComponent title="Current index(es)">
|
||||||
|
{
|
||||||
|
<>
|
||||||
|
<DetailsList
|
||||||
|
styles={customDetailsListStyles}
|
||||||
|
disableSelectionZone
|
||||||
|
items={initialIndexes}
|
||||||
|
columns={this.initialIndexesColumns}
|
||||||
|
selectionMode={SelectionMode.none}
|
||||||
|
onRenderRow={this.onRenderRow}
|
||||||
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
|
/>
|
||||||
|
{this.renderIndexesToBeAdded()}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderIndexesToBeDropped = (): JSX.Element => {
|
||||||
|
const indexesToBeDropped = this.props.indexesToDrop.map((dropIndex, arrayPosition) =>
|
||||||
|
this.getMongoIndexDisplayProps(this.props.mongoIndexes[dropIndex], arrayPosition, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack styles={mediumWidthStackStyles}>
|
||||||
|
<CollapsibleSectionComponent title="Index(es) to be dropped">
|
||||||
|
{indexesToBeDropped.length > 0 && (
|
||||||
|
<DetailsList
|
||||||
|
styles={customDetailsListStyles}
|
||||||
|
disableSelectionZone
|
||||||
|
items={indexesToBeDropped}
|
||||||
|
columns={this.indexesToBeDroppedColumns}
|
||||||
|
selectionMode={SelectionMode.none}
|
||||||
|
onRenderRow={this.onRenderRow}
|
||||||
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderWarningMessage = (): JSX.Element => {
|
||||||
|
let warningMessage: JSX.Element;
|
||||||
|
if (this.getMongoWarningNotificationMessage()) {
|
||||||
|
warningMessage = this.getMongoWarningNotificationMessage();
|
||||||
|
} else if (this.isMongoIndexingPolicySaveable()) {
|
||||||
|
warningMessage = indexingPolicynUnsavedWarningMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||||
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
|
/>
|
||||||
|
{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
if (this.props.mongoIndexes) {
|
||||||
|
return (
|
||||||
|
<Stack {...subComponentStackProps}>
|
||||||
|
{this.renderWarningMessage()}
|
||||||
|
{mongoIndexingPolicyDisclaimer}
|
||||||
|
{this.renderInitialIndexes()}
|
||||||
|
<Separator styles={separatorStyles} />
|
||||||
|
{this.renderIndexesToBeDropped()}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return window.authType !== AuthType.AAD ? mongoIndexingPolicyAADError : <Spinner size={SpinnerSize.large} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AddMongoIndexComponent renders 1`] = `
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Index Field Name 1"
|
||||||
|
componentRef={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingLeft": 10,
|
||||||
|
"width": 210,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value="sample_key"
|
||||||
|
/>
|
||||||
|
<StyledWithResponsiveMode
|
||||||
|
ariaLabel="Index Type 1"
|
||||||
|
onChange={[Function]}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"key": "Single",
|
||||||
|
"text": "Single Field",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "Wildcard",
|
||||||
|
"text": "Wildcard",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
placeholder="Select an index type"
|
||||||
|
selectedKey="Single"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"dropdown": Object {
|
||||||
|
"paddingleft": 10,
|
||||||
|
"width": 202,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CustomizedIconButton
|
||||||
|
ariaLabel="Undo Button 1"
|
||||||
|
disabled={false}
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Undo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClick={[Function]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={1}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"marginLeft": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
sample error
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
</Stack>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
refreshIndexTransformationProgress={[Function]}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Compound indexes
|
||||||
|
</StyledLinkBase>
|
||||||
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||||
|
</Text>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="Current index(es)"
|
||||||
|
>
|
||||||
|
<StyledWithViewportComponent
|
||||||
|
columns={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "definition",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "definition",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Definition",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "type",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "type",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Type",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "actionButton",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "actionButton",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Drop Index",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={Array []}
|
||||||
|
layoutMode={1}
|
||||||
|
onRenderRow={[Function]}
|
||||||
|
selectionMode={0}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
".ms-FocusZone": Object {
|
||||||
|
"paddingTop": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddMongoIndexComponent
|
||||||
|
disabled={false}
|
||||||
|
key="0"
|
||||||
|
onDiscard={[Function]}
|
||||||
|
onIndexAddOrChange={[Function]}
|
||||||
|
position={0}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
<Styled
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Array [
|
||||||
|
Object {
|
||||||
|
"selectors": Object {
|
||||||
|
"::before": Object {
|
||||||
|
"background": undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="Index(es) to be dropped"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
`;
|
||||||
@@ -5,18 +5,13 @@ import { container, collection } from "../TestUtils";
|
|||||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import * as Constants from "../../../../Common/Constants";
|
import * as Constants from "../../../../Common/Constants";
|
||||||
import { PlatformType } from "../../../../PlatformType";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import { throughputUnit } from "../SettingsRenderUtils";
|
import { throughputUnit } from "../SettingsRenderUtils";
|
||||||
import * as SharedConstants from "../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../Shared/Constants";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
|
|
||||||
describe("ScaleComponent", () => {
|
describe("ScaleComponent", () => {
|
||||||
const nonNationalCloudContainer = new Explorer({
|
const nonNationalCloudContainer = new Explorer();
|
||||||
notificationsClient: undefined,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal;
|
|
||||||
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
||||||
|
|
||||||
const targetThroughput = 6000;
|
const targetThroughput = 6000;
|
||||||
@@ -25,7 +20,6 @@ describe("ScaleComponent", () => {
|
|||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
container: container,
|
||||||
isFixedContainer: false,
|
isFixedContainer: false,
|
||||||
autoPilotTiersList: [],
|
|
||||||
onThroughputChange: () => {
|
onThroughputChange: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
@@ -88,10 +82,7 @@ describe("ScaleComponent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("autoScale enabled", () => {
|
it("autoScale enabled", () => {
|
||||||
const newContainer = new Explorer({
|
const newContainer = new Explorer();
|
||||||
notificationsClient: undefined,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
|
|
||||||
newContainer.databaseAccount({
|
newContainer.databaseAccount({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -125,7 +116,7 @@ describe("ScaleComponent", () => {
|
|||||||
|
|
||||||
it("getThroughputTitle", () => {
|
it("getThroughputTitle", () => {
|
||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - 40,000 RU/s)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||||
|
|
||||||
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
@@ -138,7 +129,7 @@ describe("ScaleComponent", () => {
|
|||||||
|
|
||||||
it("canThroughputExceedMaximumValue", () => {
|
it("canThroughputExceedMaximumValue", () => {
|
||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(false);
|
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||||
|
|
||||||
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
|
|||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import * as SharedConstants from "../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../Shared/Constants";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { PlatformType } from "../../../../PlatformType";
|
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
@@ -19,12 +18,12 @@ import {
|
|||||||
import { getMaxRUs, getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
|
import { getMaxRUs, getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
|
||||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||||
import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||||
|
import { configContext, Platform } from "../../../../ConfigContext";
|
||||||
|
|
||||||
export interface ScaleComponentProps {
|
export interface ScaleComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
isFixedContainer: boolean;
|
isFixedContainer: boolean;
|
||||||
autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
|
||||||
onThroughputChange: (newThroughput: number) => void;
|
onThroughputChange: (newThroughput: number) => void;
|
||||||
throughput: number;
|
throughput: number;
|
||||||
throughputBaseline: number;
|
throughputBaseline: number;
|
||||||
@@ -43,7 +42,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
private isEmulator: boolean;
|
private isEmulator: boolean;
|
||||||
constructor(props: ScaleComponentProps) {
|
constructor(props: ScaleComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.isEmulator = this.props.container.isEmulator;
|
this.isEmulator = configContext.platform === Platform.Emulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isAutoScaleEnabled = (): boolean => {
|
public isAutoScaleEnabled = (): boolean => {
|
||||||
@@ -77,7 +76,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public getMaxRUThroughputInputLimit = (): number => {
|
public getMaxRUThroughputInputLimit = (): number => {
|
||||||
if (this.props.container?.getPlatformType() === PlatformType.Hosted && this.props.collection.partitionKey) {
|
if (configContext.platform === Platform.Hosted && this.props.collection.partitionKey) {
|
||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
|
|
||||||
public getThroughputTitle = (): string => {
|
public getThroughputTitle = (): string => {
|
||||||
if (this.props.isAutoPilotSelected) {
|
if (this.props.isAutoPilotSelected) {
|
||||||
return AutoPilotUtils.getAutoPilotHeaderText(false);
|
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||||
}
|
}
|
||||||
|
|
||||||
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
||||||
@@ -98,12 +97,11 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public canThroughputExceedMaximumValue = (): boolean => {
|
public canThroughputExceedMaximumValue = (): boolean => {
|
||||||
const isPublicAzurePortal: boolean =
|
return (
|
||||||
this.props.container.getPlatformType() === PlatformType.Portal &&
|
!this.props.isFixedContainer &&
|
||||||
!this.props.container.isRunningOnNationalCloud();
|
configContext.platform === Platform.Portal &&
|
||||||
const hasPartitionKey = !!this.props.collection.partitionKey;
|
!this.props.container.isRunningOnNationalCloud()
|
||||||
|
);
|
||||||
return isPublicAzurePortal && hasPartitionKey;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public getInitialNotificationElement = (): JSX.Element => {
|
public getInitialNotificationElement = (): JSX.Element => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user