mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-21 18:54:22 +00:00
Compare commits
4 Commits
codescan
...
remove-red
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8180c03969 | ||
|
|
ff1cb5603b | ||
|
|
2dbbf6baab | ||
|
|
4c11a940a0 |
29
.github/workflows/automerge.yml
vendored
Normal file
29
.github/workflows/automerge.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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"
|
||||||
71
.github/workflows/codeql-analysis.yml
vendored
71
.github/workflows/codeql-analysis.yml
vendored
@@ -1,71 +0,0 @@
|
|||||||
# 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 @@
|
|||||||
# Cosmos DB Explorer
|
# CosmosDB 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,8 +3,11 @@
|
|||||||
# 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
Normal file
177
externals/jquery.contextMenu.css
vendored
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*!
|
||||||
|
* 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
Normal file
1686
externals/jquery.contextMenu.js
vendored
Normal file
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: 55,
|
slowMo: 30,
|
||||||
defaultViewport: null,
|
defaultViewport: null,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
args: ["--disable-web-security"]
|
args: ["--disable-web-security"]
|
||||||
|
|||||||
@@ -2082,8 +2082,7 @@ a:link {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow-x: auto;
|
overflow: 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.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.5.8.tgz",
|
||||||
"integrity": "sha512-9L3fb1H1as+J3J2j2EDx1HEMdrucjgR4INqahy+ZAxDPFvR3HCOedYzx645zObBIPu7QkH2LAjPk4fuNGHR1rg==",
|
"integrity": "sha512-2RsftiXa5ojNXuHRIC7RybWbN+Z7TMrLK2XGTza1Wq/KHRJNB48WmQuxjd6SzsNguqxRoHsH0sUogIwlK+NO8A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.9",
|
"@microsoft/applicationinsights-common": "2.5.8",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.9",
|
"@microsoft/applicationinsights-core-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3",
|
"@microsoft/applicationinsights-shims": "1.0.1",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-channel-js": {
|
"@microsoft/applicationinsights-channel-js": {
|
||||||
"version": "2.5.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.5.8.tgz",
|
||||||
"integrity": "sha512-NAQ/2wWmD+gaIZDCMzzwxm8RcbswDvUO5BYeuW9UHJaFuEZ9o9xpztKVz32u4CMv7OI/mLOqnmR4rb0d+kUMwQ==",
|
"integrity": "sha512-etVGzhNluflTikOpW9dlDxdg+B+8gt/nxmGeXqti9Hsqq0fTxcY9bmNklFIEKhOIwai4DodgJjflaDdgR0ObUQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.9",
|
"@microsoft/applicationinsights-common": "2.5.8",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.9",
|
"@microsoft/applicationinsights-core-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3",
|
"@microsoft/applicationinsights-shims": "1.0.1",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-common": {
|
"@microsoft/applicationinsights-common": {
|
||||||
"version": "2.5.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.5.8.tgz",
|
||||||
"integrity": "sha512-dKmXO9m55uRDhpoa0P7l+BApf+lsrqjgoLeKv+ABM8ygIyd9JH6CDcdaT3af+kUFtt9Oj3ChyfueKr1EVOdGkQ==",
|
"integrity": "sha512-RIMAJTsF0H5wiRZxYIF9H8sbMe2W5Ig4yMuH0/lho69DcNZpHf8p6PSa4Qhhli0AnoWYfLE7/WlWO1eR5SkByw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.9",
|
"@microsoft/applicationinsights-core-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3"
|
"@microsoft/applicationinsights-shims": "1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-core-js": {
|
"@microsoft/applicationinsights-core-js": {
|
||||||
"version": "2.5.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.5.8.tgz",
|
||||||
"integrity": "sha512-KE9h1wmC/Ckm7jYjsMF1SEWQnk0v0CRzZq1upSARgPH7BgmyClXz1kdnLtuTWz8Aha8IIH9dW2hUOfPCdR+BpQ==",
|
"integrity": "sha512-NxxIViHKuqgpla+KdQk7Qy48Dm8lN4oy2mMEpv9kM5GW5MBJ8nZ4A5RV1kokF3kXuDmTUTHlWBXeLR8hauA3qQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3",
|
"@microsoft/applicationinsights-shims": "1.0.1",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-dependencies-js": {
|
"@microsoft/applicationinsights-dependencies-js": {
|
||||||
"version": "2.5.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.5.8.tgz",
|
||||||
"integrity": "sha512-pNM/dkUOscV0ul/YJe928+77EBtRkRXO/le/VWzlunoUFaEEo4pirc7NycvPx9w/KxA62JMEogbQsWE6nAmqPg==",
|
"integrity": "sha512-jnXpjE/bnlLeew78OsN8aPTLOPZQJ4y1MOO8R3E+eUXdproD2TemynSk5kUfrMdry91DZOBZnrmJ2NCB+g5ArQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.9",
|
"@microsoft/applicationinsights-common": "2.5.8",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.9",
|
"@microsoft/applicationinsights-core-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3",
|
"@microsoft/applicationinsights-shims": "1.0.1",
|
||||||
"@microsoft/dynamicproto-js": "^1.0.0"
|
"@microsoft/dynamicproto-js": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-properties-js": {
|
"@microsoft/applicationinsights-properties-js": {
|
||||||
"version": "2.5.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.5.8.tgz",
|
||||||
"integrity": "sha512-mZxaC8CZsURn38IwsPaUx+o9QXQU2vm81THZL+1Lc+7scPo55ATDTFgZ2awIj7CdTp69oGzUkpB7maOn6+OVOw==",
|
"integrity": "sha512-BAOSguXe07ua6SqYmAW+8+vVvBZb4qmmUP6s5zc7kNMMgEoEGsCn2VHmM8wHHxq4J/TmYxIqwoufS+XKbUvCeQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-common": "2.5.9",
|
"@microsoft/applicationinsights-common": "2.5.8",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.9",
|
"@microsoft/applicationinsights-core-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3"
|
"@microsoft/applicationinsights-shims": "1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-shims": {
|
"@microsoft/applicationinsights-shims": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-1.0.1.tgz",
|
||||||
"integrity": "sha512-+S17aqEkOYpyBpmclhgwcEplwnxSo5AxYBdRg38GBobI1GKPSpZfnLssLzcjJ6XZCS5tqB5xjyTZs6gHj7ZJWQ=="
|
"integrity": "sha512-nPjUBSpvX5Dnkovp2lZzrg0/uSvYNtbAclWwVP7t8J1hy5OJ3xr3KPNaz79+b84G16Rj861ybau9Gbk7inXkTg=="
|
||||||
},
|
},
|
||||||
"@microsoft/applicationinsights-web": {
|
"@microsoft/applicationinsights-web": {
|
||||||
"version": "2.5.9",
|
"version": "2.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-2.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-2.5.8.tgz",
|
||||||
"integrity": "sha512-dxg5XXbQqjWw9QmGdgbd7knb1qFA58FFYj9ObqRmlqiihk25kper7H15HH8LaV0lV6goClmBWc9KsNGA2veyeA==",
|
"integrity": "sha512-ZxCUkBJrCFNHa0LgWWbtwqu4TxJPlukuSvDrdLv6XV1yX2ETq6Q1kw/IUEtKhbtNbTZQ8aJ+x8Nc/iqbssdXTA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@microsoft/applicationinsights-analytics-js": "2.5.9",
|
"@microsoft/applicationinsights-analytics-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-channel-js": "2.5.9",
|
"@microsoft/applicationinsights-channel-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-common": "2.5.9",
|
"@microsoft/applicationinsights-common": "2.5.8",
|
||||||
"@microsoft/applicationinsights-core-js": "2.5.9",
|
"@microsoft/applicationinsights-core-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-dependencies-js": "2.5.9",
|
"@microsoft/applicationinsights-dependencies-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-properties-js": "2.5.9",
|
"@microsoft/applicationinsights-properties-js": "2.5.8",
|
||||||
"@microsoft/applicationinsights-shims": "1.0.3",
|
"@microsoft/applicationinsights-shims": "1.0.1",
|
||||||
"@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.9",
|
"@microsoft/applicationinsights-web": "2.5.8",
|
||||||
"@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",
|
||||||
"url-polyfill": "1.1.7",
|
|
||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
|
"url-polyfill": "1.1.7",
|
||||||
"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,6 +117,7 @@ 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";
|
||||||
@@ -130,11 +131,6 @@ 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,6 +72,22 @@ 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,6 +157,41 @@ 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();
|
||||||
|
|||||||
46
src/Common/NotificationsClientBase.ts
Normal file
46
src/Common/NotificationsClientBase.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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,14 +20,26 @@ 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) {
|
||||||
offerId = await (window.authType === AuthType.AAD &&
|
if (
|
||||||
!userContext.useSDKOperations &&
|
window.authType === AuthType.AAD &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
!userContext.useSDKOperations &&
|
||||||
? getDatabaseOfferIdWithARM(params.databaseId)
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
: getDatabaseOfferIdWithSDK(params.databaseResourceId));
|
) {
|
||||||
if (!offerId) {
|
try {
|
||||||
clearMessage();
|
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
|
||||||
return undefined;
|
} catch (error) {
|
||||||
|
clearMessage();
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw new error();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
|
||||||
|
if (!offerId) {
|
||||||
|
clearMessage();
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,32 +75,24 @@ 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) {
|
||||||
try {
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
switch (defaultExperience) {
|
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
break;
|
||||||
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
case DefaultAccountExperienceType.MongoDB:
|
||||||
break;
|
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
break;
|
||||||
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
break;
|
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
break;
|
||||||
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
case DefaultAccountExperienceType.Graph:
|
||||||
break;
|
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
||||||
case DefaultAccountExperienceType.Graph:
|
break;
|
||||||
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
|
default:
|
||||||
break;
|
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rpResponse?.name;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== "NotFound") {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rpResponse?.name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ 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 (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (
|
||||||
|
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,7 +41,6 @@ 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
|
||||||
) {
|
) {
|
||||||
@@ -53,18 +52,16 @@ 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(
|
||||||
|
|||||||
@@ -1,421 +0,0 @@
|
|||||||
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,7 +34,6 @@ 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$`
|
||||||
@@ -51,8 +50,7 @@ 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,16 +303,6 @@ 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(collectionId: string): Collection;
|
findCollectionWithId(collectionRid: 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,6 +266,7 @@ 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;
|
||||||
@@ -304,6 +305,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +388,6 @@ 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
Normal file
42
src/Definitions/jquery.contextmenu.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// 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 { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
|
import { FontIcon } from "office-ui-fabric-react";
|
||||||
|
|
||||||
export interface TextFieldProps extends ITextFieldProps {
|
export interface TextFieldProps extends ITextFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -24,7 +24,6 @@ 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;
|
||||||
@@ -66,7 +65,6 @@ 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 = {
|
||||||
@@ -84,7 +82,6 @@ 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,6 +55,7 @@ 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,6 +178,12 @@ 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 || this.props.container.isGalleryPublishEnabled()) && (
|
{this.props.container?.isGalleryPublishEnabled() && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<InfoComponent />
|
<InfoComponent />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -3,14 +3,10 @@ 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 interface InfoComponentProps {
|
export class InfoComponent extends React.Component {
|
||||||
onReportAbuseClick?: () => void;
|
private getInfoPanel = (iconName: string, labelText: string, url: string): JSX.Element => {
|
||||||
}
|
|
||||||
|
|
||||||
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={url && "_blank"} onClick={onClick}>
|
<Link href={url} target="_blank">
|
||||||
<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>
|
||||||
@@ -29,11 +25,6 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
|
|||||||
<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,9 +77,6 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
selectedKey={0}
|
selectedKey={0}
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem>
|
|
||||||
<InfoComponent />
|
|
||||||
</StackItem>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ 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} />);
|
||||||
@@ -55,8 +54,7 @@ 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,7 +17,6 @@ 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;
|
||||||
@@ -27,7 +26,6 @@ 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> {
|
||||||
@@ -43,39 +41,24 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
|
|||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
|
||||||
<Stack.Item>
|
<Text variant="xxLarge" nowrap>
|
||||||
<Text variant="xxLarge" nowrap>
|
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
||||||
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
|
</Text>
|
||||||
</Text>
|
<Text>
|
||||||
</Stack.Item>
|
{this.props.isFavorite !== undefined && (
|
||||||
|
<>
|
||||||
<Stack.Item>
|
<IconButton
|
||||||
<Text>
|
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
|
||||||
{this.props.isFavorite !== undefined && (
|
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
|
||||||
<>
|
/>
|
||||||
<IconButton
|
{this.props.data.favorites} likes
|
||||||
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 && (
|
||||||
<Stack.Item>
|
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
||||||
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
|
|
||||||
</Stack.Item>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Stack.Item grow>
|
|
||||||
<></>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Stack.Item>
|
|
||||||
<InfoComponent onReportAbuseClick={this.props.onReportAbuseClick} />
|
|
||||||
</Stack.Item>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
|
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
*/
|
*/
|
||||||
import { Notebook } from "@nteract/commutable";
|
import { Notebook } from "@nteract/commutable";
|
||||||
import { createContentRef } from "@nteract/core";
|
import { createContentRef } from "@nteract/core";
|
||||||
import { IChoiceGroupProps, Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
|
import { 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";
|
||||||
@@ -14,13 +15,12 @@ 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, TextFieldProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } 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,8 +43,10 @@ interface NotebookViewerComponentState {
|
|||||||
showProgressBar: boolean;
|
showProgressBar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
|
export class NotebookViewerComponent extends React.Component<
|
||||||
implements DialogHost {
|
NotebookViewerComponentProps,
|
||||||
|
NotebookViewerComponentState
|
||||||
|
> {
|
||||||
private clientManager: NotebookClientV2;
|
private clientManager: NotebookClientV2;
|
||||||
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
|
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
|
||||||
|
|
||||||
@@ -138,7 +140,6 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
|||||||
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>
|
||||||
) : (
|
) : (
|
||||||
@@ -178,39 +179,6 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 })
|
||||||
@@ -228,8 +196,4 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
|||||||
this.setState({ galleryItem: item })
|
this.setState({ galleryItem: item })
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private reportAbuse = (): void => {
|
|
||||||
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,38 +17,26 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<StackItem>
|
<Text
|
||||||
<Text
|
nowrap={true}
|
||||||
nowrap={true}
|
variant="xxLarge"
|
||||||
variant="xxLarge"
|
>
|
||||||
>
|
name
|
||||||
name
|
</Text>
|
||||||
</Text>
|
<Text>
|
||||||
</StackItem>
|
<CustomizedIconButton
|
||||||
<StackItem>
|
iconProps={
|
||||||
<Text>
|
Object {
|
||||||
<CustomizedIconButton
|
"iconName": "HeartFill",
|
||||||
iconProps={
|
|
||||||
Object {
|
|
||||||
"iconName": "HeartFill",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/>
|
}
|
||||||
0
|
|
||||||
likes
|
|
||||||
</Text>
|
|
||||||
</StackItem>
|
|
||||||
<StackItem>
|
|
||||||
<CustomizedPrimaryButton
|
|
||||||
text="Download"
|
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
0
|
||||||
<StackItem
|
likes
|
||||||
grow={true}
|
</Text>
|
||||||
|
<CustomizedPrimaryButton
|
||||||
|
text="Download"
|
||||||
/>
|
/>
|
||||||
<StackItem>
|
|
||||||
<InfoComponent />
|
|
||||||
</StackItem>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
@@ -129,38 +117,26 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
|
|||||||
}
|
}
|
||||||
verticalAlign="center"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<StackItem>
|
<Text
|
||||||
<Text
|
nowrap={true}
|
||||||
nowrap={true}
|
variant="xxLarge"
|
||||||
variant="xxLarge"
|
>
|
||||||
>
|
name
|
||||||
name
|
</Text>
|
||||||
</Text>
|
<Text>
|
||||||
</StackItem>
|
<CustomizedIconButton
|
||||||
<StackItem>
|
iconProps={
|
||||||
<Text>
|
Object {
|
||||||
<CustomizedIconButton
|
"iconName": "Heart",
|
||||||
iconProps={
|
|
||||||
Object {
|
|
||||||
"iconName": "Heart",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/>
|
}
|
||||||
0
|
|
||||||
likes
|
|
||||||
</Text>
|
|
||||||
</StackItem>
|
|
||||||
<StackItem>
|
|
||||||
<CustomizedPrimaryButton
|
|
||||||
text="Download"
|
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
0
|
||||||
<StackItem
|
likes
|
||||||
grow={true}
|
</Text>
|
||||||
|
<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 } from "./SettingsUtils";
|
import { TtlType, isDirty, TtlOnNoDefault, TtlOn, TtlOff } 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/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/DocumentClientUtilityBase";
|
||||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
jest.mock("../../../Common/DocumentClientUtilityBase", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -33,6 +33,7 @@ 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
|
||||||
@@ -102,7 +103,10 @@ 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 };
|
||||||
@@ -143,7 +147,10 @@ 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,
|
||||||
@@ -213,6 +220,13 @@ 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, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceStart, traceFailure, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } 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/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/DocumentClientUtilityBase";
|
||||||
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,6 +27,9 @@ import {
|
|||||||
SettingsV2TabTypes,
|
SettingsV2TabTypes,
|
||||||
getTabTitle,
|
getTabTitle,
|
||||||
isDirty,
|
isDirty,
|
||||||
|
TtlOff,
|
||||||
|
TtlOn,
|
||||||
|
TtlOnNoDefault,
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure
|
parseConflictResolutionProcedure
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
@@ -35,7 +38,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 } from "office-ui-fabric-react";
|
import { Pivot, PivotItem, IPivotProps, IPivotItemProps, IChoiceGroupOption } from "office-ui-fabric-react";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||||
|
|
||||||
@@ -82,6 +85,7 @@ 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;
|
||||||
@@ -98,6 +102,7 @@ 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;
|
||||||
@@ -122,8 +127,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);
|
||||||
@@ -155,6 +160,7 @@ 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,
|
||||||
|
|
||||||
@@ -264,7 +270,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.SettingsV2Updated, {
|
const startKey: number = traceStart(Action.UpdateSettings, {
|
||||||
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,
|
||||||
@@ -405,7 +411,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.SettingsV2Updated,
|
Action.UpdateSettings,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
databaseName: this.collection && this.collection.databaseId,
|
||||||
@@ -420,21 +426,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions);
|
||||||
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) {
|
||||||
@@ -454,7 +446,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.SettingsV2Updated,
|
Action.UpdateSettings,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
@@ -468,7 +460,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.SettingsV2Updated,
|
Action.UpdateSettings,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
@@ -482,10 +474,6 @@ 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,
|
||||||
@@ -516,6 +504,9 @@ 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 });
|
||||||
|
|
||||||
@@ -539,34 +530,79 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
private onConflictResolutionPolicyModeChange = (
|
||||||
this.setState({ conflictResolutionPolicyMode: newMode });
|
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void =>
|
||||||
|
this.setState({
|
||||||
|
conflictResolutionPolicyMode:
|
||||||
|
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
|
||||||
|
});
|
||||||
|
|
||||||
private onConflictResolutionPolicyPathChange = (newPath: string): void =>
|
private onConflictResolutionPolicyPathChange = (
|
||||||
this.setState({ conflictResolutionPolicyPath: newPath });
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => this.setState({ conflictResolutionPolicyPath: newValue });
|
||||||
|
|
||||||
private onConflictResolutionPolicyProcedureChange = (newProcedure: string): void =>
|
private onConflictResolutionPolicyProcedureChange = (
|
||||||
this.setState({ conflictResolutionPolicyProcedure: newProcedure });
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
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 });
|
||||||
|
|
||||||
private onTtlChange = (newTtl: TtlType): void => this.setState({ timeToLive: newTtl });
|
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 onTimeToLiveSecondsChange = (newTimeToLiveSeconds: number): void =>
|
private onTtlChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption): 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 = (newGeoSpatialConfigType: GeospatialConfigType): void =>
|
private onGeoSpatialConfigTypeChange = (
|
||||||
this.setState({ geospatialConfigType: newGeoSpatialConfigType });
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void =>
|
||||||
|
this.setState({ geospatialConfigType: GeospatialConfigType[option.key as keyof typeof GeospatialConfigType] });
|
||||||
|
|
||||||
private onAnalyticalStorageTtlSelectionChange = (newAnalyticalStorageTtlSelection: TtlType): void =>
|
private onAnalyticalStorageTtlSelectionChange = (
|
||||||
this.setState({ analyticalStorageTtlSelection: newAnalyticalStorageTtlSelection });
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
): void => this.setState({ analyticalStorageTtlSelection: this.getTtlValue(option.key) });
|
||||||
|
|
||||||
private onAnalyticalStorageTtlSecondsChange = (newAnalyticalStorageTtlSeconds: number): void =>
|
private onAnalyticalStorageTtlSecondsChange = (
|
||||||
|
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 = (newChangeFeedPolicy: ChangeFeedPolicyState): void =>
|
private onChangeFeedPolicyChange = (
|
||||||
this.setState({ changeFeedPolicy: newChangeFeedPolicy });
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
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 });
|
||||||
@@ -794,6 +830,7 @@ 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,15 +18,24 @@ export interface ConflictResolutionComponentProps {
|
|||||||
container: Explorer;
|
container: Explorer;
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
|
onConflictResolutionPolicyModeChange: (
|
||||||
|
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
) => void;
|
||||||
conflictResolutionPolicyPath: string;
|
conflictResolutionPolicyPath: string;
|
||||||
conflictResolutionPolicyPathBaseline: string;
|
conflictResolutionPolicyPathBaseline: string;
|
||||||
|
|
||||||
onConflictResolutionPolicyPathChange: (newPath: string) => void;
|
onConflictResolutionPolicyPathChange: (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
) => void;
|
||||||
conflictResolutionPolicyProcedure: string;
|
conflictResolutionPolicyProcedure: string;
|
||||||
conflictResolutionPolicyProcedureBaseline: string;
|
conflictResolutionPolicyProcedureBaseline: string;
|
||||||
|
|
||||||
onConflictResolutionPolicyProcedureChange: (newProcedure: string) => void;
|
onConflictResolutionPolicyProcedureChange: (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
) => void;
|
||||||
onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void;
|
onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,30 +77,12 @@ 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.onConflictResolutionPolicyModeChange}
|
onChange={this.props.onConflictResolutionPolicyModeChange}
|
||||||
styles={getChoiceGroupStyles(
|
styles={getChoiceGroupStyles(
|
||||||
this.props.conflictResolutionPolicyMode,
|
this.props.conflictResolutionPolicyMode,
|
||||||
this.props.conflictResolutionPolicyModeBaseline
|
this.props.conflictResolutionPolicyModeBaseline
|
||||||
@@ -113,7 +104,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
this.props.conflictResolutionPolicyPathBaseline
|
this.props.conflictResolutionPolicyPathBaseline
|
||||||
)}
|
)}
|
||||||
value={this.props.conflictResolutionPolicyPath}
|
value={this.props.conflictResolutionPolicyPath}
|
||||||
onChange={this.onConflictResolutionPolicyPathChange}
|
onChange={this.props.onConflictResolutionPolicyPathChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -131,7 +122,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
this.props.conflictResolutionPolicyProcedureBaseline
|
this.props.conflictResolutionPolicyProcedureBaseline
|
||||||
)}
|
)}
|
||||||
value={this.props.conflictResolutionPolicyProcedure}
|
value={this.props.conflictResolutionPolicyProcedure}
|
||||||
onChange={this.onConflictResolutionPolicyProcedureChange}
|
onChange={this.props.onConflictResolutionPolicyProcedureChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ describe("IndexingPolicyComponent", () => {
|
|||||||
},
|
},
|
||||||
indexingPolicyContent: initialIndexingPolicyContent,
|
indexingPolicyContent: initialIndexingPolicyContent,
|
||||||
indexingPolicyContentBaseline: initialIndexingPolicyContent,
|
indexingPolicyContentBaseline: initialIndexingPolicyContent,
|
||||||
|
onIndexingPolicyElementFocusChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
onIndexingPolicyContentChange: () => {
|
onIndexingPolicyContentChange: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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;
|
||||||
@@ -88,6 +89,8 @@ 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,7 +12,10 @@ 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;
|
||||||
|
|
||||||
@@ -85,7 +88,10 @@ 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,7 +19,6 @@ 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;
|
||||||
@@ -44,7 +43,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
private isEmulator: boolean;
|
private isEmulator: boolean;
|
||||||
constructor(props: ScaleComponentProps) {
|
constructor(props: ScaleComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.isEmulator = configContext.platform === Platform.Emulator;
|
this.isEmulator = this.props.container.isEmulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
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, TtlOnNoDefault, TtlOn, TtlOff } from "../SettingsUtils";
|
import { TtlType, GeospatialConfigType, ChangeFeedPolicyState } from "../SettingsUtils";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
|
|
||||||
@@ -105,7 +105,10 @@ 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 };
|
||||||
@@ -130,11 +133,4 @@ 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,11 +5,7 @@ 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";
|
||||||
@@ -41,28 +37,40 @@ export interface SubSettingsComponentProps {
|
|||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
timeToLiveBaseline: TtlType;
|
timeToLiveBaseline: TtlType;
|
||||||
|
|
||||||
onTtlChange: (newTtl: TtlType) => void;
|
onTtlChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void;
|
||||||
timeToLiveSeconds: number;
|
timeToLiveSeconds: number;
|
||||||
timeToLiveSecondsBaseline: number;
|
timeToLiveSecondsBaseline: number;
|
||||||
onTimeToLiveSecondsChange: (newTimeToLiveSeconds: number) => void;
|
onTimeToLiveSecondsChange: (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
) => void;
|
||||||
|
|
||||||
geospatialConfigType: GeospatialConfigType;
|
geospatialConfigType: GeospatialConfigType;
|
||||||
geospatialConfigTypeBaseline: GeospatialConfigType;
|
geospatialConfigTypeBaseline: GeospatialConfigType;
|
||||||
onGeoSpatialConfigTypeChange: (newGeoSpatialConfigType: GeospatialConfigType) => void;
|
onGeoSpatialConfigTypeChange: (
|
||||||
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
) => void;
|
||||||
|
|
||||||
isAnalyticalStorageEnabled: boolean;
|
isAnalyticalStorageEnabled: boolean;
|
||||||
analyticalStorageTtlSelection: TtlType;
|
analyticalStorageTtlSelection: TtlType;
|
||||||
analyticalStorageTtlSelectionBaseline: TtlType;
|
analyticalStorageTtlSelectionBaseline: TtlType;
|
||||||
onAnalyticalStorageTtlSelectionChange: (newAnalyticalStorageTtlSelection: TtlType) => void;
|
onAnalyticalStorageTtlSelectionChange: (
|
||||||
|
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
|
option?: IChoiceGroupOption
|
||||||
|
) => void;
|
||||||
|
|
||||||
analyticalStorageTtlSeconds: number;
|
analyticalStorageTtlSeconds: number;
|
||||||
analyticalStorageTtlSecondsBaseline: number;
|
analyticalStorageTtlSecondsBaseline: number;
|
||||||
onAnalyticalStorageTtlSecondsChange: (newAnalyticalStorageTtlSeconds: number) => void;
|
onAnalyticalStorageTtlSecondsChange: (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
) => void;
|
||||||
|
|
||||||
changeFeedPolicyVisible: boolean;
|
changeFeedPolicyVisible: boolean;
|
||||||
changeFeedPolicy: ChangeFeedPolicyState;
|
changeFeedPolicy: ChangeFeedPolicyState;
|
||||||
changeFeedPolicyBaseline: ChangeFeedPolicyState;
|
changeFeedPolicyBaseline: ChangeFeedPolicyState;
|
||||||
onChangeFeedPolicyChange: (newChangeFeedPolicyState: ChangeFeedPolicyState) => void;
|
onChangeFeedPolicyChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void;
|
||||||
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
|
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
|
||||||
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
|
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -131,54 +139,6 @@ 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
|
||||||
@@ -186,7 +146,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.onTtlChange}
|
onChange={this.props.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 && (
|
||||||
@@ -203,7 +163,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.onTimeToLiveSecondsChange}
|
onChange={this.props.onTimeToLiveSecondsChange}
|
||||||
suffix="second(s)"
|
suffix="second(s)"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -223,7 +183,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.onAnalyticalStorageTtlSelectionChange}
|
onChange={this.props.onAnalyticalStorageTtlSelectionChange}
|
||||||
styles={getChoiceGroupStyles(
|
styles={getChoiceGroupStyles(
|
||||||
this.props.analyticalStorageTtlSelection,
|
this.props.analyticalStorageTtlSelection,
|
||||||
this.props.analyticalStorageTtlSelectionBaseline
|
this.props.analyticalStorageTtlSelectionBaseline
|
||||||
@@ -242,7 +202,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.onAnalyticalStorageTtlSecondsChange}
|
onChange={this.props.onAnalyticalStorageTtlSecondsChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -259,7 +219,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.onGeoSpatialConfigTypeChange}
|
onChange={this.props.onGeoSpatialConfigTypeChange}
|
||||||
styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)}
|
styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -281,7 +241,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.onChangeFeedPolicyChange}
|
onChange={this.props.onChangeFeedPolicyChange}
|
||||||
styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)}
|
styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)}
|
||||||
aria-labelledby={labelId}
|
aria-labelledby={labelId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ import {
|
|||||||
MessageBarType
|
MessageBarType
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
import { 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;
|
||||||
@@ -72,9 +71,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 throughputInputMaxValue: number;
|
private choiceGroupFixedStyle = getChoiceGroupStyles(undefined, undefined);
|
||||||
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" }
|
||||||
@@ -141,8 +140,6 @@ 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 =>
|
||||||
@@ -203,7 +200,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
newValue?: string
|
newValue?: string
|
||||||
): void => {
|
): void => {
|
||||||
const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
|
let newThroughput = parseInt(newValue);
|
||||||
|
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,7 +209,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
newValue?: string
|
newValue?: string
|
||||||
): void => {
|
): void => {
|
||||||
const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
|
let newThroughput = parseInt(newValue);
|
||||||
|
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={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected)}
|
styles={this.choiceGroupFixedStyle}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -270,7 +270,8 @@ 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={AutoPilotUtils.autoPilotIncrementStep}
|
step={this.step}
|
||||||
|
min={AutoPilotUtils.minAutoPilotThroughput}
|
||||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||||
onChange={this.onAutoPilotThroughputChange}
|
onChange={this.onAutoPilotThroughputChange}
|
||||||
/>
|
/>
|
||||||
@@ -297,6 +298,8 @@ 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": undefined,
|
"borderColor": "",
|
||||||
},
|
},
|
||||||
".ms-ChoiceField-field.is-checked::before": Object {
|
".ms-ChoiceField-field.is-checked::before": Object {
|
||||||
"borderColor": undefined,
|
"borderColor": "",
|
||||||
},
|
},
|
||||||
".ms-ChoiceField-wrapper label": Object {
|
".ms-ChoiceField-wrapper label": Object {
|
||||||
"fontFamily": undefined,
|
"fontFamily": undefined,
|
||||||
@@ -113,9 +113,10 @@ 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={1000}
|
step={100}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"fieldGroup": Object {
|
"fieldGroup": Object {
|
||||||
@@ -218,6 +219,7 @@ 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}
|
||||||
@@ -373,6 +375,7 @@ 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,7 +2,6 @@ import { collection, container } from "./TestUtils";
|
|||||||
import {
|
import {
|
||||||
getMaxRUs,
|
getMaxRUs,
|
||||||
getMinRUs,
|
getMinRUs,
|
||||||
getSanitizedInputValue,
|
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
isDirty,
|
isDirty,
|
||||||
isDirtyTypes,
|
isDirtyTypes,
|
||||||
@@ -87,11 +86,4 @@ 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,7 +6,6 @@ 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";
|
||||||
@@ -130,16 +129,6 @@ 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,7 +3,10 @@ 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,6 +84,9 @@ 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],
|
||||||
@@ -583,6 +586,9 @@ 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],
|
||||||
@@ -976,6 +982,7 @@ 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],
|
||||||
@@ -1051,6 +1058,7 @@ 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],
|
||||||
@@ -1390,6 +1398,9 @@ 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],
|
||||||
@@ -1889,6 +1900,9 @@ 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],
|
||||||
@@ -2282,6 +2296,7 @@ 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],
|
||||||
@@ -2357,6 +2372,7 @@ 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],
|
||||||
@@ -2709,6 +2725,9 @@ 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],
|
||||||
@@ -3208,6 +3227,9 @@ 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],
|
||||||
@@ -3601,6 +3623,7 @@ 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],
|
||||||
@@ -3676,6 +3699,7 @@ 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],
|
||||||
@@ -4015,6 +4039,9 @@ 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],
|
||||||
@@ -4514,6 +4541,9 @@ 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],
|
||||||
@@ -4907,6 +4937,7 @@ 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],
|
||||||
@@ -4982,6 +5013,7 @@ 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],
|
||||||
@@ -5293,6 +5325,7 @@ 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,8 +18,6 @@ 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;
|
||||||
@@ -278,12 +276,7 @@ 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: () => {
|
onClick: menuItem.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": [Function],
|
"onClick": undefined,
|
||||||
"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,6 +98,10 @@ enum ShareAccessToggleState {
|
|||||||
Read
|
Read
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExplorerOptions {
|
||||||
|
notificationsClient: NotificationsClientBase;
|
||||||
|
isEmulator: boolean;
|
||||||
|
}
|
||||||
interface AdHocAccessData {
|
interface AdHocAccessData {
|
||||||
readWriteUrl: string;
|
readWriteUrl: string;
|
||||||
readUrl: string;
|
readUrl: string;
|
||||||
@@ -131,12 +135,14 @@ 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;
|
||||||
@@ -206,7 +212,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.Observable<boolean>;
|
public isSettingsV2Enabled: ko.Computed<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>;
|
||||||
@@ -265,7 +271,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
|
||||||
constructor() {
|
constructor(options: ExplorerOptions) {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree
|
dataExplorerArea: Constants.Areas.ResourceTree
|
||||||
});
|
});
|
||||||
@@ -371,6 +377,8 @@ 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>();
|
||||||
@@ -413,8 +421,7 @@ 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);
|
||||||
@@ -1861,7 +1868,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.id() === this.selectedNode().id());
|
return _.find(this.databases(), (database: ViewModels.Database) => database.rid === this.selectedNode().rid);
|
||||||
}
|
}
|
||||||
return this.findSelectedCollection().database;
|
return this.findSelectedCollection().database;
|
||||||
}
|
}
|
||||||
@@ -1904,6 +1911,7 @@ 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);
|
||||||
@@ -1911,7 +1919,6 @@ 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);
|
||||||
@@ -1946,20 +1953,12 @@ export default class Explorer {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
|
||||||
if (!flights) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
|
|
||||||
this.isSettingsV2Enabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
public findSelectedCollection(): ViewModels.Collection {
|
||||||
return (this.selectedNode().nodeKind === "Collection"
|
if (this.selectedNode().nodeKind === "Collection") {
|
||||||
? this.selectedNode()
|
return this.findSelectedCollectionForSelectedNode();
|
||||||
: this.selectedNode().collection) as ViewModels.Collection;
|
} else {
|
||||||
|
return this.findSelectedCollectionForSubNode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
||||||
@@ -2076,11 +2075,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.id() === database.id());
|
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.rid === database.rid);
|
||||||
if (isNewDatabase) {
|
if (isNewDatabase) {
|
||||||
database.expandDatabase();
|
database.expandDatabase();
|
||||||
}
|
}
|
||||||
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().id() === database.id());
|
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().rid === database.rid);
|
||||||
});
|
});
|
||||||
|
|
||||||
Q.all(loadCollectionPromises).done(
|
Q.all(loadCollectionPromises).done(
|
||||||
@@ -2192,11 +2191,21 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findCollection(databaseId: string, collectionId: string): ViewModels.Collection {
|
private findSelectedCollectionForSelectedNode(): ViewModels.Collection {
|
||||||
const database: ViewModels.Database = this.databases().find(
|
return this.findCollection(this.selectedNode().rid);
|
||||||
(database: ViewModels.Database) => database.id() === databaseId
|
}
|
||||||
);
|
|
||||||
return database?.collections().find((collection: ViewModels.Collection) => collection.id() === collectionId);
|
public findCollection(rid: string): 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 (collection.rid === rid) {
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isLastCollection(): boolean {
|
public isLastCollection(): boolean {
|
||||||
@@ -2220,7 +2229,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.id() === database.id
|
(existingDatabase: ViewModels.Database) => existingDatabase.rid === database._rid
|
||||||
);
|
);
|
||||||
return !databaseExists;
|
return !databaseExists;
|
||||||
});
|
});
|
||||||
@@ -2232,7 +2241,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.id === database.id()
|
(db: DataModels.Database) => db._rid === database.rid
|
||||||
);
|
);
|
||||||
if (!databasePresentInUpdatedList) {
|
if (!databasePresentInUpdatedList) {
|
||||||
databasesToDelete.push(database);
|
databasesToDelete.push(database);
|
||||||
@@ -2254,7 +2263,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.id === database.id);
|
const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.rid === database.rid);
|
||||||
if (!shouldRemoveDatabase) {
|
if (!shouldRemoveDatabase) {
|
||||||
databasesToKeep.push(database);
|
databasesToKeep.push(database);
|
||||||
}
|
}
|
||||||
@@ -2263,6 +2272,19 @@ 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";
|
||||||
@@ -2359,10 +2381,7 @@ export default class Explorer {
|
|||||||
okLabel: string,
|
okLabel: string,
|
||||||
onOk: () => void,
|
onOk: () => void,
|
||||||
cancelLabel: string,
|
cancelLabel: string,
|
||||||
onCancel: () => void,
|
onCancel: () => void
|
||||||
choiceGroupProps?: IChoiceGroupProps,
|
|
||||||
textFieldProps?: TextFieldProps,
|
|
||||||
isPrimaryButtonDisabled?: boolean
|
|
||||||
): void {
|
): void {
|
||||||
this._dialogProps({
|
this._dialogProps({
|
||||||
isModal: true,
|
isModal: true,
|
||||||
@@ -2378,10 +2397,38 @@ export default class Explorer {
|
|||||||
onSecondaryButtonClick: () => {
|
onSecondaryButtonClick: () => {
|
||||||
this._closeModalDialog();
|
this._closeModalDialog();
|
||||||
onCancel && onCancel();
|
onCancel && onCancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public showOkCancelTextFieldModalDialog(
|
||||||
|
title: string,
|
||||||
|
msg: string,
|
||||||
|
okLabel: string,
|
||||||
|
onOk: () => void,
|
||||||
|
cancelLabel: string,
|
||||||
|
onCancel: () => void,
|
||||||
|
textFieldProps: TextFieldProps,
|
||||||
|
isPrimaryButtonDisabled?: boolean
|
||||||
|
): void {
|
||||||
|
let textFieldValue: string = null;
|
||||||
|
this._dialogProps({
|
||||||
|
isModal: true,
|
||||||
|
visible: true,
|
||||||
|
title,
|
||||||
|
subText: msg,
|
||||||
|
primaryButtonText: okLabel,
|
||||||
|
secondaryButtonText: cancelLabel,
|
||||||
|
onPrimaryButtonClick: () => {
|
||||||
|
this._closeModalDialog();
|
||||||
|
onOk && onOk();
|
||||||
},
|
},
|
||||||
choiceGroupProps,
|
onSecondaryButtonClick: () => {
|
||||||
textFieldProps,
|
this._closeModalDialog();
|
||||||
primaryButtonDisabled: isPrimaryButtonDisabled
|
onCancel && onCancel();
|
||||||
|
},
|
||||||
|
primaryButtonDisabled: isPrimaryButtonDisabled,
|
||||||
|
textFieldProps
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2422,6 +2469,7 @@ 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),
|
||||||
@@ -2873,6 +2921,7 @@ 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),
|
||||||
@@ -2916,6 +2965,7 @@ 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,
|
||||||
@@ -2958,6 +3008,7 @@ 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,6 +141,8 @@ 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 => {
|
||||||
@@ -158,6 +160,8 @@ 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,6 +47,8 @@ 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;
|
||||||
@@ -1759,7 +1761,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.collectionId,
|
this.props.collectionRid,
|
||||||
this.currentDocDBQueryInfo.iterator,
|
this.currentDocDBQueryInfo.iterator,
|
||||||
this.currentDocDBQueryInfo.index,
|
this.currentDocDBQueryInfo.index,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ 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;
|
||||||
@@ -47,6 +49,8 @@ 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 * as CommandBarUtil from "./CommandBarUtil";
|
import { 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 (configContext.platform !== Platform.Emulator) {
|
if (!container.hasOwnProperty("isEmulator") || !container.isEmulator) {
|
||||||
const label = "Feedback";
|
const label = "Feedback";
|
||||||
const feedbackButtonOptions: CommandButtonComponentProps = {
|
const feedbackButtonOptions: CommandButtonComponentProps = {
|
||||||
iconSrc: FeedbackIcon,
|
iconSrc: FeedbackIcon,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as CommandBarUtil from "./CommandBarUtil";
|
import { 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: jest.fn(),
|
onCommandClick: (e: React.SyntheticEvent): void => {},
|
||||||
commandButtonLabel: "label",
|
commandButtonLabel: "label",
|
||||||
ariaLabel: "ariaLabel",
|
ariaLabel: "ariaLabel",
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
@@ -29,14 +29,11 @@ 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,187 +11,177 @@ 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";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert our NavbarButtonConfig to UI Fabric buttons
|
* Utilities for CommandBar
|
||||||
* @param btns
|
|
||||||
*/
|
*/
|
||||||
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
export class CommandBarUtil {
|
||||||
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 createDivider(btn.commandButtonLabel);
|
return CommandBarUtil.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: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
|
|
||||||
btn.onCommandClick(ev);
|
|
||||||
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
|
|
||||||
},
|
|
||||||
key: `${btn.commandButtonLabel}${index}`,
|
|
||||||
text: label,
|
|
||||||
"data-test": label,
|
|
||||||
title: btn.tooltipText,
|
|
||||||
name: label,
|
|
||||||
disabled: btn.disabled,
|
|
||||||
ariaLabel: btn.ariaLabel,
|
|
||||||
buttonStyles: {
|
|
||||||
root: {
|
|
||||||
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 }
|
|
||||||
},
|
},
|
||||||
width: 16
|
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
||||||
|
iconName: btn.iconName
|
||||||
},
|
},
|
||||||
label: { fontSize: StyleConstants.mediumFontSize },
|
onClick: btn.onCommandClick,
|
||||||
rootHovered: { backgroundColor: StyleConstants.AccentLight },
|
key: `${btn.commandButtonLabel}${index}`,
|
||||||
rootPressed: { backgroundColor: StyleConstants.AccentLight },
|
text: btn.commandButtonLabel || btn.tooltipText,
|
||||||
splitButtonMenuButtonExpanded: {
|
"data-test": btn.commandButtonLabel || btn.tooltipText,
|
||||||
backgroundColor: StyleConstants.AccentExtra,
|
title: btn.tooltipText,
|
||||||
selectors: {
|
name: btn.commandButtonLabel || btn.tooltipText,
|
||||||
":hover": { backgroundColor: StyleConstants.AccentLight }
|
disabled: btn.disabled,
|
||||||
|
ariaLabel: btn.ariaLabel,
|
||||||
|
buttonStyles: {
|
||||||
|
root: {
|
||||||
|
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 }
|
||||||
|
},
|
||||||
|
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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
splitButtonDivider: {
|
className: btn.className,
|
||||||
display: "none"
|
id: btn.id
|
||||||
},
|
};
|
||||||
icon: {
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0
|
|
||||||
},
|
|
||||||
splitButtonContainer: {
|
|
||||||
marginLeft: 5,
|
|
||||||
marginRight: 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
className: btn.className,
|
|
||||||
id: btn.id
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSplit) {
|
if (isSplit) {
|
||||||
// It's a split button
|
// It's a split button
|
||||||
result.split = true;
|
result.split = true;
|
||||||
|
|
||||||
result.subMenuProps = {
|
result.subMenuProps = {
|
||||||
items: convertButton(btn.children, backgroundColor),
|
items: CommandBarUtil.convertButton(btn.children, backgroundColor),
|
||||||
styles: {
|
styles: {
|
||||||
list: {
|
list: {
|
||||||
// TODO Figure out how to do it the proper way with subComponentStyles.
|
// 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
|
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
|
||||||
selectors: {
|
selectors: {
|
||||||
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
|
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
|
||||||
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
|
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
|
||||||
".ms-ContextualMenu-icon": { width: 16, height: 16 }
|
".ms-ContextualMenu-icon": { width: 16, height: 16 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
result.menuIconProps = {
|
result.menuIconProps = {
|
||||||
iconType: IconType.image,
|
iconType: IconType.image,
|
||||||
style: {
|
style: {
|
||||||
width: 12,
|
width: 12,
|
||||||
paddingLeft: 1,
|
paddingLeft: 1,
|
||||||
paddingTop: 6
|
paddingTop: 6
|
||||||
},
|
},
|
||||||
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt }
|
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;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (btn.isDropdown) {
|
public static createDivider(key: string): ICommandBarItemProps {
|
||||||
const selectedChild = _.find(btn.children, child => child.dropdownItemKey === btn.dropdownSelectedKey);
|
return {
|
||||||
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
|
onRender: () => (
|
||||||
|
<div className="dividerContainer">
|
||||||
|
<span />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
iconOnly: true,
|
||||||
|
disabled: true,
|
||||||
|
key: key
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const dropdownStyles: Partial<IDropdownStyles> = {
|
public static createMemoryTracker(key: string, memoryUsageInfo: Observable<MemoryUsageInfo>): ICommandBarItemProps {
|
||||||
root: { margin: 5 },
|
return {
|
||||||
dropdown: { width: btn.dropdownWidth },
|
key,
|
||||||
title: { fontSize: 12, height: 30, lineHeight: 28 },
|
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />
|
||||||
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,10 +15,7 @@ export interface OpenNotebookItem {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenCollectionItem {
|
export type OpenCollectionItem = string;
|
||||||
databaseId: string;
|
|
||||||
collectionId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
type: Type;
|
type: Type;
|
||||||
@@ -124,11 +121,7 @@ export class MostRecentActivity {
|
|||||||
public onItemClicked(item: Item) {
|
public onItemClicked(item: Item) {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case Type.OpenCollection: {
|
case Type.OpenCollection: {
|
||||||
const openCollectionitem = item.data as OpenCollectionItem;
|
const collection = this.container.findCollection(item.data as OpenCollectionItem);
|
||||||
const collection = this.container.findCollection(
|
|
||||||
openCollectionitem.databaseId,
|
|
||||||
openCollectionitem.collectionId
|
|
||||||
);
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
collection.openTab();
|
collection.openTab();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,27 +32,24 @@ 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, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction } 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 logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => {
|
const logToTelemetry = (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,
|
||||||
@@ -314,7 +311,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);
|
||||||
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
logToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||||
} else {
|
} else {
|
||||||
return of(
|
return of(
|
||||||
actions.launchKernelFailed({
|
actions.launchKernelFailed({
|
||||||
@@ -340,7 +337,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
||||||
}
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
||||||
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
logToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionPayload = {
|
const sessionPayload = {
|
||||||
@@ -637,7 +634,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);
|
||||||
logFailureToTelemetry(state$.value, title, msg);
|
logToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case actions.RESTART_KERNEL_FAILED:
|
case actions.RESTART_KERNEL_FAILED:
|
||||||
@@ -648,7 +645,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);
|
||||||
logFailureToTelemetry(state$.value, title, msg);
|
logToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case actions.FETCH_CONTENT_FAILED: {
|
case actions.FETCH_CONTENT_FAILED: {
|
||||||
@@ -657,7 +654,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);
|
||||||
logFailureToTelemetry(state$.value, title, msg);
|
logToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -682,7 +679,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);
|
||||||
logFailureToTelemetry(state, "Error", "Kernel connection error");
|
logToTelemetry(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>);
|
||||||
@@ -695,7 +692,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);
|
||||||
logFailureToTelemetry(state, "Kernel restart error", msg);
|
logToTelemetry(state, "Kernel restart error", msg);
|
||||||
|
|
||||||
const explorer = window.dataExplorer;
|
const explorer = window.dataExplorer;
|
||||||
if (explorer) {
|
if (explorer) {
|
||||||
@@ -847,105 +844,6 @@ 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,
|
||||||
@@ -958,8 +856,5 @@ export const allEpics = [
|
|||||||
executeFocusedCellAndFocusNextEpic,
|
executeFocusedCellAndFocusNextEpic,
|
||||||
closeUnsupportedMimetypesEpic,
|
closeUnsupportedMimetypesEpic,
|
||||||
closeContentFailedToFetchEpic,
|
closeContentFailedToFetchEpic,
|
||||||
restartWebSocketKernelEpic,
|
restartWebSocketKernelEpic
|
||||||
traceNotebookTelemetryEpic,
|
|
||||||
traceNotebookInfoEpic,
|
|
||||||
traceNotebookKernelEpic
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
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";
|
||||||
|
|
||||||
@@ -70,6 +72,17 @@ 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.showOkCancelModalDialog(
|
this.params.container.showOkCancelTextFieldModalDialog(
|
||||||
title || "Commit",
|
title || "Commit",
|
||||||
undefined,
|
undefined,
|
||||||
primaryButtonLabel || "Commit",
|
primaryButtonLabel || "Commit",
|
||||||
@@ -181,7 +181,6 @@ 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,8 +14,6 @@ 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;
|
||||||
@@ -31,7 +29,6 @@ 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 {
|
||||||
@@ -51,18 +48,12 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
{
|
{
|
||||||
key: "Run",
|
key: "Run",
|
||||||
text: "Run",
|
text: "Run",
|
||||||
onClick: () => {
|
onClick: this.props.executeCell
|
||||||
this.props.executeCell();
|
|
||||||
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Clear Outputs",
|
key: "Clear Outputs",
|
||||||
text: "Clear Outputs",
|
text: "Clear Outputs",
|
||||||
onClick: () => {
|
onClick: this.props.clearOutputs
|
||||||
this.props.clearOutputs();
|
|
||||||
this.props.traceNotebookTelemetry(Action.NotebooksClearOutputsFromMenu, ActionModifiers.Mark);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Divider",
|
key: "Divider",
|
||||||
@@ -73,43 +64,31 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
|
|||||||
|
|
||||||
items = items.concat([
|
items = items.concat([
|
||||||
{
|
{
|
||||||
key: "Divider2",
|
key: "Divider",
|
||||||
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: () => {
|
onClick: this.props.insertCodeCellAbove
|
||||||
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: () => {
|
onClick: this.props.insertCodeCellBelow
|
||||||
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: () => {
|
onClick: this.props.insertTextCellAbove
|
||||||
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: () => {
|
onClick: this.props.insertTextCellBelow
|
||||||
this.props.insertTextCellBelow();
|
|
||||||
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellBelowFromMenu, ActionModifiers.Mark);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Divider3",
|
key: "Divider",
|
||||||
itemType: ContextualMenuItemType.Divider
|
itemType: ContextualMenuItemType.Divider
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -119,10 +98,7 @@ 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: () => {
|
onClick: () => this.props.moveCell(this.props.cellIdAbove, true)
|
||||||
this.props.moveCell(this.props.cellIdAbove, true);
|
|
||||||
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellUpFromMenu, ActionModifiers.Mark);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,16 +106,13 @@ 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: () => {
|
onClick: () => this.props.moveCell(this.props.cellIdBelow, false)
|
||||||
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: "Divider4",
|
key: "Divider",
|
||||||
itemType: ContextualMenuItemType.Divider
|
itemType: ContextualMenuItemType.Divider
|
||||||
});
|
});
|
||||||
items = items.concat(moveItems);
|
items = items.concat(moveItems);
|
||||||
@@ -148,10 +121,7 @@ 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: () => {
|
onClick: this.props.deleteCell
|
||||||
this.props.deleteCell();
|
|
||||||
this.props.traceNotebookTelemetry(Action.DeleteCellFromMenu, ActionModifiers.Mark);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuItemLabel = "More";
|
const menuItemLabel = "More";
|
||||||
@@ -186,9 +156,7 @@ 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: visible;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -101,10 +101,12 @@ 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());
|
||||||
@@ -325,7 +327,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
configContext.platform !== Platform.Emulator &&
|
!this.container.isEmulator &&
|
||||||
!this.container.isTryCosmosDBSubscription() &&
|
!this.container.isTryCosmosDBSubscription() &&
|
||||||
this.container.getPlatformType() !== PlatformType.Portal
|
this.container.getPlatformType() !== PlatformType.Portal
|
||||||
) {
|
) {
|
||||||
@@ -337,7 +339,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return configContext.platform !== Platform.Emulator;
|
return !this.container.isEmulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.maxCollectionsReached = ko.computed<boolean>(() => {
|
this.maxCollectionsReached = ko.computed<boolean>(() => {
|
||||||
@@ -479,10 +481,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.databaseCreateNew()) {
|
if (!this.databaseCreateNew()) {
|
||||||
const selectedDatabase: ViewModels.Database = this.container
|
this.databaseHasSharedOffer(this._databaseOffers.has(selectedDatabaseId));
|
||||||
.databases()
|
|
||||||
.find((database: ViewModels.Database) => database.id() === selectedDatabaseId);
|
|
||||||
this.databaseHasSharedOffer(!!selectedDatabase?.offer());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -750,7 +749,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
|
private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) {
|
||||||
this.databaseIds(newDatabaseIds?.map((database: ViewModels.Database) => database.id()));
|
const cachedDatabaseIdsList = _.map(newDatabaseIds, (database: ViewModels.Database) => {
|
||||||
|
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,7 +40,10 @@ 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,7 +12,6 @@ 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>;
|
||||||
@@ -181,7 +180,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
configContext.platform !== Platform.Emulator &&
|
!this.container.isEmulator &&
|
||||||
!this.container.isTryCosmosDBSubscription() &&
|
!this.container.isTryCosmosDBSubscription() &&
|
||||||
this.container.getPlatformType() !== PlatformType.Portal
|
this.container.getPlatformType() !== PlatformType.Portal
|
||||||
) {
|
) {
|
||||||
@@ -204,7 +203,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return configContext.platform !== Platform.Emulator;
|
return !this.container.isEmulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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>;
|
||||||
@@ -232,11 +231,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.pureComputed(() => {
|
this.costsVisible = ko.pureComputed(() => {
|
||||||
return configContext.platform !== Platform.Emulator;
|
return !this.container.isEmulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (configContext.platform !== Platform.Emulator && !this.container.isTryCosmosDBSubscription()) {
|
if (!this.container.isEmulator && !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();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
let fakeExplorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||||
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
||||||
|
|
||||||
|
|||||||
@@ -66,11 +66,7 @@ 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(
|
this.container.tabsManager?.closeTabsByComparator(tab => tab.node && tab.node.rid === selectedCollection.rid);
|
||||||
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();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if only 1 database", () => {
|
it("should be true if only 1 database", () => {
|
||||||
|
|||||||
@@ -69,16 +69,12 @@ 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?.id() === selectedDatabase.id());
|
this.container.tabsManager.closeTabsByComparator(tab => tab.node && tab.node.rid === selectedDatabase.rid);
|
||||||
this.container.selectedNode(null);
|
this.container.selectedNode(null);
|
||||||
selectedDatabase
|
selectedDatabase
|
||||||
.collections()
|
.collections()
|
||||||
.forEach((collection: ViewModels.Collection) =>
|
.forEach((collection: ViewModels.Collection) =>
|
||||||
this.container.tabsManager.closeTabsByComparator(
|
this.container.tabsManager.closeTabsByComparator(tab => tab.node && tab.node.rid === collection.rid)
|
||||||
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();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
const mock = new Explorer({} as any);
|
||||||
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,8 +71,6 @@ 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",
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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"""');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
export function getQuotedCqlIdentifier(identifier: string): string {
|
|
||||||
let result = identifier;
|
|
||||||
if (!identifier) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identifier.includes('"')) {
|
|
||||||
result = identifier.replace(/"/g, '""');
|
|
||||||
}
|
|
||||||
|
|
||||||
return `"${result}"`;
|
|
||||||
}
|
|
||||||
@@ -143,21 +143,6 @@ 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) {
|
||||||
|
|||||||
150
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
Normal file
150
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
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,6 +41,18 @@ 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,
|
||||||
@@ -281,6 +293,7 @@ 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 ${getQuotedCqlIdentifier(this.queryTablesTab.collection.databaseId)}.${getQuotedCqlIdentifier(
|
`SELECT * FROM ${this.queryTablesTab.collection.databaseId}.${this.queryTablesTab.collection.id()}`
|
||||||
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,6 +1,5 @@
|
|||||||
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";
|
||||||
@@ -238,7 +237,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 = `${getQuotedCqlIdentifier(databaseId)}.${getQuotedCqlIdentifier(collectionId)}`;
|
const tableToQuery = `${databaseId}.${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,7 +7,6 @@ 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.";
|
||||||
@@ -190,9 +189,7 @@ 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 ${getQuotedCqlIdentifier(this.queryTablesTab.collection.databaseId)}.${getQuotedCqlIdentifier(
|
`SELECT * FROM ${this.queryTablesTab.collection.databaseId}.${this.queryTablesTab.collection.id()}`
|
||||||
this.queryTablesTab.collection.id()
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
return this._tableEntityListViewModel.reloadTable(false);
|
return this._tableEntityListViewModel.reloadTable(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ 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);
|
||||||
@@ -73,6 +74,7 @@ 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,11 +16,10 @@ 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/dataAccess/updateOffer";
|
import { updateOffer } from "../../Common/DocumentClientUtilityBase";
|
||||||
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.
|
||||||
@@ -197,7 +196,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.computed(() => {
|
this.costsVisible = ko.computed(() => {
|
||||||
return configContext.platform !== Platform.Emulator;
|
return !this.container.isEmulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
|
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
|
||||||
@@ -208,7 +207,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
);
|
);
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (
|
if (
|
||||||
configContext.platform === Platform.Emulator ||
|
!!this.container.isEmulator ||
|
||||||
this.container.getPlatformType() === PlatformType.Hosted ||
|
this.container.getPlatformType() === PlatformType.Hosted ||
|
||||||
this.canThroughputExceedMaximumValue()
|
this.canThroughputExceedMaximumValue()
|
||||||
) {
|
) {
|
||||||
@@ -456,7 +455,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
this._buildCommandBarOptions();
|
this._buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSaveClick = async (): Promise<any> => {
|
public onSaveClick = (): Q.Promise<any> => {
|
||||||
|
let promises: Q.Promise<void>[] = [];
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
|
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
@@ -470,81 +470,163 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
|
|
||||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||||
|
|
||||||
try {
|
if (this.isAutoPilotSelected()) {
|
||||||
if (this.isAutoPilotSelected()) {
|
const offer = this.database.offer();
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
let offerAutopilotSettings: any = {};
|
||||||
databaseId: this.database.id(),
|
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||||
currentOffer: this.database.offer(),
|
offerAutopilotSettings.maxThroughput = this.autoPilotThroughput();
|
||||||
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 {
|
||||||
if (this.throughput.editableIsDirty() || this.isAutoPilotSelected.editableIsDirty()) {
|
offerAutopilotSettings.tier = this.selectedAutoPilotTier();
|
||||||
const originalThroughputValue = this.throughput.getEditableOriginalValue();
|
}
|
||||||
const newThroughput = this.throughput();
|
const newOffer: DataModels.Offer = {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
if (
|
// user has changed from provisioned --> autoscale
|
||||||
this.canThroughputExceedMaximumValue() &&
|
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||||
this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million
|
delete newOffer.content.offerAutopilotSettings;
|
||||||
) {
|
}
|
||||||
const requestPayload = {
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
const updateOfferPromise = updateOffer(this.database.offer(), newOffer, headerOptions).then(
|
||||||
databaseAccountName: userContext.databaseAccount.name,
|
(updatedOffer: DataModels.Offer) => {
|
||||||
resourceGroup: userContext.resourceGroup,
|
this.database.offer(updatedOffer);
|
||||||
databaseName: this.database.id(),
|
this.database.offer.valueHasMutated();
|
||||||
throughput: newThroughput,
|
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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
|
||||||
};
|
},
|
||||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
_etag: undefined,
|
||||||
this.database.offer().content.offerThroughput = originalThroughputValue;
|
_ts: undefined,
|
||||||
this.throughput(originalThroughputValue);
|
_rid: offer._rid,
|
||||||
this.notificationStatusInfo(
|
_self: offer._self,
|
||||||
throughputApplyDelayedMessage(this.isAutoPilotSelected(), newThroughput, this.database.id())
|
id: offer.id,
|
||||||
);
|
offerResourceId: offer.offerResourceId,
|
||||||
this.throughput.valueHasMutated(); // force component re-render
|
offerVersion: offer.offerVersion,
|
||||||
} else {
|
offerType: offer.offerType,
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
resource: offer.resource
|
||||||
databaseId: this.database.id(),
|
};
|
||||||
currentOffer: this.database.offer(),
|
|
||||||
autopilotThroughput: undefined,
|
|
||||||
manualThroughput: newThroughput,
|
|
||||||
migrateToManual: this._hasProvisioningTypeChanged()
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatedOffer = await updateOffer(updateOfferParams);
|
// user has changed from autoscale --> provisioned
|
||||||
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||||
this.database.offer(updatedOffer);
|
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||||
this.database.offer.valueHasMutated();
|
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
|
||||||
this.isExecutionError(true);
|
if (promises.length === 0) {
|
||||||
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,6 +15,7 @@ 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),
|
||||||
|
|
||||||
@@ -26,9 +27,15 @@ 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>{
|
||||||
@@ -88,6 +95,7 @@ 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),
|
||||||
|
|
||||||
@@ -105,6 +113,7 @@ 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),
|
||||||
|
|
||||||
@@ -122,6 +131,7 @@ 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),
|
||||||
|
|
||||||
@@ -139,6 +149,7 @@ 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),
|
||||||
|
|
||||||
@@ -156,6 +167,7 @@ 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,6 +70,7 @@ 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);
|
||||||
@@ -90,6 +91,7 @@ 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,6 +91,8 @@ 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,6 +24,7 @@ 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 => {}
|
||||||
@@ -48,7 +49,7 @@ describe("Query Tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true for accounts using SQL API", () => {
|
it("should be true for accounts using SQL API", () => {
|
||||||
@@ -68,7 +69,7 @@ describe("Query Tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be visible when using a supported API", () => {
|
it("should be visible when using a supported API", () => {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ 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;
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ 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,6 +64,7 @@ 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(
|
||||||
@@ -78,7 +79,7 @@ describe("Settings tab", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ describe("Settings tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ 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),
|
||||||
@@ -207,6 +209,8 @@ 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),
|
||||||
@@ -223,6 +227,8 @@ 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),
|
||||||
@@ -250,7 +256,7 @@ describe("Settings tab", () => {
|
|||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
explorer = new Explorer({ notificationsClient: null, isEmulator: false });
|
||||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -259,6 +265,8 @@ 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),
|
||||||
@@ -273,6 +281,8 @@ 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),
|
||||||
@@ -296,6 +306,8 @@ 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),
|
||||||
@@ -325,7 +337,10 @@ 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);
|
||||||
|
|
||||||
@@ -368,6 +383,8 @@ 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),
|
||||||
@@ -453,7 +470,10 @@ 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({
|
||||||
@@ -506,6 +526,8 @@ 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,16 +13,15 @@ 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, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
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/dataAccess/updateOffer";
|
import { updateOffer } from "../../Common/DocumentClientUtilityBase";
|
||||||
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.
|
||||||
@@ -455,7 +454,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.rupmVisible = ko.computed(() => {
|
this.rupmVisible = ko.computed(() => {
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (this.container.isEmulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
||||||
@@ -485,7 +484,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.costsVisible = ko.computed(() => {
|
this.costsVisible = ko.computed(() => {
|
||||||
return configContext.platform !== Platform.Emulator;
|
return !this.container.isEmulator;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isTryCosmosDBSubscription = ko.computed<boolean>(() => {
|
this.isTryCosmosDBSubscription = ko.computed<boolean>(() => {
|
||||||
@@ -501,7 +500,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.canRequestSupport = ko.pureComputed(() => {
|
this.canRequestSupport = ko.pureComputed(() => {
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (this.container.isEmulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,7 +711,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs();
|
const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs();
|
||||||
const isEmulator = configContext.platform === Platform.Emulator;
|
const isEmulator = this.container.isEmulator;
|
||||||
if (isThroughputGreaterThanMaxRus && isEmulator) {
|
if (isThroughputGreaterThanMaxRus && isEmulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -882,8 +881,7 @@ 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 =
|
const throughputExceedsMaxValue: boolean = !this.container.isEmulator && this.throughput() > this.maxRUs();
|
||||||
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 =
|
||||||
@@ -1177,21 +1175,7 @@ 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 updateOfferParams: DataModels.UpdateOfferParams = {
|
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions);
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -1233,9 +1217,6 @@ 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,7 +9,6 @@ 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,8 +88,7 @@ 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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +145,7 @@ 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();
|
explorer = new Explorer({ notificationsClient: undefined, isEmulator: false });
|
||||||
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
|
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
|
||||||
id: "test",
|
id: "test",
|
||||||
name: "test",
|
name: "test",
|
||||||
@@ -50,6 +50,7 @@ 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
|
||||||
@@ -62,6 +63,7 @@ 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,7 +40,6 @@ 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;
|
||||||
@@ -239,9 +238,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.expandCollection();
|
this.expandCollection();
|
||||||
}
|
}
|
||||||
this.container.onUpdateTabsButtons([]);
|
this.container.onUpdateTabsButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
||||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public collapseCollection() {
|
public collapseCollection() {
|
||||||
@@ -292,7 +289,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.databaseId === this.databaseId && tab.collection.id() === this.id()
|
tab => tab.collection && tab.collection.rid === this.rid
|
||||||
) as DocumentsTab[];
|
) as DocumentsTab[];
|
||||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||||
|
|
||||||
@@ -314,6 +311,8 @@ 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,
|
||||||
@@ -341,7 +340,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.databaseId === this.databaseId && tab.collection.id() === this.id()
|
tab => tab.collection && tab.collection.rid === this.rid
|
||||||
) as ConflictsTab[];
|
) as ConflictsTab[];
|
||||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||||
|
|
||||||
@@ -363,6 +362,8 @@ 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,
|
||||||
@@ -396,7 +397,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.databaseId === this.databaseId && tab.collection.id() === this.id()
|
tab => tab.collection && tab.collection.rid === this.rid
|
||||||
) as QueryTablesTab[];
|
) as QueryTablesTab[];
|
||||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||||
|
|
||||||
@@ -425,6 +426,7 @@ 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,
|
||||||
@@ -449,7 +451,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.databaseId === this.databaseId && tab.collection.id() === this.id()
|
tab => tab.collection && tab.collection.rid === this.rid
|
||||||
) as GraphTab[];
|
) as GraphTab[];
|
||||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||||
|
|
||||||
@@ -475,6 +477,7 @@ 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`,
|
||||||
@@ -504,7 +507,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.databaseId === this.databaseId && tab.collection.id() === this.id()
|
tab => tab.collection && tab.collection.rid === this.rid
|
||||||
) as MongoDocumentsTab[];
|
) as MongoDocumentsTab[];
|
||||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||||
|
|
||||||
@@ -531,6 +534,7 @@ 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,
|
||||||
@@ -556,7 +560,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.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.rid === this.rid;
|
||||||
});
|
});
|
||||||
|
|
||||||
const traceStartData = {
|
const traceStartData = {
|
||||||
@@ -574,6 +578,7 @@ 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
|
||||||
@@ -666,6 +671,7 @@ 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,
|
||||||
@@ -697,6 +703,7 @@ 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,
|
||||||
@@ -727,6 +734,7 @@ 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`,
|
||||||
@@ -750,6 +758,7 @@ 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
|
||||||
});
|
});
|
||||||
@@ -812,9 +821,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandStoredProcedures();
|
this.expandStoredProcedures();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
||||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandStoredProcedures() {
|
public expandStoredProcedures() {
|
||||||
@@ -871,9 +878,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandUserDefinedFunctions();
|
this.expandUserDefinedFunctions();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
||||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandUserDefinedFunctions() {
|
public expandUserDefinedFunctions() {
|
||||||
@@ -930,9 +935,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandTriggers();
|
this.expandTriggers();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
this.container.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.rid === this.rid);
|
||||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandTriggers() {
|
public expandTriggers() {
|
||||||
@@ -1025,6 +1028,26 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1213,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>();
|
||||||
fetchPortalNotifications().then(
|
this.container.notificationsClient.fetchNotifications().then(
|
||||||
(notifications: DataModels.Notification[]) => {
|
(notifications: DataModels.Notification[]) => {
|
||||||
if (!notifications || notifications.length === 0) {
|
if (!notifications || notifications.length === 0) {
|
||||||
deferred.resolve(undefined);
|
deferred.resolve(undefined);
|
||||||
@@ -1260,6 +1283,10 @@ 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,8 +14,6 @@ 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;
|
||||||
@@ -57,7 +55,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.node?.id() === this.id()
|
tab => tab.rid === this.rid
|
||||||
);
|
);
|
||||||
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
||||||
if (!settingsTab) {
|
if (!settingsTab) {
|
||||||
@@ -79,6 +77,7 @@ 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
|
||||||
@@ -127,8 +126,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().nodeKind === "Database" &&
|
this.container.selectedNode().rid === this.rid &&
|
||||||
this.container.selectedNode().id() === this.id()
|
this.container.selectedNode().nodeKind === "Database"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +215,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>();
|
||||||
fetchPortalNotifications().then(
|
this.container.notificationsClient.fetchNotifications().then(
|
||||||
notifications => {
|
(notifications: DataModels.Notification[]) => {
|
||||||
if (!notifications || notifications.length === 0) {
|
if (!notifications || notifications.length === 0) {
|
||||||
deferred.resolve(undefined);
|
deferred.resolve(undefined);
|
||||||
return;
|
return;
|
||||||
@@ -261,7 +260,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.id() === collection.id
|
(existingCollection: Collection) => existingCollection.rid === collection._rid
|
||||||
);
|
);
|
||||||
return !collectionExists;
|
return !collectionExists;
|
||||||
}
|
}
|
||||||
@@ -271,7 +270,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.id === collection.id()
|
(coll: DataModels.Collection) => coll._rid === collection.rid
|
||||||
);
|
);
|
||||||
if (!collectionPresentInUpdatedList) {
|
if (!collectionPresentInUpdatedList) {
|
||||||
collectionsToDelete.push(collection);
|
collectionsToDelete.push(collection);
|
||||||
@@ -297,7 +296,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.id() === collection.id());
|
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.rid === collection.rid);
|
||||||
if (!shouldRemoveCollection) {
|
if (!shouldRemoveCollection) {
|
||||||
collectionsToKeep.push(collection);
|
collectionsToKeep.push(collection);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ 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,
|
||||||
@@ -120,9 +121,7 @@ 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: TabsBase) => tab.collection && tab.collection.rid === this.rid
|
||||||
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];
|
||||||
|
|
||||||
@@ -144,6 +143,7 @@ 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,
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ describe("ResourceTreeAdapter", () => {
|
|||||||
explorer.selectedNode(({
|
explorer.selectedNode(({
|
||||||
nodeKind: "Database",
|
nodeKind: "Database",
|
||||||
rid: "dbrid",
|
rid: "dbrid",
|
||||||
id: ko.observable<string>("dbid"),
|
id: ko.observable<string>("id"),
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||||
} as unknown) as ViewModels.TreeNode);
|
} as unknown) as ViewModels.TreeNode);
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", undefined, [
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbrid", "Database", [
|
||||||
ViewModels.CollectionTabKind.Documents
|
ViewModels.CollectionTabKind.Documents
|
||||||
]);
|
]);
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
@@ -70,12 +70,11 @@ describe("ResourceTreeAdapter", () => {
|
|||||||
explorer.selectedNode(({
|
explorer.selectedNode(({
|
||||||
nodeKind: "Collection",
|
nodeKind: "Collection",
|
||||||
rid: "collrid",
|
rid: "collrid",
|
||||||
databaseId: "dbid",
|
id: ko.observable<string>("id"),
|
||||||
id: ko.observable<string>("collid"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||||
} as unknown) as ViewModels.TreeNode);
|
} as unknown) as ViewModels.TreeNode);
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [subNodeKind]);
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
|
|
||||||
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
||||||
@@ -85,11 +84,10 @@ describe("ResourceTreeAdapter", () => {
|
|||||||
explorer.selectedNode(({
|
explorer.selectedNode(({
|
||||||
nodeKind: "Collection",
|
nodeKind: "Collection",
|
||||||
rid: "collrid",
|
rid: "collrid",
|
||||||
databaseId: "dbid",
|
id: ko.observable<string>("id"),
|
||||||
id: ko.observable<string>("collid"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||||
} as unknown) as ViewModels.TreeNode);
|
} as unknown) as ViewModels.TreeNode);
|
||||||
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [subNodeKind]);
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,15 +96,14 @@ describe("ResourceTreeAdapter", () => {
|
|||||||
explorer.selectedNode(({
|
explorer.selectedNode(({
|
||||||
nodeKind: "Collection",
|
nodeKind: "Collection",
|
||||||
rid: "collrid",
|
rid: "collrid",
|
||||||
databaseId: "dbid",
|
id: ko.observable<string>("id"),
|
||||||
id: ko.observable<string>("collid"),
|
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents)
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents)
|
||||||
} as unknown) as ViewModels.TreeNode);
|
} as unknown) as ViewModels.TreeNode);
|
||||||
explorer.tabsManager.activeTab({
|
explorer.tabsManager.activeTab({
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents
|
tabKind: ViewModels.CollectionTabKind.Documents
|
||||||
} as TabsBase);
|
} as TabsBase);
|
||||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [
|
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("collrid", "Collection", [
|
||||||
ViewModels.CollectionTabKind.Settings
|
ViewModels.CollectionTabKind.Settings
|
||||||
]);
|
]);
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user