mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-30 06:11:38 +00:00
Compare commits
24 Commits
remove-red
...
codescan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a266fdc89c | ||
|
|
39f7ef331a | ||
|
|
9933a4988a | ||
|
|
d525afa142 | ||
|
|
cfb9a0b321 | ||
|
|
3b64d75322 | ||
|
|
daba1c4ed4 | ||
|
|
a698e08638 | ||
|
|
88d630fef4 | ||
|
|
5ffa746adb | ||
|
|
a9a57f4ba9 | ||
|
|
47cc6fd7a8 | ||
|
|
14cdf19efb | ||
|
|
5191ae3f3a | ||
|
|
ba862a8106 | ||
|
|
fe085b3e5a | ||
|
|
8028734cb0 | ||
|
|
444f663733 | ||
|
|
8c1ca35420 | ||
|
|
b69174788d | ||
|
|
ff03c79399 | ||
|
|
0382628249 | ||
|
|
d346ebe054 | ||
|
|
f5ecb8a04f |
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 }}
|
||||||
|
|||||||
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [master]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 5 * * 5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# Override automatic language detection by changing the below list
|
||||||
|
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||||
|
language: ['javascript']
|
||||||
|
# Learn more...
|
||||||
|
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
# We must fetch at least the immediate parents so that if this is
|
||||||
|
# a pull request then we can checkout the head.
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
# If this run was triggered by a pull request event, then checkout
|
||||||
|
# the head of the pull request instead of the merge commit.
|
||||||
|
- run: git checkout HEAD^2
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
# Add steps that build, run tests, deploy, and more:
|
# Add steps that build, run tests, deploy, and more:
|
||||||
# https://aka.ms/yaml
|
# https://aka.ms/yaml
|
||||||
|
|
||||||
trigger:
|
|
||||||
- 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%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
92
package-lock.json
generated
92
package-lock.json
generated
@@ -2326,83 +2326,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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,11 @@ 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";
|
||||||
|
}
|
||||||
|
|
||||||
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";
|
||||||
|
|||||||
@@ -72,22 +72,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
|
|||||||
return [partitionKeyValue];
|
return [partitionKeyValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOffer(
|
|
||||||
offer: DataModels.Offer,
|
|
||||||
newOffer: DataModels.Offer,
|
|
||||||
options?: RequestOptions
|
|
||||||
): Q.Promise<DataModels.Offer> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.offer(offer.id)
|
|
||||||
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
|
||||||
.replace((newOffer as unknown) as OfferDefinition, options)
|
|
||||||
.then(response => {
|
|
||||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
export function updateDocument(
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
|
|||||||
@@ -157,41 +157,6 @@ export function updateDocument(
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOffer(
|
|
||||||
offer: DataModels.Offer,
|
|
||||||
newOffer: DataModels.Offer,
|
|
||||||
options: RequestOptions
|
|
||||||
): Q.Promise<DataModels.Offer> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const clearMessage = logConsoleProgress(`Updating offer for resource ${offer.resource}`);
|
|
||||||
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
|
|
||||||
.then(
|
|
||||||
(replacedOffer: DataModels.Offer) => {
|
|
||||||
logConsoleInfo(`Successfully updated offer for resource ${offer.resource}`);
|
|
||||||
deferred.resolve(replacedOffer);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
logConsoleError(`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`);
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
oldOffer: offer,
|
|
||||||
newOffer: newOffer,
|
|
||||||
error: error
|
|
||||||
}),
|
|
||||||
"UpdateOffer",
|
|
||||||
error.code
|
|
||||||
);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||||
var deferred = Q.defer<any>();
|
var deferred = Q.defer<any>();
|
||||||
const entityName = getEntityName();
|
const entityName = getEntityName();
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
};
|
||||||
@@ -20,26 +20,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,24 +63,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> => {
|
||||||
|
|||||||
@@ -12,16 +12,14 @@ 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[]> {
|
||||||
|
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||||
|
return [{ id: "TablesDB" } as DataModels.Database];
|
||||||
|
}
|
||||||
|
|
||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
try {
|
try {
|
||||||
if (
|
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||||
window.authType === AuthType.AAD &&
|
|
||||||
!userContext.useSDKOperations &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
|
|
||||||
) {
|
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
|
|||||||
@@ -41,6 +41,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
|
||||||
) {
|
) {
|
||||||
@@ -52,16 +53,18 @@ export async function updateCollection(
|
|||||||
.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)}`);
|
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||||
sendNotificationForError(error);
|
sendNotificationForError(error);
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
return collection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCollectionWithARM(
|
async function updateCollectionWithARM(
|
||||||
|
|||||||
421
src/Common/dataAccess/updateOffer.ts
Normal file
421
src/Common/dataAccess/updateOffer.ts
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
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 { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { logError } from "../Logger";
|
||||||
|
import { readCollectionOffer } from "./readCollectionOffer";
|
||||||
|
import { readDatabaseOffer } from "./readDatabaseOffer";
|
||||||
|
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||||
|
import { sendNotificationForError } from "./sendNotificationForError";
|
||||||
|
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) {
|
||||||
|
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
|
||||||
|
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||||
|
sendNotificationForError(error);
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export interface Database extends TreeNode {
|
|||||||
collapseDatabase(): void;
|
collapseDatabase(): void;
|
||||||
|
|
||||||
loadCollections(): Promise<void>;
|
loadCollections(): Promise<void>;
|
||||||
findCollectionWithId(collectionRid: string): Collection;
|
findCollectionWithId(collectionId: string): Collection;
|
||||||
openAddCollection(database: Database, event: MouseEvent): void;
|
openAddCollection(database: Database, event: MouseEvent): void;
|
||||||
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
@@ -266,7 +266,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;
|
||||||
@@ -305,7 +304,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 +386,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;
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
|
|||||||
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
||||||
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
import { Link } from "office-ui-fabric-react/lib/Link";
|
import { Link } from "office-ui-fabric-react/lib/Link";
|
||||||
import { FontIcon } from "office-ui-fabric-react";
|
import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
|
|
||||||
export interface TextFieldProps extends ITextFieldProps {
|
export interface TextFieldProps extends ITextFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -24,6 +24,7 @@ export interface DialogProps {
|
|||||||
subText: string;
|
subText: string;
|
||||||
isModal: boolean;
|
isModal: boolean;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
choiceGroupProps?: IChoiceGroupProps;
|
||||||
textFieldProps?: TextFieldProps;
|
textFieldProps?: TextFieldProps;
|
||||||
linkProps?: LinkProps;
|
linkProps?: LinkProps;
|
||||||
primaryButtonText: string;
|
primaryButtonText: string;
|
||||||
@@ -65,6 +66,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
|||||||
minWidth: DIALOG_MIN_WIDTH,
|
minWidth: DIALOG_MIN_WIDTH,
|
||||||
maxWidth: DIALOG_MAX_WIDTH
|
maxWidth: DIALOG_MAX_WIDTH
|
||||||
};
|
};
|
||||||
|
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
|
||||||
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
||||||
const linkProps: LinkProps = this.props.linkProps;
|
const linkProps: LinkProps = this.props.linkProps;
|
||||||
const primaryButtonProps: IButtonProps = {
|
const primaryButtonProps: IButtonProps = {
|
||||||
@@ -82,6 +84,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...dialogProps}>
|
<Dialog {...dialogProps}>
|
||||||
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
{textFieldProps && <TextField {...textFieldProps} />}
|
{textFieldProps && <TextField {...textFieldProps} />}
|
||||||
{linkProps && (
|
{linkProps && (
|
||||||
<Link href={linkProps.linkUrl} target="_blank">
|
<Link href={linkProps.linkUrl} target="_blank">
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
|||||||
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
||||||
value: "true"
|
value: "true"
|
||||||
},
|
},
|
||||||
{ key: "feature.enablesettingsv2", label: "Enable SettingsV2 Tab", value: "true" },
|
|
||||||
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
||||||
{
|
{
|
||||||
key: "feature.enablefixedcollectionwithsharedthroughput",
|
key: "feature.enablefixedcollectionwithsharedthroughput",
|
||||||
|
|||||||
@@ -178,12 +178,6 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
className="checkboxRow"
|
className="checkboxRow"
|
||||||
horizontalAlign="space-between"
|
horizontalAlign="space-between"
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
|
||||||
checked={false}
|
|
||||||
key="feature.enablesettingsv2"
|
|
||||||
label="Enable SettingsV2 Tab"
|
|
||||||
onChange={[Function]}
|
|
||||||
/>
|
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.canexceedmaximumvalue"
|
key="feature.canexceedmaximumvalue"
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ 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>
|
||||||
|
|||||||
@@ -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 !== undefined && (
|
||||||
|
<Stack.Item>
|
||||||
|
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
||||||
|
</Stack.Item>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
selectedKey={0}
|
selectedKey={0}
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
|
<StackItem>
|
||||||
|
<InfoComponent />
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
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} />);
|
||||||
@@ -54,7 +55,8 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
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,7 +6,7 @@ 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";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||||
@@ -20,8 +20,8 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
|||||||
geospatialConfig: undefined
|
geospatialConfig: undefined
|
||||||
} as DataModels.Collection)
|
} as DataModels.Collection)
|
||||||
}));
|
}));
|
||||||
import { updateOffer } from "../../../Common/DocumentClientUtilityBase";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
jest.mock("../../../Common/DocumentClientUtilityBase", () => ({
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -33,7 +33,6 @@ 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
|
||||||
@@ -103,10 +102,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 +143,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,
|
||||||
@@ -220,13 +213,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,11 +6,11 @@ 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 } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
@@ -27,9 +27,6 @@ import {
|
|||||||
SettingsV2TabTypes,
|
SettingsV2TabTypes,
|
||||||
getTabTitle,
|
getTabTitle,
|
||||||
isDirty,
|
isDirty,
|
||||||
TtlOff,
|
|
||||||
TtlOn,
|
|
||||||
TtlOnNoDefault,
|
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure
|
parseConflictResolutionProcedure
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
@@ -38,7 +35,7 @@ import {
|
|||||||
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";
|
||||||
|
|
||||||
@@ -85,7 +82,6 @@ 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;
|
||||||
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
@@ -102,7 +98,6 @@ 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;
|
|
||||||
|
|
||||||
public saveSettingsButton: ButtonV2;
|
public saveSettingsButton: ButtonV2;
|
||||||
public discardSettingsChangesButton: ButtonV2;
|
public discardSettingsChangesButton: ButtonV2;
|
||||||
@@ -127,8 +122,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,7 +155,6 @@ 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,
|
||||||
|
|
||||||
@@ -270,7 +264,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,
|
||||||
@@ -411,7 +405,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
traceFailure(
|
traceFailure(
|
||||||
Action.UpdateSettings,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
databaseName: this.collection && this.collection.databaseId,
|
||||||
@@ -426,7 +420,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
throw error;
|
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,7 +454,7 @@ 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,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
@@ -460,7 +468,7 @@ 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,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
@@ -474,6 +482,10 @@ 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,
|
||||||
@@ -504,9 +516,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
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 +539,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onConflictResolutionPolicyModeChange = (
|
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
||||||
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
this.setState({ conflictResolutionPolicyMode: newMode });
|
||||||
option?: IChoiceGroupOption
|
|
||||||
): void =>
|
|
||||||
this.setState({
|
|
||||||
conflictResolutionPolicyMode:
|
|
||||||
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
|
|
||||||
});
|
|
||||||
|
|
||||||
private onConflictResolutionPolicyPathChange = (
|
private onConflictResolutionPolicyPathChange = (newPath: string): void =>
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
this.setState({ conflictResolutionPolicyPath: newPath });
|
||||||
newValue?: string
|
|
||||||
): void => this.setState({ conflictResolutionPolicyPath: newValue });
|
|
||||||
|
|
||||||
private onConflictResolutionPolicyProcedureChange = (
|
private onConflictResolutionPolicyProcedureChange = (newProcedure: string): void =>
|
||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
this.setState({ conflictResolutionPolicyProcedure: newProcedure });
|
||||||
newValue?: string
|
|
||||||
): void => this.setState({ conflictResolutionPolicyProcedure: newValue });
|
|
||||||
|
|
||||||
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 });
|
||||||
@@ -830,7 +794,6 @@ 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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ 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.getPlatformType = () => PlatformType.Portal;
|
||||||
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
||||||
|
|
||||||
@@ -88,10 +85,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,
|
||||||
|
|||||||
@@ -19,6 +19,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 +44,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 => {
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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 = 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}
|
||||||
/>
|
/>
|
||||||
@@ -298,8 +297,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()
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { collection, container } from "./TestUtils";
|
|||||||
import {
|
import {
|
||||||
getMaxRUs,
|
getMaxRUs,
|
||||||
getMinRUs,
|
getMinRUs,
|
||||||
|
getSanitizedInputValue,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
isDirty,
|
isDirty,
|
||||||
isDirtyTypes,
|
isDirtyTypes,
|
||||||
@@ -86,4 +87,11 @@ 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(99);
|
||||||
|
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as PricingUtils from "../../../Utils/PricingUtils";
|
|||||||
|
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
|
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";
|
||||||
@@ -129,6 +130,16 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
|
|||||||
return procedureFromBackEnd;
|
return procedureFromBackEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
||||||
|
let newValue = parseInt(newValueString);
|
||||||
|
if (isNaN(newValue)) {
|
||||||
|
newValue = zeroValue;
|
||||||
|
} else if (newValue > max) {
|
||||||
|
newValue = Math.floor(newValue / 10);
|
||||||
|
}
|
||||||
|
return newValue;
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -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],
|
||||||
@@ -1058,7 +1051,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 +1390,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 +1889,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 +2282,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],
|
||||||
@@ -2372,7 +2357,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 +2709,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 +3208,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 +3601,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],
|
||||||
@@ -3699,7 +3676,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 +4015,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 +4514,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 +4907,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],
|
||||||
@@ -5013,7 +4982,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 +5293,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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -82,12 +82,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 +98,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 +131,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 +206,7 @@ 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 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>;
|
||||||
@@ -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.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
|
||||||
|
this.isSettingsV2Enabled = 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);
|
||||||
@@ -1868,7 +1861,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;
|
||||||
}
|
}
|
||||||
@@ -1911,7 +1904,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 +1911,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);
|
||||||
@@ -1953,12 +1946,20 @@ export default class Explorer {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
||||||
if (this.selectedNode().nodeKind === "Collection") {
|
if (!flights) {
|
||||||
return this.findSelectedCollectionForSelectedNode();
|
return;
|
||||||
} else {
|
|
||||||
return this.findSelectedCollectionForSubNode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
|
||||||
|
this.isSettingsV2Enabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public findSelectedCollection(): ViewModels.Collection {
|
||||||
|
return (this.selectedNode().nodeKind === "Collection"
|
||||||
|
? this.selectedNode()
|
||||||
|
: this.selectedNode().collection) as ViewModels.Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
||||||
@@ -2075,11 +2076,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(
|
||||||
@@ -2191,21 +2192,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 +2220,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 +2232,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 +2254,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 +2263,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 +2354,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 +2379,9 @@ export default class Explorer {
|
|||||||
this._closeModalDialog();
|
this._closeModalDialog();
|
||||||
onCancel && onCancel();
|
onCancel && onCancel();
|
||||||
},
|
},
|
||||||
primaryButtonDisabled: isPrimaryButtonDisabled,
|
choiceGroupProps,
|
||||||
textFieldProps
|
textFieldProps,
|
||||||
|
primaryButtonDisabled: isPrimaryButtonDisabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2469,7 +2422,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),
|
||||||
@@ -2921,7 +2873,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 +2916,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 +2958,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),
|
||||||
|
|||||||
@@ -141,8 +141,6 @@ describe("getPkIdFromDocumentId", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("GraphExplorer", () => {
|
describe("GraphExplorer", () => {
|
||||||
const COLLECTION_RID = "collectionRid";
|
|
||||||
const COLLECTION_SELF_LINK = "collectionSelfLink";
|
|
||||||
const gremlinRU = 789.12;
|
const gremlinRU = 789.12;
|
||||||
|
|
||||||
const createMockProps = (): GraphExplorerProps => {
|
const createMockProps = (): GraphExplorerProps => {
|
||||||
@@ -160,8 +158,6 @@ describe("GraphExplorer", () => {
|
|||||||
onIsValidQueryChange: (isValidQuery: boolean): void => {},
|
onIsValidQueryChange: (isValidQuery: boolean): void => {},
|
||||||
|
|
||||||
collectionPartitionKeyProperty: "collectionPartitionKeyProperty",
|
collectionPartitionKeyProperty: "collectionPartitionKeyProperty",
|
||||||
collectionRid: COLLECTION_RID,
|
|
||||||
collectionSelfLink: COLLECTION_SELF_LINK,
|
|
||||||
graphBackendEndpoint: "graphBackendEndpoint",
|
graphBackendEndpoint: "graphBackendEndpoint",
|
||||||
databaseId: "databaseId",
|
databaseId: "databaseId",
|
||||||
collectionId: "collectionId",
|
collectionId: "collectionId",
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ export interface GraphExplorerProps {
|
|||||||
onIsValidQueryChange: (isValidQuery: boolean) => void;
|
onIsValidQueryChange: (isValidQuery: boolean) => void;
|
||||||
|
|
||||||
collectionPartitionKeyProperty: string;
|
collectionPartitionKeyProperty: string;
|
||||||
collectionRid: string;
|
|
||||||
collectionSelfLink: string;
|
|
||||||
graphBackendEndpoint: string;
|
graphBackendEndpoint: string;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -1761,7 +1759,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
||||||
|
|
||||||
return queryDocumentsPage(
|
return queryDocumentsPage(
|
||||||
this.props.collectionRid,
|
this.props.collectionId,
|
||||||
this.currentDocDBQueryInfo.iterator,
|
this.currentDocDBQueryInfo.iterator,
|
||||||
this.currentDocDBQueryInfo.index,
|
this.currentDocDBQueryInfo.index,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ interface Parameter {
|
|||||||
graphConfig?: GraphConfig;
|
graphConfig?: GraphConfig;
|
||||||
|
|
||||||
collectionPartitionKeyProperty: string;
|
collectionPartitionKeyProperty: string;
|
||||||
collectionRid: string;
|
|
||||||
collectionSelfLink: string;
|
|
||||||
graphBackendEndpoint: string;
|
graphBackendEndpoint: string;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -49,8 +47,6 @@ export class GraphExplorerAdapter implements ReactAdapter {
|
|||||||
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
|
onIsGraphDisplayed={this.params.onIsGraphDisplayed}
|
||||||
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
|
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
|
||||||
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
|
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
|
||||||
collectionRid={this.params.collectionRid}
|
|
||||||
collectionSelfLink={this.params.collectionSelfLink}
|
|
||||||
graphBackendEndpoint={this.params.graphBackendEndpoint}
|
graphBackendEndpoint={this.params.graphBackendEndpoint}
|
||||||
databaseId={this.params.databaseId}
|
databaseId={this.params.databaseId}
|
||||||
collectionId={this.params.collectionId}
|
collectionId={this.params.collectionId}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
|
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
|
||||||
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import { CommandBarUtil } from "./CommandBarUtil";
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export class CommandBarComponentButtonFactory {
|
|||||||
buttons.push(fullScreenButton);
|
buttons.push(fullScreenButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container.hasOwnProperty("isEmulator") || !container.isEmulator) {
|
if (configContext.platform !== Platform.Emulator) {
|
||||||
const label = "Feedback";
|
const label = "Feedback";
|
||||||
const feedbackButtonOptions: CommandButtonComponentProps = {
|
const feedbackButtonOptions: CommandButtonComponentProps = {
|
||||||
iconSrc: FeedbackIcon,
|
iconSrc: FeedbackIcon,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CommandBarUtil } from "./CommandBarUtil";
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
@@ -8,7 +8,7 @@ describe("CommandBarUtil tests", () => {
|
|||||||
return {
|
return {
|
||||||
iconSrc: "icon",
|
iconSrc: "icon",
|
||||||
iconAlt: "label",
|
iconAlt: "label",
|
||||||
onCommandClick: (e: React.SyntheticEvent): void => {},
|
onCommandClick: jest.fn(),
|
||||||
commandButtonLabel: "label",
|
commandButtonLabel: "label",
|
||||||
ariaLabel: "ariaLabel",
|
ariaLabel: "ariaLabel",
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
@@ -29,11 +29,14 @@ describe("CommandBarUtil tests", () => {
|
|||||||
expect(!converted.split);
|
expect(!converted.split);
|
||||||
expect(converted.iconProps.imageProps.src).toEqual(btn.iconSrc);
|
expect(converted.iconProps.imageProps.src).toEqual(btn.iconSrc);
|
||||||
expect(converted.iconProps.imageProps.alt).toEqual(btn.iconAlt);
|
expect(converted.iconProps.imageProps.alt).toEqual(btn.iconAlt);
|
||||||
expect(converted.onClick).toEqual(btn.onCommandClick);
|
|
||||||
expect(converted.text).toEqual(btn.commandButtonLabel);
|
expect(converted.text).toEqual(btn.commandButtonLabel);
|
||||||
expect(converted.ariaLabel).toEqual(btn.ariaLabel);
|
expect(converted.ariaLabel).toEqual(btn.ariaLabel);
|
||||||
expect(converted.disabled).toEqual(btn.disabled);
|
expect(converted.disabled).toEqual(btn.disabled);
|
||||||
expect(converted.className).toEqual(btn.className);
|
expect(converted.className).toEqual(btn.className);
|
||||||
|
|
||||||
|
// Click gets called
|
||||||
|
converted.onClick();
|
||||||
|
expect(btn.onCommandClick).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should convert NavbarButtonConfig to split button", () => {
|
it("should convert NavbarButtonConfig to split button", () => {
|
||||||
|
|||||||
@@ -11,177 +11,187 @@ import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
|||||||
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
|
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
|
||||||
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
||||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for CommandBar
|
* Convert our NavbarButtonConfig to UI Fabric buttons
|
||||||
|
* @param btns
|
||||||
*/
|
*/
|
||||||
export class CommandBarUtil {
|
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
||||||
/**
|
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
|
||||||
* Convert our NavbarButtonConfig to UI Fabric buttons
|
|
||||||
* @param btns
|
|
||||||
*/
|
|
||||||
public static convertButton(btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] {
|
|
||||||
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
|
|
||||||
|
|
||||||
return btns
|
return btns
|
||||||
.filter(btn => btn)
|
.filter(btn => btn)
|
||||||
.map(
|
.map(
|
||||||
(btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => {
|
(btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => {
|
||||||
if (btn.isDivider) {
|
if (btn.isDivider) {
|
||||||
return CommandBarUtil.createDivider(btn.commandButtonLabel);
|
return createDivider(btn.commandButtonLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSplit = !!btn.children && btn.children.length > 0;
|
const isSplit = !!btn.children && btn.children.length > 0;
|
||||||
|
const label = btn.commandButtonLabel || btn.tooltipText;
|
||||||
const result: ICommandBarItemProps = {
|
const result: ICommandBarItemProps = {
|
||||||
iconProps: {
|
iconProps: {
|
||||||
style: {
|
style: {
|
||||||
width: StyleConstants.CommandBarIconWidth, // 16
|
width: StyleConstants.CommandBarIconWidth, // 16
|
||||||
alignSelf: btn.iconName ? "baseline" : undefined
|
alignSelf: btn.iconName ? "baseline" : undefined
|
||||||
},
|
|
||||||
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
|
||||||
iconName: btn.iconName
|
|
||||||
},
|
},
|
||||||
onClick: btn.onCommandClick,
|
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
||||||
key: `${btn.commandButtonLabel}${index}`,
|
iconName: btn.iconName
|
||||||
text: btn.commandButtonLabel || btn.tooltipText,
|
},
|
||||||
"data-test": btn.commandButtonLabel || btn.tooltipText,
|
onClick: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
|
||||||
title: btn.tooltipText,
|
btn.onCommandClick(ev);
|
||||||
name: btn.commandButtonLabel || btn.tooltipText,
|
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
|
||||||
disabled: btn.disabled,
|
},
|
||||||
ariaLabel: btn.ariaLabel,
|
key: `${btn.commandButtonLabel}${index}`,
|
||||||
buttonStyles: {
|
text: label,
|
||||||
root: {
|
"data-test": label,
|
||||||
backgroundColor: backgroundColor,
|
title: btn.tooltipText,
|
||||||
height: buttonHeightPx,
|
name: label,
|
||||||
paddingRight: 0,
|
disabled: btn.disabled,
|
||||||
paddingLeft: 0,
|
ariaLabel: btn.ariaLabel,
|
||||||
minWidth: 24,
|
buttonStyles: {
|
||||||
marginLeft: isSplit ? 0 : 5,
|
root: {
|
||||||
marginRight: isSplit ? 0 : 5
|
backgroundColor: backgroundColor,
|
||||||
|
height: buttonHeightPx,
|
||||||
|
paddingRight: 0,
|
||||||
|
paddingLeft: 0,
|
||||||
|
minWidth: 24,
|
||||||
|
marginLeft: isSplit ? 0 : 5,
|
||||||
|
marginRight: isSplit ? 0 : 5
|
||||||
|
},
|
||||||
|
rootDisabled: {
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
pointerEvents: "auto"
|
||||||
|
},
|
||||||
|
splitButtonMenuButton: {
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
selectors: {
|
||||||
|
":hover": { backgroundColor: StyleConstants.AccentLight }
|
||||||
},
|
},
|
||||||
rootDisabled: {
|
width: 16
|
||||||
backgroundColor: backgroundColor,
|
},
|
||||||
pointerEvents: "auto"
|
label: { fontSize: StyleConstants.mediumFontSize },
|
||||||
},
|
rootHovered: { backgroundColor: StyleConstants.AccentLight },
|
||||||
splitButtonMenuButton: {
|
rootPressed: { backgroundColor: StyleConstants.AccentLight },
|
||||||
backgroundColor: backgroundColor,
|
splitButtonMenuButtonExpanded: {
|
||||||
selectors: {
|
backgroundColor: StyleConstants.AccentExtra,
|
||||||
":hover": { backgroundColor: StyleConstants.AccentLight }
|
selectors: {
|
||||||
},
|
":hover": { backgroundColor: StyleConstants.AccentLight }
|
||||||
width: 16
|
|
||||||
},
|
|
||||||
label: { fontSize: StyleConstants.mediumFontSize },
|
|
||||||
rootHovered: { backgroundColor: StyleConstants.AccentLight },
|
|
||||||
rootPressed: { backgroundColor: StyleConstants.AccentLight },
|
|
||||||
splitButtonMenuButtonExpanded: {
|
|
||||||
backgroundColor: StyleConstants.AccentExtra,
|
|
||||||
selectors: {
|
|
||||||
":hover": { backgroundColor: StyleConstants.AccentLight }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
splitButtonDivider: {
|
|
||||||
display: "none"
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0
|
|
||||||
},
|
|
||||||
splitButtonContainer: {
|
|
||||||
marginLeft: 5,
|
|
||||||
marginRight: 5
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: btn.className,
|
splitButtonDivider: {
|
||||||
id: btn.id
|
display: "none"
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 0
|
||||||
|
},
|
||||||
|
splitButtonContainer: {
|
||||||
|
marginLeft: 5,
|
||||||
|
marginRight: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
className: btn.className,
|
||||||
|
id: btn.id
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSplit) {
|
||||||
|
// It's a split button
|
||||||
|
result.split = true;
|
||||||
|
|
||||||
|
result.subMenuProps = {
|
||||||
|
items: convertButton(btn.children, backgroundColor),
|
||||||
|
styles: {
|
||||||
|
list: {
|
||||||
|
// TODO Figure out how to do it the proper way with subComponentStyles.
|
||||||
|
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
|
||||||
|
selectors: {
|
||||||
|
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
|
||||||
|
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
|
||||||
|
".ms-ContextualMenu-icon": { width: 16, height: 16 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isSplit) {
|
result.menuIconProps = {
|
||||||
// It's a split button
|
iconType: IconType.image,
|
||||||
result.split = true;
|
style: {
|
||||||
|
width: 12,
|
||||||
result.subMenuProps = {
|
paddingLeft: 1,
|
||||||
items: CommandBarUtil.convertButton(btn.children, backgroundColor),
|
paddingTop: 6
|
||||||
styles: {
|
},
|
||||||
list: {
|
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt }
|
||||||
// TODO Figure out how to do it the proper way with subComponentStyles.
|
};
|
||||||
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
|
|
||||||
selectors: {
|
|
||||||
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
|
|
||||||
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
|
|
||||||
".ms-ContextualMenu-icon": { width: 16, height: 16 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result.menuIconProps = {
|
|
||||||
iconType: IconType.image,
|
|
||||||
style: {
|
|
||||||
width: 12,
|
|
||||||
paddingLeft: 1,
|
|
||||||
paddingTop: 6
|
|
||||||
},
|
|
||||||
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btn.isDropdown) {
|
|
||||||
const selectedChild = _.find(btn.children, child => child.dropdownItemKey === btn.dropdownSelectedKey);
|
|
||||||
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
|
|
||||||
|
|
||||||
const dropdownStyles: Partial<IDropdownStyles> = {
|
|
||||||
root: { margin: 5 },
|
|
||||||
dropdown: { width: btn.dropdownWidth },
|
|
||||||
title: { fontSize: 12, height: 30, lineHeight: 28 },
|
|
||||||
dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
|
|
||||||
dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 }
|
|
||||||
};
|
|
||||||
|
|
||||||
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
|
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
placeholder={btn.dropdownPlaceholder}
|
|
||||||
defaultSelectedKey={btn.dropdownSelectedKey}
|
|
||||||
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void =>
|
|
||||||
btn.children[index].onCommandClick(event)
|
|
||||||
}
|
|
||||||
options={btn.children.map((child: CommandButtonComponentProps) => ({
|
|
||||||
key: child.dropdownItemKey,
|
|
||||||
text: child.commandButtonLabel
|
|
||||||
}))}
|
|
||||||
styles={dropdownStyles}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btn.isArcadiaPicker && btn.arcadiaProps) {
|
|
||||||
result.commandBarButtonAs = () => <ArcadiaMenuPicker {...btn.arcadiaProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createDivider(key: string): ICommandBarItemProps {
|
if (btn.isDropdown) {
|
||||||
return {
|
const selectedChild = _.find(btn.children, child => child.dropdownItemKey === btn.dropdownSelectedKey);
|
||||||
onRender: () => (
|
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
|
||||||
<div className="dividerContainer">
|
|
||||||
<span />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
iconOnly: true,
|
|
||||||
disabled: true,
|
|
||||||
key: key
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static createMemoryTracker(key: string, memoryUsageInfo: Observable<MemoryUsageInfo>): ICommandBarItemProps {
|
const dropdownStyles: Partial<IDropdownStyles> = {
|
||||||
return {
|
root: { margin: 5 },
|
||||||
key,
|
dropdown: { width: btn.dropdownWidth },
|
||||||
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />
|
title: { fontSize: 12, height: 30, lineHeight: 28 },
|
||||||
};
|
dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
|
||||||
}
|
dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 }
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const onDropdownChange = (
|
||||||
|
event: React.FormEvent<HTMLDivElement>,
|
||||||
|
option?: IDropdownOption,
|
||||||
|
index?: number
|
||||||
|
): void => {
|
||||||
|
btn.children[index].onCommandClick(event);
|
||||||
|
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
|
||||||
|
};
|
||||||
|
|
||||||
|
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
placeholder={btn.dropdownPlaceholder}
|
||||||
|
defaultSelectedKey={btn.dropdownSelectedKey}
|
||||||
|
onChange={onDropdownChange}
|
||||||
|
options={btn.children.map((child: CommandButtonComponentProps) => ({
|
||||||
|
key: child.dropdownItemKey,
|
||||||
|
text: child.commandButtonLabel
|
||||||
|
}))}
|
||||||
|
styles={dropdownStyles}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn.isArcadiaPicker && btn.arcadiaProps) {
|
||||||
|
result.commandBarButtonAs = () => <ArcadiaMenuPicker {...btn.arcadiaProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDivider = (key: string): ICommandBarItemProps => {
|
||||||
|
return {
|
||||||
|
onRender: () => (
|
||||||
|
<div className="dividerContainer">
|
||||||
|
<span />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
iconOnly: true,
|
||||||
|
disabled: true,
|
||||||
|
key: key
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createMemoryTracker = (
|
||||||
|
key: string,
|
||||||
|
memoryUsageInfo: Observable<MemoryUsageInfo>
|
||||||
|
): ICommandBarItemProps => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ export interface OpenNotebookItem {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OpenCollectionItem = string;
|
export interface OpenCollectionItem {
|
||||||
|
databaseId: string;
|
||||||
|
collectionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
type: Type;
|
type: Type;
|
||||||
@@ -121,7 +124,11 @@ export class MostRecentActivity {
|
|||||||
public onItemClicked(item: Item) {
|
public onItemClicked(item: Item) {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case Type.OpenCollection: {
|
case Type.OpenCollection: {
|
||||||
const collection = this.container.findCollection(item.data as OpenCollectionItem);
|
const openCollectionitem = item.data as OpenCollectionItem;
|
||||||
|
const collection = this.container.findCollection(
|
||||||
|
openCollectionitem.databaseId,
|
||||||
|
openCollectionitem.collectionId
|
||||||
|
);
|
||||||
if (collection) {
|
if (collection) {
|
||||||
collection.openTab();
|
collection.openTab();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,24 +32,27 @@ import {
|
|||||||
import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging";
|
import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging";
|
||||||
import { sessions, kernels } from "rx-jupyter";
|
import { sessions, kernels } from "rx-jupyter";
|
||||||
import { RecordOf } from "immutable";
|
import { RecordOf } from "immutable";
|
||||||
|
import { AnyAction } from "redux";
|
||||||
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||||
import * as TextFile from "./contents/file/text-file";
|
import * as TextFile from "./contents/file/text-file";
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import { FileSystemUtil } from "../FileSystemUtil";
|
import { FileSystemUtil } from "../FileSystemUtil";
|
||||||
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
|
import { Areas } from "../../../Common/Constants";
|
||||||
|
|
||||||
interface NotebookServiceConfig extends JupyterServerConfig {
|
interface NotebookServiceConfig extends JupyterServerConfig {
|
||||||
userPuid?: string;
|
userPuid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logToTelemetry = (state: CdbAppState, title: string, error?: string) => {
|
const logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => {
|
||||||
TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, {
|
TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, {
|
||||||
databaseAccountName: state.cdb.databaseAccountName,
|
databaseAccountName: state.cdb.databaseAccountName,
|
||||||
defaultExperience: state.cdb.defaultExperience,
|
defaultExperience: state.cdb.defaultExperience,
|
||||||
@@ -311,7 +314,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
||||||
const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`;
|
const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||||
logToTelemetry(state$.value, "Launching alternate kernel", msg);
|
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||||
} else {
|
} else {
|
||||||
return of(
|
return of(
|
||||||
actions.launchKernelFailed({
|
actions.launchKernelFailed({
|
||||||
@@ -337,7 +340,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
||||||
}
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||||
logToTelemetry(state$.value, "Launching alternate kernel", msg);
|
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionPayload = {
|
const sessionPayload = {
|
||||||
@@ -634,7 +637,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
|||||||
const title = "Kernel restart";
|
const title = "Kernel restart";
|
||||||
const msg = "Kernel successfully restarted";
|
const msg = "Kernel successfully restarted";
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||||
logToTelemetry(state$.value, title, msg);
|
logFailureToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case actions.RESTART_KERNEL_FAILED:
|
case actions.RESTART_KERNEL_FAILED:
|
||||||
@@ -645,7 +648,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
|||||||
const title = "Save failure";
|
const title = "Save failure";
|
||||||
const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`;
|
const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
logToTelemetry(state$.value, title, msg);
|
logFailureToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case actions.FETCH_CONTENT_FAILED: {
|
case actions.FETCH_CONTENT_FAILED: {
|
||||||
@@ -654,7 +657,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
|||||||
const title = "Fetching content failure";
|
const title = "Fetching content failure";
|
||||||
const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`;
|
const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
logToTelemetry(state$.value, title, msg);
|
logFailureToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,7 +682,7 @@ const handleKernelConnectionLostEpic = (
|
|||||||
|
|
||||||
const msg = "Notebook was disconnected from kernel";
|
const msg = "Notebook was disconnected from kernel";
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
logToTelemetry(state, "Error", "Kernel connection error");
|
logFailureToTelemetry(state, "Error", "Kernel connection error");
|
||||||
|
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host as RecordOf<JupyterHostRecordProps>);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host as RecordOf<JupyterHostRecordProps>);
|
||||||
@@ -692,7 +695,7 @@ const handleKernelConnectionLostEpic = (
|
|||||||
const msg =
|
const msg =
|
||||||
"Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically.";
|
"Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically.";
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
logToTelemetry(state, "Kernel restart error", msg);
|
logFailureToTelemetry(state, "Kernel restart error", msg);
|
||||||
|
|
||||||
const explorer = window.dataExplorer;
|
const explorer = window.dataExplorer;
|
||||||
if (explorer) {
|
if (explorer) {
|
||||||
@@ -844,6 +847,105 @@ const closeContentFailedToFetchEpic = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const traceNotebookTelemetryEpic = (
|
||||||
|
action$: Observable<cdbActions.TraceNotebookTelemetryAction>,
|
||||||
|
state$: StateObservable<CdbAppState>
|
||||||
|
): Observable<{}> => {
|
||||||
|
return action$.pipe(
|
||||||
|
ofType(cdbActions.TRACE_NOTEBOOK_TELEMETRY),
|
||||||
|
mergeMap((action: cdbActions.TraceNotebookTelemetryAction) => {
|
||||||
|
const state = state$.value;
|
||||||
|
|
||||||
|
TelemetryProcessor.trace(action.payload.action, action.payload.actionModifier, {
|
||||||
|
...action.payload.data,
|
||||||
|
databaseAccountName: state.cdb.databaseAccountName,
|
||||||
|
defaultExperience: state.cdb.defaultExperience,
|
||||||
|
dataExplorerArea: Areas.Notebook
|
||||||
|
});
|
||||||
|
return EMPTY;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log notebook information to telemetry
|
||||||
|
* # raw cells, # markdown cells, # code cells, total
|
||||||
|
* @param action$
|
||||||
|
* @param state$
|
||||||
|
*/
|
||||||
|
const traceNotebookInfoEpic = (
|
||||||
|
action$: Observable<actions.FetchContentFulfilled>,
|
||||||
|
state$: StateObservable<AppState>
|
||||||
|
): Observable<{} | cdbActions.TraceNotebookTelemetryAction> => {
|
||||||
|
return action$.pipe(
|
||||||
|
ofType(actions.FETCH_CONTENT_FULFILLED),
|
||||||
|
mergeMap((action: { payload: any }) => {
|
||||||
|
const state = state$.value;
|
||||||
|
const contentRef = action.payload.contentRef;
|
||||||
|
const model = selectors.model(state, { contentRef });
|
||||||
|
|
||||||
|
// If it's not a notebook, we shouldn't be here
|
||||||
|
if (!model || model.type !== "notebook") {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataToLog = {
|
||||||
|
nbCodeCells: 0,
|
||||||
|
nbRawCells: 0,
|
||||||
|
nbMarkdownCells: 0,
|
||||||
|
nbCells: 0
|
||||||
|
};
|
||||||
|
for (let [id, cell] of selectors.notebook.cellMap(model)) {
|
||||||
|
switch (cell.cell_type) {
|
||||||
|
case "code":
|
||||||
|
dataToLog.nbCodeCells++;
|
||||||
|
break;
|
||||||
|
case "markdown":
|
||||||
|
dataToLog.nbMarkdownCells++;
|
||||||
|
break;
|
||||||
|
case "raw":
|
||||||
|
dataToLog.nbRawCells++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dataToLog.nbCells++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return of(
|
||||||
|
cdbActions.traceNotebookTelemetry({
|
||||||
|
action: TelemetryAction.NotebooksFetched,
|
||||||
|
actionModifier: ActionModifiers.Mark,
|
||||||
|
data: dataToLog
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log Kernel spec to start
|
||||||
|
* @param action$
|
||||||
|
* @param state$
|
||||||
|
*/
|
||||||
|
const traceNotebookKernelEpic = (
|
||||||
|
action$: Observable<AnyAction>,
|
||||||
|
state$: StateObservable<AppState>
|
||||||
|
): Observable<cdbActions.TraceNotebookTelemetryAction> => {
|
||||||
|
return action$.pipe(
|
||||||
|
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
|
||||||
|
mergeMap((action: { payload: any; type: string }) => {
|
||||||
|
return of(
|
||||||
|
cdbActions.traceNotebookTelemetry({
|
||||||
|
action: TelemetryAction.NotebooksKernelSpecName,
|
||||||
|
actionModifier: ActionModifiers.Mark,
|
||||||
|
data: {
|
||||||
|
kernelSpecName: action.payload.kernel.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const allEpics = [
|
export const allEpics = [
|
||||||
addInitialCodeCellEpic,
|
addInitialCodeCellEpic,
|
||||||
focusInitialCodeCellEpic,
|
focusInitialCodeCellEpic,
|
||||||
@@ -856,5 +958,8 @@ export const allEpics = [
|
|||||||
executeFocusedCellAndFocusNextEpic,
|
executeFocusedCellAndFocusNextEpic,
|
||||||
closeUnsupportedMimetypesEpic,
|
closeUnsupportedMimetypesEpic,
|
||||||
closeContentFailedToFetchEpic,
|
closeContentFailedToFetchEpic,
|
||||||
restartWebSocketKernelEpic
|
restartWebSocketKernelEpic,
|
||||||
|
traceNotebookTelemetryEpic,
|
||||||
|
traceNotebookInfoEpic,
|
||||||
|
traceNotebookKernelEpic
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
|
||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
import { Areas } from "../../../Common/Constants";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as cdbActions from "./actions";
|
import * as cdbActions from "./actions";
|
||||||
import { CdbRecord } from "./types";
|
import { CdbRecord } from "./types";
|
||||||
|
|
||||||
@@ -72,17 +70,6 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
|
|||||||
return state.set("hoveredCellId", typedAction.payload.cellId);
|
return state.set("hoveredCellId", typedAction.payload.cellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
case cdbActions.TRACE_NOTEBOOK_TELEMETRY: {
|
|
||||||
const typedAction = action as cdbActions.TraceNotebookTelemetryAction;
|
|
||||||
TelemetryProcessor.trace(typedAction.payload.action, typedAction.payload.actionModifier, {
|
|
||||||
...typedAction.payload.data,
|
|
||||||
databaseAccountName: state.databaseAccountName,
|
|
||||||
defaultExperience: state.defaultExperience,
|
|
||||||
dataExplorerArea: Areas.Notebook
|
|
||||||
});
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
|
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
|
||||||
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
|
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
|
||||||
var parentEltsMap = state.get("currentNotebookParentElements");
|
var parentEltsMap = state.get("currentNotebookParentElements");
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ export default class NotebookManager {
|
|||||||
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
|
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
let commitMsg = "Committed from Azure Cosmos DB Notebooks";
|
let commitMsg = "Committed from Azure Cosmos DB Notebooks";
|
||||||
this.params.container.showOkCancelTextFieldModalDialog(
|
this.params.container.showOkCancelModalDialog(
|
||||||
title || "Commit",
|
title || "Commit",
|
||||||
undefined,
|
undefined,
|
||||||
primaryButtonLabel || "Commit",
|
primaryButtonLabel || "Commit",
|
||||||
@@ -181,6 +181,7 @@ export default class NotebookManager {
|
|||||||
},
|
},
|
||||||
"Cancel",
|
"Cancel",
|
||||||
() => reject(new Error("Commit dialog canceled")),
|
() => reject(new Error("Commit dialog canceled")),
|
||||||
|
undefined,
|
||||||
{
|
{
|
||||||
label: "Commit message",
|
label: "Commit message",
|
||||||
autoAdjustHeight: true,
|
autoAdjustHeight: true,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import { CellToolbarContext } from "@nteract/stateful-components";
|
|||||||
import { CellType, CellId } from "@nteract/commutable";
|
import { CellType, CellId } from "@nteract/commutable";
|
||||||
import * as selectors from "@nteract/selectors";
|
import * as selectors from "@nteract/selectors";
|
||||||
import { RecordOf } from "immutable";
|
import { RecordOf } from "immutable";
|
||||||
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
|
||||||
export interface ComponentProps {
|
export interface ComponentProps {
|
||||||
contentRef: ContentRef;
|
contentRef: ContentRef;
|
||||||
@@ -29,6 +31,7 @@ interface DispatchProps {
|
|||||||
moveCell: (destinationId: CellId, above: boolean) => void;
|
moveCell: (destinationId: CellId, above: boolean) => void;
|
||||||
clearOutputs: () => void;
|
clearOutputs: () => void;
|
||||||
deleteCell: () => void;
|
deleteCell: () => void;
|
||||||
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
@@ -48,12 +51,18 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
{
|
{
|
||||||
key: "Run",
|
key: "Run",
|
||||||
text: "Run",
|
text: "Run",
|
||||||
onClick: this.props.executeCell
|
onClick: () => {
|
||||||
|
this.props.executeCell();
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Clear Outputs",
|
key: "Clear Outputs",
|
||||||
text: "Clear Outputs",
|
text: "Clear Outputs",
|
||||||
onClick: this.props.clearOutputs
|
onClick: () => {
|
||||||
|
this.props.clearOutputs();
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksClearOutputsFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Divider",
|
key: "Divider",
|
||||||
@@ -64,31 +73,43 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
|
|
||||||
items = items.concat([
|
items = items.concat([
|
||||||
{
|
{
|
||||||
key: "Divider",
|
key: "Divider2",
|
||||||
itemType: ContextualMenuItemType.Divider
|
itemType: ContextualMenuItemType.Divider
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Insert Code Cell Above",
|
key: "Insert Code Cell Above",
|
||||||
text: "Insert Code Cell Above",
|
text: "Insert Code Cell Above",
|
||||||
onClick: this.props.insertCodeCellAbove
|
onClick: () => {
|
||||||
|
this.props.insertCodeCellAbove();
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertCodeCellAboveFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Insert Code Cell Below",
|
key: "Insert Code Cell Below",
|
||||||
text: "Insert Code Cell Below",
|
text: "Insert Code Cell Below",
|
||||||
onClick: this.props.insertCodeCellBelow
|
onClick: () => {
|
||||||
|
this.props.insertCodeCellBelow();
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertCodeCellBelowFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Insert Text Cell Above",
|
key: "Insert Text Cell Above",
|
||||||
text: "Insert Text Cell Above",
|
text: "Insert Text Cell Above",
|
||||||
onClick: this.props.insertTextCellAbove
|
onClick: () => {
|
||||||
|
this.props.insertTextCellAbove();
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellAboveFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Insert Text Cell Below",
|
key: "Insert Text Cell Below",
|
||||||
text: "Insert Text Cell Below",
|
text: "Insert Text Cell Below",
|
||||||
onClick: this.props.insertTextCellBelow
|
onClick: () => {
|
||||||
|
this.props.insertTextCellBelow();
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellBelowFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Divider",
|
key: "Divider3",
|
||||||
itemType: ContextualMenuItemType.Divider
|
itemType: ContextualMenuItemType.Divider
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -98,7 +119,10 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
moveItems.push({
|
moveItems.push({
|
||||||
key: "Move Cell Up",
|
key: "Move Cell Up",
|
||||||
text: "Move Cell Up",
|
text: "Move Cell Up",
|
||||||
onClick: () => this.props.moveCell(this.props.cellIdAbove, true)
|
onClick: () => {
|
||||||
|
this.props.moveCell(this.props.cellIdAbove, true);
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellUpFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +130,16 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
moveItems.push({
|
moveItems.push({
|
||||||
key: "Move Cell Down",
|
key: "Move Cell Down",
|
||||||
text: "Move Cell Down",
|
text: "Move Cell Down",
|
||||||
onClick: () => this.props.moveCell(this.props.cellIdBelow, false)
|
onClick: () => {
|
||||||
|
this.props.moveCell(this.props.cellIdBelow, false);
|
||||||
|
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellDownFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moveItems.length > 0) {
|
if (moveItems.length > 0) {
|
||||||
moveItems.push({
|
moveItems.push({
|
||||||
key: "Divider",
|
key: "Divider4",
|
||||||
itemType: ContextualMenuItemType.Divider
|
itemType: ContextualMenuItemType.Divider
|
||||||
});
|
});
|
||||||
items = items.concat(moveItems);
|
items = items.concat(moveItems);
|
||||||
@@ -121,7 +148,10 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
items.push({
|
items.push({
|
||||||
key: "Delete Cell",
|
key: "Delete Cell",
|
||||||
text: "Delete Cell",
|
text: "Delete Cell",
|
||||||
onClick: this.props.deleteCell
|
onClick: () => {
|
||||||
|
this.props.deleteCell();
|
||||||
|
this.props.traceNotebookTelemetry(Action.DeleteCellFromMenu, ActionModifiers.Mark);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuItemLabel = "More";
|
const menuItemLabel = "More";
|
||||||
@@ -156,7 +186,9 @@ const mapDispatchToProps = (
|
|||||||
moveCell: (destinationId: CellId, above: boolean) =>
|
moveCell: (destinationId: CellId, above: boolean) =>
|
||||||
dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
|
dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
|
||||||
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
|
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
|
||||||
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef }))
|
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
||||||
|
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) =>
|
||||||
|
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data }))
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => {
|
const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
.nteract-cell-input .nteract-cell-source {
|
.nteract-cell-input .nteract-cell-source {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adaptation for the R kernel's inline lists **/
|
/** Adaptation for the R kernel's inline lists **/
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe("Add Collection Pane", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -101,12 +101,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
public showUpsellMessage: ko.PureComputed<boolean>;
|
public showUpsellMessage: ko.PureComputed<boolean>;
|
||||||
public shouldCreateMongoWildcardIndex: ko.Observable<boolean>;
|
public shouldCreateMongoWildcardIndex: ko.Observable<boolean>;
|
||||||
|
|
||||||
private _databaseOffers: HashMap<DataModels.Offer>;
|
|
||||||
private _isSynapseLinkEnabled: ko.Computed<boolean>;
|
private _isSynapseLinkEnabled: ko.Computed<boolean>;
|
||||||
|
|
||||||
constructor(options: AddCollectionPaneOptions) {
|
constructor(options: AddCollectionPaneOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this._databaseOffers = new HashMap<DataModels.Offer>();
|
|
||||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
|
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
|
||||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||||
@@ -327,7 +325,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
!this.container.isEmulator &&
|
configContext.platform !== Platform.Emulator &&
|
||||||
!this.container.isTryCosmosDBSubscription() &&
|
!this.container.isTryCosmosDBSubscription() &&
|
||||||
this.container.getPlatformType() !== PlatformType.Portal
|
this.container.getPlatformType() !== PlatformType.Portal
|
||||||
) {
|
) {
|
||||||
@@ -339,7 +337,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.maxCollectionsReached = ko.computed<boolean>(() => {
|
this.maxCollectionsReached = ko.computed<boolean>(() => {
|
||||||
@@ -481,7 +479,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.databaseCreateNew()) {
|
if (!this.databaseCreateNew()) {
|
||||||
this.databaseHasSharedOffer(this._databaseOffers.has(selectedDatabaseId));
|
const selectedDatabase: ViewModels.Database = this.container
|
||||||
|
.databases()
|
||||||
|
.find((database: ViewModels.Database) => database.id() === selectedDatabaseId);
|
||||||
|
this.databaseHasSharedOffer(!!selectedDatabase?.offer());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -749,15 +750,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
|
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
|
||||||
const cachedDatabaseIdsList = _.map(newDatabaseIds, (database: ViewModels.Database) => {
|
this.databaseIds(newDatabaseIds?.map((database: ViewModels.Database) => database.id()));
|
||||||
if (database && database.offer && database.offer()) {
|
|
||||||
this._databaseOffers.set(database.id(), database.offer());
|
|
||||||
}
|
|
||||||
|
|
||||||
return database.id();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.databaseIds(cachedDatabaseIdsList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeOfferThroughput(): number {
|
private _computeOfferThroughput(): number {
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ describe("Add Database Pane", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({
|
explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if subscription type is Benefits", () => {
|
it("should be true if subscription type is Benefits", () => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
|||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
@@ -180,7 +181,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
!this.container.isEmulator &&
|
configContext.platform !== Platform.Emulator &&
|
||||||
!this.container.isTryCosmosDBSubscription() &&
|
!this.container.isTryCosmosDBSubscription() &&
|
||||||
this.container.getPlatformType() !== PlatformType.Portal
|
this.container.getPlatformType() !== PlatformType.Portal
|
||||||
) {
|
) {
|
||||||
@@ -203,7 +204,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
|||||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { HashMap } from "../../Common/HashMap";
|
import { HashMap } from "../../Common/HashMap";
|
||||||
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||||
public createTableQuery: ko.Observable<string>;
|
public createTableQuery: ko.Observable<string>;
|
||||||
@@ -231,11 +232,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (!this.container.isEmulator && !this.container.isTryCosmosDBSubscription()) {
|
if (configContext.platform !== Platform.Emulator && !this.container.isTryCosmosDBSubscription()) {
|
||||||
const offerThroughput: number = this.throughput();
|
const offerThroughput: number = this.throughput();
|
||||||
return offerThroughput <= 100000;
|
return offerThroughput <= 100000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if 1 database and 1 collection", () => {
|
it("should be true if 1 database and 1 collection", () => {
|
||||||
@@ -56,7 +56,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
|
|
||||||
describe("shouldRecordFeedback()", () => {
|
describe("shouldRecordFeedback()", () => {
|
||||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||||
let fakeExplorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
let fakeExplorer = new Explorer();
|
||||||
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||||
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,11 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
|
|||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.close();
|
this.close();
|
||||||
this.container.selectedNode(selectedCollection.database);
|
this.container.selectedNode(selectedCollection.database);
|
||||||
this.container.tabsManager?.closeTabsByComparator(tab => tab.node && tab.node.rid === selectedCollection.rid);
|
this.container.tabsManager?.closeTabsByComparator(
|
||||||
|
tab =>
|
||||||
|
tab.node?.id() === selectedCollection.id() &&
|
||||||
|
(tab.node as ViewModels.Collection).databaseId === selectedCollection.databaseId
|
||||||
|
);
|
||||||
this.container.refreshAllDatabases();
|
this.container.refreshAllDatabases();
|
||||||
this.resetData();
|
this.resetData();
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if only 1 database", () => {
|
it("should be true if only 1 database", () => {
|
||||||
|
|||||||
@@ -69,12 +69,16 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
|||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.close();
|
this.close();
|
||||||
this.container.refreshAllDatabases();
|
this.container.refreshAllDatabases();
|
||||||
this.container.tabsManager.closeTabsByComparator(tab => tab.node && tab.node.rid === selectedDatabase.rid);
|
this.container.tabsManager.closeTabsByComparator(tab => tab.node?.id() === selectedDatabase.id());
|
||||||
this.container.selectedNode(null);
|
this.container.selectedNode(null);
|
||||||
selectedDatabase
|
selectedDatabase
|
||||||
.collections()
|
.collections()
|
||||||
.forEach((collection: ViewModels.Collection) =>
|
.forEach((collection: ViewModels.Collection) =>
|
||||||
this.container.tabsManager.closeTabsByComparator(tab => tab.node && tab.node.rid === collection.rid)
|
this.container.tabsManager.closeTabsByComparator(
|
||||||
|
tab =>
|
||||||
|
tab.node?.id() === collection.id() &&
|
||||||
|
(tab.node as ViewModels.Collection).databaseId === collection.databaseId
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this.resetData();
|
this.resetData();
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ describe("Settings Pane", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true for SQL API", () => {
|
it("should be true for SQL API", () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Explorer from "../Explorer";
|
|||||||
jest.mock("../Explorer");
|
jest.mock("../Explorer");
|
||||||
|
|
||||||
const createExplorer = () => {
|
const createExplorer = () => {
|
||||||
const mock = new Explorer({} as any);
|
const mock = new Explorer();
|
||||||
mock.selectedNode = ko.observable();
|
mock.selectedNode = ko.observable();
|
||||||
mock.isNotebookEnabled = ko.observable(false);
|
mock.isNotebookEnabled = ko.observable(false);
|
||||||
mock.addCollectionText = ko.observable("add collection");
|
mock.addCollectionText = ko.observable("add collection");
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ export var htmlSelectors = {
|
|||||||
dataTableScrollContainerSelector: ".dataTables_scroll",
|
dataTableScrollContainerSelector: ".dataTables_scroll",
|
||||||
dataTableHeaderTypeSelector: "table thead th",
|
dataTableHeaderTypeSelector: "table thead th",
|
||||||
dataTablePaginationButtonSelector: ".paginate_button",
|
dataTablePaginationButtonSelector: ".paginate_button",
|
||||||
|
dataTableHeaderTableSelector: "#storageTable_wrapper .dataTables_scrollHeadInner table",
|
||||||
|
dataTableBodyTableSelector: "#storageTable_wrapper .dataTables_scrollBody table",
|
||||||
searchInputField: ".search-input",
|
searchInputField: ".search-input",
|
||||||
uploadDropdownSelector: "#upload-dropdown",
|
uploadDropdownSelector: "#upload-dropdown",
|
||||||
navigationDropdownSelector: "#navigation-dropdown",
|
navigationDropdownSelector: "#navigation-dropdown",
|
||||||
|
|||||||
18
src/Explorer/Tables/CqlUtilities.test.ts
Normal file
18
src/Explorer/Tables/CqlUtilities.test.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { getQuotedCqlIdentifier } from "./CqlUtilities";
|
||||||
|
|
||||||
|
describe("getQuotedCqlIdentifier", () => {
|
||||||
|
it("undefined id", () => {
|
||||||
|
const result = getQuotedCqlIdentifier(undefined);
|
||||||
|
expect(result).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("id with no quotes", () => {
|
||||||
|
const result = getQuotedCqlIdentifier("foo");
|
||||||
|
expect(result).toBe('"foo"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("id with quotes", () => {
|
||||||
|
const result = getQuotedCqlIdentifier('"foo"');
|
||||||
|
expect(result).toBe('"""foo"""');
|
||||||
|
});
|
||||||
|
});
|
||||||
12
src/Explorer/Tables/CqlUtilities.ts
Normal file
12
src/Explorer/Tables/CqlUtilities.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export function getQuotedCqlIdentifier(identifier: string): string {
|
||||||
|
let result = identifier;
|
||||||
|
if (!identifier) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identifier.includes('"')) {
|
||||||
|
result = identifier.replace(/"/g, '""');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `"${result}"`;
|
||||||
|
}
|
||||||
@@ -143,6 +143,21 @@ function createDataTable(
|
|||||||
fnInitComplete: initializeTable,
|
fnInitComplete: initializeTable,
|
||||||
fnDrawCallback: updateSelectionStatus
|
fnDrawCallback: updateSelectionStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(tableEntityListViewModel.table.table(0).container() as Element)
|
||||||
|
.querySelectorAll(Constants.htmlSelectors.dataTableHeaderTableSelector)
|
||||||
|
.forEach(table => {
|
||||||
|
table.setAttribute(
|
||||||
|
"summary",
|
||||||
|
`Header for sorting results for container ${tableEntityListViewModel.queryTablesTab.collection.id()}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
(tableEntityListViewModel.table.table(0).container() as Element)
|
||||||
|
.querySelectorAll(Constants.htmlSelectors.dataTableBodyTableSelector)
|
||||||
|
.forEach(table => {
|
||||||
|
table.setAttribute("summary", `Results for container ${tableEntityListViewModel.queryTablesTab.collection.id()}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindColumn(data: any, type: string, full: any) {
|
function bindColumn(data: any, type: string, full: any) {
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
import Q from "q";
|
|
||||||
|
|
||||||
import * as Constants from "../Constants";
|
|
||||||
import TableCommands from "./TableCommands";
|
|
||||||
import TableEntityListViewModel from "./TableEntityListViewModel";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ContextMenu view representation
|
|
||||||
*/
|
|
||||||
export default class DataTableContextMenu {
|
|
||||||
public viewModel: TableEntityListViewModel;
|
|
||||||
|
|
||||||
// There is one context menu for each selector on each tab and they should all be registered here.
|
|
||||||
// Once the context menus are registered, we should access them through this instance.
|
|
||||||
public static Instance: { [key: string]: { contextMenu: DataTableContextMenu } } = {};
|
|
||||||
|
|
||||||
private _tableCommands: TableCommands;
|
|
||||||
|
|
||||||
constructor(viewModel: TableEntityListViewModel, tableCommands: TableCommands) {
|
|
||||||
this.viewModel = viewModel;
|
|
||||||
this._tableCommands = tableCommands;
|
|
||||||
|
|
||||||
this.registerTableBodyContextMenu();
|
|
||||||
this.registerTableHeaderContextMenu();
|
|
||||||
|
|
||||||
DataTableContextMenu.Instance[viewModel.queryTablesTab.tabId] = { contextMenu: this };
|
|
||||||
}
|
|
||||||
|
|
||||||
public unregisterContextMenu(selector: string): void {
|
|
||||||
$.contextMenu("destroy", "div#" + this.viewModel.queryTablesTab.tabId + ".tab-pane " + selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerTableBodyContextMenu(): void {
|
|
||||||
// Localize
|
|
||||||
$.contextMenu({
|
|
||||||
selector:
|
|
||||||
"div#" + this.viewModel.queryTablesTab.tabId + ".tab-pane " + Constants.htmlSelectors.dataTableBodyRowSelector,
|
|
||||||
callback: this.bodyContextMenuSelect,
|
|
||||||
items: {
|
|
||||||
edit: {
|
|
||||||
name: "Edit",
|
|
||||||
cmd: TableCommands.editEntityCommand,
|
|
||||||
icon: "edit-entity",
|
|
||||||
disabled: () => !this.isEnabled(TableCommands.editEntityCommand)
|
|
||||||
},
|
|
||||||
delete: {
|
|
||||||
name: "Delete",
|
|
||||||
cmd: TableCommands.deleteEntitiesCommand,
|
|
||||||
icon: "delete-entity",
|
|
||||||
disabled: () => !this.isEnabled(TableCommands.deleteEntitiesCommand)
|
|
||||||
},
|
|
||||||
reorder: {
|
|
||||||
name: "Reorder Columns Based on Schema",
|
|
||||||
cmd: TableCommands.reorderColumnsCommand,
|
|
||||||
icon: "shift-non-empty-columns-left",
|
|
||||||
disabled: () => !this.isEnabled(TableCommands.reorderColumnsCommand)
|
|
||||||
},
|
|
||||||
reset: {
|
|
||||||
name: "Reset Columns",
|
|
||||||
cmd: TableCommands.resetColumnsCommand,
|
|
||||||
icon: "reset-column-order"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerTableHeaderContextMenu(): void {
|
|
||||||
// Localize
|
|
||||||
$.contextMenu({
|
|
||||||
selector:
|
|
||||||
"div#" + this.viewModel.queryTablesTab.tabId + ".tab-pane " + Constants.htmlSelectors.dataTableHeadRowSelector,
|
|
||||||
callback: this.headerContextMenuSelect,
|
|
||||||
items: {
|
|
||||||
customizeColumns: {
|
|
||||||
name: "Column Options",
|
|
||||||
cmd: TableCommands.customizeColumnsCommand,
|
|
||||||
icon: "customize-columns"
|
|
||||||
},
|
|
||||||
reset: {
|
|
||||||
name: "Reset Columns",
|
|
||||||
cmd: TableCommands.resetColumnsCommand,
|
|
||||||
icon: "reset-column-order"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private isEnabled(commandName: string): boolean {
|
|
||||||
return this._tableCommands.isEnabled(commandName, this.viewModel.selected());
|
|
||||||
}
|
|
||||||
|
|
||||||
private headerContextMenuSelect = (key: any, options: any): void => {
|
|
||||||
var promise: Q.Promise<any> = null;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case TableCommands.customizeColumnsCommand:
|
|
||||||
promise = this._tableCommands.customizeColumnsCommand(this.viewModel);
|
|
||||||
break;
|
|
||||||
case TableCommands.resetColumnsCommand:
|
|
||||||
promise = Q.resolve(this._tableCommands.resetColumns(this.viewModel));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (promise) {
|
|
||||||
promise.then(() => {
|
|
||||||
this.viewModel.focusDataTable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private bodyContextMenuSelect = (key: any, options: any): void => {
|
|
||||||
var promise: Q.Promise<any> = null;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case TableCommands.editEntityCommand:
|
|
||||||
promise = this._tableCommands.editEntityCommand(this.viewModel);
|
|
||||||
break;
|
|
||||||
case TableCommands.deleteEntitiesCommand:
|
|
||||||
promise = this._tableCommands.deleteEntitiesCommand(this.viewModel);
|
|
||||||
break;
|
|
||||||
case TableCommands.reorderColumnsCommand:
|
|
||||||
promise = this._tableCommands.reorderColumnsBasedOnSelectedEntities(this.viewModel);
|
|
||||||
break;
|
|
||||||
case TableCommands.resetColumnsCommand:
|
|
||||||
promise = Q.resolve(this._tableCommands.resetColumns(this.viewModel));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (promise) {
|
|
||||||
promise.then(() => {
|
|
||||||
this.viewModel.focusDataTable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A context menu factory to construct the one context menu for each tab/table view model.
|
|
||||||
*/
|
|
||||||
public static contextMenuFactory(viewModel: TableEntityListViewModel, tableCommands: TableCommands) {
|
|
||||||
if (!DataTableContextMenu.Instance[viewModel.queryTablesTab.tabId]) {
|
|
||||||
DataTableContextMenu.Instance[viewModel.queryTablesTab.tabId] = {
|
|
||||||
contextMenu: new DataTableContextMenu(viewModel, tableCommands)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,18 +41,6 @@ export default class DataTableOperationManager {
|
|||||||
this.tryOpenEditor();
|
this.tryOpenEditor();
|
||||||
};
|
};
|
||||||
|
|
||||||
private contextMenu = (event: JQueryEventObject) => {
|
|
||||||
var elem: JQuery = $(event.currentTarget);
|
|
||||||
this.updateLastSelectedItem(elem, event.shiftKey);
|
|
||||||
|
|
||||||
this.applyContextMenuSelection(elem);
|
|
||||||
setTimeout(function() {
|
|
||||||
$(".context-menu-list")
|
|
||||||
.attr("tabindex", -1)
|
|
||||||
.focus();
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
private keyDown = (event: JQueryEventObject): boolean => {
|
private keyDown = (event: JQueryEventObject): boolean => {
|
||||||
var isUpArrowKey: boolean = event.keyCode === Constants.keyCodes.UpArrow,
|
var isUpArrowKey: boolean = event.keyCode === Constants.keyCodes.UpArrow,
|
||||||
isDownArrowKey: boolean = event.keyCode === Constants.keyCodes.DownArrow,
|
isDownArrowKey: boolean = event.keyCode === Constants.keyCodes.DownArrow,
|
||||||
@@ -293,7 +281,6 @@ export default class DataTableOperationManager {
|
|||||||
public bind() {
|
public bind() {
|
||||||
this.dataTable.on("click", "tr", this.click);
|
this.dataTable.on("click", "tr", this.click);
|
||||||
this.dataTable.on("dblclick", "tr", this.doubleClick);
|
this.dataTable.on("dblclick", "tr", this.doubleClick);
|
||||||
this.dataTable.on("contextmenu", "tr", this.contextMenu);
|
|
||||||
this.dataTable.on("keydown", "td", this.keyDown);
|
this.dataTable.on("keydown", "td", this.keyDown);
|
||||||
this.dataTable.on("keyup", "td", this.keyUp);
|
this.dataTable.on("keyup", "td", this.keyUp);
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import Q from "q";
|
|||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CassandraTableKey, CassandraAPIDataClient } from "../TableDataClient";
|
import { CassandraTableKey, CassandraAPIDataClient } from "../TableDataClient";
|
||||||
import DataTableViewModel from "./DataTableViewModel";
|
import DataTableViewModel from "./DataTableViewModel";
|
||||||
import DataTableContextMenu from "./DataTableContextMenu";
|
|
||||||
import * as DataTableUtilities from "./DataTableUtilities";
|
import * as DataTableUtilities from "./DataTableUtilities";
|
||||||
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import TableCommands from "./TableCommands";
|
import TableCommands from "./TableCommands";
|
||||||
import TableEntityCache from "./TableEntityCache";
|
import TableEntityCache from "./TableEntityCache";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
@@ -56,11 +56,11 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
this.cache = new TableEntityCache();
|
this.cache = new TableEntityCache();
|
||||||
this.queryErrorMessage = ko.observable<string>();
|
this.queryErrorMessage = ko.observable<string>();
|
||||||
this.queryTablesTab = queryTablesTab;
|
this.queryTablesTab = queryTablesTab;
|
||||||
// Enable Context menu for the data table.
|
|
||||||
DataTableContextMenu.contextMenuFactory(this, tableCommands);
|
|
||||||
this.id = `tableEntityListViewModel${this.queryTablesTab.tabId}`;
|
this.id = `tableEntityListViewModel${this.queryTablesTab.tabId}`;
|
||||||
this.cqlQuery = ko.observable<string>(
|
this.cqlQuery = ko.observable<string>(
|
||||||
`SELECT * FROM ${this.queryTablesTab.collection.databaseId}.${this.queryTablesTab.collection.id()}`
|
`SELECT * FROM ${getQuotedCqlIdentifier(this.queryTablesTab.collection.databaseId)}.${getQuotedCqlIdentifier(
|
||||||
|
this.queryTablesTab.collection.id()
|
||||||
|
)}`
|
||||||
);
|
);
|
||||||
this.oDataQuery = ko.observable<string>();
|
this.oDataQuery = ko.observable<string>();
|
||||||
this.sqlQuery = ko.observable<string>("SELECT * FROM c");
|
this.sqlQuery = ko.observable<string>("SELECT * FROM c");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as CustomTimestampHelper from "./CustomTimestampHelper";
|
import * as CustomTimestampHelper from "./CustomTimestampHelper";
|
||||||
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
import QueryClauseViewModel from "./QueryClauseViewModel";
|
import QueryClauseViewModel from "./QueryClauseViewModel";
|
||||||
import ClauseGroup from "./ClauseGroup";
|
import ClauseGroup from "./ClauseGroup";
|
||||||
import ClauseGroupViewModel from "./ClauseGroupViewModel";
|
import ClauseGroupViewModel from "./ClauseGroupViewModel";
|
||||||
@@ -237,7 +238,7 @@ export default class QueryBuilderViewModel {
|
|||||||
public getCqlFilterFromClauses = (): string => {
|
public getCqlFilterFromClauses = (): string => {
|
||||||
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
|
const databaseId = this._queryViewModel.queryTablesTab.collection.databaseId;
|
||||||
const collectionId = this._queryViewModel.queryTablesTab.collection.id();
|
const collectionId = this._queryViewModel.queryTablesTab.collection.id();
|
||||||
const tableToQuery = `${databaseId}.${collectionId}`;
|
const tableToQuery = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
|
||||||
var filterString: string = `SELECT * FROM ${tableToQuery}`;
|
var filterString: string = `SELECT * FROM ${tableToQuery}`;
|
||||||
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
if (this._queryViewModel.selectText() && this._queryViewModel.selectText().length > 0) {
|
||||||
filterString = "SELECT";
|
filterString = "SELECT";
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
|
|||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
|
|
||||||
export default class QueryViewModel {
|
export default class QueryViewModel {
|
||||||
public topValueLimitMessage: string = "Please input a number between 0 and 1000.";
|
public topValueLimitMessage: string = "Please input a number between 0 and 1000.";
|
||||||
@@ -189,7 +190,9 @@ export default class QueryViewModel {
|
|||||||
this._tableEntityListViewModel.oDataQuery("");
|
this._tableEntityListViewModel.oDataQuery("");
|
||||||
this._tableEntityListViewModel.sqlQuery("SELECT * FROM c");
|
this._tableEntityListViewModel.sqlQuery("SELECT * FROM c");
|
||||||
this._tableEntityListViewModel.cqlQuery(
|
this._tableEntityListViewModel.cqlQuery(
|
||||||
`SELECT * FROM ${this.queryTablesTab.collection.databaseId}.${this.queryTablesTab.collection.id()}`
|
`SELECT * FROM ${getQuotedCqlIdentifier(this.queryTablesTab.collection.databaseId)}.${getQuotedCqlIdentifier(
|
||||||
|
this.queryTablesTab.collection.id()
|
||||||
|
)}`
|
||||||
);
|
);
|
||||||
return this._tableEntityListViewModel.reloadTable(false);
|
return this._tableEntityListViewModel.reloadTable(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
private _documentsIterator: MinimalQueryIterator;
|
private _documentsIterator: MinimalQueryIterator;
|
||||||
private _container: Explorer;
|
private _container: Explorer;
|
||||||
private _acceptButtonLabel: ko.Observable<string> = ko.observable("Save");
|
private _acceptButtonLabel: ko.Observable<string> = ko.observable("Save");
|
||||||
protected _selfLink: string;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.ConflictsTabOptions) {
|
constructor(options: ViewModels.ConflictsTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
@@ -74,7 +73,6 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
this.selectedConflictCurrent = editable.observable<any>("");
|
this.selectedConflictCurrent = editable.observable<any>("");
|
||||||
this.partitionKey = options.partitionKey || (this.collection && this.collection.partitionKey);
|
this.partitionKey = options.partitionKey || (this.collection && this.collection.partitionKey);
|
||||||
this.conflictIds = options.conflictIds;
|
this.conflictIds = options.conflictIds;
|
||||||
this._selfLink = options.selfLink || (this.collection && this.collection.self);
|
|
||||||
this.partitionKeyPropertyHeader =
|
this.partitionKeyPropertyHeader =
|
||||||
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
|
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
|
||||||
this.partitionKeyProperty = !!this.partitionKeyPropertyHeader
|
this.partitionKeyProperty = !!this.partitionKeyPropertyHeader
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
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 { 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 { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
const updateThroughputBeyondLimitWarningMessage: string = `
|
const updateThroughputBeyondLimitWarningMessage: string = `
|
||||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||||
@@ -196,7 +197,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.computed(() => {
|
this.costsVisible = ko.computed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
|
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
|
||||||
@@ -207,7 +208,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
);
|
);
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
!!this.container.isEmulator ||
|
configContext.platform === Platform.Emulator ||
|
||||||
this.container.getPlatformType() === PlatformType.Hosted ||
|
this.container.getPlatformType() === PlatformType.Hosted ||
|
||||||
this.canThroughputExceedMaximumValue()
|
this.canThroughputExceedMaximumValue()
|
||||||
) {
|
) {
|
||||||
@@ -455,8 +456,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
this._buildCommandBarOptions();
|
this._buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSaveClick = (): Q.Promise<any> => {
|
public onSaveClick = async (): Promise<any> => {
|
||||||
let promises: Q.Promise<void>[] = [];
|
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
|
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
@@ -470,163 +470,81 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
|
|
||||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||||
|
|
||||||
if (this.isAutoPilotSelected()) {
|
try {
|
||||||
const offer = this.database.offer();
|
if (this.isAutoPilotSelected()) {
|
||||||
let offerAutopilotSettings: any = {};
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
databaseId: this.database.id(),
|
||||||
offerAutopilotSettings.maxThroughput = this.autoPilotThroughput();
|
currentOffer: this.database.offer(),
|
||||||
|
autopilotThroughput: this.autoPilotThroughput(),
|
||||||
|
manualThroughput: undefined,
|
||||||
|
migrateToAutoPilot: this._hasProvisioningTypeChanged()
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
|
this.database.offer(updatedOffer);
|
||||||
|
this.database.offer.valueHasMutated();
|
||||||
|
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
||||||
} else {
|
} else {
|
||||||
offerAutopilotSettings.tier = this.selectedAutoPilotTier();
|
if (this.throughput.editableIsDirty() || this.isAutoPilotSelected.editableIsDirty()) {
|
||||||
}
|
const originalThroughputValue = this.throughput.getEditableOriginalValue();
|
||||||
const newOffer: DataModels.Offer = {
|
const newThroughput = this.throughput();
|
||||||
content: {
|
|
||||||
offerThroughput: undefined,
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: false,
|
|
||||||
offerAutopilotSettings
|
|
||||||
},
|
|
||||||
_etag: undefined,
|
|
||||||
_ts: undefined,
|
|
||||||
_rid: offer._rid,
|
|
||||||
_self: offer._self,
|
|
||||||
id: offer.id,
|
|
||||||
offerResourceId: offer.offerResourceId,
|
|
||||||
offerVersion: offer.offerVersion,
|
|
||||||
offerType: offer.offerType,
|
|
||||||
resource: offer.resource
|
|
||||||
};
|
|
||||||
|
|
||||||
// user has changed from provisioned --> autoscale
|
if (
|
||||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
this.canThroughputExceedMaximumValue() &&
|
||||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||||
delete newOffer.content.offerAutopilotSettings;
|
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million
|
||||||
}
|
) {
|
||||||
|
const requestPayload = {
|
||||||
const updateOfferPromise = updateOffer(this.database.offer(), newOffer, headerOptions).then(
|
subscriptionId: userContext.subscriptionId,
|
||||||
(updatedOffer: DataModels.Offer) => {
|
databaseAccountName: userContext.databaseAccount.name,
|
||||||
this.database.offer(updatedOffer);
|
resourceGroup: userContext.resourceGroup,
|
||||||
this.database.offer.valueHasMutated();
|
databaseName: this.database.id(),
|
||||||
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
throughput: newThroughput,
|
||||||
}
|
|
||||||
);
|
|
||||||
promises.push(updateOfferPromise);
|
|
||||||
} else {
|
|
||||||
if (this.throughput.editableIsDirty() || this.isAutoPilotSelected.editableIsDirty()) {
|
|
||||||
const offer = this.database.offer();
|
|
||||||
const originalThroughputValue = this.throughput.getEditableOriginalValue();
|
|
||||||
const newThroughput = this.throughput();
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.canThroughputExceedMaximumValue() &&
|
|
||||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
|
||||||
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million
|
|
||||||
) {
|
|
||||||
const requestPayload = {
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
databaseAccountName: userContext.databaseAccount.name,
|
|
||||||
resourceGroup: userContext.resourceGroup,
|
|
||||||
databaseName: this.database.id(),
|
|
||||||
throughput: newThroughput,
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: false
|
|
||||||
};
|
|
||||||
const updateOfferBeyondLimitPromise = updateOfferThroughputBeyondLimit(requestPayload).then(
|
|
||||||
() => {
|
|
||||||
this.database.offer().content.offerThroughput = originalThroughputValue;
|
|
||||||
this.throughput(originalThroughputValue);
|
|
||||||
this.notificationStatusInfo(
|
|
||||||
throughputApplyDelayedMessage(this.isAutoPilotSelected(), newThroughput, this.database.id())
|
|
||||||
);
|
|
||||||
this.throughput.valueHasMutated(); // force component re-render
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.UpdateSettings,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
databaseName: this.database && this.database.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
error: error
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
promises.push(Q(updateOfferBeyondLimitPromise));
|
|
||||||
} else {
|
|
||||||
const newOffer: DataModels.Offer = {
|
|
||||||
content: {
|
|
||||||
offerThroughput: newThroughput,
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: false
|
offerIsRUPerMinuteThroughputEnabled: false
|
||||||
},
|
};
|
||||||
_etag: undefined,
|
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||||
_ts: undefined,
|
this.database.offer().content.offerThroughput = originalThroughputValue;
|
||||||
_rid: offer._rid,
|
this.throughput(originalThroughputValue);
|
||||||
_self: offer._self,
|
this.notificationStatusInfo(
|
||||||
id: offer.id,
|
throughputApplyDelayedMessage(this.isAutoPilotSelected(), newThroughput, this.database.id())
|
||||||
offerResourceId: offer.offerResourceId,
|
);
|
||||||
offerVersion: offer.offerVersion,
|
this.throughput.valueHasMutated(); // force component re-render
|
||||||
offerType: offer.offerType,
|
} else {
|
||||||
resource: offer.resource
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
};
|
databaseId: this.database.id(),
|
||||||
|
currentOffer: this.database.offer(),
|
||||||
|
autopilotThroughput: undefined,
|
||||||
|
manualThroughput: newThroughput,
|
||||||
|
migrateToManual: this._hasProvisioningTypeChanged()
|
||||||
|
};
|
||||||
|
|
||||||
// user has changed from autoscale --> provisioned
|
const updatedOffer = await updateOffer(updateOfferParams);
|
||||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
||||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
this.database.offer(updatedOffer);
|
||||||
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
this.database.offer.valueHasMutated();
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateOfferPromise = updateOffer(this.database.offer(), newOffer, headerOptions).then(
|
|
||||||
(updatedOffer: DataModels.Offer) => {
|
|
||||||
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
|
||||||
this.database.offer(updatedOffer);
|
|
||||||
this.database.offer.valueHasMutated();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(updateOfferPromise);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
this.container.isRefreshingExplorer(false);
|
||||||
if (promises.length === 0) {
|
this.isExecutionError(true);
|
||||||
|
console.error(error);
|
||||||
|
this.displayedError(ErrorParserUtility.parse(error)[0].message);
|
||||||
|
TelemetryProcessor.traceFailure(
|
||||||
|
Action.UpdateSettings,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
|
databaseName: this.database && this.database.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle(),
|
||||||
|
error: error
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q.all(promises)
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
this.container.isRefreshingExplorer(false);
|
|
||||||
this._setBaseline();
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.UpdateSettings,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle()
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(reason: any) => {
|
|
||||||
this.container.isRefreshingExplorer(false);
|
|
||||||
this.isExecutionError(true);
|
|
||||||
console.error(reason);
|
|
||||||
this.displayedError(ErrorParserUtility.parse(reason)[0].message);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.UpdateSettings,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle()
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => this.isExecuting(false));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public onRevertClick = (): Q.Promise<any> => {
|
public onRevertClick = (): Q.Promise<any> => {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
|
|
||||||
@@ -27,15 +26,9 @@ describe("Documents tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("showPartitionKey", () => {
|
describe("showPartitionKey", () => {
|
||||||
const explorer = new Explorer({
|
const explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const mongoExplorer = new Explorer({
|
const mongoExplorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
|
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
|
||||||
|
|
||||||
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
|
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
|
||||||
@@ -95,7 +88,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
|
|
||||||
@@ -113,7 +105,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
|
|
||||||
@@ -131,7 +122,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
|
|
||||||
@@ -149,7 +139,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
|
|
||||||
@@ -167,7 +156,6 @@ describe("Documents tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
|
|
||||||
private _documentsIterator: QueryIterator<ItemDefinition & Resource>;
|
private _documentsIterator: QueryIterator<ItemDefinition & Resource>;
|
||||||
private _resourceTokenPartitionKey: string;
|
private _resourceTokenPartitionKey: string;
|
||||||
protected _selfLink: string;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.DocumentsTabOptions) {
|
constructor(options: ViewModels.DocumentsTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
@@ -91,7 +90,6 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
this.partitionKey = options.partitionKey || (this.collection && this.collection.partitionKey);
|
this.partitionKey = options.partitionKey || (this.collection && this.collection.partitionKey);
|
||||||
this._resourceTokenPartitionKey = options.resourceTokenPartitionKey;
|
this._resourceTokenPartitionKey = options.resourceTokenPartitionKey;
|
||||||
this.documentIds = options.documentIds;
|
this.documentIds = options.documentIds;
|
||||||
this._selfLink = options.selfLink || (this.collection && this.collection.self);
|
|
||||||
|
|
||||||
this.partitionKeyPropertyHeader =
|
this.partitionKeyPropertyHeader =
|
||||||
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
|
(this.collection && this.collection.partitionKeyPropertyHeader) || this._getPartitionKeyPropertyHeader();
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ export default class GraphTab extends TabsBase {
|
|||||||
onIsFilterQueryLoading: (isFilterQueryLoading: boolean): void => this.isFilterQueryLoading(isFilterQueryLoading),
|
onIsFilterQueryLoading: (isFilterQueryLoading: boolean): void => this.isFilterQueryLoading(isFilterQueryLoading),
|
||||||
onIsValidQuery: (isValidQuery: boolean): void => this.isValidQuery(isValidQuery),
|
onIsValidQuery: (isValidQuery: boolean): void => this.isValidQuery(isValidQuery),
|
||||||
collectionPartitionKeyProperty: options.collectionPartitionKeyProperty,
|
collectionPartitionKeyProperty: options.collectionPartitionKeyProperty,
|
||||||
collectionRid: this.rid,
|
|
||||||
collectionSelfLink: options.selfLink,
|
|
||||||
graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account),
|
graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account),
|
||||||
databaseId: options.databaseId,
|
databaseId: options.databaseId,
|
||||||
collectionId: options.collectionId,
|
collectionId: options.collectionId,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ describe("Query Tab", () => {
|
|||||||
database: database,
|
database: database,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||||
@@ -49,7 +48,7 @@ describe("Query Tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true for accounts using SQL API", () => {
|
it("should be true for accounts using SQL API", () => {
|
||||||
@@ -69,7 +68,7 @@ describe("Query Tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be visible when using a supported API", () => {
|
it("should be visible when using a supported API", () => {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
protected monacoSettings: ViewModels.MonacoEditorSettings;
|
protected monacoSettings: ViewModels.MonacoEditorSettings;
|
||||||
private _executeQueryButtonTitle: ko.Observable<string>;
|
private _executeQueryButtonTitle: ko.Observable<string>;
|
||||||
protected _iterator: MinimalQueryIterator;
|
protected _iterator: MinimalQueryIterator;
|
||||||
private _selfLink: string;
|
|
||||||
private _isSaveQueriesEnabled: ko.Computed<boolean>;
|
private _isSaveQueriesEnabled: ko.Computed<boolean>;
|
||||||
private _resourceTokenPartitionKey: string;
|
private _resourceTokenPartitionKey: string;
|
||||||
|
|
||||||
@@ -86,7 +85,6 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this.errors = ko.observableArray<ViewModels.QueryError>([]);
|
this.errors = ko.observableArray<ViewModels.QueryError>([]);
|
||||||
this._partitionKey = options.partitionKey;
|
this._partitionKey = options.partitionKey;
|
||||||
this._resourceTokenPartitionKey = options.resourceTokenPartitionKey;
|
this._resourceTokenPartitionKey = options.resourceTokenPartitionKey;
|
||||||
this._selfLink = options.selfLink;
|
|
||||||
this.splitterId = this.tabId + "_splitter";
|
this.splitterId = this.tabId + "_splitter";
|
||||||
this.isPreferredApiMongoDB = false;
|
this.isPreferredApiMongoDB = false;
|
||||||
this.aggregatedQueryMetrics = ko.observable<DataModels.QueryMetrics>();
|
this.aggregatedQueryMetrics = ko.observable<DataModels.QueryMetrics>();
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(
|
collection: new Collection(
|
||||||
@@ -79,7 +78,7 @@ describe("Settings tab", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,7 +177,7 @@ describe("Settings tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,7 +186,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||||
@@ -209,8 +207,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||||
@@ -227,8 +223,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||||
@@ -256,7 +250,7 @@ describe("Settings tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -265,8 +259,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||||
@@ -281,8 +273,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||||
@@ -306,8 +296,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||||
@@ -337,10 +325,7 @@ describe("Settings tab", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
||||||
const explorer = new Explorer({
|
const explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
explorer.defaultExperience(defaultApi);
|
explorer.defaultExperience(defaultApi);
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
|
|
||||||
@@ -383,8 +368,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: getCollection(defaultApi, partitionKeyOption),
|
collection: getCollection(defaultApi, partitionKeyOption),
|
||||||
@@ -470,10 +453,7 @@ describe("Settings tab", () => {
|
|||||||
|
|
||||||
describe("AutoPilot", () => {
|
describe("AutoPilot", () => {
|
||||||
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
|
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
|
||||||
const explorer = new Explorer({
|
const explorer = new Explorer();
|
||||||
notificationsClient: null,
|
|
||||||
isEmulator: false
|
|
||||||
});
|
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
|
|
||||||
explorer.databaseAccount({
|
explorer.databaseAccount({
|
||||||
@@ -526,8 +506,6 @@ describe("Settings tab", () => {
|
|||||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
collection: getCollection(autoPilotTier),
|
collection: getCollection(autoPilotTier),
|
||||||
|
|||||||
@@ -13,15 +13,16 @@ import Q from "q";
|
|||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { PlatformType } from "../../PlatformType";
|
import { PlatformType } from "../../PlatformType";
|
||||||
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 } 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 { configContext, Platform } from "../../ConfigContext";
|
||||||
|
|
||||||
const ttlWarning: string = `
|
const ttlWarning: string = `
|
||||||
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
|
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
|
||||||
@@ -454,7 +455,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.rupmVisible = ko.computed(() => {
|
this.rupmVisible = ko.computed(() => {
|
||||||
if (this.container.isEmulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
||||||
@@ -484,7 +485,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.computed(() => {
|
this.costsVisible = ko.computed(() => {
|
||||||
return !this.container.isEmulator;
|
return configContext.platform !== Platform.Emulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isTryCosmosDBSubscription = ko.computed<boolean>(() => {
|
this.isTryCosmosDBSubscription = ko.computed<boolean>(() => {
|
||||||
@@ -500,7 +501,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (this.container.isEmulator) {
|
if (configContext.platform === Platform.Emulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +712,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs();
|
const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs();
|
||||||
const isEmulator = this.container.isEmulator;
|
const isEmulator = configContext.platform === Platform.Emulator;
|
||||||
if (isThroughputGreaterThanMaxRus && isEmulator) {
|
if (isThroughputGreaterThanMaxRus && isEmulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -881,7 +882,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||||
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
||||||
|
|
||||||
const throughputExceedsMaxValue: boolean = !this.container.isEmulator && this.throughput() > this.maxRUs();
|
const throughputExceedsMaxValue: boolean =
|
||||||
|
configContext.platform !== Platform.Emulator && this.throughput() > this.maxRUs();
|
||||||
|
|
||||||
const ttlOptionDirty: boolean = this.timeToLive.editableIsDirty();
|
const ttlOptionDirty: boolean = this.timeToLive.editableIsDirty();
|
||||||
const ttlOrIndexingPolicyFieldsDirty: boolean =
|
const ttlOrIndexingPolicyFieldsDirty: boolean =
|
||||||
@@ -1175,7 +1177,21 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
);
|
);
|
||||||
this.throughput.valueHasMutated(); // force component re-render
|
this.throughput.valueHasMutated(); // force component re-render
|
||||||
} 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.isAutoPilotSelected() ? this.autoPilotThroughput() : undefined,
|
||||||
|
manualThroughput: this.isAutoPilotSelected() ? undefined : newThroughput
|
||||||
|
};
|
||||||
|
if (this._hasProvisioningTypeChanged()) {
|
||||||
|
if (this.isAutoPilotSelected()) {
|
||||||
|
updateOfferParams.migrateToAutoPilot = true;
|
||||||
|
} else {
|
||||||
|
updateOfferParams.migrateToManual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
this.collection.offer(updatedOffer);
|
this.collection.offer(updatedOffer);
|
||||||
this.collection.offer.valueHasMutated();
|
this.collection.offer.valueHasMutated();
|
||||||
}
|
}
|
||||||
@@ -1217,6 +1233,9 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onRevertClick = (): Q.Promise<any> => {
|
public onRevertClick = (): Q.Promise<any> => {
|
||||||
|
TelemetryProcessor.trace(Action.DiscardSettings, ActionModifiers.Mark, {
|
||||||
|
message: "Settings Discarded"
|
||||||
|
});
|
||||||
this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
|
this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
|
||||||
this.timeToLive.setBaseline(this.timeToLive.getEditableOriginalValue());
|
this.timeToLive.setBaseline(this.timeToLive.getEditableOriginalValue());
|
||||||
this.timeToLiveSeconds.setBaseline(this.timeToLiveSeconds.getEditableOriginalValue());
|
this.timeToLiveSeconds.setBaseline(this.timeToLiveSeconds.getEditableOriginalValue());
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default class SettingsTabV2 extends TabsBase {
|
|||||||
|
|
||||||
constructor(options: ViewModels.TabOptions) {
|
constructor(options: ViewModels.TabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
|
this.tabId = "SettingsV2-" + this.tabId;
|
||||||
const props: SettingsComponentProps = {
|
const props: SettingsComponentProps = {
|
||||||
settingsTab: this
|
settingsTab: this
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
databaseAccountName: this.getContainer().databaseAccount().name,
|
databaseAccountName: this.getContainer().databaseAccount().name,
|
||||||
defaultExperience: this.getContainer().defaultExperience(),
|
defaultExperience: this.getContainer().defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.tabTitle()
|
tabTitle: this.tabTitle(),
|
||||||
|
tabId: this.tabId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +146,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
databaseAccountName: this.getContainer().databaseAccount().name,
|
databaseAccountName: this.getContainer().databaseAccount().name,
|
||||||
defaultExperience: this.getContainer().defaultExperience(),
|
defaultExperience: this.getContainer().defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.tabTitle()
|
tabTitle: this.tabTitle(),
|
||||||
|
tabId: this.tabId
|
||||||
});
|
});
|
||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe("Tabs manager tests", () => {
|
|||||||
let documentsTab: DocumentsTab;
|
let documentsTab: DocumentsTab;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
explorer = new Explorer({ notificationsClient: undefined, isEmulator: false });
|
explorer = new Explorer();
|
||||||
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
|
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
|
||||||
id: "test",
|
id: "test",
|
||||||
name: "test",
|
name: "test",
|
||||||
@@ -50,7 +50,6 @@ describe("Tabs manager tests", () => {
|
|||||||
database,
|
database,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
onUpdateTabsButtons: undefined
|
onUpdateTabsButtons: undefined
|
||||||
@@ -63,7 +62,6 @@ describe("Tabs manager tests", () => {
|
|||||||
collection,
|
collection,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
selfLink: "",
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
onUpdateTabsButtons: undefined
|
onUpdateTabsButtons: undefined
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { configContext } from "../../ConfigContext";
|
|||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||||
|
|
||||||
export default class Collection implements ViewModels.Collection {
|
export default class Collection implements ViewModels.Collection {
|
||||||
public nodeKind: string;
|
public nodeKind: string;
|
||||||
@@ -238,7 +239,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.expandCollection();
|
this.expandCollection();
|
||||||
}
|
}
|
||||||
this.container.onUpdateTabsButtons([]);
|
this.container.onUpdateTabsButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
this.container.tabsManager.refreshActiveTab(
|
||||||
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public collapseCollection() {
|
public collapseCollection() {
|
||||||
@@ -289,7 +292,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
tab => tab.collection && tab.collection.rid === this.rid
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as DocumentsTab[];
|
) as DocumentsTab[];
|
||||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||||
|
|
||||||
@@ -311,8 +314,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
documentIds: ko.observableArray<DocumentId>([]),
|
documentIds: ko.observableArray<DocumentId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "Items",
|
title: "Items",
|
||||||
|
|
||||||
selfLink: this.self,
|
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
@@ -340,7 +341,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.Conflicts,
|
ViewModels.CollectionTabKind.Conflicts,
|
||||||
tab => tab.collection && tab.collection.rid === this.rid
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as ConflictsTab[];
|
) as ConflictsTab[];
|
||||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||||
|
|
||||||
@@ -362,8 +363,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
conflictIds: ko.observableArray<ConflictId>([]),
|
conflictIds: ko.observableArray<ConflictId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Conflicts,
|
tabKind: ViewModels.CollectionTabKind.Conflicts,
|
||||||
title: "Conflicts",
|
title: "Conflicts",
|
||||||
|
|
||||||
selfLink: this.self,
|
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
@@ -397,7 +396,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.QueryTables,
|
ViewModels.CollectionTabKind.QueryTables,
|
||||||
tab => tab.collection && tab.collection.rid === this.rid
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as QueryTablesTab[];
|
) as QueryTablesTab[];
|
||||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||||
|
|
||||||
@@ -426,7 +425,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
|
|
||||||
node: this,
|
node: this,
|
||||||
selfLink: this.self,
|
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
@@ -451,7 +449,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.Graph,
|
ViewModels.CollectionTabKind.Graph,
|
||||||
tab => tab.collection && tab.collection.rid === this.rid
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as GraphTab[];
|
) as GraphTab[];
|
||||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||||
|
|
||||||
@@ -477,7 +475,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
collection: this,
|
collection: this,
|
||||||
selfLink: this.self,
|
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
||||||
@@ -507,7 +504,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
tab => tab.collection && tab.collection.rid === this.rid
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as MongoDocumentsTab[];
|
) as MongoDocumentsTab[];
|
||||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||||
|
|
||||||
@@ -534,7 +531,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
|
|
||||||
node: this,
|
node: this,
|
||||||
selfLink: this.self,
|
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
@@ -560,7 +556,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
||||||
return tab.collection && tab.collection.rid === this.rid;
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
});
|
});
|
||||||
|
|
||||||
const traceStartData = {
|
const traceStartData = {
|
||||||
@@ -578,7 +574,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
selfLink: this.self,
|
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||||
@@ -671,7 +666,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
selfLink: this.self,
|
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
queryText: queryText,
|
queryText: queryText,
|
||||||
@@ -703,7 +697,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
selfLink: this.self,
|
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
partitionKey: collection.partitionKey,
|
partitionKey: collection.partitionKey,
|
||||||
@@ -734,7 +727,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
title: title,
|
title: title,
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
collection: this,
|
collection: this,
|
||||||
selfLink: this.self,
|
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
||||||
@@ -758,7 +750,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
||||||
selfLink: this.self,
|
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||||
});
|
});
|
||||||
@@ -821,7 +812,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandStoredProcedures();
|
this.expandStoredProcedures();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
this.container.tabsManager.refreshActiveTab(
|
||||||
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandStoredProcedures() {
|
public expandStoredProcedures() {
|
||||||
@@ -878,7 +871,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandUserDefinedFunctions();
|
this.expandUserDefinedFunctions();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
this.container.tabsManager.refreshActiveTab(
|
||||||
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandUserDefinedFunctions() {
|
public expandUserDefinedFunctions() {
|
||||||
@@ -935,7 +930,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandTriggers();
|
this.expandTriggers();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
this.container.tabsManager.refreshActiveTab(
|
||||||
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandTriggers() {
|
public expandTriggers() {
|
||||||
@@ -1028,26 +1025,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.uploadFiles(event.originalEvent.dataTransfer.files);
|
this.uploadFiles(event.originalEvent.dataTransfer.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isCollectionNodeSelected(): boolean {
|
|
||||||
return (
|
|
||||||
this.isSubNodeSelected(ViewModels.CollectionTabKind.Query) ||
|
|
||||||
(!this.isCollectionExpanded() &&
|
|
||||||
this.container.selectedNode &&
|
|
||||||
this.container.selectedNode() &&
|
|
||||||
this.container.selectedNode().rid === this.rid &&
|
|
||||||
this.container.selectedNode().nodeKind === "Collection")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isSubNodeSelected(nodeKind: ViewModels.CollectionTabKind): boolean {
|
|
||||||
return (
|
|
||||||
this.container.selectedNode &&
|
|
||||||
this.container.selectedNode() &&
|
|
||||||
this.container.selectedNode().rid === this.rid &&
|
|
||||||
this.selectedSubnodeKind() === nodeKind
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onDeleteCollectionContextMenuClick(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
|
public onDeleteCollectionContextMenuClick(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
|
||||||
this.container.deleteCollectionConfirmationPane.open();
|
this.container.deleteCollectionConfirmationPane.open();
|
||||||
}
|
}
|
||||||
@@ -1213,7 +1190,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
||||||
this.container.notificationsClient.fetchNotifications().then(
|
fetchPortalNotifications().then(
|
||||||
(notifications: DataModels.Notification[]) => {
|
(notifications: DataModels.Notification[]) => {
|
||||||
if (!notifications || notifications.length === 0) {
|
if (!notifications || notifications.length === 0) {
|
||||||
deferred.resolve(undefined);
|
deferred.resolve(undefined);
|
||||||
@@ -1283,10 +1260,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _getOfferForCollection(offers: DataModels.Offer[], collection: DataModels.Collection): DataModels.Offer {
|
|
||||||
return _.find(offers, (offer: DataModels.Offer) => offer.resource.indexOf(collection._rid) >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level method that will open the correct tab type depending on account API
|
* Top-level method that will open the correct tab type depending on account API
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import * as Logger from "../../Common/Logger";
|
|||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { readCollections } from "../../Common/dataAccess/readCollections";
|
import { readCollections } from "../../Common/dataAccess/readCollections";
|
||||||
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||||
|
|
||||||
export default class Database implements ViewModels.Database {
|
export default class Database implements ViewModels.Database {
|
||||||
public nodeKind: string;
|
public nodeKind: string;
|
||||||
@@ -55,7 +57,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(
|
const matchingTabs = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
ViewModels.CollectionTabKind.DatabaseSettings,
|
||||||
tab => tab.rid === this.rid
|
tab => tab.node?.id() === this.id()
|
||||||
);
|
);
|
||||||
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
||||||
if (!settingsTab) {
|
if (!settingsTab) {
|
||||||
@@ -77,7 +79,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
rid: this.rid,
|
rid: this.rid,
|
||||||
database: this,
|
database: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
||||||
selfLink: this.self,
|
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||||
@@ -126,8 +127,8 @@ export default class Database implements ViewModels.Database {
|
|||||||
!this.isDatabaseExpanded() &&
|
!this.isDatabaseExpanded() &&
|
||||||
this.container.selectedNode &&
|
this.container.selectedNode &&
|
||||||
this.container.selectedNode() &&
|
this.container.selectedNode() &&
|
||||||
this.container.selectedNode().rid === this.rid &&
|
this.container.selectedNode().nodeKind === "Database" &&
|
||||||
this.container.selectedNode().nodeKind === "Database"
|
this.container.selectedNode().id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,8 +216,8 @@ export default class Database implements ViewModels.Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
||||||
this.container.notificationsClient.fetchNotifications().then(
|
fetchPortalNotifications().then(
|
||||||
(notifications: DataModels.Notification[]) => {
|
notifications => {
|
||||||
if (!notifications || notifications.length === 0) {
|
if (!notifications || notifications.length === 0) {
|
||||||
deferred.resolve(undefined);
|
deferred.resolve(undefined);
|
||||||
return;
|
return;
|
||||||
@@ -260,7 +261,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
(collection: DataModels.Collection) => {
|
(collection: DataModels.Collection) => {
|
||||||
const collectionExists = _.some(
|
const collectionExists = _.some(
|
||||||
this.collections(),
|
this.collections(),
|
||||||
(existingCollection: Collection) => existingCollection.rid === collection._rid
|
(existingCollection: Collection) => existingCollection.id() === collection.id
|
||||||
);
|
);
|
||||||
return !collectionExists;
|
return !collectionExists;
|
||||||
}
|
}
|
||||||
@@ -270,7 +271,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||||
const collectionPresentInUpdatedList = _.some(
|
const collectionPresentInUpdatedList = _.some(
|
||||||
updatedCollectionsList,
|
updatedCollectionsList,
|
||||||
(coll: DataModels.Collection) => coll._rid === collection.rid
|
(coll: DataModels.Collection) => coll.id === collection.id()
|
||||||
);
|
);
|
||||||
if (!collectionPresentInUpdatedList) {
|
if (!collectionPresentInUpdatedList) {
|
||||||
collectionsToDelete.push(collection);
|
collectionsToDelete.push(collection);
|
||||||
@@ -296,7 +297,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
const collectionsToKeep: Collection[] = [];
|
const collectionsToKeep: Collection[] = [];
|
||||||
|
|
||||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||||
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.rid === collection.rid);
|
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.id() === collection.id());
|
||||||
if (!shouldRemoveCollection) {
|
if (!shouldRemoveCollection) {
|
||||||
collectionsToKeep.push(collection);
|
collectionsToKeep.push(collection);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
selfLink: this.self,
|
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
queryText: queryText,
|
queryText: queryText,
|
||||||
@@ -121,7 +120,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
|
|
||||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab: TabsBase) => tab.collection && tab.collection.rid === this.rid
|
(tab: TabsBase) =>
|
||||||
|
tab.collection?.id() === this.id() &&
|
||||||
|
(tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
|
||||||
) as DocumentsTab[];
|
) as DocumentsTab[];
|
||||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||||
|
|
||||||
@@ -143,7 +144,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
documentIds: ko.observableArray<DocumentId>([]),
|
documentIds: ko.observableArray<DocumentId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "Items",
|
title: "Items",
|
||||||
selfLink: this.self,
|
|
||||||
isActive: ko.observable<boolean>(false),
|
isActive: ko.observable<boolean>(false),
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user