mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-07 19:46:53 +00:00
Compare commits
44 Commits
remove-red
...
jbunster/n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6adfea03ab | ||
|
|
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 |
@@ -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"
|
|
||||||
72
.github/workflows/ci.yml
vendored
72
.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]
|
||||||
@@ -221,7 +153,7 @@ jobs:
|
|||||||
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 +177,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 }}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
202
package-lock.json
generated
202
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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -7203,11 +7201,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 +8501,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 +9646,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 +11846,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 +13344,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": {
|
||||||
@@ -16619,6 +16527,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 +18156,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 +19917,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",
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -117,7 +117,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";
|
||||||
@@ -131,6 +130,12 @@ export class Features {
|
|||||||
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";
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ describe("endpoint", () => {
|
|||||||
|
|
||||||
describe("requestPlugin", () => {
|
describe("requestPlugin", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
delete window.dataExplorerPlatform;
|
|
||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,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}: ${error.message}`);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
Resource
|
Resource
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import Q from "q";
|
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
@@ -39,18 +38,17 @@ export function getCommonQueryOptions(options: FeedOptions): any {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryDocuments(
|
export async function queryDocuments(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
containerId: string,
|
containerId: string,
|
||||||
query: string,
|
query: string,
|
||||||
options: any
|
options: any
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
): Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||||
options = getCommonQueryOptions(options);
|
options = getCommonQueryOptions(options);
|
||||||
const documentsIterator = client()
|
return client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(containerId)
|
.container(containerId)
|
||||||
.items.query(query, options);
|
.items.query(query, options);
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
||||||
@@ -72,37 +70,19 @@ 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,
|
||||||
newDocument: any
|
newDocument: any
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
return Q(
|
return client()
|
||||||
client()
|
.database(collection.databaseId)
|
||||||
.database(collection.databaseId)
|
.container(collection.id())
|
||||||
.container(collection.id())
|
.item(documentId.id(), partitionKey)
|
||||||
.item(documentId.id(), partitionKey)
|
.replace(newDocument)
|
||||||
.replace(newDocument)
|
.then(response => response.resource);
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function executeStoredProcedure(
|
export function executeStoredProcedure(
|
||||||
@@ -110,105 +90,89 @@ export function executeStoredProcedure(
|
|||||||
storedProcedure: StoredProcedure,
|
storedProcedure: StoredProcedure,
|
||||||
partitionKeyValue: any,
|
partitionKeyValue: any,
|
||||||
params: any[]
|
params: any[]
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
return Promise.race([
|
||||||
const deferred = Q.defer<any>();
|
client()
|
||||||
|
.database(collection.databaseId)
|
||||||
client()
|
.container(collection.id())
|
||||||
.database(collection.databaseId)
|
.scripts.storedProcedure(storedProcedure.id())
|
||||||
.container(collection.id())
|
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
||||||
.scripts.storedProcedure(storedProcedure.id())
|
.then(response => ({
|
||||||
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
|
||||||
.then(response =>
|
|
||||||
deferred.resolve({
|
|
||||||
result: response.resource,
|
result: response.resource,
|
||||||
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
||||||
})
|
})),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() => reject(`Request timed out while executing stored procedure ${storedProcedure.id()}`),
|
||||||
|
Constants.ClientDefaults.requestTimeoutMs
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.catch(error => deferred.reject(error));
|
]);
|
||||||
|
|
||||||
return deferred.promise.timeout(
|
|
||||||
Constants.ClientDefaults.requestTimeoutMs,
|
|
||||||
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> {
|
||||||
return Q(
|
return client()
|
||||||
client()
|
.database(collection.databaseId)
|
||||||
.database(collection.databaseId)
|
.container(collection.id())
|
||||||
.container(collection.id())
|
.items.create(newDocument)
|
||||||
.items.create(newDocument)
|
.then(response => response.resource);
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
return Q(
|
return client()
|
||||||
client()
|
.database(collection.databaseId)
|
||||||
.database(collection.databaseId)
|
.container(collection.id())
|
||||||
.container(collection.id())
|
.item(documentId.id(), partitionKey)
|
||||||
.item(documentId.id(), partitionKey)
|
.read()
|
||||||
.read()
|
.then(response => response.resource);
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
return Q(
|
return client()
|
||||||
client()
|
.database(collection.databaseId)
|
||||||
.database(collection.databaseId)
|
.container(collection.id())
|
||||||
.container(collection.id())
|
.item(documentId.id(), partitionKey)
|
||||||
.item(documentId.id(), partitionKey)
|
.delete();
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteConflict(
|
export function deleteConflict(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
conflictId: ConflictId,
|
conflictId: ConflictId,
|
||||||
options: any = {}
|
options: any = {}
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
||||||
|
|
||||||
return Q(
|
return client()
|
||||||
client()
|
.database(collection.databaseId)
|
||||||
.database(collection.databaseId)
|
.container(collection.id())
|
||||||
.container(collection.id())
|
.conflict(conflictId.id())
|
||||||
.conflict(conflictId.id())
|
.delete(options);
|
||||||
.delete(options)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
export async function refreshCachedOffers(): Promise<void> {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
export async function refreshCachedResources(options?: any): Promise<void> {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryConflicts(
|
export async function queryConflicts(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
containerId: string,
|
containerId: string,
|
||||||
query: string,
|
query: string,
|
||||||
options: any
|
options: any
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
): Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||||
const documentsIterator = client()
|
return client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(containerId)
|
.container(containerId)
|
||||||
.conflicts.query(query, options);
|
.conflicts.query(query, options);
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
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 * 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(
|
||||||
@@ -19,7 +15,7 @@ export function queryDocuments(
|
|||||||
containerId: string,
|
containerId: string,
|
||||||
query: string,
|
query: string,
|
||||||
options: any
|
options: any
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
): Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||||
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
|
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +24,7 @@ export function queryConflicts(
|
|||||||
containerId: string,
|
containerId: string,
|
||||||
query: string,
|
query: string,
|
||||||
options: any
|
options: any
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
): Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||||
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
|
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,34 +42,26 @@ export function executeStoredProcedure(
|
|||||||
storedProcedure: StoredProcedure,
|
storedProcedure: StoredProcedure,
|
||||||
partitionKeyValue: any,
|
partitionKeyValue: any,
|
||||||
params: any[]
|
params: any[]
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||||
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
return DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
||||||
.then(
|
.then(
|
||||||
(response: any) => {
|
(response: any) => {
|
||||||
deferred.resolve(response);
|
|
||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
);
|
);
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
error,
|
||||||
error
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`,
|
||||||
)}`
|
"ExecuteStoredProcedure"
|
||||||
);
|
);
|
||||||
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(clearMessage);
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryDocumentsPage(
|
export function queryDocumentsPage(
|
||||||
@@ -81,199 +69,114 @@ export function queryDocumentsPage(
|
|||||||
documentsIterator: MinimalQueryIterator,
|
documentsIterator: MinimalQueryIterator,
|
||||||
firstItemIndex: number,
|
firstItemIndex: number,
|
||||||
options: any
|
options: any
|
||||||
): Q.Promise<ViewModels.QueryResults> {
|
): Promise<ViewModels.QueryResults> {
|
||||||
var deferred = Q.defer<ViewModels.QueryResults>();
|
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||||
Q(nextPage(documentsIterator, firstItemIndex))
|
return nextPage(documentsIterator, firstItemIndex)
|
||||||
.then(
|
.then(
|
||||||
(result: ViewModels.QueryResults) => {
|
(result: ViewModels.QueryResults) => {
|
||||||
const itemCount = (result.documents && result.documents.length) || 0;
|
const itemCount = (result.documents && result.documents.length) || 0;
|
||||||
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||||
deferred.resolve(result);
|
return result;
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage");
|
||||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(clearMessage);
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||||
DataAccessUtilityBase.readDocument(collection, documentId)
|
return DataAccessUtilityBase.readDocument(collection, documentId)
|
||||||
.then(
|
.catch((error: any) => {
|
||||||
(document: any) => {
|
handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
|
||||||
deferred.resolve(document);
|
throw error;
|
||||||
},
|
})
|
||||||
(error: any) => {
|
.finally(clearMessage);
|
||||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
|
||||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDocument(
|
export function updateDocument(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
newDocument: any
|
newDocument: any
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||||
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
return DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
||||||
.then(
|
.then(
|
||||||
(updatedDocument: any) => {
|
(updatedDocument: any) => {
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
deferred.resolve(updatedDocument);
|
return updatedDocument;
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument");
|
||||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(clearMessage);
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOffer(
|
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> {
|
||||||
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);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||||
DataAccessUtilityBase.createDocument(collection, newDocument)
|
return DataAccessUtilityBase.createDocument(collection, newDocument)
|
||||||
.then(
|
.then(
|
||||||
(savedDocument: any) => {
|
(savedDocument: any) => {
|
||||||
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||||
deferred.resolve(savedDocument);
|
return savedDocument;
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument");
|
||||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
throw error;
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(clearMessage);
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||||
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
return DataAccessUtilityBase.deleteDocument(collection, documentId)
|
||||||
.then(
|
.then(
|
||||||
(response: any) => {
|
(response: any) => {
|
||||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
deferred.resolve(response);
|
return response;
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument");
|
||||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(clearMessage);
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteConflict(
|
export function deleteConflict(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
conflictId: ConflictId,
|
conflictId: ConflictId,
|
||||||
options?: any
|
options?: any
|
||||||
): Q.Promise<any> {
|
): Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||||
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
return DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
||||||
.then(
|
.then(
|
||||||
(response: any) => {
|
(response: any) => {
|
||||||
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||||
deferred.resolve(response);
|
return response;
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict");
|
||||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(clearMessage);
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedResources(options: any = {}): Q.Promise<void> {
|
export function refreshCachedResources(options: any = {}): Promise<void> {
|
||||||
return DataAccessUtilityBase.refreshCachedResources(options);
|
return DataAccessUtilityBase.refreshCachedResources(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
export function refreshCachedOffers(): Promise<void> {
|
||||||
return DataAccessUtilityBase.refreshCachedOffers();
|
return DataAccessUtilityBase.refreshCachedOffers();
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/Common/ErrorHandlingUtils.ts
Normal file
11
src/Common/ErrorHandlingUtils.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { CosmosError, sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||||
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import { logError } from "./Logger";
|
||||||
|
import { replaceKnownError } from "./ErrorParserUtility";
|
||||||
|
|
||||||
|
export const handleError = (error: CosmosError, consoleErrorPrefix: string, area: string): void => {
|
||||||
|
const sanitizedErrorMsg = replaceKnownError(error.message);
|
||||||
|
logConsoleError(`${consoleErrorPrefix}:\n ${sanitizedErrorMsg}`);
|
||||||
|
logError(sanitizedErrorMsg, area, error.code);
|
||||||
|
sendNotificationForError(error);
|
||||||
|
};
|
||||||
@@ -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[];
|
||||||
|
};
|
||||||
@@ -53,7 +53,7 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
const stringifiedError: string = error.message;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
`Failed to set up account for saving queries: ${stringifiedError}`
|
||||||
@@ -136,7 +136,7 @@ export class QueriesClient {
|
|||||||
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
|
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
|
||||||
.then(
|
.then(
|
||||||
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
|
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
|
||||||
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
const fetchQueries = (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
||||||
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
|
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
|
||||||
return QueryUtils.queryAllPages(fetchQueries).then(
|
return QueryUtils.queryAllPages(fetchQueries).then(
|
||||||
(results: ViewModels.QueryResults) => {
|
(results: ViewModels.QueryResults) => {
|
||||||
@@ -163,7 +163,7 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queries);
|
return Promise.resolve(queries);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
const stringifiedError: string = error.message;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
`Failed to fetch saved queries: ${stringifiedError}`
|
||||||
@@ -175,7 +175,7 @@ export class QueriesClient {
|
|||||||
},
|
},
|
||||||
(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);
|
const stringifiedError: string = error.message;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
`Failed to fetch saved queries: ${stringifiedError}`
|
||||||
@@ -232,7 +232,7 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
const stringifiedError: string = error.message;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
||||||
|
|||||||
@@ -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,20 @@ 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 { 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 +52,16 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
} else {
|
} else {
|
||||||
collection = await createCollectionWithSDK(params);
|
collection = await createCollectionWithSDK(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||||
|
await refreshCachedResources();
|
||||||
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");
|
||||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
|
||||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
clearMessage();
|
|
||||||
return collection;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
|
|||||||
@@ -24,36 +24,31 @@ 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 { 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);
|
|
||||||
}
|
await refreshCachedResources();
|
||||||
|
await refreshCachedOffers();
|
||||||
|
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||||
|
return database;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase");
|
||||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
await refreshCachedOffers();
|
|
||||||
clearMessage();
|
|
||||||
return database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure");
|
||||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdStoredProcedure;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,76 @@
|
|||||||
|
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 { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
import { logError } from "../Logger";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { sendNotificationForError } from "./sendNotificationForError";
|
||||||
|
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)}`);
|
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`);
|
||||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
logError(error.message, "CreateTrigger", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
|
throw 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);
|
`Error while creating user defined function ${userDefinedFunction.id}`,
|
||||||
|
"CreateUserupdateUserDefinedFunction"
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdUserDefinedFunction;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ 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";
|
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||||
@@ -23,15 +22,14 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
|||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||||
|
await refreshCachedResources();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
|
||||||
logError(JSON.stringify(error), "DeleteCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ 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 { 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 +24,14 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||||
|
await refreshCachedResources();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
|
||||||
logError(JSON.stringify(error), "DeleteDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
|
||||||
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 {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.storedProcedure(storedProcedureId)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedureId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.storedProcedure(storedProcedureId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure");
|
||||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(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 {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.trigger(triggerId)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
triggerId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.trigger(triggerId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger");
|
||||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(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 {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.userDefinedFunction(id)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.userDefinedFunction(id)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction");
|
||||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, `Error while querying container ${collectionId}`, "ReadCollection");
|
||||||
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, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer");
|
||||||
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, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
|
||||||
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()
|
|
||||||
.database(databaseId)
|
|
||||||
.containers.readAll()
|
|
||||||
.fetchAll();
|
|
||||||
collections = sdkResponse.resources as DataModels.Collection[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sdkResponse = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.containers.readAll()
|
||||||
|
.fetchAll();
|
||||||
|
return sdkResponse.resources as DataModels.Collection[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while querying containers for database ${databaseId}`, "ReadCollections");
|
||||||
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,26 +19,14 @@ 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)
|
||||||
) {
|
: getDatabaseOfferIdWithSDK(params.databaseResourceId));
|
||||||
try {
|
if (!offerId) {
|
||||||
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
clearMessage();
|
||||||
} catch (error) {
|
return undefined;
|
||||||
clearMessage();
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw new error();
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
|
|
||||||
if (!offerId) {
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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, `Error while querying offer for database ${params.databaseId}`, "ReadDatabaseOffer");
|
||||||
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -75,24 +60,32 @@ 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;
|
||||||
switch (defaultExperience) {
|
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
|
||||||
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
|
||||||
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
case DefaultAccountExperienceType.Graph:
|
|
||||||
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rpResponse?.name;
|
try {
|
||||||
|
switch (defaultExperience) {
|
||||||
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
|
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.MongoDB:
|
||||||
|
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
|
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
|
break;
|
||||||
|
case DefaultAccountExperienceType.Graph:
|
||||||
|
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, `Error while querying databases`, "ReadDatabases");
|
||||||
logError(JSON.stringify(error), "ReadDatabases", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
58
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
58
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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 * as Constants from "../Constants";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
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, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMongoDBCollectionIndexTransformationProgress(
|
||||||
|
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, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return indexTransformationPercentage;
|
||||||
|
}
|
||||||
@@ -3,20 +3,22 @@ import { ClientDefaults } from "../Constants";
|
|||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||||
import { Platform, configContext } from "../../ConfigContext";
|
import { Platform, configContext } from "../../ConfigContext";
|
||||||
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 { sendCachedDataMessage } from "../MessageHandler";
|
import { sendCachedDataMessage } from "../MessageHandler";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
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 {
|
try {
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
const offers = sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
||||||
userContext.databaseAccount.id,
|
userContext.databaseAccount.id,
|
||||||
ClientDefaults.portalCacheTimeoutMs
|
ClientDefaults.portalCacheTimeoutMs
|
||||||
]);
|
]);
|
||||||
|
clearMessage();
|
||||||
|
|
||||||
|
return offers;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If error getting cached Offers, continue on and read via SDK
|
// If error getting cached Offers, continue on and read via SDK
|
||||||
@@ -33,9 +35,7 @@ export const readOffers = async (): Promise<Offer[]> => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
handleError(error, `Error while querying offers`, "ReadOffers");
|
||||||
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, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures");
|
||||||
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, `Failed to query triggers for container ${collectionId}`, "ReadTriggers");
|
||||||
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);
|
`Failed to query user defined functions for container ${collectionId}`,
|
||||||
|
"ReadUserDefinedFunctions"
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return udfs;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as Constants from "../Constants";
|
|||||||
import { sendMessage } from "../MessageHandler";
|
import { sendMessage } from "../MessageHandler";
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||||
|
|
||||||
interface CosmosError {
|
export interface CosmosError {
|
||||||
code: number;
|
code: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,9 @@ 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 { 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 +43,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 +53,19 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||||
|
await refreshCachedResources();
|
||||||
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
return collection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCollectionWithARM(
|
async function updateCollectionWithARM(
|
||||||
@@ -77,15 +81,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 +117,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(
|
||||||
|
|||||||
422
src/Common/dataAccess/updateOffer.ts
Normal file
422
src/Common/dataAccess/updateOffer.ts
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
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 { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
await refreshCachedOffers();
|
||||||
|
await refreshCachedResources();
|
||||||
|
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||||
|
return updatedOffer;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection");
|
||||||
|
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,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, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure");
|
||||||
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, `Error while updating trigger ${trigger.id}`, "UpdateTrigger");
|
||||||
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);
|
`Error while updating user defined function ${userDefinedFunction.id}`,
|
||||||
|
"UpdateUserupdateUserDefinedFunction"
|
||||||
|
);
|
||||||
|
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 {
|
||||||
|
|||||||
@@ -303,6 +303,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;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
TriggerDefinition,
|
TriggerDefinition,
|
||||||
UserDefinedFunctionDefinition
|
UserDefinedFunctionDefinition
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import Q from "q";
|
|
||||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
@@ -85,7 +84,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;
|
||||||
@@ -107,7 +106,7 @@ export interface CollectionBase extends TreeNode {
|
|||||||
|
|
||||||
onDocumentDBDocumentsClick(): void;
|
onDocumentDBDocumentsClick(): void;
|
||||||
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
|
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
|
||||||
expandCollection(): Q.Promise<any>;
|
expandCollection(): Promise<any>;
|
||||||
collapseCollection(): void;
|
collapseCollection(): void;
|
||||||
getDatabase(): Database;
|
getDatabase(): Database;
|
||||||
}
|
}
|
||||||
@@ -172,7 +171,7 @@ export interface Collection extends CollectionBase {
|
|||||||
|
|
||||||
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
uploadFiles(fileList: FileList): Q.Promise<UploadDetails>;
|
uploadFiles(fileList: FileList): Promise<UploadDetails>;
|
||||||
|
|
||||||
getLabel(): string;
|
getLabel(): string;
|
||||||
}
|
}
|
||||||
@@ -266,7 +265,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 +288,10 @@ export interface DocumentsTabOptions extends TabOptions {
|
|||||||
resourceTokenPartitionKey?: string;
|
resourceTokenPartitionKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SettingsTabV2Options extends TabOptions {
|
||||||
|
getPendingNotification: 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 +307,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 +389,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;
|
|
||||||
}
|
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class EditorViewModel extends JsonEditorViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
|
protected async getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
|
||||||
return ErrorMarkProvider.getErrorMark(input);
|
return ErrorMarkProvider.getErrorMark(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import Q from "q";
|
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||||
@@ -107,8 +106,8 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
|
|||||||
protected registerCompletionItemProvider() {}
|
protected registerCompletionItemProvider() {}
|
||||||
|
|
||||||
// Interface. Will be implemented in children editor view model such as EditorViewModel.
|
// Interface. Will be implemented in children editor view model such as EditorViewModel.
|
||||||
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
|
protected getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
|
||||||
return Q.Promise(() => {});
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getEditorLanguage(): string {
|
protected getEditorLanguage(): string {
|
||||||
|
|||||||
@@ -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,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
IPivotItemProps,
|
IPivotItemProps,
|
||||||
@@ -11,7 +12,8 @@ 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 * as Logger from "../../../Common/Logger";
|
||||||
@@ -151,7 +153,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 +199,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 +261,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 +278,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 +302,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:
|
||||||
|
|||||||
@@ -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,24 +43,39 @@ 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 }}>
|
||||||
<Text variant="xxLarge" nowrap>
|
<Stack.Item>
|
||||||
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
<Text variant="xxLarge" nowrap>
|
||||||
</Text>
|
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
||||||
<Text>
|
</Text>
|
||||||
{this.props.isFavorite !== undefined && (
|
</Stack.Item>
|
||||||
<>
|
|
||||||
<IconButton
|
<Stack.Item>
|
||||||
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
<Text>
|
||||||
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
{this.props.isFavorite !== undefined && (
|
||||||
/>
|
<>
|
||||||
{this.props.data.favorites} likes
|
<IconButton
|
||||||
</>
|
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
||||||
)}
|
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
||||||
</Text>
|
/>
|
||||||
|
{this.props.data.favorites} likes
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
{this.props.downloadButtonText && (
|
{this.props.downloadButtonText && (
|
||||||
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
<Stack.Item>
|
||||||
|
<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,13 @@ 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";
|
||||||
|
|
||||||
export interface NotebookViewerComponentProps {
|
export interface NotebookViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -43,10 +43,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;
|
||||||
|
|
||||||
@@ -140,6 +138,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 +178,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 +228,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,26 +17,38 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<Text
|
<StackItem>
|
||||||
nowrap={true}
|
<Text
|
||||||
variant="xxLarge"
|
nowrap={true}
|
||||||
>
|
variant="xxLarge"
|
||||||
name
|
>
|
||||||
</Text>
|
name
|
||||||
<Text>
|
</Text>
|
||||||
<CustomizedIconButton
|
</StackItem>
|
||||||
iconProps={
|
<StackItem>
|
||||||
Object {
|
<Text>
|
||||||
"iconName": "HeartFill",
|
<CustomizedIconButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "HeartFill",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
|
0
|
||||||
|
likes
|
||||||
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<CustomizedPrimaryButton
|
||||||
|
text="Download"
|
||||||
/>
|
/>
|
||||||
0
|
</StackItem>
|
||||||
likes
|
<StackItem
|
||||||
</Text>
|
grow={true}
|
||||||
<CustomizedPrimaryButton
|
|
||||||
text="Download"
|
|
||||||
/>
|
/>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
@@ -117,26 +129,38 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<Text
|
<StackItem>
|
||||||
nowrap={true}
|
<Text
|
||||||
variant="xxLarge"
|
nowrap={true}
|
||||||
>
|
variant="xxLarge"
|
||||||
name
|
>
|
||||||
</Text>
|
name
|
||||||
<Text>
|
</Text>
|
||||||
<CustomizedIconButton
|
</StackItem>
|
||||||
iconProps={
|
<StackItem>
|
||||||
Object {
|
<Text>
|
||||||
"iconName": "Heart",
|
<CustomizedIconButton
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Heart",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
|
0
|
||||||
|
likes
|
||||||
|
</Text>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<CustomizedPrimaryButton
|
||||||
|
text="Download"
|
||||||
/>
|
/>
|
||||||
0
|
</StackItem>
|
||||||
likes
|
<StackItem
|
||||||
</Text>
|
grow={true}
|
||||||
<CustomizedPrimaryButton
|
|
||||||
text="Download"
|
|
||||||
/>
|
/>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
|
|||||||
@@ -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/readMongoDBCollection", () => ({
|
||||||
|
getMongoDBCollectionIndexTransformationProgress: 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,17 @@ 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";
|
||||||
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -33,10 +43,10 @@ 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: Promise.resolve(undefined)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,10 +113,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 +154,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 +199,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 +228,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,25 @@ 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 {
|
||||||
|
getMongoDBCollectionIndexTransformationProgress,
|
||||||
|
readMongoDBCollectionThroughRP
|
||||||
|
} from "../../../Common/dataAccess/readMongoDBCollection";
|
||||||
|
|
||||||
interface SettingsV2TabInfo {
|
interface SettingsV2TabInfo {
|
||||||
tab: SettingsV2TabTypes;
|
tab: SettingsV2TabTypes;
|
||||||
@@ -85,9 +94,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 +117,17 @@ 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 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 +141,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 +174,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 +212,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
this.loadMongoIndexes();
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
@@ -205,6 +226,35 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadMongoIndexes = async (): Promise<void> => {
|
||||||
|
if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.databaseAccount()
|
||||||
|
) {
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
|
||||||
|
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 getMongoDBCollectionIndexTransformationProgress(
|
||||||
|
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 +264,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 +274,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 +322,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,
|
||||||
@@ -342,6 +394,27 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||||
|
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]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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,35 +471,34 @@ 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({
|
||||||
throughput: originalThroughputValue,
|
isScaleSaveable: false,
|
||||||
throughputBaseline: originalThroughputValue,
|
isScaleDiscardable: false,
|
||||||
initialNotification: {
|
throughput: originalThroughputValue,
|
||||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
throughputBaseline: originalThroughputValue,
|
||||||
} as DataModels.Notification
|
initialNotification: {
|
||||||
});
|
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
} as DataModels.Notification
|
||||||
} 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 = {
|
||||||
|
databaseId: this.collection.databaseId,
|
||||||
|
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) {
|
||||||
|
updateOfferParams.migrateToAutoPilot = true;
|
||||||
|
} else {
|
||||||
|
updateOfferParams.migrateToManual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
this.collection.offer(updatedOffer);
|
this.collection.offer(updatedOffer);
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||||
if (this.state.isAutoPilotSelected) {
|
if (this.state.isAutoPilotSelected) {
|
||||||
@@ -446,9 +518,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
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()
|
||||||
@@ -460,12 +534,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.props.settingsTab.isExecutionError(true);
|
this.props.settingsTab.isExecutionError(true);
|
||||||
console.error(reason);
|
console.error(reason);
|
||||||
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: reason
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
@@ -474,12 +551,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 +577,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 +623,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 +691,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) {
|
||||||
@@ -830,12 +914,25 @@ 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,
|
||||||
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 +949,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 +966,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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -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 } };
|
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";
|
||||||
@@ -313,6 +386,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>Refreshing index transformation progress</Text>
|
||||||
|
<Spinner size={SpinnerSize.medium} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const renderMongoIndexTransformationRefreshMessage = (
|
||||||
|
progress: number,
|
||||||
|
performRefresh: () => void
|
||||||
|
): JSX.Element => {
|
||||||
|
if (progress === 0) {
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{"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>
|
||||||
|
{`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;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export interface IndexingPolicyComponentProps {
|
|||||||
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;
|
||||||
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
||||||
@@ -89,8 +88,6 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
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();
|
||||||
|
|||||||
@@ -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,122 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
|
||||||
|
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
|
||||||
|
|
||||||
|
describe("MongoIndexingPolicyComponent", () => {
|
||||||
|
const baseProps: MongoIndexingPolicyComponentProps = {
|
||||||
|
mongoIndexes: [],
|
||||||
|
onIndexDrop: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexesToDrop: [],
|
||||||
|
onRevertIndexDrop: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexesToAdd: [],
|
||||||
|
onRevertIndexAdd: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onIndexAddOrChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
refreshIndexTransformationProgress: () =>
|
||||||
|
new Promise(() => {
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
onMongoIndexingPolicySaveableChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onMongoIndexingPolicyDiscardableChange: () => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isIndexingTransforming", () => {
|
||||||
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
|
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||||
|
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
|
||||||
|
wrapper.setProps({ indexTransformationProgress: 50 });
|
||||||
|
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(true);
|
||||||
|
wrapper.setProps({ indexTransformationProgress: 100 });
|
||||||
|
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
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()).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(
|
||||||
|
mongoWarningNotificationMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
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,
|
||||||
|
mongoIndexTransformationRefreshingMessage,
|
||||||
|
renderMongoIndexTransformationRefreshMessage
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import {
|
||||||
|
MongoIndexTypes,
|
||||||
|
AddMongoIndexProps,
|
||||||
|
MongoIndexIdField,
|
||||||
|
MongoNotificationType,
|
||||||
|
getMongoIndexType,
|
||||||
|
getMongoIndexTypeText
|
||||||
|
} from "../../SettingsUtils";
|
||||||
|
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||||
|
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
|
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||||
|
import { AuthType } from "../../../../../AuthType";
|
||||||
|
|
||||||
|
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 MongoIndexingPolicyComponentState {
|
||||||
|
isRefreshingIndexTransformationProgress: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoIndexDisplayProps {
|
||||||
|
definition: JSX.Element;
|
||||||
|
type: JSX.Element;
|
||||||
|
actionButton: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MongoIndexingPolicyComponent extends React.Component<
|
||||||
|
MongoIndexingPolicyComponentProps,
|
||||||
|
MongoIndexingPolicyComponentState
|
||||||
|
> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(props: MongoIndexingPolicyComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isRefreshingIndexTransformationProgress: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (): string => {
|
||||||
|
return this.props.indexesToAdd.find(
|
||||||
|
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
|
||||||
|
)?.notification.message;
|
||||||
|
};
|
||||||
|
|
||||||
|
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={this.isIndexingTransforming()}
|
||||||
|
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={this.isIndexingTransforming()}
|
||||||
|
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 refreshIndexTransformationProgress = async () => {
|
||||||
|
this.setState({ isRefreshingIndexTransformationProgress: true });
|
||||||
|
try {
|
||||||
|
await this.props.refreshIndexTransformationProgress();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
|
||||||
|
} finally {
|
||||||
|
this.setState({ isRefreshingIndexTransformationProgress: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public isIndexingTransforming = (): boolean =>
|
||||||
|
// index transformation progress can be 0
|
||||||
|
this.props.indexTransformationProgress !== undefined && this.props.indexTransformationProgress !== 100;
|
||||||
|
|
||||||
|
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
|
||||||
|
|
||||||
|
private renderIndexTransformationWarning = (): JSX.Element => {
|
||||||
|
if (this.state.isRefreshingIndexTransformationProgress) {
|
||||||
|
return mongoIndexTransformationRefreshingMessage;
|
||||||
|
} else if (this.isIndexingTransforming()) {
|
||||||
|
return renderMongoIndexTransformationRefreshMessage(
|
||||||
|
this.props.indexTransformationProgress,
|
||||||
|
this.onClickRefreshIndexingTransformationLink
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderWarningMessage = (): JSX.Element => {
|
||||||
|
let warningMessage: string;
|
||||||
|
if (this.getMongoWarningNotificationMessage()) {
|
||||||
|
warningMessage = this.getMongoWarningNotificationMessage();
|
||||||
|
} else if (this.isMongoIndexingPolicySaveable()) {
|
||||||
|
warningMessage =
|
||||||
|
"You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.renderIndexTransformationWarning() && (
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{warningMessage && (
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>
|
||||||
|
<Text>{warningMessage}</Text>
|
||||||
|
</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,137 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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;
|
||||||
@@ -88,10 +83,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 +117,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 +130,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,6 +18,7 @@ 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;
|
||||||
@@ -43,7 +43,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 +77,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +98,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 => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { shallow } from "enzyme";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
||||||
import { container, collection } from "../TestUtils";
|
import { container, collection } from "../TestUtils";
|
||||||
import { TtlType, GeospatialConfigType, ChangeFeedPolicyState } from "../SettingsUtils";
|
import { TtlType, GeospatialConfigType, ChangeFeedPolicyState, TtlOnNoDefault, TtlOn, TtlOff } from "../SettingsUtils";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
|
|
||||||
@@ -105,10 +105,7 @@ describe("SubSettingsComponent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("partitionKey not visible", () => {
|
it("partitionKey not visible", () => {
|
||||||
const newContainer = new Explorer({
|
const newContainer = new Explorer();
|
||||||
notificationsClient: undefined,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
|
|
||||||
newContainer.isPreferredApiCassandra = ko.computed(() => true);
|
newContainer.isPreferredApiCassandra = ko.computed(() => true);
|
||||||
const props = { ...baseProps, container: newContainer };
|
const props = { ...baseProps, container: newContainer };
|
||||||
@@ -133,4 +130,11 @@ describe("SubSettingsComponent", () => {
|
|||||||
expect(isComponentDirtyResult.isSaveable).toEqual(true);
|
expect(isComponentDirtyResult.isSaveable).toEqual(true);
|
||||||
expect(isComponentDirtyResult.isDiscardable).toEqual(true);
|
expect(isComponentDirtyResult.isDiscardable).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("getTtlValue", async () => {
|
||||||
|
const subSettingsComponentInstance = new SubSettingsComponent(baseProps);
|
||||||
|
expect(subSettingsComponentInstance.getTtlValue(TtlType.OnNoDefault)).toEqual(TtlOnNoDefault);
|
||||||
|
expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
|
||||||
|
expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import {
|
|||||||
TtlType,
|
TtlType,
|
||||||
ChangeFeedPolicyState,
|
ChangeFeedPolicyState,
|
||||||
isDirty,
|
isDirty,
|
||||||
IsComponentDirtyResult
|
IsComponentDirtyResult,
|
||||||
|
TtlOn,
|
||||||
|
TtlOff,
|
||||||
|
TtlOnNoDefault,
|
||||||
|
getSanitizedInputValue
|
||||||
} from "../SettingsUtils";
|
} from "../SettingsUtils";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
@@ -37,40 +41,28 @@ export interface SubSettingsComponentProps {
|
|||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
timeToLiveBaseline: TtlType;
|
timeToLiveBaseline: TtlType;
|
||||||
|
|
||||||
onTtlChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void;
|
onTtlChange: (newTtl: TtlType) => void;
|
||||||
timeToLiveSeconds: number;
|
timeToLiveSeconds: number;
|
||||||
timeToLiveSecondsBaseline: number;
|
timeToLiveSecondsBaseline: number;
|
||||||
onTimeToLiveSecondsChange: (
|
onTimeToLiveSecondsChange: (newTimeToLiveSeconds: number) => void;
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
geospatialConfigType: GeospatialConfigType;
|
geospatialConfigType: GeospatialConfigType;
|
||||||
geospatialConfigTypeBaseline: GeospatialConfigType;
|
geospatialConfigTypeBaseline: GeospatialConfigType;
|
||||||
onGeoSpatialConfigTypeChange: (
|
onGeoSpatialConfigTypeChange: (newGeoSpatialConfigType: GeospatialConfigType) => void;
|
||||||
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
|
||||||
option?: IChoiceGroupOption
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
isAnalyticalStorageEnabled: boolean;
|
isAnalyticalStorageEnabled: boolean;
|
||||||
analyticalStorageTtlSelection: TtlType;
|
analyticalStorageTtlSelection: TtlType;
|
||||||
analyticalStorageTtlSelectionBaseline: TtlType;
|
analyticalStorageTtlSelectionBaseline: TtlType;
|
||||||
onAnalyticalStorageTtlSelectionChange: (
|
onAnalyticalStorageTtlSelectionChange: (newAnalyticalStorageTtlSelection: TtlType) => void;
|
||||||
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
|
||||||
option?: IChoiceGroupOption
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
analyticalStorageTtlSeconds: number;
|
analyticalStorageTtlSeconds: number;
|
||||||
analyticalStorageTtlSecondsBaseline: number;
|
analyticalStorageTtlSecondsBaseline: number;
|
||||||
onAnalyticalStorageTtlSecondsChange: (
|
onAnalyticalStorageTtlSecondsChange: (newAnalyticalStorageTtlSeconds: number) => void;
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
newValue?: string
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
changeFeedPolicyVisible: boolean;
|
changeFeedPolicyVisible: boolean;
|
||||||
changeFeedPolicy: ChangeFeedPolicyState;
|
changeFeedPolicy: ChangeFeedPolicyState;
|
||||||
changeFeedPolicyBaseline: ChangeFeedPolicyState;
|
changeFeedPolicyBaseline: ChangeFeedPolicyState;
|
||||||
onChangeFeedPolicyChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void;
|
onChangeFeedPolicyChange: (newChangeFeedPolicyState: ChangeFeedPolicyState) => void;
|
||||||
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
|
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
|
||||||
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
|
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -139,6 +131,54 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
{ key: TtlType.On, text: "On" }
|
{ key: TtlType.On, text: "On" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public getTtlValue = (value: string): TtlType => {
|
||||||
|
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 =>
|
||||||
|
this.props.onTtlChange(this.getTtlValue(option.key));
|
||||||
|
|
||||||
|
private onTimeToLiveSecondsChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => {
|
||||||
|
const newTimeToLiveSeconds = getSanitizedInputValue(newValue, Int32.Max);
|
||||||
|
this.props.onTimeToLiveSecondsChange(newTimeToLiveSeconds);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onGeoSpatialConfigTypeChange = (
|
||||||
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void =>
|
||||||
|
this.props.onGeoSpatialConfigTypeChange(GeospatialConfigType[option.key as keyof typeof GeospatialConfigType]);
|
||||||
|
|
||||||
|
private onAnalyticalStorageTtlSelectionChange = (
|
||||||
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void => this.props.onAnalyticalStorageTtlSelectionChange(this.getTtlValue(option.key));
|
||||||
|
|
||||||
|
private onAnalyticalStorageTtlSecondsChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => {
|
||||||
|
const newAnalyticalStorageTtlSeconds = getSanitizedInputValue(newValue, Int32.Max);
|
||||||
|
this.props.onAnalyticalStorageTtlSecondsChange(newAnalyticalStorageTtlSeconds);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChangeFeedPolicyChange = (
|
||||||
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void =>
|
||||||
|
this.props.onChangeFeedPolicyChange(ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState]);
|
||||||
|
|
||||||
private getTtlComponent = (): JSX.Element => (
|
private getTtlComponent = (): JSX.Element => (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
<ChoiceGroup
|
<ChoiceGroup
|
||||||
@@ -146,7 +186,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
label="Time to Live"
|
label="Time to Live"
|
||||||
selectedKey={this.props.timeToLive}
|
selectedKey={this.props.timeToLive}
|
||||||
options={this.ttlChoiceGroupOptions}
|
options={this.ttlChoiceGroupOptions}
|
||||||
onChange={this.props.onTtlChange}
|
onChange={this.onTtlChange}
|
||||||
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
|
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
|
||||||
/>
|
/>
|
||||||
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
|
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
|
||||||
@@ -163,7 +203,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
min={1}
|
min={1}
|
||||||
max={Int32.Max}
|
max={Int32.Max}
|
||||||
value={this.props.timeToLiveSeconds?.toString()}
|
value={this.props.timeToLiveSeconds?.toString()}
|
||||||
onChange={this.props.onTimeToLiveSecondsChange}
|
onChange={this.onTimeToLiveSecondsChange}
|
||||||
suffix="second(s)"
|
suffix="second(s)"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -183,7 +223,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
label="Analytical Storage Time to Live"
|
label="Analytical Storage Time to Live"
|
||||||
selectedKey={this.props.analyticalStorageTtlSelection}
|
selectedKey={this.props.analyticalStorageTtlSelection}
|
||||||
options={this.analyticalTtlChoiceGroupOptions}
|
options={this.analyticalTtlChoiceGroupOptions}
|
||||||
onChange={this.props.onAnalyticalStorageTtlSelectionChange}
|
onChange={this.onAnalyticalStorageTtlSelectionChange}
|
||||||
styles={getChoiceGroupStyles(
|
styles={getChoiceGroupStyles(
|
||||||
this.props.analyticalStorageTtlSelection,
|
this.props.analyticalStorageTtlSelection,
|
||||||
this.props.analyticalStorageTtlSelectionBaseline
|
this.props.analyticalStorageTtlSelectionBaseline
|
||||||
@@ -202,7 +242,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
max={Int32.Max}
|
max={Int32.Max}
|
||||||
value={this.props.analyticalStorageTtlSeconds?.toString()}
|
value={this.props.analyticalStorageTtlSeconds?.toString()}
|
||||||
suffix="second(s)"
|
suffix="second(s)"
|
||||||
onChange={this.props.onAnalyticalStorageTtlSecondsChange}
|
onChange={this.onAnalyticalStorageTtlSecondsChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -219,7 +259,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
label="Geospatial Configuration"
|
label="Geospatial Configuration"
|
||||||
selectedKey={this.props.geospatialConfigType}
|
selectedKey={this.props.geospatialConfigType}
|
||||||
options={this.geoSpatialConfigTypeChoiceGroupOptions}
|
options={this.geoSpatialConfigTypeChoiceGroupOptions}
|
||||||
onChange={this.props.onGeoSpatialConfigTypeChange}
|
onChange={this.onGeoSpatialConfigTypeChange}
|
||||||
styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)}
|
styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -241,7 +281,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
id="changeFeedPolicy"
|
id="changeFeedPolicy"
|
||||||
selectedKey={this.props.changeFeedPolicy}
|
selectedKey={this.props.changeFeedPolicy}
|
||||||
options={this.changeFeedChoiceGroupOptions}
|
options={this.changeFeedChoiceGroupOptions}
|
||||||
onChange={this.props.onChangeFeedPolicyChange}
|
onChange={this.onChangeFeedPolicyChange}
|
||||||
styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)}
|
styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)}
|
||||||
aria-labelledby={labelId}
|
aria-labelledby={labelId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
|||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
getToolTipContainer,
|
getToolTipContainer,
|
||||||
spendAckCheckBoxStyle,
|
noLeftPaddingCheckBoxStyle,
|
||||||
titleAndInputStackProps,
|
titleAndInputStackProps,
|
||||||
checkBoxAndInputStackProps,
|
checkBoxAndInputStackProps,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
@@ -26,9 +26,10 @@ import {
|
|||||||
MessageBarType
|
MessageBarType
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||||
import { IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||||
import * as SharedConstants from "../../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../../Shared/Constants";
|
||||||
import * as DataModels from "../../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../../Contracts/DataModels";
|
||||||
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
|
|
||||||
export interface ThroughputInputAutoPilotV3Props {
|
export interface ThroughputInputAutoPilotV3Props {
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
@@ -71,9 +72,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
> {
|
> {
|
||||||
private shouldCheckComponentIsDirty = true;
|
private shouldCheckComponentIsDirty = true;
|
||||||
private static readonly defaultStep = 100;
|
private static readonly defaultStep = 100;
|
||||||
private static readonly zeroThroughput = 0;
|
|
||||||
private step: number;
|
private step: number;
|
||||||
private choiceGroupFixedStyle = getChoiceGroupStyles(undefined, undefined);
|
private throughputInputMaxValue: number;
|
||||||
|
private autoPilotInputMaxValue: number;
|
||||||
private options: IChoiceGroupOption[] = [
|
private options: IChoiceGroupOption[] = [
|
||||||
{ key: "true", text: "Autoscale" },
|
{ key: "true", text: "Autoscale" },
|
||||||
{ key: "false", text: "Manual" }
|
{ key: "false", text: "Manual" }
|
||||||
@@ -140,6 +141,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||||
|
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
|
||||||
|
this.autoPilotInputMaxValue = this.props.isFixed ? this.props.maximum : Int32.Max;
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasProvisioningTypeChanged = (): boolean =>
|
public hasProvisioningTypeChanged = (): boolean =>
|
||||||
@@ -200,8 +203,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
newValue?: string
|
newValue?: string
|
||||||
): void => {
|
): void => {
|
||||||
let newThroughput = parseInt(newValue);
|
const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
|
||||||
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
|
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,9 +211,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
newValue?: string
|
newValue?: string
|
||||||
): void => {
|
): void => {
|
||||||
let newThroughput = parseInt(newValue);
|
const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
|
||||||
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
|
|
||||||
|
|
||||||
if (this.overrideWithAutoPilotSettings()) {
|
if (this.overrideWithAutoPilotSettings()) {
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
} else {
|
} else {
|
||||||
@@ -245,7 +245,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
onChange={this.onChoiceGroupChange}
|
onChange={this.onChoiceGroupChange}
|
||||||
required={this.props.showAsMandatory}
|
required={this.props.showAsMandatory}
|
||||||
ariaLabelledBy={labelId}
|
ariaLabelledBy={labelId}
|
||||||
styles={this.choiceGroupFixedStyle}
|
styles={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -270,8 +270,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
key="auto pilot throughput input"
|
key="auto pilot throughput input"
|
||||||
styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)}
|
styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)}
|
||||||
disabled={this.overrideWithProvisionedThroughputSettings()}
|
disabled={this.overrideWithProvisionedThroughputSettings()}
|
||||||
step={this.step}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
min={AutoPilotUtils.minAutoPilotThroughput}
|
|
||||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||||
onChange={this.onAutoPilotThroughputChange}
|
onChange={this.onAutoPilotThroughputChange}
|
||||||
/>
|
/>
|
||||||
@@ -279,12 +278,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
{this.props.spendAckVisible && (
|
{this.props.spendAckVisible && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
styles={spendAckCheckBoxStyle}
|
styles={noLeftPaddingCheckBoxStyle}
|
||||||
label={this.props.spendAckText}
|
label={this.props.spendAckText}
|
||||||
checked={this.state.spendAckChecked}
|
checked={this.state.spendAckChecked}
|
||||||
onChange={this.onSpendAckChecked}
|
onChange={this.onSpendAckChecked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -298,8 +298,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)}
|
styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)}
|
||||||
disabled={this.overrideWithAutoPilotSettings()}
|
disabled={this.overrideWithAutoPilotSettings()}
|
||||||
step={this.step}
|
step={this.step}
|
||||||
min={this.props.minimum}
|
|
||||||
max={this.props.canExceedMaximumValue ? undefined : this.props.maximum}
|
|
||||||
value={
|
value={
|
||||||
this.overrideWithAutoPilotSettings()
|
this.overrideWithAutoPilotSettings()
|
||||||
? this.props.maxAutoPilotThroughputBaseline?.toString()
|
? this.props.maxAutoPilotThroughputBaseline?.toString()
|
||||||
@@ -319,21 +317,21 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
{this.props.spendAckVisible && (
|
{this.props.spendAckVisible && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
styles={spendAckCheckBoxStyle}
|
styles={noLeftPaddingCheckBoxStyle}
|
||||||
label={this.props.spendAckText}
|
label={this.props.spendAckText}
|
||||||
checked={this.state.spendAckChecked}
|
checked={this.state.spendAckChecked}
|
||||||
onChange={this.onSpendAckChecked}
|
onChange={this.onSpendAckChecked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.props.isFixed && <p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>}
|
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...checkBoxAndInputStackProps}>
|
<Stack {...checkBoxAndInputStackProps}>
|
||||||
{!this.props.isFixed && this.renderThroughputModeChoices()}
|
{this.renderThroughputModeChoices()}
|
||||||
|
|
||||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"selectors": Object {
|
"selectors": Object {
|
||||||
".ms-ChoiceField-field.is-checked::after": Object {
|
".ms-ChoiceField-field.is-checked::after": Object {
|
||||||
"borderColor": "",
|
"borderColor": undefined,
|
||||||
},
|
},
|
||||||
".ms-ChoiceField-field.is-checked::before": Object {
|
".ms-ChoiceField-field.is-checked::before": Object {
|
||||||
"borderColor": "",
|
"borderColor": undefined,
|
||||||
},
|
},
|
||||||
".ms-ChoiceField-wrapper label": Object {
|
".ms-ChoiceField-wrapper label": Object {
|
||||||
"fontFamily": undefined,
|
"fontFamily": undefined,
|
||||||
@@ -113,10 +113,9 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
id="autopilotInput"
|
id="autopilotInput"
|
||||||
key="auto pilot throughput input"
|
key="auto pilot throughput input"
|
||||||
label="Max RU/s"
|
label="Max RU/s"
|
||||||
min={4000}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={100}
|
step={1000}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"fieldGroup": Object {
|
"fieldGroup": Object {
|
||||||
@@ -219,7 +218,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
key="provisioned throughput input"
|
key="provisioned throughput input"
|
||||||
min={10000}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={100}
|
step={100}
|
||||||
@@ -375,7 +373,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
key="provisioned throughput input"
|
key="provisioned throughput input"
|
||||||
min={10000}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={100}
|
step={100}
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
canExceedMaximumValue={false}
|
canExceedMaximumValue={true}
|
||||||
getThroughputWarningMessage={[Function]}
|
getThroughputWarningMessage={[Function]}
|
||||||
isAutoPilotSelected={false}
|
isAutoPilotSelected={false}
|
||||||
isEmulator={false}
|
isEmulator={false}
|
||||||
isEnabled={true}
|
isEnabled={true}
|
||||||
isFixed={false}
|
isFixed={false}
|
||||||
label="Throughput (6,000 - 40,000 RU/s)"
|
label="Throughput (6,000 - unlimited RU/s)"
|
||||||
maxAutoPilotThroughput={4000}
|
maxAutoPilotThroughput={4000}
|
||||||
maxAutoPilotThroughputBaseline={4000}
|
maxAutoPilotThroughputBaseline={4000}
|
||||||
maximum={40000}
|
maximum={40000}
|
||||||
|
|||||||
@@ -2,11 +2,20 @@ import { collection, container } from "./TestUtils";
|
|||||||
import {
|
import {
|
||||||
getMaxRUs,
|
getMaxRUs,
|
||||||
getMinRUs,
|
getMinRUs,
|
||||||
|
getMongoIndexType,
|
||||||
|
getMongoNotification,
|
||||||
|
getSanitizedInputValue,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
isDirty,
|
isDirty,
|
||||||
isDirtyTypes,
|
isDirtyTypes,
|
||||||
|
MongoIndexTypes,
|
||||||
|
MongoNotificationType,
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure
|
parseConflictResolutionProcedure,
|
||||||
|
MongoWildcardPlaceHolder,
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
SingleFieldText,
|
||||||
|
WildcardText
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
@@ -86,4 +95,47 @@ describe("SettingsUtils", () => {
|
|||||||
expect(isDirty(baseline, current)).toEqual(true);
|
expect(isDirty(baseline, current)).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("getSanitizedInputValue", () => {
|
||||||
|
const max = 100;
|
||||||
|
expect(getSanitizedInputValue("", max)).toEqual(0);
|
||||||
|
expect(getSanitizedInputValue("999", max)).toEqual(100);
|
||||||
|
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getMongoIndexType", () => {
|
||||||
|
expect(getMongoIndexType(["Single"])).toEqual(MongoIndexTypes.Single);
|
||||||
|
expect(getMongoIndexType(["Wildcard.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||||
|
expect(getMongoIndexType(["Key1", "Key2"])).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getMongoIndexTypeText", () => {
|
||||||
|
expect(getMongoIndexTypeText(MongoIndexTypes.Single)).toEqual(SingleFieldText);
|
||||||
|
expect(getMongoIndexTypeText(MongoIndexTypes.Wildcard)).toEqual(WildcardText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getMongoNotification", () => {
|
||||||
|
const singleIndexDescription = "sampleKey";
|
||||||
|
const wildcardIndexDescription = "sampleKey.$**";
|
||||||
|
|
||||||
|
let notification = getMongoNotification(singleIndexDescription, undefined);
|
||||||
|
expect(notification.message).toEqual("Please select a type for each index.");
|
||||||
|
expect(notification.type).toEqual(MongoNotificationType.Warning);
|
||||||
|
|
||||||
|
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Single);
|
||||||
|
expect(notification).toEqual(undefined);
|
||||||
|
|
||||||
|
notification = getMongoNotification(wildcardIndexDescription, MongoIndexTypes.Wildcard);
|
||||||
|
expect(notification).toEqual(undefined);
|
||||||
|
|
||||||
|
notification = getMongoNotification("", MongoIndexTypes.Single);
|
||||||
|
expect(notification.message).toEqual("Please enter a field name.");
|
||||||
|
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||||
|
|
||||||
|
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Wildcard);
|
||||||
|
expect(notification.message).toEqual(
|
||||||
|
"Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||||
|
);
|
||||||
|
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ import * as SharedConstants from "../../../Shared/Constants";
|
|||||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||||
|
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
|
||||||
|
const zeroValue = 0;
|
||||||
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
|
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
|
||||||
export const TtlOff = "off";
|
export const TtlOff = "off";
|
||||||
export const TtlOn = "on";
|
export const TtlOn = "on";
|
||||||
export const TtlOnNoDefault = "on-nodefault";
|
export const TtlOnNoDefault = "on-nodefault";
|
||||||
|
export const MongoIndexIdField = "_id";
|
||||||
|
export const MongoWildcardPlaceHolder = "properties.$**";
|
||||||
|
export const SingleFieldText = "Single Field";
|
||||||
|
export const WildcardText = "Wildcard";
|
||||||
|
|
||||||
export enum ChangeFeedPolicyState {
|
export enum ChangeFeedPolicyState {
|
||||||
Off = "Off",
|
Off = "Off",
|
||||||
@@ -27,6 +33,17 @@ export enum GeospatialConfigType {
|
|||||||
Geometry = "Geometry"
|
Geometry = "Geometry"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MongoIndexTypes {
|
||||||
|
Single = "Single",
|
||||||
|
Wildcard = "Wildcard"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddMongoIndexProps {
|
||||||
|
mongoIndex: MongoIndex;
|
||||||
|
type: MongoIndexTypes;
|
||||||
|
notification: MongoNotificationMessage;
|
||||||
|
}
|
||||||
|
|
||||||
export enum SettingsV2TabTypes {
|
export enum SettingsV2TabTypes {
|
||||||
ScaleTab,
|
ScaleTab,
|
||||||
ConflictResolutionTab,
|
ConflictResolutionTab,
|
||||||
@@ -39,6 +56,16 @@ export interface IsComponentDirtyResult {
|
|||||||
isDiscardable: boolean;
|
isDiscardable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MongoNotificationType {
|
||||||
|
Warning = "Warning",
|
||||||
|
Error = "Error"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MongoNotificationMessage {
|
||||||
|
type: MongoNotificationType;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
||||||
const database: ViewModels.Database = collection.getDatabase();
|
const database: ViewModels.Database = collection.getDatabase();
|
||||||
return database?.isDatabaseShared() && !collection.offer();
|
return database?.isDatabaseShared() && !collection.offer();
|
||||||
@@ -53,7 +80,7 @@ export const getMaxRUs = (collection: ViewModels.Collection, container: Explorer
|
|||||||
const numPartitionsFromOffer: number =
|
const numPartitionsFromOffer: number =
|
||||||
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
|
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
|
||||||
|
|
||||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo().numPartitions;
|
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo()?.numPartitions;
|
||||||
|
|
||||||
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
|
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
|
||||||
|
|
||||||
@@ -78,7 +105,7 @@ export const getMinRUs = (collection: ViewModels.Collection, container: Explorer
|
|||||||
return collectionThroughputInfo.minimumRUForCollection;
|
return collectionThroughputInfo.minimumRUForCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo().numPartitions;
|
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo()?.numPartitions;
|
||||||
|
|
||||||
if (!numPartitions || numPartitions === 1) {
|
if (!numPartitions || numPartitions === 1) {
|
||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||||
@@ -129,6 +156,15 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
|
|||||||
return procedureFromBackEnd;
|
return procedureFromBackEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
||||||
|
const newValue = parseInt(newValueString);
|
||||||
|
if (isNaN(newValue)) {
|
||||||
|
return zeroValue;
|
||||||
|
}
|
||||||
|
// make sure new value does not exceed the maximum throughput
|
||||||
|
return Math.min(newValue, max);
|
||||||
|
};
|
||||||
|
|
||||||
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
||||||
const currentType = typeof current;
|
const currentType = typeof current;
|
||||||
const baselineType = typeof baseline;
|
const baselineType = typeof baseline;
|
||||||
@@ -169,3 +205,48 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
|||||||
throw new Error(`Unknown tab ${tab}`);
|
throw new Error(`Unknown tab ${tab}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMongoNotification = (description: string, type: MongoIndexTypes): MongoNotificationMessage => {
|
||||||
|
if (description && !type) {
|
||||||
|
return {
|
||||||
|
type: MongoNotificationType.Warning,
|
||||||
|
message: "Please select a type for each index."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type && (!description || description.trim().length === 0)) {
|
||||||
|
return {
|
||||||
|
type: MongoNotificationType.Error,
|
||||||
|
message: "Please enter a field name."
|
||||||
|
};
|
||||||
|
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
|
||||||
|
return {
|
||||||
|
type: MongoNotificationType.Error,
|
||||||
|
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMongoIndexType = (keys: string[]): MongoIndexTypes => {
|
||||||
|
const length = keys?.length;
|
||||||
|
let type: MongoIndexTypes;
|
||||||
|
|
||||||
|
if (length === 1) {
|
||||||
|
if (keys[0].indexOf("$**") !== -1) {
|
||||||
|
type = MongoIndexTypes.Wildcard;
|
||||||
|
} else {
|
||||||
|
type = MongoIndexTypes.Single;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
|
||||||
|
if (index === MongoIndexTypes.Single) {
|
||||||
|
return SingleFieldText;
|
||||||
|
}
|
||||||
|
return WildcardText;
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
|
|
||||||
export const container = new Explorer({
|
export const container = new Explorer();
|
||||||
notificationsClient: undefined,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
|
|
||||||
export const collection = ({
|
export const collection = ({
|
||||||
container: container,
|
container: container,
|
||||||
|
|||||||
@@ -84,9 +84,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -586,9 +583,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -982,7 +976,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEmulator": false,
|
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isGalleryPublishEnabled": [Function],
|
"isGalleryPublishEnabled": [Function],
|
||||||
@@ -990,6 +983,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -1058,7 +1052,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"notificationConsoleData": [Function],
|
"notificationConsoleData": [Function],
|
||||||
"notificationsClient": undefined,
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
@@ -1398,9 +1391,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -1900,9 +1890,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -2296,7 +2283,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEmulator": false,
|
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isGalleryPublishEnabled": [Function],
|
"isGalleryPublishEnabled": [Function],
|
||||||
@@ -2304,6 +2290,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -2372,7 +2359,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"notificationConsoleData": [Function],
|
"notificationConsoleData": [Function],
|
||||||
"notificationsClient": undefined,
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
@@ -2725,9 +2711,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -3227,9 +3210,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -3623,7 +3603,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEmulator": false,
|
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isGalleryPublishEnabled": [Function],
|
"isGalleryPublishEnabled": [Function],
|
||||||
@@ -3631,6 +3610,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -3699,7 +3679,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"notificationConsoleData": [Function],
|
"notificationConsoleData": [Function],
|
||||||
"notificationsClient": undefined,
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
@@ -4039,9 +4018,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -4541,9 +4517,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_refreshSparkEnabledStateForAccount": [Function],
|
"_refreshSparkEnabledStateForAccount": [Function],
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_databaseOffers": HashMap {
|
|
||||||
"container": Object {},
|
|
||||||
},
|
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
"autoPilotTiersList": [Function],
|
||||||
@@ -4937,7 +4910,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEmulator": false,
|
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isGalleryPublishEnabled": [Function],
|
"isGalleryPublishEnabled": [Function],
|
||||||
@@ -4945,6 +4917,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -5013,7 +4986,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"notificationConsoleData": [Function],
|
"notificationConsoleData": [Function],
|
||||||
"notificationsClient": undefined,
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
@@ -5325,7 +5297,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
logIndexingPolicySuccessMessage={[Function]}
|
logIndexingPolicySuccessMessage={[Function]}
|
||||||
onIndexingPolicyContentChange={[Function]}
|
onIndexingPolicyContentChange={[Function]}
|
||||||
onIndexingPolicyDirtyChange={[Function]}
|
onIndexingPolicyDirtyChange={[Function]}
|
||||||
onIndexingPolicyElementFocusChange={[Function]}
|
|
||||||
resetShouldDiscardIndexingPolicy={[Function]}
|
resetShouldDiscardIndexingPolicy={[Function]}
|
||||||
shouldDiscardIndexingPolicy={false}
|
shouldDiscardIndexingPolicy={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -310,5 +310,59 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
>
|
>
|
||||||
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected.
|
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected.
|
||||||
</Text>
|
</Text>
|
||||||
|
<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>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={1}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
To use the indexing policy editor, please login to the
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://portal.azure.com"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
azure portal.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
Refreshing index transformation progress
|
||||||
|
</Text>
|
||||||
|
<StyledSpinnerBase
|
||||||
|
size={2}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Text>
|
||||||
|
You can make more indexing changes once the current index transformation is complete.
|
||||||
|
<StyledLinkBase
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Refresh to check if it has completed.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
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>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
class="throughputModeRadio nonFirstRadio"
|
class="throughputModeRadio nonFirstRadio"
|
||||||
aria-label="Provisioned Throughput mode"
|
aria-label="Manual mode"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
class="throughputModeRadio nonFirstRadio"
|
class="throughputModeRadio nonFirstRadio"
|
||||||
aria-label="Provisioned Throughput mode"
|
aria-label="Manual mode"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -119,6 +119,10 @@
|
|||||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
||||||
</p>
|
</p>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
|
||||||
|
<!-- ko if: isFixed -->
|
||||||
|
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
|
||||||
|
<!-- /ko -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
<div data-bind="visible: !isAutoPilotSelected()">
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
||||||
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
||||||
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
export interface TreeNodeMenuItem {
|
export interface TreeNodeMenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -276,7 +278,12 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
text: menuItem.label,
|
text: menuItem.label,
|
||||||
disabled: menuItem.isDisabled,
|
disabled: menuItem.isDisabled,
|
||||||
className: menuItem.styleClass,
|
className: menuItem.styleClass,
|
||||||
onClick: menuItem.onClick,
|
onClick: () => {
|
||||||
|
menuItem.onClick();
|
||||||
|
TelemetryProcessor.trace(Action.ClickResourceTreeNodeContextMenuItem, ActionModifiers.Mark, {
|
||||||
|
label: menuItem.label
|
||||||
|
});
|
||||||
|
},
|
||||||
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
|
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
|
||||||
}))
|
}))
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
|||||||
"className": undefined,
|
"className": undefined,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"key": "menuLabel",
|
"key": "menuLabel",
|
||||||
"onClick": undefined,
|
"onClick": [Function],
|
||||||
"onRenderIcon": [Function],
|
"onRenderIcon": [Function],
|
||||||
"text": "menuLabel",
|
"text": "menuLabel",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
|||||||
jest.mock("../../Common/dataAccess/createCollection");
|
jest.mock("../../Common/dataAccess/createCollection");
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Q from "q";
|
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@@ -21,7 +20,7 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
||||||
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
explorerStub.findDatabaseWithId = () => database;
|
explorerStub.findDatabaseWithId = () => database;
|
||||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
explorerStub.refreshAllDatabases = () => Promise.resolve();
|
||||||
return explorerStub;
|
return explorerStub;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export class ContainerSampleGenerator {
|
|||||||
if (!collection) {
|
if (!collection) {
|
||||||
throw new Error("No container to populate");
|
throw new Error("No container to populate");
|
||||||
}
|
}
|
||||||
const promises: Q.Promise<any>[] = [];
|
const promises: Promise<any>[] = [];
|
||||||
|
|
||||||
if (this.container.isPreferredApiGraph()) {
|
if (this.container.isPreferredApiGraph()) {
|
||||||
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
|
|||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { configContext, updateConfigContext } from "../ConfigContext";
|
import { configContext, Platform, updateConfigContext } from "../ConfigContext";
|
||||||
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||||
@@ -58,7 +58,6 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
|
|||||||
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
|
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
|
||||||
import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter";
|
import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { PlatformType } from "../PlatformType";
|
|
||||||
import { QueriesClient } from "../Common/QueriesClient";
|
import { QueriesClient } from "../Common/QueriesClient";
|
||||||
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
|
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
|
||||||
import { RenewAdHocAccessPane } from "./Panes/RenewAdHocAccessPane";
|
import { RenewAdHocAccessPane } from "./Panes/RenewAdHocAccessPane";
|
||||||
@@ -82,12 +81,12 @@ import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
|
|||||||
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
import Trigger from "./Tree/Trigger";
|
import Trigger from "./Tree/Trigger";
|
||||||
import { NotificationsClientBase } from "../Common/NotificationsClientBase";
|
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
import TabsBase from "./Tabs/TabsBase";
|
import TabsBase from "./Tabs/TabsBase";
|
||||||
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
|
import { IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
BindingHandlersRegisterer.registerBindingHandlers();
|
||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
@@ -98,10 +97,6 @@ enum ShareAccessToggleState {
|
|||||||
Read
|
Read
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExplorerOptions {
|
|
||||||
notificationsClient: NotificationsClientBase;
|
|
||||||
isEmulator: boolean;
|
|
||||||
}
|
|
||||||
interface AdHocAccessData {
|
interface AdHocAccessData {
|
||||||
readWriteUrl: string;
|
readWriteUrl: string;
|
||||||
readUrl: string;
|
readUrl: string;
|
||||||
@@ -135,14 +130,12 @@ export default class Explorer {
|
|||||||
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
||||||
public isEnableMongoCapabilityPresent: ko.Computed<boolean>;
|
public isEnableMongoCapabilityPresent: ko.Computed<boolean>;
|
||||||
public isServerlessEnabled: ko.Computed<boolean>;
|
public isServerlessEnabled: ko.Computed<boolean>;
|
||||||
public isEmulator: boolean;
|
|
||||||
public isAccountReady: ko.Observable<boolean>;
|
public isAccountReady: ko.Observable<boolean>;
|
||||||
public canSaveQueries: ko.Computed<boolean>;
|
public canSaveQueries: ko.Computed<boolean>;
|
||||||
public features: ko.Observable<any>;
|
public features: ko.Observable<any>;
|
||||||
public serverId: ko.Observable<string>;
|
public serverId: ko.Observable<string>;
|
||||||
public armEndpoint: ko.Observable<string>;
|
public armEndpoint: ko.Observable<string>;
|
||||||
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
||||||
public notificationsClient: NotificationsClientBase;
|
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
@@ -212,7 +205,8 @@ export default class Explorer {
|
|||||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
||||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||||
public isSettingsV2Enabled: ko.Computed<boolean>;
|
public isSettingsV2Enabled: ko.Observable<boolean>;
|
||||||
|
public isMongoIndexEditorEnabled: ko.Observable<boolean>;
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
@@ -223,7 +217,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
||||||
public shareAccessData: ko.Observable<AdHocAccessData>;
|
public shareAccessData: ko.Observable<AdHocAccessData>;
|
||||||
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
|
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Promise<void>;
|
||||||
public renewTokenError: ko.Observable<string>;
|
public renewTokenError: ko.Observable<string>;
|
||||||
public tokenForRenewal: ko.Observable<string>;
|
public tokenForRenewal: ko.Observable<string>;
|
||||||
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
|
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
|
||||||
@@ -271,7 +265,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
|
||||||
constructor(options: ExplorerOptions) {
|
constructor() {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
});
|
});
|
||||||
@@ -377,8 +371,6 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
|
||||||
this.notificationsClient = options.notificationsClient;
|
|
||||||
this.isEmulator = options.isEmulator;
|
|
||||||
|
|
||||||
this.features = ko.observable();
|
this.features = ko.observable();
|
||||||
this.serverId = ko.observable<string>();
|
this.serverId = ko.observable<string>();
|
||||||
@@ -421,7 +413,8 @@ export default class Explorer {
|
|||||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||||
);
|
);
|
||||||
this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
|
this.isSettingsV2Enabled = ko.observable(false);
|
||||||
|
this.isMongoIndexEditorEnabled = ko.observable(false);
|
||||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
@@ -572,9 +565,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.isHostedDataExplorerEnabled = ko.computed<boolean>(
|
this.isHostedDataExplorerEnabled = ko.computed<boolean>(
|
||||||
() =>
|
() =>
|
||||||
this.getPlatformType() === PlatformType.Portal &&
|
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
|
||||||
!this.isRunningOnNationalCloud() &&
|
|
||||||
!this.isPreferredApiGraph()
|
|
||||||
);
|
);
|
||||||
this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
|
this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
|
this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
|
||||||
@@ -1129,8 +1120,8 @@ export default class Explorer {
|
|||||||
"Initiating connection to account"
|
"Initiating connection to account"
|
||||||
);
|
);
|
||||||
this.renewExplorerShareAccess(this, this.tokenForRenewal())
|
this.renewExplorerShareAccess(this, this.tokenForRenewal())
|
||||||
.fail((error: any) => {
|
.catch((error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
const stringifiedError: string = error.message;
|
||||||
this.renewTokenError("Invalid connection string specified");
|
this.renewTokenError("Invalid connection string specified");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
@@ -1159,43 +1150,34 @@ export default class Explorer {
|
|||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Failed to generate share url: ${JSON.stringify(error)}`
|
`Failed to generate share url: ${error.message}`
|
||||||
);
|
);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renewShareAccess(token: string): Q.Promise<void> {
|
public async renewShareAccess(token: string): Promise<void> {
|
||||||
if (!this.renewExplorerShareAccess) {
|
if (!this.renewExplorerShareAccess) {
|
||||||
return Q.reject("Not implemented");
|
throw "Not implemented";
|
||||||
}
|
}
|
||||||
|
|
||||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
|
||||||
const id: string = NotificationConsoleUtils.logConsoleMessage(
|
const id: string = NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.InProgress,
|
ConsoleDataType.InProgress,
|
||||||
"Initiating connection to account"
|
"Initiating connection to account"
|
||||||
);
|
);
|
||||||
this.renewExplorerShareAccess(this, token)
|
return this.renewExplorerShareAccess(this, token)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful");
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful");
|
||||||
this.renewAdHocAccessPane && this.renewAdHocAccessPane.close();
|
this.renewAdHocAccessPane && this.renewAdHocAccessPane.close();
|
||||||
deferred.resolve();
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
|
||||||
ConsoleDataType.Error,
|
throw error;
|
||||||
`Failed to connect: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public displayGuestAccessTokenRenewalPrompt(): void {
|
public displayGuestAccessTokenRenewalPrompt(): void {
|
||||||
@@ -1390,24 +1372,19 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshDatabaseForResourceToken(): Q.Promise<any> {
|
public async refreshDatabaseForResourceToken(): Promise<any> {
|
||||||
const databaseId = this.resourceTokenDatabaseId();
|
const databaseId = this.resourceTokenDatabaseId();
|
||||||
const collectionId = this.resourceTokenCollectionId();
|
const collectionId = this.resourceTokenCollectionId();
|
||||||
if (!databaseId || !collectionId) {
|
if (!databaseId || !collectionId) {
|
||||||
return Q.reject();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const deferred: Q.Deferred<void> = Q.defer();
|
const collection = await readCollection(databaseId, collectionId);
|
||||||
readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
|
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
|
||||||
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
|
this.selectedNode(this.resourceTokenCollection());
|
||||||
this.selectedNode(this.resourceTokenCollection());
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
|
public refreshAllDatabases(isInitialLoad?: boolean): Promise<any> {
|
||||||
this.isRefreshingExplorer(true);
|
this.isRefreshingExplorer(true);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
|
||||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||||
@@ -1424,89 +1401,85 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor
|
// TODO: Refactor
|
||||||
const deferred: Q.Deferred<any> = Q.defer();
|
|
||||||
this._setLoadingStatusText("Fetching databases...");
|
this._setLoadingStatusText("Fetching databases...");
|
||||||
readDatabases().then(
|
return readDatabases()
|
||||||
(databases: DataModels.Database[]) => {
|
.then(
|
||||||
this._setLoadingStatusText("Successfully fetched databases.");
|
(databases: DataModels.Database[]) => {
|
||||||
TelemetryProcessor.traceSuccess(
|
this._setLoadingStatusText("Successfully fetched databases.");
|
||||||
Action.LoadDatabases,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.databaseAccount().name,
|
|
||||||
defaultExperience: this.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
|
||||||
const deltaDatabases = this.getDeltaDatabases(databases);
|
|
||||||
this.addDatabasesToList(deltaDatabases.toAdd);
|
|
||||||
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
|
||||||
this.selectedNode(currentlySelectedNode);
|
|
||||||
this._setLoadingStatusText("Fetching containers...");
|
|
||||||
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
this._setLoadingStatusText("Successfully fetched containers.");
|
|
||||||
deferred.resolve();
|
|
||||||
},
|
|
||||||
reason => {
|
|
||||||
this._setLoadingStatusText("Failed to fetch containers.");
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => this.isRefreshingExplorer(false));
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
this._setLoadingStatusText("Failed to fetch databases.");
|
|
||||||
this.isRefreshingExplorer(false);
|
|
||||||
deferred.reject(error);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.LoadDatabases,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.databaseAccount().name,
|
|
||||||
defaultExperience: this.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
error: JSON.stringify(error)
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while refreshing databases: ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise.then(
|
|
||||||
() => {
|
|
||||||
if (resourceTreeStartKey != null) {
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadResourceTree,
|
Action.LoadDatabases,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
databaseAccountName: this.databaseAccount().name,
|
||||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
defaultExperience: this.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
},
|
},
|
||||||
resourceTreeStartKey
|
startKey
|
||||||
);
|
);
|
||||||
}
|
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
||||||
},
|
const deltaDatabases = this.getDeltaDatabases(databases);
|
||||||
reason => {
|
this.addDatabasesToList(deltaDatabases.toAdd);
|
||||||
if (resourceTreeStartKey != null) {
|
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
||||||
|
this.selectedNode(currentlySelectedNode);
|
||||||
|
this._setLoadingStatusText("Fetching containers...");
|
||||||
|
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
|
||||||
|
.then(
|
||||||
|
() => this._setLoadingStatusText("Successfully fetched containers."),
|
||||||
|
reason => {
|
||||||
|
this._setLoadingStatusText("Failed to fetch containers.");
|
||||||
|
throw reason;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => this.isRefreshingExplorer(false));
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this._setLoadingStatusText("Failed to fetch databases.");
|
||||||
|
this.isRefreshingExplorer(false);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.LoadResourceTree,
|
Action.LoadDatabases,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
databaseAccountName: this.databaseAccount().name,
|
||||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
defaultExperience: this.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
error: reason
|
error: error.message
|
||||||
},
|
},
|
||||||
resourceTreeStartKey
|
startKey
|
||||||
);
|
);
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Error while refreshing databases: ${error.message}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
);
|
.then(
|
||||||
|
() => {
|
||||||
|
if (resourceTreeStartKey != null) {
|
||||||
|
TelemetryProcessor.traceSuccess(
|
||||||
|
Action.LoadResourceTree,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||||
|
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
|
},
|
||||||
|
resourceTreeStartKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
if (resourceTreeStartKey != null) {
|
||||||
|
TelemetryProcessor.traceFailure(
|
||||||
|
Action.LoadResourceTree,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||||
|
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
|
error: reason
|
||||||
|
},
|
||||||
|
resourceTreeStartKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||||
@@ -1543,7 +1516,7 @@ export default class Explorer {
|
|||||||
this.isRefreshingExplorer(false);
|
this.isRefreshingExplorer(false);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Error while refreshing data: ${JSON.stringify(error)}`
|
`Error while refreshing data: ${error.message}`
|
||||||
);
|
);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.LoadDatabases,
|
Action.LoadDatabases,
|
||||||
@@ -1610,7 +1583,7 @@ export default class Explorer {
|
|||||||
return Promise.all(sparkPromises).then(() => workspaceItems);
|
return Promise.all(sparkPromises).then(() => workspaceItems);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
|
Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, JSON.stringify(error));
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error.message);
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1648,7 +1621,7 @@ export default class Explorer {
|
|||||||
Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
|
Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Failed to get notebook workspace connection info: ${JSON.stringify(error)}`
|
`Failed to get notebook workspace connection info: ${error.message}`
|
||||||
);
|
);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1725,7 +1698,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
|
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${JSON.stringify(error)}`);
|
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${error.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage && clearMessage();
|
clearMessage && clearMessage();
|
||||||
}
|
}
|
||||||
@@ -1800,13 +1773,13 @@ export default class Explorer {
|
|||||||
const message: any = event.data.data;
|
const message: any = event.data.data;
|
||||||
const inputs: ViewModels.DataExplorerInputsFrame = message.inputs;
|
const inputs: ViewModels.DataExplorerInputsFrame = message.inputs;
|
||||||
|
|
||||||
const isRunningInPortal = window.dataExplorerPlatform == PlatformType.Portal;
|
const isRunningInPortal = configContext.platform === Platform.Portal;
|
||||||
const isRunningInDevMode = process.env.NODE_ENV === "development";
|
const isRunningInDevMode = process.env.NODE_ENV === "development";
|
||||||
if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) {
|
if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) {
|
||||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
|
const initPromise: Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Promise.resolve();
|
||||||
|
|
||||||
initPromise.then(() => {
|
initPromise.then(() => {
|
||||||
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
||||||
@@ -1868,7 +1841,7 @@ export default class Explorer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (this.selectedNode().nodeKind === "Database") {
|
if (this.selectedNode().nodeKind === "Database") {
|
||||||
return _.find(this.databases(), (database: ViewModels.Database) => database.rid === this.selectedNode().rid);
|
return _.find(this.databases(), (database: ViewModels.Database) => database.id() === this.selectedNode().id());
|
||||||
}
|
}
|
||||||
return this.findSelectedCollection().database;
|
return this.findSelectedCollection().database;
|
||||||
}
|
}
|
||||||
@@ -1900,7 +1873,7 @@ export default class Explorer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
|
public async initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Promise<void> {
|
||||||
if (inputs != null) {
|
if (inputs != null) {
|
||||||
const authorizationToken = inputs.authorizationToken || "";
|
const authorizationToken = inputs.authorizationToken || "";
|
||||||
const masterKey = inputs.masterKey || "";
|
const masterKey = inputs.masterKey || "";
|
||||||
@@ -1911,7 +1884,6 @@ export default class Explorer {
|
|||||||
this.features(inputs.features);
|
this.features(inputs.features);
|
||||||
this.serverId(inputs.serverId);
|
this.serverId(inputs.serverId);
|
||||||
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
||||||
this.notificationsClient.setExtensionEndpoint(configContext.BACKEND_ENDPOINT);
|
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType);
|
this.subscriptionType(inputs.subscriptionType);
|
||||||
this.quotaId(inputs.quotaId);
|
this.quotaId(inputs.quotaId);
|
||||||
@@ -1919,6 +1891,7 @@ export default class Explorer {
|
|||||||
this.flight(inputs.addCollectionDefaultFlight);
|
this.flight(inputs.addCollectionDefaultFlight);
|
||||||
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
||||||
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
||||||
|
this.setFeatureFlagsFromFlights(inputs.flights);
|
||||||
|
|
||||||
if (!!inputs.dataExplorerVersion) {
|
if (!!inputs.dataExplorerVersion) {
|
||||||
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
||||||
@@ -1950,15 +1923,26 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.isAccountReady(true);
|
this.isAccountReady(true);
|
||||||
}
|
}
|
||||||
return Q();
|
}
|
||||||
|
|
||||||
|
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
||||||
|
if (!flights) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
|
||||||
|
this.isSettingsV2Enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flights.indexOf(Constants.Flights.MongoIndexEditor) !== -1) {
|
||||||
|
this.isMongoIndexEditorEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
public findSelectedCollection(): ViewModels.Collection {
|
||||||
if (this.selectedNode().nodeKind === "Collection") {
|
return (this.selectedNode().nodeKind === "Collection"
|
||||||
return this.findSelectedCollectionForSelectedNode();
|
? this.selectedNode()
|
||||||
} else {
|
: this.selectedNode().collection) as ViewModels.Collection;
|
||||||
return this.findSelectedCollectionForSubNode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
||||||
@@ -2008,10 +1992,6 @@ export default class Explorer {
|
|||||||
this._panes.forEach((pane: ContextualPaneBase) => pane.close());
|
this._panes.forEach((pane: ContextualPaneBase) => pane.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPlatformType(): PlatformType {
|
|
||||||
return window.dataExplorerPlatform;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isRunningOnNationalCloud(): boolean {
|
public isRunningOnNationalCloud(): boolean {
|
||||||
return (
|
return (
|
||||||
this.serverId() === Constants.ServerIds.blackforest ||
|
this.serverId() === Constants.ServerIds.blackforest ||
|
||||||
@@ -2060,7 +2040,7 @@ export default class Explorer {
|
|||||||
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
||||||
// i.e addition of stored procedures, etc.
|
// i.e addition of stored procedures, etc.
|
||||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||||
let loadCollectionPromises: Q.Promise<void>[] = [];
|
let loadCollectionPromises: Promise<void>[] = [];
|
||||||
|
|
||||||
// If the user has a lot of databases, only load expanded databases.
|
// If the user has a lot of databases, only load expanded databases.
|
||||||
const databasesToLoad =
|
const databasesToLoad =
|
||||||
@@ -2075,11 +2055,11 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
databasesToLoad.forEach(async (database: ViewModels.Database) => {
|
databasesToLoad.forEach(async (database: ViewModels.Database) => {
|
||||||
await database.loadCollections();
|
await database.loadCollections();
|
||||||
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.rid === database.rid);
|
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
||||||
if (isNewDatabase) {
|
if (isNewDatabase) {
|
||||||
database.expandDatabase();
|
database.expandDatabase();
|
||||||
}
|
}
|
||||||
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().rid === database.rid);
|
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().id() === database.id());
|
||||||
});
|
});
|
||||||
|
|
||||||
Q.all(loadCollectionPromises).done(
|
Q.all(loadCollectionPromises).done(
|
||||||
@@ -2099,7 +2079,7 @@ export default class Explorer {
|
|||||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
trace: JSON.stringify(error)
|
trace: error.message
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
@@ -2191,21 +2171,11 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private findSelectedCollectionForSelectedNode(): ViewModels.Collection {
|
public findCollection(databaseId: string, collectionId: string): ViewModels.Collection {
|
||||||
return this.findCollection(this.selectedNode().rid);
|
const database: ViewModels.Database = this.databases().find(
|
||||||
}
|
(database: ViewModels.Database) => database.id() === databaseId
|
||||||
|
);
|
||||||
public findCollection(rid: string): ViewModels.Collection {
|
return database?.collections().find((collection: ViewModels.Collection) => collection.id() === collectionId);
|
||||||
for (let i = 0; i < this.databases().length; i++) {
|
|
||||||
const database = this.databases()[i];
|
|
||||||
for (let j = 0; j < database.collections().length; j++) {
|
|
||||||
const collection = database.collections()[j];
|
|
||||||
if (collection.rid === rid) {
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isLastCollection(): boolean {
|
public isLastCollection(): boolean {
|
||||||
@@ -2229,7 +2199,7 @@ export default class Explorer {
|
|||||||
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
||||||
const databaseExists = _.some(
|
const databaseExists = _.some(
|
||||||
this.databases(),
|
this.databases(),
|
||||||
(existingDatabase: ViewModels.Database) => existingDatabase.rid === database._rid
|
(existingDatabase: ViewModels.Database) => existingDatabase.id() === database.id
|
||||||
);
|
);
|
||||||
return !databaseExists;
|
return !databaseExists;
|
||||||
});
|
});
|
||||||
@@ -2241,7 +2211,7 @@ export default class Explorer {
|
|||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
||||||
const databasePresentInUpdatedList = _.some(
|
const databasePresentInUpdatedList = _.some(
|
||||||
updatedDatabaseList,
|
updatedDatabaseList,
|
||||||
(db: DataModels.Database) => db._rid === database.rid
|
(db: DataModels.Database) => db.id === database.id()
|
||||||
);
|
);
|
||||||
if (!databasePresentInUpdatedList) {
|
if (!databasePresentInUpdatedList) {
|
||||||
databasesToDelete.push(database);
|
databasesToDelete.push(database);
|
||||||
@@ -2263,7 +2233,7 @@ export default class Explorer {
|
|||||||
const databasesToKeep: ViewModels.Database[] = [];
|
const databasesToKeep: ViewModels.Database[] = [];
|
||||||
|
|
||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
||||||
const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.rid === database.rid);
|
const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.id === database.id);
|
||||||
if (!shouldRemoveDatabase) {
|
if (!shouldRemoveDatabase) {
|
||||||
databasesToKeep.push(database);
|
databasesToKeep.push(database);
|
||||||
}
|
}
|
||||||
@@ -2272,19 +2242,6 @@ export default class Explorer {
|
|||||||
this.databases(databasesToKeep);
|
this.databases(databasesToKeep);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findSelectedCollectionForSubNode(): ViewModels.Collection {
|
|
||||||
for (let i = 0; i < this.databases().length; i++) {
|
|
||||||
const database = this.databases()[i];
|
|
||||||
for (let j = 0; j < database.collections().length; j++) {
|
|
||||||
const collection = database.collections()[j];
|
|
||||||
if (this.selectedNode().collection && collection.rid === this.selectedNode().collection.rid) {
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||||
const error = "Attempt to upload notebook, but notebook is not enabled";
|
const error = "Attempt to upload notebook, but notebook is not enabled";
|
||||||
@@ -2376,42 +2333,16 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public showOkCancelModalDialog(
|
public showOkCancelModalDialog(
|
||||||
title: string,
|
|
||||||
msg: string,
|
|
||||||
okLabel: string,
|
|
||||||
onOk: () => void,
|
|
||||||
cancelLabel: string,
|
|
||||||
onCancel: () => void
|
|
||||||
): void {
|
|
||||||
this._dialogProps({
|
|
||||||
isModal: true,
|
|
||||||
visible: true,
|
|
||||||
title,
|
|
||||||
subText: msg,
|
|
||||||
primaryButtonText: okLabel,
|
|
||||||
secondaryButtonText: cancelLabel,
|
|
||||||
onPrimaryButtonClick: () => {
|
|
||||||
this._closeModalDialog();
|
|
||||||
onOk && onOk();
|
|
||||||
},
|
|
||||||
onSecondaryButtonClick: () => {
|
|
||||||
this._closeModalDialog();
|
|
||||||
onCancel && onCancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public showOkCancelTextFieldModalDialog(
|
|
||||||
title: string,
|
title: string,
|
||||||
msg: string,
|
msg: string,
|
||||||
okLabel: string,
|
okLabel: string,
|
||||||
onOk: () => void,
|
onOk: () => void,
|
||||||
cancelLabel: string,
|
cancelLabel: string,
|
||||||
onCancel: () => void,
|
onCancel: () => void,
|
||||||
textFieldProps: TextFieldProps,
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
|
textFieldProps?: TextFieldProps,
|
||||||
isPrimaryButtonDisabled?: boolean
|
isPrimaryButtonDisabled?: boolean
|
||||||
): void {
|
): void {
|
||||||
let textFieldValue: string = null;
|
|
||||||
this._dialogProps({
|
this._dialogProps({
|
||||||
isModal: true,
|
isModal: true,
|
||||||
visible: true,
|
visible: true,
|
||||||
@@ -2427,8 +2358,9 @@ export default class Explorer {
|
|||||||
this._closeModalDialog();
|
this._closeModalDialog();
|
||||||
onCancel && onCancel();
|
onCancel && onCancel();
|
||||||
},
|
},
|
||||||
primaryButtonDisabled: isPrimaryButtonDisabled,
|
choiceGroupProps,
|
||||||
textFieldProps
|
textFieldProps,
|
||||||
|
primaryButtonDisabled: isPrimaryButtonDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2469,7 +2401,6 @@ export default class Explorer {
|
|||||||
title: notebookContentItem.name,
|
title: notebookContentItem.name,
|
||||||
tabPath: notebookContentItem.path,
|
tabPath: notebookContentItem.path,
|
||||||
collection: null,
|
collection: null,
|
||||||
selfLink: null,
|
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
hashLocation: "notebooks",
|
hashLocation: "notebooks",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
@@ -2493,7 +2424,7 @@ export default class Explorer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> {
|
public async renameNotebook(notebookFile: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||||
const error = "Attempt to rename notebook, but notebook is not enabled";
|
const error = "Attempt to rename notebook, but notebook is not enabled";
|
||||||
Logger.logError(error, "Explorer/renameNotebook");
|
Logger.logError(error, "Explorer/renameNotebook");
|
||||||
@@ -2510,39 +2441,35 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
if (openedNotebookTabs.length > 0) {
|
if (openedNotebookTabs.length > 0) {
|
||||||
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
||||||
return Q.reject();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalPath = notebookFile.path;
|
const originalPath = notebookFile.path;
|
||||||
const result = this.stringInputPane
|
const newNotebookFile = await this.stringInputPane.openWithOptions<NotebookContentItem>({
|
||||||
.openWithOptions<NotebookContentItem>({
|
errorMessage: "Could not rename notebook",
|
||||||
errorMessage: "Could not rename notebook",
|
inProgressMessage: "Renaming notebook to",
|
||||||
inProgressMessage: "Renaming notebook to",
|
successMessage: "Renamed notebook to",
|
||||||
successMessage: "Renamed notebook to",
|
inputLabel: "Enter new notebook name",
|
||||||
inputLabel: "Enter new notebook name",
|
paneTitle: "Rename Notebook",
|
||||||
paneTitle: "Rename Notebook",
|
submitButtonLabel: "Rename",
|
||||||
submitButtonLabel: "Rename",
|
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
|
||||||
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
|
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
|
||||||
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
|
});
|
||||||
})
|
const notebookTabs = this.tabsManager.getTabs(
|
||||||
.then(newNotebookFile => {
|
ViewModels.CollectionTabKind.NotebookV2,
|
||||||
const notebookTabs = this.tabsManager.getTabs(
|
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||||
ViewModels.CollectionTabKind.NotebookV2,
|
);
|
||||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
notebookTabs.forEach(tab => {
|
||||||
);
|
tab.tabTitle(newNotebookFile.name);
|
||||||
notebookTabs.forEach(tab => {
|
tab.tabPath(newNotebookFile.path);
|
||||||
tab.tabTitle(newNotebookFile.name);
|
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
|
||||||
tab.tabPath(newNotebookFile.path);
|
});
|
||||||
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
|
|
||||||
});
|
|
||||||
|
|
||||||
return newNotebookFile;
|
this.resourceTree.triggerRender();
|
||||||
});
|
return newNotebookFile;
|
||||||
result.then(() => this.resourceTree.triggerRender());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> {
|
public async onCreateDirectory(parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||||
const error = "Attempt to create notebook directory, but notebook is not enabled";
|
const error = "Attempt to create notebook directory, but notebook is not enabled";
|
||||||
Logger.logError(error, "Explorer/onCreateDirectory");
|
Logger.logError(error, "Explorer/onCreateDirectory");
|
||||||
@@ -2550,7 +2477,7 @@ export default class Explorer {
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = this.stringInputPane.openWithOptions<NotebookContentItem>({
|
const result = await this.stringInputPane.openWithOptions<NotebookContentItem>({
|
||||||
errorMessage: "Could not create directory ",
|
errorMessage: "Could not create directory ",
|
||||||
inProgressMessage: "Creating directory ",
|
inProgressMessage: "Creating directory ",
|
||||||
successMessage: "Created directory ",
|
successMessage: "Created directory ",
|
||||||
@@ -2560,7 +2487,7 @@ export default class Explorer {
|
|||||||
defaultInput: "",
|
defaultInput: "",
|
||||||
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input)
|
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input)
|
||||||
});
|
});
|
||||||
result.then(() => this.resourceTree.triggerRender());
|
this.resourceTree.triggerRender();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2610,7 +2537,7 @@ export default class Explorer {
|
|||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
ConsoleDataType.Error,
|
ConsoleDataType.Error,
|
||||||
`Could not download notebook ${JSON.stringify(error)}`
|
`Could not download notebook ${error.message}`
|
||||||
);
|
);
|
||||||
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -2921,7 +2848,6 @@ export default class Explorer {
|
|||||||
title: title,
|
title: title,
|
||||||
tabPath: title,
|
tabPath: title,
|
||||||
collection: null,
|
collection: null,
|
||||||
selfLink: null,
|
|
||||||
hashLocation: hashLocation,
|
hashLocation: hashLocation,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
@@ -2965,7 +2891,6 @@ export default class Explorer {
|
|||||||
title: title,
|
title: title,
|
||||||
tabPath: title,
|
tabPath: title,
|
||||||
documentClientUtility: null,
|
documentClientUtility: null,
|
||||||
selfLink: null,
|
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
hashLocation: hashLocation,
|
hashLocation: hashLocation,
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
||||||
@@ -3008,7 +2933,6 @@ export default class Explorer {
|
|||||||
tabPath: title,
|
tabPath: title,
|
||||||
documentClientUtility: null,
|
documentClientUtility: null,
|
||||||
collection: null,
|
collection: null,
|
||||||
selfLink: null,
|
|
||||||
hashLocation: hashLocation,
|
hashLocation: hashLocation,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user