Compare commits

..

35 Commits

Author SHA1 Message Date
dependabot[bot]
275a167c55 Bump i18next-http-backend from 1.0.23 to 1.2.1
Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 1.0.23 to 1.2.1.
- [Release notes](https://github.com/i18next/i18next-http-backend/releases)
- [Commits](https://github.com/i18next/i18next-http-backend/compare/v1.0.23...v1.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-29 22:30:14 +00:00
Jordi Bunster
6b73560122 Create dependabot.yml 2021-03-29 15:09:18 -07:00
Steve Faulkner
9108c01e62 Drop IE11 Support (#476) 2021-03-29 13:31:52 -05:00
Jordi Bunster
4c2f22c2b1 When switching between tabs, editors remain open (#586)
I misunderstood the purpose of a ko.foreach() loop in my changes
back in #534. This re-introduces the loop.
2021-03-29 12:38:41 -05:00
Jordi Bunster
f82b0b442e Update README.md 2021-03-27 11:18:59 -07:00
Armando Trejo Oliver
a66f042c10 Fix table api query projections (#584)
When building queries with projections, the resulting query does not include the "id" property as part of the projection. The "id" property is used by the results grid to display as the RowKey so the result is queries wih projections do not show the RowKey.

This change fixes this by including "id" as part of the projections.
2021-03-26 12:18:04 -07:00
Jordi Bunster
8cc04bab87 MongoDocumentsTab.html was not used here (#582) 2021-03-25 15:24:43 -07:00
victor-meng
ca7cd139ba Use window instead of window.parent in extractFeatures function (#577) 2021-03-23 17:21:41 -07:00
Jordi Bunster
f33ec09040 Remove Explorer.features (#563)
In addition this makes the URL-passed feature flags type safe
2021-03-22 19:04:06 +00:00
Jordi Bunster
b1aeab6b84 Knockout tab changes
This reduces the work involved in rendering knockout tabs, which will make moving them to React under a feature flag a bit easier.
2021-03-22 10:13:44 -07:00
Steve Faulkner
8bf976026f Pass masterkey in connection string mode (#572) 2021-03-19 18:37:13 -05:00
Steve Faulkner
c7ba5de90d Fix Test Explorer AAD Authority (#571) 2021-03-19 14:45:58 -05:00
Steve Faulkner
ddf59d6b24 Fix SplashScreen/Tabs Visible bug (#570) 2021-03-19 12:53:34 -05:00
Tanuj Mittal
316fe7e8bb Reset cell status after execution is canceled (#564) 2021-03-19 22:55:54 +05:30
Steve Faulkner
ee8d2070bf Remove Explorer.isRefreshing (#539) 2021-03-19 12:13:52 -05:00
Armando Trejo Oliver
e97a1643fb Make ready message backwards compatible (#569) 2021-03-19 08:38:57 -07:00
Srinath Narayanan
049e3c36d8 Dedicated gateway portal changes (#568)
* Portal changes for DedicatedGateway

Changes to support creation and deletion of DedicatedGateway resource.

Tested locally with various scenarios.

* Portal changes for DedicatedGateway. CR feedback

* Stylecop changes

* Removing TODO comments

* exposed baselineValues

* added getOnSaveNotification

* disable UI when onSave is taking place

* minro edits

* made polling optional

* added optional polling

* added default

* Added portal notifications

* merged more changes

* minor edits

* added label for description

* Added correlationids and polling of refresh

* Added correlationids and polling of refresh

* minor edit

* added label tooltip

* removed ClassInfo decorator

* Added dynamic decription

* added info and warninf types for description

* more changes to promise retry

* promise retry changes

* compile errors fixed

* New changes

* added operationstatus link

* merged sqlxEdits

* undid sqlx changes

* added completed notification

* passed retryInterval in notif options

* more changes

* added polling on landing on the page

* edits for error display

* added keys blade link

* added link generation

* added link to blade

* Modified info and description

* fixed format errors

* Second cut of the Portal

* OnChange for Number of instances

* added keys for texts

* fixed lint errors

* Added support for undefined dynamic description

* fixed failing test

* disable save/discard buttons

* fixed sqlx errors

* Dedicated Gateway changes to add the keys blade

* Change connectionStringText

* Change connectionStringText

* Text changes

* Added UI improvements

* Code review feedback

* undid package lock changes

* updated package.json

* undid package reverts

Co-authored-by: Balaji Sridharan <fnbalaji@microsoft.com>
Co-authored-by: fnbalaji <75445927+fnbalaji@users.noreply.github.com>
2021-03-19 00:54:13 -07:00
Srinath Narayanan
159c297e8d removed change to package.json (#567) 2021-03-19 00:05:15 -07:00
Srinath Narayanan
4e09e4c7fa Revert "Dedicated Gateway Portal Changes (#540)" (#566)
This reverts commit 909a9fa522.
2021-03-19 00:01:15 -07:00
Armando Trejo Oliver
19880203ec Fix ready message (format) (#565)
* Fix ready message sent to parent frame

* format
2021-03-18 21:40:31 -07:00
artrejo
f929a638d6 Fix ready message sent to parent frame 2021-03-18 20:41:43 -07:00
Steve Faulkner
3cccbdfe81 Fix Lint errors in URLUtility (#462) 2021-03-18 22:19:35 -05:00
victor-meng
65c859c835 Move add collection pane to React (#486)
* Move add collection pane to React

* Add feature flag

* fix unit tests

* FIx merge conflicts and address comments

* Resolve merge conflicts

* Address comments

* Fix e2e test failure

* Update test snapshots

* Update test snapshots
2021-03-18 20:06:13 -05:00
Steve Faulkner
c6090e2663 Update Puppeteer (#562) 2021-03-18 17:53:15 -05:00
Steve Faulkner
c43e24061c [Tables] Check for undefined before compare (#561) 2021-03-18 16:16:10 -05:00
fnbalaji
909a9fa522 Dedicated Gateway Portal Changes (#540)
* Portal changes for DedicatedGateway

Changes to support creation and deletion of DedicatedGateway resource.

Tested locally with various scenarios.

* Portal changes for DedicatedGateway. CR feedback

* Stylecop changes

* Removing TODO comments

* exposed baselineValues

* added getOnSaveNotification

* disable UI when onSave is taking place

* minro edits

* made polling optional

* added optional polling

* added default

* Added portal notifications

* merged more changes

* minor edits

* added label for description

* Added correlationids and polling of refresh

* Added correlationids and polling of refresh

* minor edit

* added label tooltip

* removed ClassInfo decorator

* Added dynamic decription

* added info and warninf types for description

* more changes to promise retry

* promise retry changes

* compile errors fixed

* New changes

* added operationstatus link

* merged sqlxEdits

* undid sqlx changes

* added completed notification

* passed retryInterval in notif options

* more changes

* added polling on landing on the page

* edits for error display

* added keys blade link

* added link generation

* added link to blade

* Modified info and description

* fixed format errors

* Second cut of the Portal

* OnChange for Number of instances

* added keys for texts

* fixed lint errors

* Added support for undefined dynamic description

* fixed failing test

* disable save/discard buttons

* fixed sqlx errors

* Dedicated Gateway changes to add the keys blade

* Change connectionStringText

* Change connectionStringText

* Text changes

* Added UI improvements

* Code review feedback

* undid package lock changes

Co-authored-by: Srinath Narayanan <srnara@microsoft.com>
2021-03-18 14:00:28 -07:00
Srinath Narayanan
be4e490a64 Added SelfServe UI updates (#559)
* Added SelfServe UI modifications

* fixed lint error

* addressed PR comments
2021-03-18 13:40:48 -07:00
Steve Faulkner
9db0975f7f Remove Explorer.isTryCosmos (#556) 2021-03-17 16:02:20 -05:00
Steve Faulkner
a2e3be9680 Remove Explorer.serverId (#555) 2021-03-17 15:24:21 -05:00
Srinath Narayanan
eab9b0ce9c mongo index policy editor bug fix (#550) 2021-03-17 11:52:16 -07:00
Steve Faulkner
d9d88c1517 Remove isAuthWithResourceToken from Explorer (#553)
* Remove isAuthWithResourceToken from Explorer

* Update test

* Remove ifs
2021-03-17 10:41:15 -05:00
Ken Dale
e10ab08d5c Small README.md update (#554) 2021-03-17 10:14:32 -05:00
Tanuj Mittal
3eda8029ba Change default Sort order for Gallery to MostRecent & fix gallery card height for empty tags (#545) 2021-03-17 01:40:38 +00:00
Steve Faulkner
6582d3be37 Fix Mongo AAD bug where collections are not load (#552) 2021-03-16 18:04:26 -05:00
Jordi Bunster
3530633fa2 Test coverage: include all source files (#551)
In addition this changes the thresholds to meet the existing level
of test coverage. This was motivated by work that imported a lot
of existing yet untested (and unexercised) code, which brought down
the total % without adding any more code.
2021-03-16 14:13:36 -07:00
117 changed files with 3998 additions and 2862 deletions

View File

@@ -24,7 +24,6 @@ src/Common/ObjectCache.test.ts
src/Common/ObjectCache.ts
src/Common/QueriesClient.ts
src/Common/Splitter.ts
src/Common/UrlUtility.ts
src/Config.ts
src/Contracts/ActionContracts.ts
src/Contracts/DataModels.ts

9
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"

View File

@@ -18,7 +18,6 @@ Run `npm start` to start the development server and automatically rebuild on cha
### Hosted Development (https://cosmos.azure.com)
- Visit: `https://localhost:1234/hostedExplorer.html`
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
- The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
### Emulator Development
@@ -69,7 +68,7 @@ Jest and Puppeteer are used for end to end browser based tests and are contained
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
### Architechture
### Architecture
[![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)

View File

@@ -1,4 +0,0 @@
{
"GITHUB_CLIENT_ID": "167ea4b09801db1de03d",
"GITHUB_CLIENT_SECRET": "e7bb10a3a8da428815805c6fc483560a035a73c1"
}

View File

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

View File

@@ -21,17 +21,13 @@ module.exports = {
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: [
// "src/Common/Headers*"
// ],
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"],
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
coveragePathIgnorePatterns: ["/node_modules/"],
// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ["json", "text", "cobertura"],
@@ -39,10 +35,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 22,
functions: 28,
lines: 33,
statements: 31,
branches: 25,
functions: 25,
lines: 30,
statements: 30,
},
},
@@ -71,7 +67,8 @@ module.exports = {
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
"^.*[.](svg|png|gif|less)$": "<rootDir>/mockModule",
"^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule",
"@nteract/stateful-components/(.*)$": "<rootDir>/mockModule",
"worker-loader": "<rootDir>/mockModule",
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
"^dnd-core$": "dnd-core/dist/cjs",

View File

@@ -718,7 +718,7 @@ execute-sproc-params-pane {
}
}
stored-procedure-tab {
.stored-procedure-tab {
@ToggleHeight: 30px;
@ToggleWidth: 180px;

532
package-lock.json generated
View File

@@ -1552,17 +1552,6 @@
}
}
},
"@fluentui/react-icons": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-0.3.9.tgz",
"integrity": "sha512-xGisio0Ds8/TWkbERtg6akoegp68/Vop3n6eD7X+0HqVL0rOl44iW+cmQrnOh1xIWiz7EqLVQU0w70bL2oLCGw==",
"requires": {
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/set-version": "^7.0.23",
"@uifabric/utilities": "^7.33.2",
"tslib": "^1.10.0"
}
},
"@fluentui/react-window-provider": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.1.tgz",
@@ -3684,30 +3673,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz",
"integrity": "sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw=="
},
"@peculiar/asn1-schema": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-1.1.2.tgz",
"integrity": "sha512-ntQ4UnUFgdjs0tfWR6YmEQm/x0glV4OFus/RjxLkaJUKfu/R7VilefBntyUO3MoKWdlCgib30KN+JpCY1HqU2A==",
"requires": {
"asn1js": "^2.0.26",
"tslib": "^1.11.1"
}
},
"@peculiar/json-schema": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz",
"integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==",
"requires": {
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
"@phosphor/algorithm": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz",
@@ -4072,11 +4037,6 @@
"resolved": "https://registry.npmjs.org/@types/asap/-/asap-2.0.0.tgz",
"integrity": "sha512-upIS0Gt9Mc8eEpCbYMZ1K8rhNosfKUtimNcINce+zLwJF5UpM3Vv7yz3S5l/1IX+DxTa8lTkUjqynvjRXyJzsg=="
},
"@types/asn1js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-2.0.0.tgz",
"integrity": "sha512-Jjzp5EqU0hNpADctc/UqhiFbY1y2MqIxBVa2S4dBlbnZHTLPMuggoL5q43X63LpsOIINRDirBjP56DUUKIUWIA=="
},
"@types/babel__core": {
"version": "7.1.12",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
@@ -4428,9 +4388,9 @@
}
},
"@types/expect-puppeteer": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.3.tgz",
"integrity": "sha512-jWZOO9d8ST2vutV5yxZ1OYxxtYD0lOufIgOUlDjyTNBGo8um67shJs2NQDLVDG06wWrabpPPOUQlI8GSvsdKVQ==",
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-4.4.5.tgz",
"integrity": "sha512-vxPaumA8Fj6xlm3llKCR9V8L936HX4PyipaNMxDbWQIOWZoCl99jabD/6xuxXnCptOWUdUhXwDuX5cAJgCHsLg==",
"dev": true,
"requires": {
"@types/jest": "*",
@@ -4637,16 +4597,14 @@
}
},
"@types/jest-environment-puppeteer": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.3.2.tgz",
"integrity": "sha512-QVR49cGaQMOrWRN7CXlvtPMuVAxa3Z+W3APxhWoSQLG/lvz1y03ECPvS7Y9eK+hgfndK+39400rO6IifDJV9YA==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.1.tgz",
"integrity": "sha512-LiZTD6i63le6QMnxi7pJB0SFv/fWtss6VVEEDm/UaeowBgjduf8txyE//j3WEeDPxngTvioUjbzA7Rc6Wc3cBA==",
"dev": true,
"requires": {
"@jest/environment": "^24",
"@jest/fake-timers": "^24",
"@jest/types": "^24",
"@jest/types": ">=24 <=26",
"@types/puppeteer": "*",
"jest-mock": "^24"
"jest-environment-node": ">=24 <=26"
}
},
"@types/json-schema": {
@@ -4684,8 +4642,7 @@
"@types/node": {
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==",
"dev": true
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A=="
},
"@types/node-fetch": {
"version": "2.5.7",
@@ -4738,9 +4695,9 @@
"dev": true
},
"@types/puppeteer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz",
"integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz",
"integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -4879,12 +4836,6 @@
"@types/jest": "*"
}
},
"@types/text-encoding": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/text-encoding/-/text-encoding-0.0.33.tgz",
"integrity": "sha512-kAMOjud0Nw3HPY0Cu8cTFk0LVya8skY+ajb2rgrSahPQ6AreN0cpGBNrs8Kjlu9EhFIeh5cp7phovL7v9HrPdQ==",
"dev": true
},
"@types/tunnel": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.1.tgz",
@@ -4933,12 +4884,6 @@
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
},
"@types/webfontloader": {
"version": "1.6.29",
"resolved": "https://registry.npmjs.org/@types/webfontloader/-/webfontloader-1.6.29.tgz",
"integrity": "sha512-wobuM+LvpkzU296NsFVRGDAFWw3X2XEhrLHuvV+VGSbok6aOxQcymmopUFwNB69qy5oudHt9lYC0JF+z+DxFLw==",
"dev": true
},
"@types/webpack": {
"version": "4.41.26",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz",
@@ -6029,11 +5974,6 @@
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asmcrypto.js": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/asmcrypto.js/-/asmcrypto.js-2.3.2.tgz",
"integrity": "sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA=="
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@@ -6054,14 +5994,6 @@
"safer-buffer": "^2.1.0"
}
},
"asn1js": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.26.tgz",
"integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==",
"requires": {
"pvutils": "^1.0.17"
}
},
"assert": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
@@ -6339,23 +6271,6 @@
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
},
"babel-polyfill": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"requires": {
"babel-runtime": "^6.26.0",
"core-js": "^2.5.0",
"regenerator-runtime": "^0.10.5"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
}
}
},
"babel-preset-current-node-syntax": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz",
@@ -6580,7 +6495,8 @@
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
},
"body-parser": {
"version": "1.19.0",
@@ -6726,7 +6642,8 @@
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-process-hrtime": {
"version": "1.0.0",
@@ -8871,6 +8788,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
"integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
"dev": true,
"requires": {
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
@@ -8904,6 +8822,12 @@
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"dev": true
},
"devtools-protocol": {
"version": "0.0.854822",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz",
"integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==",
"dev": true
},
"diagnostic-channel": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.3.1.tgz",
@@ -9181,6 +9105,7 @@
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
@@ -9421,11 +9346,6 @@
"es6-symbol": "^3.1.1"
}
},
"es6-object-assign": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
},
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
@@ -11564,6 +11484,7 @@
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
@@ -11608,6 +11529,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
@@ -11946,9 +11868,9 @@
}
},
"i18next-http-backend": {
"version": "1.0.23",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.0.23.tgz",
"integrity": "sha512-2iXwUmawM4kozvGN+k7G9u/bYQdgqtTXVK0cWvLSOpUCTaW30ZzRhgu0FBfinb71XjwUEvdqb95jNrEFjrwGKw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.2.1.tgz",
"integrity": "sha512-9L2sa+wybqi57/+VsrJPo5DCI7WLoudaK12xz0okYSmsi3Izyj7sCgrYL52WHy/O3BFY9HGORBwSwlD1WYuH6Q==",
"requires": {
"node-fetch": "2.6.1"
}
@@ -15904,12 +15826,14 @@
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"dev": true
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"minimatch": {
"version": "3.0.4",
@@ -15983,12 +15907,6 @@
"through2": "^2.0.0"
}
},
"mitt": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
"integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==",
"dev": true
},
"mixin-deep": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -16528,6 +16446,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
"integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.12.0",
@@ -16539,6 +16458,7 @@
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz",
"integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
@@ -16611,45 +16531,148 @@
"dev": true
},
"office-ui-fabric-react": {
"version": "7.134.1",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.134.1.tgz",
"integrity": "sha512-yQhPdt5kQfzI/MQnqQMu9lf2mgdHSogEfVIYgfOjbvfMJoUzqA/hH3X2Z7RbncdJ/ca2H+GXVp5GvSgahCOs6g==",
"version": "7.164.2",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.164.2.tgz",
"integrity": "sha512-qx8WbSXDJbcfq7MdJujUSAkiaYTQIQBkz4pb03r9LncwlrcnVx+thgKn8yztb2g0FlP0z5ONHv1wufrOHYxnHQ==",
"requires": {
"@fluentui/date-time-utilities": "^7.7.0",
"@fluentui/react-focus": "^7.15.0",
"@fluentui/react-icons": "^0.3.0",
"@fluentui/react-window-provider": "^0.3.0",
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.17.6",
"@fluentui/react-window-provider": "^1.0.2",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/foundation": "^7.9.0",
"@uifabric/icons": "^7.5.0",
"@uifabric/merge-styles": "^7.18.0",
"@uifabric/react-hooks": "^7.11.0",
"@uifabric/set-version": "^7.0.22",
"@uifabric/styling": "^7.16.0",
"@uifabric/utilities": "^7.31.0",
"@uifabric/foundation": "^7.9.26",
"@uifabric/icons": "^7.5.23",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/react-hooks": "^7.13.12",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"@uifabric/utilities": "^7.33.5",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
},
"dependencies": {
"@fluentui/react-window-provider": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-0.3.3.tgz",
"integrity": "sha512-MVPf2hqOQ17LAZsuvGcr3oOHksAskUm+fCYdXFhbVoAgsCDVTIuH6i8XgHFd6YjBtzjZmI4+k/3NTQfDqBX8EQ==",
"@fluentui/date-time-utilities": {
"version": "7.9.1",
"resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-7.9.1.tgz",
"integrity": "sha512-o8iU1VIY+QsqVRWARKiky29fh4KR1xaKSgMClXIi65qkt8EDDhjmlzL0KVDEoDA2GWukwb/1PpaVCWDg4v3cUQ==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@fluentui/dom-utilities": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-1.1.2.tgz",
"integrity": "sha512-XqPS7l3YoMwxdNlaYF6S2Mp0K3FmVIOIy2K3YkMc+eRxu9wFK6emr2Q/3rBhtG5u/On37NExRT7/5CTLnoi9gw==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@fluentui/react-focus": {
"version": "7.17.6",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.17.6.tgz",
"integrity": "sha512-JkLWNDe567lhvbnIhbYv9nUWYDIVN06utc3krs0UZBI+A0YZtQmftBtY0ghXo4PSjgozZocdu9sYkkgZOgyRLg==",
"requires": {
"@fluentui/keyboard-key": "^0.2.12",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@fluentui/react-window-provider": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.2.tgz",
"integrity": "sha512-fGSgL3Vp/+6t1Ysfz21FWZmqsU+iFVxOigvHnm5uKVyyRPwtaabv/F6kQ2y5isLMI2YmJaUd2i0cDJKu8ggrvw==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@fluentui/theme": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-1.7.4.tgz",
"integrity": "sha512-o4eo7lstLxxXl1g2RR9yz18Yt8yjQO/LbQuZjsiAfv/4Bf0CRnb+3j1F7gxIdBWAchKj9gzaMpIFijfI98pvYQ==",
"requires": {
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/foundation": {
"version": "7.9.26",
"resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.9.26.tgz",
"integrity": "sha512-1FLTb+jlH/Tuel2L9wT/zLl5ZW6W4Lbjrs5VUVjv81vWxzznvPnTf8+Ew0qkzaH7xDuMNMl7okswhV0IfJyheg==",
"requires": {
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/icons": {
"version": "7.5.23",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.5.23.tgz",
"integrity": "sha512-eIvUbH0EWgFgdfgFfINgqS2ZVZTyJ/9n5nR4bmcyAe75wsKxm4ser4WIT9IvaBF6+HFVfjUF/v6+VMD7y2LBng==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.0",
"tslib": "^1.10.0"
}
},
"@uifabric/merge-styles": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-7.19.2.tgz",
"integrity": "sha512-kTlhwglDqwVgIaJq+0yXgzi65plGhmFcPrfme/rXUGMJZoU+qlGT5jXj5d3kuI59p6VB8jWEg9DAxHozhYeu0g==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"tslib": "^1.10.0"
}
},
"@uifabric/react-hooks": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@uifabric/react-hooks/-/react-hooks-7.14.0.tgz",
"integrity": "sha512-Ndu/DEKHF4gFXEZa2AGgSkdWaj+njVrsSyXbkWRh2UZReFWnH1LMko9p/ZCwk1i9kAd5CUmyIfURUzIEya9YCg==",
"requires": {
"@fluentui/react-window-provider": "^1.0.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/set-version": {
"version": "7.0.24",
"resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.24.tgz",
"integrity": "sha512-t0Pt21dRqdC707/ConVJC0WvcQ/KF7tKLU8AZY7YdjgJpMHi1c0C427DB4jfUY19I92f60LOQyhJ4efH+KpFEg==",
"requires": {
"@uifabric/set-version": "^7.0.23",
"tslib": "^1.10.0"
}
},
"@uifabric/styling": {
"version": "7.16.19",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.16.19.tgz",
"integrity": "sha512-T5fjUCx0LbzUC8Myw15YCaBjdGbSrihWSiPHtLVW77k59yWAW947XnH73QngE8xU7kyKPH3AhQrUEBMB2NjHag==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-7.19.0.tgz",
"integrity": "sha512-fXComDtGV7dHF4rP4cLHwI6fC+1f/nvPavpMBz4IQdySwixta9TVMKbzt0OA6i0mJztqZCVAd27F/sl9R/JmcQ==",
"requires": {
"@fluentui/theme": "^1.7.1",
"@fluentui/theme": "^1.7.4",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/merge-styles": "^7.19.1",
"@uifabric/set-version": "^7.0.23",
"@uifabric/utilities": "^7.33.2",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"@uifabric/utilities": "^7.33.5",
"tslib": "^1.10.0"
}
},
"@uifabric/utilities": {
"version": "7.33.5",
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-7.33.5.tgz",
"integrity": "sha512-I+Oi0deD/xltSluFY8l2EVd/J4mvOaMljxKO2knSD9/KoGDlo/o5GN4gbnVo8nIt76HWHLAk3KtlJKJm6BhbIQ==",
"requires": {
"@fluentui/dom-utilities": "^1.1.2",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/set-version": "^7.0.24",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
}
}
@@ -17505,41 +17528,6 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"promise-polyfill": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz",
"integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA=="
},
"promise.prototype.finally": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz",
"integrity": "sha512-7p/K2f6dI+dM8yjRQEGrTQs5hTQixUAdOGpMEA3+pVxpX5oHKRSKAXyLw9Q9HUWDTdwtoo39dSHGQtN90HcEwQ==",
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.9.0",
"function-bind": "^1.1.1"
},
"dependencies": {
"es-abstract": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz",
"integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
}
}
},
"prompts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
@@ -17690,38 +17678,66 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"puppeteer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-4.0.0.tgz",
"integrity": "sha512-yNshT0M5DWfZ8DQoduZuGYpcwqXxKOZdgt5G0IF5VEKbydaDbWP/f5pQRfzQ4e+a4w0Rge3uzcogHeUPQM8nCA==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz",
"integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"devtools-protocol": "0.0.854822",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^4.0.0",
"mime": "^2.0.3",
"mitt": "^2.0.1",
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.1",
"pkg-dir": "^4.2.0",
"progress": "^2.0.1",
"proxy-from-env": "^1.0.0",
"proxy-from-env": "^1.1.0",
"rimraf": "^3.0.2",
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
"dev": true
},
"https-proxy-agent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"agent-base": "5",
"debug": "4"
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"dev": true,
"requires": {
"find-up": "^4.0.0"
}
},
"rimraf": {
@@ -17740,26 +17756,6 @@
"resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz",
"integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4="
},
"pvtsutils": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.1.tgz",
"integrity": "sha512-Evbhe6L4Sxwu4SPLQ4LQZhgfWDQO3qa1lju9jM5cxsQp8vE10VipcSmo7hiJW48TmiHgVLgDtC2TL6/+ND+IVg==",
"requires": {
"tslib": "^2.0.3"
},
"dependencies": {
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
"pvutils": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz",
"integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ=="
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -20460,7 +20456,6 @@
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
@@ -20470,39 +20465,35 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"terser-webpack-plugin": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.0.5.tgz",
"integrity": "sha512-pyHUyfQUZB3ciYL81GgXzDh8Qb3fGED77xDjZVSFYSN1cQnWgC51OMPKj7vBWVZx0GGuYhpa9+Vz2KxkzXWhBA==",
"dev": true,
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.1.0.tgz",
"integrity": "sha512-cjdZte66fYkZ65rQ2oJfrdCAkkhJA7YLYk5eGOcGCSGlq0ieZupRdjedSQXYknMPo2IveQL+tPdrxUkERENCFA==",
"requires": {
"cacache": "^15.0.4",
"cacache": "^15.0.5",
"find-cache-dir": "^3.3.1",
"jest-worker": "^26.0.0",
"p-limit": "^3.0.1",
"jest-worker": "^26.2.1",
"p-limit": "^3.0.2",
"schema-utils": "^2.6.6",
"serialize-javascript": "^4.0.0",
"source-map": "^0.6.1",
"terser": "^4.6.13",
"terser": "^4.8.0",
"webpack-sources": "^1.4.3"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"jest-worker": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
"dev": true,
"requires": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -20513,7 +20504,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
"yocto-queue": "^0.1.0"
}
@@ -20522,7 +20512,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
@@ -20530,14 +20519,12 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -20555,11 +20542,6 @@
"require-main-filename": "^2.0.0"
}
},
"text-encoding": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz",
"integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -21313,11 +21295,6 @@
"requires-port": "^1.0.0"
}
},
"url-polyfill": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.7.tgz",
"integrity": "sha512-ZrAxYWCREjmMtL8gSbSiKKLZZticgihCvVBtrFbUVpyoETt8GQJeG2okMWA8XryDAaHMjJfhnc+rnhXRbI4DXA=="
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -21710,65 +21687,6 @@
"minimalistic-assert": "^1.0.0"
}
},
"webcrypto-core": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.9.tgz",
"integrity": "sha512-Ac7yZQpz9+oDpKgltmHUb7Czlw6fahe9AhbBOkXkaU3y7vmvrRASNmU1T0VdH4iJsNEFgYh7R49qJjru4huzmw==",
"requires": {
"@peculiar/asn1-schema": "^2.0.12",
"@peculiar/json-schema": "^1.1.12",
"asn1js": "^2.0.26",
"pvtsutils": "^1.0.11",
"tslib": "^2.0.1"
},
"dependencies": {
"@peculiar/asn1-schema": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.0.27.tgz",
"integrity": "sha512-1tIx7iL3Ma3HtnNS93nB7nhyI0soUJypElj9owd4tpMrRDmeJ8eZubsdq1sb0KSaCs5RqZNoABCP6m5WtnlVhQ==",
"requires": {
"@types/asn1js": "^2.0.0",
"asn1js": "^2.0.26",
"pvtsutils": "^1.1.1",
"tslib": "^2.0.3"
}
},
"tslib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
"webcrypto-liner": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/webcrypto-liner/-/webcrypto-liner-1.1.4.tgz",
"integrity": "sha512-p4ovRsS8hIyG8KnW2FyrWZG4rZY5eHow5IlNiyOXeisE4gbX3FMZzJPej1dhdxL67111yf0zW2H24qt0ny+JHQ==",
"requires": {
"@peculiar/asn1-schema": "^1.0.3",
"@peculiar/json-schema": "^1.1.9",
"asmcrypto.js": "^2.3.2",
"asn1js": "^2.0.26",
"core-js": "^3.5.0",
"des.js": "^1.0.1",
"elliptic": "^6.5.2",
"pvtsutils": "^1.0.9",
"tslib": "^1.10.0",
"webcrypto-core": "^1.0.17"
},
"dependencies": {
"core-js": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz",
"integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q=="
}
}
},
"webfontloader": {
"version": "1.6.28",
"resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz",
"integrity": "sha1-23hhKSU8tujq5UwvsF+HCvZnW64="
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@@ -22448,11 +22366,6 @@
"iconv-lite": "0.4.24"
}
},
"whatwg-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
"whatwg-mimetype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
@@ -22762,8 +22675,7 @@
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
}
}
}

View File

@@ -44,9 +44,7 @@
"@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7",
"abort-controller": "3.0.0",
"applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0",
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19",
@@ -60,15 +58,13 @@
"date-fns": "1.29.0",
"dayjs": "1.8.19",
"dotenv": "8.2.0",
"es6-object-assign": "1.1.0",
"es6-symbol": "3.1.3",
"eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0",
"hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"i18next": "19.8.4",
"i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23",
"i18next-http-backend": "1.2.1",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.5.1",
@@ -79,12 +75,9 @@
"monaco-editor": "0.18.1",
"ms": "2.1.3",
"msal": "1.4.4",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.134.1",
"office-ui-fabric-react": "7.164.2",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0",
"q": "1.5.1",
"react": "16.13.1",
"react-animate-height": "2.0.8",
@@ -101,13 +94,9 @@
"rxjs": "6.6.3",
"styled-components": "4.3.2",
"swr": "0.4.0",
"text-encoding": "0.7.0",
"terser-webpack-plugin": "3.1.0",
"underscore": "1.9.1",
"url-polyfill": "1.1.7",
"utility-types": "3.10.0",
"webcrypto-liner": "1.1.4",
"webfontloader": "1.6.28",
"whatwg-fetch": "3.0.0"
"utility-types": "3.10.0"
},
"devDependencies": {
"@babel/core": "7.9.0",
@@ -121,15 +110,15 @@
"@types/d3": "5.9.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.3",
"@types/expect-puppeteer": "4.4.5",
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jest-environment-puppeteer": "4.3.2",
"@types/jest-environment-puppeteer": "4.4.1",
"@types/memoize-one": "4.1.1",
"@types/node": "12.11.1",
"@types/promise.prototype.finally": "2.0.3",
"@types/prop-types": "15.5.8",
"@types/puppeteer": "3.0.1",
"@types/puppeteer": "5.4.3",
"@types/q": "1.5.1",
"@types/react": "17.0.0",
"@types/react-dom": "17.0.0",
@@ -137,9 +126,7 @@
"@types/react-redux": "7.1.7",
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",
"@types/text-encoding": "0.0.33",
"@types/underscore": "1.7.36",
"@types/webfontloader": "1.6.29",
"@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1",
"axe-puppeteer": "1.1.0",
@@ -176,12 +163,11 @@
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1",
"prettier": "2.2.1",
"puppeteer": "4.0.0",
"puppeteer": "8.0.0",
"raw-loader": "0.5.1",
"rimraf": "3.0.0",
"sinon": "3.2.1",
"style-loader": "0.23.0",
"terser-webpack-plugin": "3.0.5",
"ts-loader": "6.2.2",
"tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0",

View File

@@ -98,30 +98,6 @@ export class CapabilityNames {
public static readonly EnableServerless: string = "EnableServerless";
}
export class Features {
public static readonly cosmosdb = "cosmosdb";
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
public static readonly executeSproc = "dataexplorerexecutesproc";
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl";
public static readonly notebookServerToken = "notebookservertoken";
public static readonly notebookBasePath = "notebookbasepath";
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSchema = "enableschema";
public static readonly enableSDKoperations = "enablesdkoperations";
public static readonly showMinRUSurvey = "showminrusurvey";
public static readonly enableDatabaseSettingsTabV1 = "enabledbsettingsv1";
public static readonly selfServeType = "selfservetype";
public static readonly enableKOPanel = "enablekopanel";
}
// flight names returned from the portal are always lowercase
export class Flights {
public static readonly SettingsV2 = "settingsv2";

View File

@@ -1,8 +1,8 @@
import { MessageTypes } from "../Contracts/ExplorerContracts";
import Q from "q";
import * as _ from "underscore";
import * as Constants from "./Constants";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { getDataExplorerWindow } from "../Utils/WindowUtils";
import * as Constants from "./Constants";
export interface CachedDataPromise<T> {
deferred: Q.Deferred<T>;
@@ -61,6 +61,21 @@ export function sendMessage(data: any): void {
}
}
export function sendReadyMessage(): void {
if (canSendMessage()) {
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe",
kind: "ready",
data: "ready",
},
portalChildWindow.document.referrer
);
}
}
export function canSendMessage(): boolean {
return window.parent !== window;
}

View File

@@ -1,55 +1,61 @@
export default class UrlUtility {
public static parseDocumentsPath(resourcePath: string): any {
if (typeof resourcePath !== "string") {
return {};
}
if (resourcePath.length === 0) {
return {};
}
if (resourcePath[resourcePath.length - 1] !== "/") {
resourcePath = resourcePath + "/";
}
if (resourcePath[0] !== "/") {
resourcePath = "/" + resourcePath;
}
var id: string;
var type: string;
var pathParts = resourcePath.split("/");
if (pathParts.length % 2 === 0) {
id = pathParts[pathParts.length - 2];
type = pathParts[pathParts.length - 3];
} else {
id = pathParts[pathParts.length - 3];
type = pathParts[pathParts.length - 2];
}
var result = {
type: type,
objectBody: {
id: id,
self: resourcePath,
},
};
return result;
}
public static createUri(baseUri: string, relativeUri: string): string {
if (!baseUri) {
throw new Error("baseUri is null or empty");
}
var slashAtEndOfUriRegex = /\/$/,
slashAtStartOfUriRegEx = /^\//;
var normalizedBaseUri = baseUri.replace(slashAtEndOfUriRegex, "") + "/",
normalizedRelativeUri = (relativeUri && relativeUri.replace(slashAtStartOfUriRegEx, "")) || "";
return normalizedBaseUri + normalizedRelativeUri;
}
interface Result {
type?: string;
objectBody?: {
id: string;
self: string;
};
}
export function parseDocumentsPath(resourcePath: string): Result {
if (typeof resourcePath !== "string") {
return {};
}
if (resourcePath.length === 0) {
return {};
}
if (resourcePath[resourcePath.length - 1] !== "/") {
resourcePath = resourcePath + "/";
}
if (resourcePath[0] !== "/") {
resourcePath = "/" + resourcePath;
}
let id: string;
let type: string;
const pathParts = resourcePath.split("/");
if (pathParts.length % 2 === 0) {
id = pathParts[pathParts.length - 2];
type = pathParts[pathParts.length - 3];
} else {
id = pathParts[pathParts.length - 3];
type = pathParts[pathParts.length - 2];
}
const result = {
type: type,
objectBody: {
id: id,
self: resourcePath,
},
};
return result;
}
export function createUri(baseUri: string, relativeUri: string): string {
if (!baseUri) {
throw new Error("baseUri is null or empty");
}
const slashAtEndOfUriRegex = /\/$/,
slashAtStartOfUriRegEx = /^\//;
const normalizedBaseUri = baseUri.replace(slashAtEndOfUriRegex, "") + "/",
normalizedRelativeUri = (relativeUri && relativeUri.replace(slashAtStartOfUriRegEx, "")) || "";
return normalizedBaseUri + normalizedRelativeUri;
}

View File

@@ -376,7 +376,6 @@ export interface DataExplorerInputsFrame {
masterKey?: string;
hasWriteAccess?: boolean;
authorizationToken?: string;
features: { [key: string]: string };
csmEndpoint?: string;
dnsSuffix?: string;
serverId?: string;
@@ -390,7 +389,6 @@ export interface DataExplorerInputsFrame {
sharedThroughputMaximum?: number;
sharedThroughputDefault?: number;
dataExplorerVersion?: string;
isAuthWithresourceToken?: boolean;
defaultCollectionThroughput?: CollectionCreationDefaults;
flights?: readonly string[];
}

View File

@@ -1,5 +1,10 @@
import * as Plotly from "plotly.js-cartesian-dist-min";
import dayjs from "dayjs";
import * as Plotly from "plotly.js-cartesian-dist-min";
import { StyleConstants } from "../../Common/Constants";
import { sendCachedDataMessage, sendReadyMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import "./Heatmap.less";
import {
ChartSettings,
DataPayload,
@@ -11,11 +16,6 @@ import {
PartitionTimeStampToData,
PortalTheme,
} from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { sendCachedDataMessage, sendMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { StyleConstants } from "../../Common/Constants";
import "./Heatmap.less";
export class Heatmap {
public static readonly elementId: string = "heatmap";
@@ -266,4 +266,4 @@ export function handleMessage(event: MessageEvent) {
}
window.addEventListener("message", handleMessage, false);
sendMessage("ready");
sendReadyMessage();

View File

@@ -1,6 +1,5 @@
import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent";
@@ -9,9 +8,25 @@ import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleCompo
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import DocumentsTab from "./Tabs/DocumentsTab";
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
import TriggerTab from "./Tabs/TriggerTab";
import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab";
import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
import QueryTab from "./Tabs/QueryTab";
import QueryTablesTab from "./Tabs/QueryTablesTab";
import GraphTab from "./Tabs/GraphTab";
import MongoShellTab from "./Tabs/MongoShellTab";
import ConflictsTab from "./Tabs/ConflictsTab";
import NotebookTabV2 from "./Tabs/NotebookV2Tab";
import TerminalTab from "./Tabs/TerminalTab";
import GalleryTab from "./Tabs/GalleryTab";
import NotebookViewerTab from "./Tabs/NotebookViewerTab";
import DatabaseSettingsTab from "./Tabs/DatabaseSettingsTab";
import TabsManagerTemplate from "./Tabs/TabsManager.html";
ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent());
@@ -21,28 +36,27 @@ ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
ko.components.register("tabs-manager", { template: TabsManagerTemplate });
// Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
ko.components.register("collection-settings-tab-v2", new TabComponents.SettingsTabV2());
ko.components.register("query-tab", new TabComponents.QueryTab());
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
ko.components.register("graph-tab", new TabComponents.GraphTab());
ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("gallery-tab", new TabComponents.GalleryTab());
ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab());
// Database Tabs
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
ko.components.register("database-settings-tab-v2", new TabComponents.SettingsTabV2());
[
DocumentsTab,
StoredProcedureTab,
TriggerTab,
UserDefinedFunctionTab,
SettingsTabV2,
QueryTab,
QueryTablesTab,
GraphTab,
MongoShellTab,
ConflictsTab,
NotebookTabV2,
TerminalTab,
GalleryTab,
NotebookViewerTab,
DatabaseSettingsTab,
DatabaseSettingsTabV2,
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
// Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());

View File

@@ -6,6 +6,7 @@ describe("CollapsibleSectionComponent", () => {
it("renders", () => {
const props: CollapsibleSectionProps = {
title: "Sample title",
isExpandedByDefault: true,
};
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);

View File

@@ -1,9 +1,10 @@
import { Icon, Label, Stack } from "office-ui-fabric-react";
import * as React from "react";
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
title: string;
isExpandedByDefault: boolean;
}
export interface CollapsibleSectionState {
@@ -14,7 +15,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
constructor(props: CollapsibleSectionProps) {
super(props);
this.state = {
isExpanded: true,
isExpanded: this.props.isExpandedByDefault,
};
}
@@ -25,8 +26,14 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
public render(): JSX.Element {
return (
<>
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
<Stack
className="collapsibleSection"
horizontal
verticalAlign="center"
tokens={accordionStackTokens}
onClick={this.toggleCollapsed}
>
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
<Label>{this.props.title}</Label>
</Stack>
{this.state.isExpanded && this.props.children}

View File

@@ -11,16 +11,10 @@ exports[`CollapsibleSectionComponent renders 1`] = `
"childrenGap": 10,
}
}
verticalAlign="center"
>
<StyledIconBase
<Icon
iconName="ChevronDown"
styles={
Object {
"root": Object {
"paddingTop": 7,
},
}
}
/>
<StyledLabelBase>
Sample title

View File

@@ -354,7 +354,6 @@ exports[`test render renders with filters 1`] = `
data-is-scrollable="true"
>
<div
aria-hidden="true"
className="stickyAbove-42"
style={
Object {
@@ -375,7 +374,6 @@ exports[`test render renders with filters 1`] = `
>
<div>
<div
aria-hidden={true}
style={
Object {
"pointerEvents": "none",
@@ -395,7 +393,6 @@ exports[`test render renders with filters 1`] = `
style={Object {}}
>
<div
aria-hidden={false}
style={
Object {
"backgroundColor": "",
@@ -411,6 +408,7 @@ exports[`test render renders with filters 1`] = `
>
<TextFieldBase
ariaLabel="Directory filter text box"
canRevealPassword={false}
className="directoryListFilterTextBox"
deferredValidationTime={200}
onChange={[Function]}
@@ -1123,7 +1121,7 @@ exports[`test render renders with filters 1`] = `
"iconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1149,7 +1147,7 @@ exports[`test render renders with filters 1`] = `
"menuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1168,7 +1166,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1247,7 +1245,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1279,8 +1277,10 @@ exports[`test render renders with filters 1`] = `
},
},
Object {
"backgroundColor": "#f3f2f1",
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1300,7 +1300,7 @@ exports[`test render renders with filters 1`] = `
"backgroundColor": "#f3f2f1",
"color": "#201f1e",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"borderColor": "Highlight",
"color": "Highlight",
},
@@ -1326,7 +1326,7 @@ exports[`test render renders with filters 1`] = `
"splitButtonContainer": Array [
Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "none",
},
},
@@ -1344,7 +1344,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -1373,19 +1373,20 @@ exports[`test render renders with filters 1`] = `
"borderBottomRightRadius": "0",
"borderTopRightRadius": "0",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"border": "1px solid WindowText",
"borderRightWidth": "0",
"color": "WindowText",
"forcedColorAdjust": "none",
},
},
},
".ms-Button--primary + .ms-Button": Object {
"border": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"border": "1px solid WindowText",
"borderLeftWidth": "0",
},
@@ -1398,10 +1399,11 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
"forcedColorAdjust": "none",
},
},
},
@@ -1411,10 +1413,11 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
"forcedColorAdjust": "none",
},
},
},
@@ -1424,12 +1427,11 @@ exports[`test render renders with filters 1`] = `
"border": "none",
"outline": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
},
"@media screen and (forced-colors: active)": Object {
"forcedColorAdjust": "none",
},
},
@@ -1441,7 +1443,7 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"color": "Window",
},
@@ -1450,7 +1452,7 @@ exports[`test render renders with filters 1`] = `
".ms-Button.is-disabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1466,7 +1468,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -1478,7 +1480,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -1495,7 +1497,7 @@ exports[`test render renders with filters 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "GrayText",
},
},
@@ -1518,7 +1520,7 @@ exports[`test render renders with filters 1`] = `
":hover": Object {
"backgroundColor": "#edebe9",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "Highlight",
},
},
@@ -1526,6 +1528,11 @@ exports[`test render renders with filters 1`] = `
},
},
Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
".ms-Button-menuIcon": Object {
"color": "WindowText",
},
},
"border": "1px solid #8a8886",
"borderBottomRightRadius": "2px",
"borderLeft": "none",
@@ -1571,7 +1578,7 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1580,7 +1587,7 @@ exports[`test render renders with filters 1`] = `
},
".ms-Button-menuIcon": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1588,7 +1595,7 @@ exports[`test render renders with filters 1`] = `
":hover": Object {
"cursor": "default",
},
"@media screen and (-ms-high-contrast: active)": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "Window",
"border": "1px solid GrayText",
"color": "GrayText",
@@ -1893,7 +1900,7 @@ exports[`test render renders with filters 1`] = `
>
<button
aria-disabled={true}
className="ms-Button ms-Button--default is-disabled directoryListButton root-54"
className="ms-Button ms-Button--default is-disabled directoryListButton root-57"
data-is-focusable={false}
disabled={true}
onClick={[Function]}
@@ -1905,7 +1912,7 @@ exports[`test render renders with filters 1`] = `
type="button"
>
<span
className="ms-Button-flexContainer flexContainer-55"
className="ms-Button-flexContainer flexContainer-58"
data-automationid="splitbuttonprimary"
>
<div
@@ -1936,7 +1943,6 @@ exports[`test render renders with filters 1`] = `
</List>
</div>
<div
aria-hidden="true"
className="stickyBelow-43"
style={
Object {

View File

@@ -7,7 +7,7 @@ import { ChildrenMargin } from "./GitHubStyleConstants";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import * as UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {

View File

@@ -47,6 +47,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8;
private static readonly cardDeleteSpinnerHeight = 360;
private static readonly smallTextLineHeight = 18;
constructor(props: GalleryCardComponentProps) {
super(props);
@@ -103,7 +104,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
</Card.Item>
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap>
<Text variant="small" nowrap styles={{ root: { height: GalleryCardComponent.smallTextLineHeight } }}>
{this.props.data.tags ? (
this.props.data.tags.map((tag, index, array) => (
<span key={tag}>
@@ -129,7 +130,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
{cardTitle}
</Text>
<Text variant="small" styles={{ root: { height: 36 } }}>
<Text variant="small" styles={{ root: { height: GalleryCardComponent.smallTextLineHeight * 2 } }}>
{this.renderTruncatedDescription()}
</Text>

View File

@@ -50,6 +50,13 @@ exports[`GalleryCardComponent renders 1`] = `
>
<Text
nowrap={true}
styles={
Object {
"root": Object {
"height": 18,
},
}
}
variant="small"
>
<span
@@ -100,7 +107,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Icon
iconName="RedEye"
styles={
Object {
@@ -124,7 +131,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Icon
iconName="Download"
styles={
Object {
@@ -148,7 +155,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Icon
iconName="Heart"
styles={
Object {
@@ -173,7 +180,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
}
>
<Styled
<Separator
styles={
Object {
"root": Object {

View File

@@ -13,7 +13,7 @@ exports[`InfoComponent renders 1`] = `
<div
className="infoPanelMain"
>
<StyledIconBase
<Icon
className="infoIconMain"
iconName="Help"
styles={

View File

@@ -68,14 +68,14 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
Invalid Date
</Text>
<Text>
<StyledIconBase
<Icon
iconName="RedEye"
/>
0
</Text>
<Text>
<StyledIconBase
<Icon
iconName="Download"
/>
0
@@ -180,14 +180,14 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
Invalid Date
</Text>
<Text>
<StyledIconBase
<Icon
iconName="RedEye"
/>
0
</Text>
<Text>
<StyledIconBase
<Icon
iconName="Download"
/>
0

View File

@@ -1,49 +1,51 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "office-ui-fabric-react";
import * as React from "react";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
import { traceStart, traceFailure, traceSuccess, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import Explorer from "../../Explorer";
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
import {
MongoIndexingPolicyComponent,
MongoIndexingPolicyComponentProps,
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
import {
hasDatabaseSharedThroughput,
GeospatialConfigType,
TtlType,
ChangeFeedPolicyState,
SettingsV2TabTypes,
getTabTitle,
isDirty,
AddMongoIndexProps,
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
getMongoNotification,
} from "./SettingsUtils";
import {
ConflictResolutionComponent,
ConflictResolutionComponentProps,
} from "./SettingsSubComponents/ConflictResolutionComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
import "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import {
MongoIndexingPolicyComponent,
MongoIndexingPolicyComponentProps,
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
import {
AddMongoIndexProps,
ChangeFeedPolicyState,
GeospatialConfigType,
getMongoNotification,
getTabTitle,
hasDatabaseSharedThroughput,
isDirty,
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
SettingsV2TabTypes,
TtlType,
} from "./SettingsUtils";
interface SettingsV2TabInfo {
tab: SettingsV2TabTypes;
@@ -137,9 +139,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.shouldShowIndexingPolicyEditor =
this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB();
this.changeFeedPolicyVisible = this.collection?.container.isFeatureEnabled(
Constants.Features.enableChangeFeedPolicy
);
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
// Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer =
@@ -325,7 +325,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
? this.saveCollectionSettings(startKey)
: this.saveDatabaseSettings(startKey));
} catch (error) {
this.container.isRefreshingExplorer(false);
this.props.settingsTab.isExecutionError(true);
console.error(error);
traceFailure(
@@ -699,7 +698,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}
}
this.container.isRefreshingExplorer(false);
this.setBaseline();
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
traceSuccess(
@@ -862,7 +860,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
});
}
}
this.container.isRefreshingExplorer(false);
this.setBaseline();
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
traceSuccess(
@@ -877,6 +874,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
);
};
public getMongoIndexTabContent = (
mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps
): JSX.Element => {
if (userContext.authType === AuthType.AAD) {
if (this.container.isEnableMongoCapabilityPresent()) {
return <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />;
}
return undefined;
}
return mongoIndexingPolicyAADError;
};
public render(): JSX.Element {
const scaleComponentProps: ScaleComponentProps = {
collection: this.collection,
@@ -994,15 +1003,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />,
});
} else if (this.container.isPreferredApiMongoDB()) {
if (this.container.isEnableMongoCapabilityPresent()) {
const mongoIndexTabContext = this.getMongoIndexTabContent(mongoIndexingPolicyComponentProps);
if (mongoIndexTabContext) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />,
});
} else {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: mongoIndexingPolicyAADError,
content: mongoIndexTabContext,
});
}
}

View File

@@ -23,7 +23,6 @@ import {
ITextStyles,
IDetailsRowStyles,
IStackStyles,
IIconStyles,
IDetailsListStyles,
IDropdownStyles,
ISeparatorStyles,
@@ -116,8 +115,6 @@ export const addMongoIndexSubElementsTokens: IStackTokens = {
childrenGap: 20,
};
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };

View File

@@ -239,7 +239,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return (
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
<CollapsibleSectionComponent title="Current index(es)">
<CollapsibleSectionComponent title="Current index(es)" isExpandedByDefault={true}>
{
<>
<DetailsList
@@ -266,7 +266,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return (
<Stack styles={mediumWidthStackStyles}>
<CollapsibleSectionComponent title="Index(es) to be dropped">
<CollapsibleSectionComponent title="Index(es) to be dropped" isExpandedByDefault={true}>
{indexesToBeDropped.length > 0 && (
<DetailsList
styles={customDetailsListStyles}

View File

@@ -42,6 +42,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
}
>
<CollapsibleSectionComponent
isExpandedByDefault={true}
title="Current index(es)"
>
<StyledWithViewportComponent
@@ -114,7 +115,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
</Stack>
</CollapsibleSectionComponent>
</Stack>
<Styled
<Separator
styles={
Object {
"root": Array [
@@ -139,6 +140,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
}
>
<CollapsibleSectionComponent
isExpandedByDefault={true}
title="Index(es) to be dropped"
/>
</Stack>

View File

@@ -1,23 +1,24 @@
import { Label, Link, MessageBar, MessageBarType, Stack, Text, TextField } from "office-ui-fabric-react";
import * as React from "react";
import * as Constants from "../../../../Common/Constants";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import * as ViewModels from "../../../../Contracts/ViewModels";
import { configContext, Platform } from "../../../../ConfigContext";
import * as DataModels from "../../../../Contracts/DataModels";
import * as ViewModels from "../../../../Contracts/ViewModels";
import * as SharedConstants from "../../../../Shared/Constants";
import { userContext } from "../../../../UserContext";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import Explorer from "../../../Explorer";
import {
getTextFieldStyles,
subComponentStackProps,
titleAndInputStackProps,
throughputUnit,
getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage,
subComponentStackProps,
throughputUnit,
titleAndInputStackProps,
updateThroughputBeyondLimitWarningMessage,
} from "../SettingsRenderUtils";
import { hasDatabaseSharedThroughput } from "../SettingsUtils";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { Link, Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
import { configContext, Platform } from "../../../../ConfigContext";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
export interface ScaleComponentProps {
collection: ViewModels.Collection;
@@ -79,7 +80,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMaxRUs = (): number => {
if (this.props.container?.isTryCosmosDBSubscription()) {
if (userContext.isTryCosmosDBSubscription) {
return Constants.TryCosmosExperience.maxRU;
}
@@ -91,7 +92,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMinRUs = (): number => {
if (this.props.container?.isTryCosmosDBSubscription()) {
if (userContext.isTryCosmosDBSubscription) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
}
@@ -172,7 +173,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
databaseAccount={this.props.container.databaseAccount()}
databaseName={this.databaseId}
collectionName={this.collectionId}
serverId={this.props.container.serverId()}
throughput={this.props.throughput}
throughputBaseline={this.props.throughputBaseline}
onThroughputChange={this.props.onThroughputChange}

View File

@@ -1,17 +1,16 @@
import { shallow } from "enzyme";
import React from "react";
import * as DataModels from "../../../../../Contracts/DataModels";
import {
ThroughputInputAutoPilotV3Component,
ThroughputInputAutoPilotV3Props,
} from "./ThroughputInputAutoPilotV3Component";
import * as DataModels from "../../../../../Contracts/DataModels";
describe("ThroughputInputAutoPilotV3Component", () => {
const baseProps: ThroughputInputAutoPilotV3Props = {
databaseAccount: {} as DataModels.DatabaseAccount,
databaseName: "test",
collectionName: "test",
serverId: undefined,
wasAutopilotOriginallySet: false,
throughput: 100,
throughputBaseline: 100,

View File

@@ -1,55 +1,52 @@
import React from "react";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import {
getTextFieldStyles,
getToolTipContainer,
noLeftPaddingCheckBoxStyle,
titleAndInputStackProps,
checkBoxAndInputStackProps,
getChoiceGroupStyles,
messageBarStyles,
getEstimatedSpendingElement,
getAutoPilotV3SpendElement,
manualToAutoscaleDisclaimerElement,
saveThroughputWarningMessage,
ManualEstimatedSpendingDisplayProps,
AutoscaleEstimatedSpendingDisplayProps,
PriceBreakdown,
getRuPriceBreakdown,
transparentDetailsHeaderStyle,
} from "../../SettingsRenderUtils";
import {
Text,
TextField,
ChoiceGroup,
IChoiceGroupOption,
Checkbox,
Stack,
ChoiceGroup,
FontIcon,
IChoiceGroupOption,
IColumn,
Label,
Link,
MessageBar,
FontIcon,
IColumn,
Stack,
Text,
TextField,
} from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import * as SharedConstants from "../../../../../Shared/Constants";
import React from "react";
import * as DataModels from "../../../../../Contracts/DataModels";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import { userContext } from "../../../../../UserContext";
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
import { Features } from "../../../../../Common/Constants";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import * as SharedConstants from "../../../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../../../UserContext";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import {
AutoscaleEstimatedSpendingDisplayProps,
checkBoxAndInputStackProps,
getAutoPilotV3SpendElement,
getChoiceGroupStyles,
getEstimatedSpendingElement,
getRuPriceBreakdown,
getTextFieldStyles,
getToolTipContainer,
ManualEstimatedSpendingDisplayProps,
manualToAutoscaleDisclaimerElement,
messageBarStyles,
noLeftPaddingCheckBoxStyle,
PriceBreakdown,
saveThroughputWarningMessage,
titleAndInputStackProps,
transparentDetailsHeaderStyle,
} from "../../SettingsRenderUtils";
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
export interface ThroughputInputAutoPilotV3Props {
databaseAccount: DataModels.DatabaseAccount;
databaseName: string;
collectionName: string;
serverId: string;
throughput: number;
throughputBaseline: number;
onThroughputChange: (newThroughput: number) => void;
@@ -182,7 +179,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
}
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
const serverId: string = this.props.serverId;
const regions = account?.properties?.readLocations?.length || 1;
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
@@ -192,7 +188,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
estimatedSpend = this.getEstimatedManualSpendElement(
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline,
serverId,
userContext.portalEnv,
regions,
multimaster,
isDirty ? this.props.throughput : undefined
@@ -200,7 +196,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
} else {
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
this.props.maxAutoPilotThroughputBaseline,
serverId,
userContext.portalEnv,
regions,
multimaster,
isDirty ? this.props.maxAutoPilotThroughput : undefined
@@ -468,7 +464,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
const href = `https://ncv.microsoft.com/vRBTO37jmO?ctx={"AzureSubscriptionId":"${userContext.subscriptionId}","CosmosDBAccountName":"${userContext.databaseAccount?.name}"}`;
const oneTBinKB = 1000000000;
const minRUperGB = 10;
const featureFlagEnabled = window.dataExplorer?.isFeatureEnabled(Features.showMinRUSurvey);
const featureFlagEnabled = userContext.features.showMinRUSurvey;
const collectionIsEligible =
userContext.subscriptionType !== SubscriptionType.Internal &&
this.props.usageSizeInKB > oneTBinKB &&

View File

@@ -41,7 +41,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
}
}
>
<StyledIconBase
<Icon
ariaLabel="Info"
iconName="Info"
styles={

View File

@@ -895,7 +895,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
"features": [Function],
"flight": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
@@ -920,7 +919,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -937,7 +935,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -946,7 +943,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -1060,7 +1056,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
@@ -2096,7 +2091,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
"features": [Function],
"flight": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
@@ -2121,7 +2115,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -2138,7 +2131,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -2147,7 +2139,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -2261,7 +2252,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
@@ -3310,7 +3300,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
"features": [Function],
"flight": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
@@ -3335,7 +3324,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -3352,7 +3340,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -3361,7 +3348,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -3475,7 +3461,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
@@ -4511,7 +4496,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function],
"visible": [Function],
},
"features": [Function],
"flight": [Function],
"graphStylingPane": GraphStylingPane {
"container": [Circular],
@@ -4536,7 +4520,6 @@ exports[`SettingsComponent renders 1`] = `
"hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function],
"isAccountReady": [Function],
"isAuthWithResourceToken": [Function],
"isAutoscaleDefaultEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function],
"isEnableMongoCapabilityPresent": [Function],
@@ -4553,7 +4536,6 @@ exports[`SettingsComponent renders 1`] = `
"isPreferredApiMongoDB": [Function],
"isPreferredApiTable": [Function],
"isPublishNotebookPaneEnabled": [Function],
"isRefreshingExplorer": [Function],
"isResourceTokenCollectionNodeSelected": [Function],
"isRightPanelV2Enabled": [Function],
"isSchemaEnabled": [Function],
@@ -4562,7 +4544,6 @@ exports[`SettingsComponent renders 1`] = `
"isSparkEnabledForAccount": [Function],
"isSynapseLinkUpdating": [Function],
"isTabsContentExpanded": [Function],
"isTryCosmosDBSubscription": [Function],
"loadQueryPane": LoadQueryPane {
"container": [Circular],
"files": [Function],
@@ -4676,7 +4657,6 @@ exports[`SettingsComponent renders 1`] = `
},
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"serverId": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,

View File

@@ -1,14 +1,13 @@
import * as React from "react";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import { TFunction } from "i18next";
import { Label, Link, MessageBar, MessageBarType, Toggle } from "office-ui-fabric-react";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { Slider } from "office-ui-fabric-react/lib/Slider";
import { SpinButton } from "office-ui-fabric-react/lib/SpinButton";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack";
import { Text } from "office-ui-fabric-react/lib/Text";
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
import { Label, Link, MessageBar, MessageBarType, Toggle } from "office-ui-fabric-react";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import * as React from "react";
import {
ChoiceItem,
Description,
@@ -19,8 +18,9 @@ import {
NumberUiType,
SmartUiInput,
} from "../../../SelfServe/SelfServeTypes";
import { TFunction } from "i18next";
import { ToolTipLabelComponent } from "../Settings/SettingsSubComponents/ToolTipLabelComponent";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
/**
* Generic UX renderer
@@ -138,11 +138,12 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
);
}
private renderTextInput(input: StringInput, labelId: string): JSX.Element {
private renderTextInput(input: StringInput, labelId: string, labelElement: JSX.Element): JSX.Element {
const value = this.props.currentValues.get(input.dataFieldName)?.value as string;
const disabled = this.props.disabled || this.props.currentValues.get(input.dataFieldName)?.disabled;
return (
<div className="stringInputContainer">
<Stack>
{labelElement}
<TextField
id={`${input.dataFieldName}-textField-input`}
aria-labelledby={labelId}
@@ -155,25 +156,32 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
root: { width: 400 },
}}
/>
</div>
</Stack>
);
}
private renderDescription(input: DescriptionDisplay, labelId: string): JSX.Element {
private renderDescription(input: DescriptionDisplay, labelId: string, labelElement: JSX.Element): JSX.Element {
const dataFieldName = input.dataFieldName;
const description = input.description || (this.props.currentValues.get(dataFieldName)?.value as Description);
if (!description) {
return this.renderError("Description is not provided.");
if (!input.isDynamicDescription) {
return this.renderError("Description is not provided.");
}
// If input is a dynamic description and description is not available, empty element is rendered
return <></>;
}
const descriptionElement = (
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId}>
{this.props.getTranslation(description.textTKey)}{" "}
{description.link && (
<Link target="_blank" href={description.link.href}>
{this.props.getTranslation(description.link.textTKey)}
</Link>
)}
</Text>
<Stack>
{labelElement}
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId}>
{this.props.getTranslation(description.textTKey)}{" "}
{description.link && (
<Link target="_blank" href={description.link.href}>
{this.props.getTranslation(description.link.textTKey)}
</Link>
)}
</Text>
</Stack>
);
if (description.type === DescriptionType.Text) {
@@ -227,7 +235,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return undefined;
};
private renderNumberInput(input: NumberInput, labelId: string): JSX.Element {
private renderNumberInput(input: NumberInput, labelId: string, labelElement: JSX.Element): JSX.Element {
const { labelTKey, min, max, dataFieldName, step } = input;
const props = {
min: min,
@@ -240,61 +248,72 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
const disabled = this.props.disabled || this.props.currentValues.get(dataFieldName)?.disabled;
if (input.uiType === NumberUiType.Spinner) {
return (
<Stack styles={{ root: { width: 400 } }} tokens={{ childrenGap: 2 }}>
<SpinButton
{...props}
id={`${input.dataFieldName}-spinner-input`}
value={value?.toString()}
onValidate={(newValue) => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={(newValue) => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={(newValue) => this.onDecrement(input, newValue, props.step, props.min)}
labelPosition={Position.top}
aria-labelledby={labelId}
disabled={disabled}
/>
{this.state.errors.has(dataFieldName) && (
<MessageBar messageBarType={MessageBarType.error}>Error: {this.state.errors.get(dataFieldName)}</MessageBar>
)}
<Stack>
{labelElement}
<Stack styles={{ root: { width: 400 } }} tokens={{ childrenGap: 2 }}>
<SpinButton
{...props}
id={`${input.dataFieldName}-spinner-input`}
value={value?.toString()}
onValidate={(newValue) => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={(newValue) => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={(newValue) => this.onDecrement(input, newValue, props.step, props.min)}
labelPosition={Position.top}
aria-labelledby={labelId}
disabled={disabled}
/>
{this.state.errors.has(dataFieldName) && (
<MessageBar messageBarType={MessageBarType.error}>
Error: {this.state.errors.get(dataFieldName)}
</MessageBar>
)}
</Stack>
</Stack>
);
} else if (input.uiType === NumberUiType.Slider) {
return (
<div id={`${input.dataFieldName}-slider-input`}>
<Slider
{...props}
value={value}
disabled={disabled}
onChange={(newValue) => this.props.onInputChange(input, newValue)}
styles={{
root: { width: 400 },
valueLabel: SmartUiComponent.labelStyle,
}}
/>
</div>
<Stack>
{labelElement}
<div id={`${input.dataFieldName}-slider-input`}>
<Slider
{...props}
value={value}
disabled={disabled}
onChange={(newValue) => this.props.onInputChange(input, newValue)}
styles={{
root: { width: 400 },
valueLabel: SmartUiComponent.labelStyle,
}}
/>
</div>
</Stack>
);
} else {
return <>Unsupported number UI type {input.uiType}</>;
}
}
private renderBooleanInput(input: BooleanInput, labelId: string): JSX.Element {
private renderBooleanInput(input: BooleanInput, labelId: string, labelElement: JSX.Element): JSX.Element {
const value = this.props.currentValues.get(input.dataFieldName)?.value as boolean;
const disabled = this.props.disabled || this.props.currentValues.get(input.dataFieldName)?.disabled;
return (
<Toggle
id={`${input.dataFieldName}-toggle-input`}
aria-labelledby={labelId}
checked={value || false}
onText={this.props.getTranslation(input.trueLabelTKey)}
offText={this.props.getTranslation(input.falseLabelTKey)}
disabled={disabled}
onChange={(event, checked: boolean) => this.props.onInputChange(input, checked)}
styles={{ root: { width: 400 } }}
/>
<Stack>
{labelElement}
<Toggle
id={`${input.dataFieldName}-toggle-input`}
aria-labelledby={labelId}
checked={value || false}
onText={this.props.getTranslation(input.trueLabelTKey)}
offText={this.props.getTranslation(input.falseLabelTKey)}
disabled={disabled}
onChange={(event, checked: boolean) => this.props.onInputChange(input, checked)}
styles={{ root: { width: 400 } }}
/>
</Stack>
);
}
private renderChoiceInput(input: ChoiceInput, labelId: string): JSX.Element {
private renderChoiceInput(input: ChoiceInput, labelId: string, labelElement: JSX.Element): JSX.Element {
const { defaultKey, dataFieldName, choices, placeholderTKey } = input;
const value = this.props.currentValues.get(dataFieldName)?.value as string;
const disabled = this.props.disabled || this.props.currentValues.get(dataFieldName)?.disabled;
@@ -303,22 +322,26 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
selectedKey = "";
}
return (
<Dropdown
id={`${input.dataFieldName}-dropdown-input`}
aria-labelledby={labelId}
selectedKey={selectedKey}
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
placeholder={this.props.getTranslation(placeholderTKey)}
disabled={disabled}
options={choices.map((c) => ({
key: c.key,
text: this.props.getTranslation(c.label),
}))}
styles={{
root: { width: 400 },
dropdown: SmartUiComponent.labelStyle,
}}
/>
<Stack>
{labelElement}
<Dropdown
id={`${input.dataFieldName}-dropdown-input`}
aria-labelledby={labelId}
selectedKey={selectedKey}
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
placeholder={this.props.getTranslation(placeholderTKey)}
disabled={disabled}
dropdownWidth="auto"
options={choices.map((c) => ({
key: c.key,
text: this.props.getTranslation(c.label),
}))}
styles={{
root: { width: 400 },
dropdown: SmartUiComponent.labelStyle,
}}
/>
</Stack>
);
}
@@ -326,7 +349,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return <MessageBar messageBarType={MessageBarType.error}>Error: {errorMessage}</MessageBar>;
}
private renderDisplayWithInfoBubble(input: AnyDisplay, info: Info): JSX.Element {
private renderElement(input: AnyDisplay, info: Info): JSX.Element {
if (input.errorMessage) {
return this.renderError(input.errorMessage);
}
@@ -335,34 +358,31 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return <></>;
}
const labelId = `${input.dataFieldName}-label`;
return (
<Stack>
{input.labelTKey && (
<Label id={labelId}>
<ToolTipLabelComponent
label={this.props.getTranslation(input.labelTKey)}
toolTipElement={this.renderInfo(info)}
/>
</Label>
)}
{this.renderDisplay(input, labelId)}
</Stack>
const labelElement: JSX.Element = input.labelTKey && (
<Label id={labelId}>
<ToolTipLabelComponent
label={this.props.getTranslation(input.labelTKey)}
toolTipElement={this.renderInfo(info)}
/>
</Label>
);
return <Stack>{this.renderControl(input, labelId, labelElement)}</Stack>;
}
private renderDisplay(input: AnyDisplay, labelId: string): JSX.Element {
private renderControl(input: AnyDisplay, labelId: string, labelElement: JSX.Element): JSX.Element {
switch (input.type) {
case "string":
if ("description" in input || "isDynamicDescription" in input) {
return this.renderDescription(input as DescriptionDisplay, labelId);
return this.renderDescription(input as DescriptionDisplay, labelId, labelElement);
}
return this.renderTextInput(input as StringInput, labelId);
return this.renderTextInput(input as StringInput, labelId, labelElement);
case "number":
return this.renderNumberInput(input as NumberInput, labelId);
return this.renderNumberInput(input as NumberInput, labelId, labelElement);
case "boolean":
return this.renderBooleanInput(input as BooleanInput, labelId);
return this.renderBooleanInput(input as BooleanInput, labelId, labelElement);
case "object":
return this.renderChoiceInput(input as ChoiceInput, labelId);
return this.renderChoiceInput(input as ChoiceInput, labelId, labelElement);
default:
throw new Error(`Unknown input type: ${input.type}`);
}
@@ -373,7 +393,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
return (
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
<Stack.Item>{node.input && this.renderDisplayWithInfoBubble(node.input, node.info as Info)}</Stack.Item>
<Stack.Item>{node.input && this.renderElement(node.input, node.info as Info)}</Stack.Item>
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)}
</Stack>
);

View File

@@ -23,19 +23,21 @@ exports[`SmartUiComponent disable all inputs 1`] = `
>
<StackItem>
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
Click here for more information.
</StyledLinkBase>
</Text>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
>
Click here for more information.
</StyledLinkBase>
</Text>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -53,51 +55,53 @@ exports[`SmartUiComponent disable all inputs 1`] = `
>
<StackItem>
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
aria-labelledby="throughput-label"
ariaLabel="Throughput (input)"
decrementButtonIcon={
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"iconName": "ChevronDownSmall",
"root": Object {
"width": 400,
},
}
}
disabled={true}
id="throughput-spinner-input"
incrementButtonIcon={
tokens={
Object {
"iconName": "ChevronUpSmall",
"childrenGap": 2,
}
}
label=""
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
/>
>
<CustomizedSpinButton
aria-labelledby="throughput-label"
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
disabled={true}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label=""
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
/>
</Stack>
</Stack>
</Stack>
</StackItem>
@@ -116,37 +120,39 @@ exports[`SmartUiComponent disable all inputs 1`] = `
>
<StackItem>
<Stack>
<StyledLabelBase
id="throughput2-label"
>
<ToolTipLabelComponent
label="Throughput (Slider)"
/>
</StyledLabelBase>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
disabled={true}
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
<Stack>
<StyledLabelBase
id="throughput2-label"
>
<ToolTipLabelComponent
label="Throughput (Slider)"
/>
</StyledLabelBase>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
disabled={true}
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
}
}
}
/>
</div>
/>
</div>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -185,16 +191,14 @@ exports[`SmartUiComponent disable all inputs 1`] = `
>
<StackItem>
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<div
className="stringInputContainer"
>
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<StyledTextFieldBase
aria-labelledby="containerId-label"
disabled={true}
@@ -210,7 +214,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
type="text"
value=""
/>
</div>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -228,29 +232,31 @@ exports[`SmartUiComponent disable all inputs 1`] = `
>
<StackItem>
<Stack>
<StyledLabelBase
id="analyticalStore-label"
>
<ToolTipLabelComponent
label="Analytical Store"
/>
</StyledLabelBase>
<StyledToggleBase
aria-labelledby="analyticalStore-label"
checked={false}
disabled={true}
id="analyticalStore-toggle-input"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="analyticalStore-label"
>
<ToolTipLabelComponent
label="Analytical Store"
/>
</StyledLabelBase>
<StyledToggleBase
aria-labelledby="analyticalStore-label"
checked={false}
disabled={true}
id="analyticalStore-toggle-input"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
}
}
}
/>
/>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -268,48 +274,51 @@ exports[`SmartUiComponent disable all inputs 1`] = `
>
<StackItem>
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
disabled={true}
id="database-dropdown-input"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
disabled={true}
dropdownWidth="auto"
id="database-dropdown-input"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
}
/>
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": Object {
"width": 400,
},
}
}
/>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -340,19 +349,21 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
>
<StackItem>
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
<Stack>
<Text
aria-labelledby="description-label"
id="description-text-display"
>
Click here for more information.
</StyledLinkBase>
</Text>
this is an example description text.
<StyledLinkBase
href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"
target="_blank"
>
Click here for more information.
</StyledLinkBase>
</Text>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -370,51 +381,53 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
>
<StackItem>
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"root": Object {
"width": 400,
},
}
}
tokens={
Object {
"childrenGap": 2,
}
}
>
<CustomizedSpinButton
aria-labelledby="throughput-label"
ariaLabel="Throughput (input)"
decrementButtonIcon={
<Stack>
<StyledLabelBase
id="throughput-label"
>
<ToolTipLabelComponent
label="Throughput (input)"
/>
</StyledLabelBase>
<Stack
styles={
Object {
"iconName": "ChevronDownSmall",
"root": Object {
"width": 400,
},
}
}
disabled={false}
id="throughput-spinner-input"
incrementButtonIcon={
tokens={
Object {
"iconName": "ChevronUpSmall",
"childrenGap": 2,
}
}
label=""
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
/>
>
<CustomizedSpinButton
aria-labelledby="throughput-label"
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
disabled={false}
id="throughput-spinner-input"
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label=""
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
/>
</Stack>
</Stack>
</Stack>
</StackItem>
@@ -433,36 +446,38 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
>
<StackItem>
<Stack>
<StyledLabelBase
id="throughput2-label"
>
<ToolTipLabelComponent
label="Throughput (Slider)"
/>
</StyledLabelBase>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
<Stack>
<StyledLabelBase
id="throughput2-label"
>
<ToolTipLabelComponent
label="Throughput (Slider)"
/>
</StyledLabelBase>
<div
id="throughput2-slider-input"
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"root": Object {
"width": 400,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
}
}
}
/>
</div>
/>
</div>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -501,16 +516,14 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
>
<StackItem>
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<div
className="stringInputContainer"
>
<Stack>
<StyledLabelBase
id="containerId-label"
>
<ToolTipLabelComponent
label="Container id"
/>
</StyledLabelBase>
<StyledTextFieldBase
aria-labelledby="containerId-label"
id="containerId-textField-input"
@@ -525,7 +538,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
type="text"
value=""
/>
</div>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -543,28 +556,30 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
>
<StackItem>
<Stack>
<StyledLabelBase
id="analyticalStore-label"
>
<ToolTipLabelComponent
label="Analytical Store"
/>
</StyledLabelBase>
<StyledToggleBase
aria-labelledby="analyticalStore-label"
checked={false}
id="analyticalStore-toggle-input"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="analyticalStore-label"
>
<ToolTipLabelComponent
label="Analytical Store"
/>
</StyledLabelBase>
<StyledToggleBase
aria-labelledby="analyticalStore-label"
checked={false}
id="analyticalStore-toggle-input"
offText="Disabled"
onChange={[Function]}
onText="Enabled"
styles={
Object {
"root": Object {
"width": 400,
},
}
}
}
/>
/>
</Stack>
</Stack>
</StackItem>
</Stack>
@@ -582,47 +597,50 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
>
<StackItem>
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
id="database-dropdown-input"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": Object {
"width": 400,
},
<Stack>
<StyledLabelBase
id="database-label"
>
<ToolTipLabelComponent
label="Database"
/>
</StyledLabelBase>
<StyledWithResponsiveMode
aria-labelledby="database-label"
dropdownWidth="auto"
id="database-dropdown-input"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "Database 1",
},
Object {
"key": "db2",
"text": "Database 2",
},
Object {
"key": "db3",
"text": "Database 3",
},
]
}
}
/>
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"root": Object {
"width": 400,
},
}
}
/>
</Stack>
</Stack>
</StackItem>
</Stack>

View File

@@ -0,0 +1,20 @@
@import "../../../../less/Common/Constants";
.throughputInputContainer {
.throughputInputRadioBtn {
margin: 0;
}
}
.throughputInputRadioBtnLabel {
font-size: @mediumFontSize;
padding: 0 @LargeSpace 0 @SmallSpace;
}
.throughputInputSpacing {
margin-bottom: @SmallSpace;
& > * {
margin-bottom: @SmallSpace;
}
}

View File

@@ -0,0 +1,302 @@
import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "office-ui-fabric-react";
import React from "react";
import * as Constants from "../../../Common/Constants";
import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
export interface ThroughputInputProps {
isDatabase: boolean;
showFreeTierExceedThroughputTooltip: boolean;
setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void;
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
}
export interface ThroughputInputState {
isAutoscaleSelected: boolean;
throughput: number;
isCostAcknowledged: boolean;
}
export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> {
constructor(props: ThroughputInputProps) {
super(props);
this.state = {
isAutoscaleSelected: true,
throughput: AutoPilotUtils.minAutoPilotThroughput,
isCostAcknowledged: false,
};
this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
this.props.setIsAutoscale(true);
}
render(): JSX.Element {
return (
<div className="throughputInputContainer throughputInputSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text variant="small" style={{ lineHeight: "20px" }}>
{this.getThroughputLabelText()}
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center">
<input
className="throughputInputRadioBtn"
aria-label="Autoscale mode"
checked={this.state.isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={this.onAutoscaleRadioBtnChange.bind(this)}
/>
<span className="throughputInputRadioBtnLabel">Autoscale</span>
<input
className="throughputInputRadioBtn"
aria-label="Manual mode"
checked={!this.state.isAutoscaleSelected}
type="radio"
role="radio"
tabIndex={0}
onChange={this.onManualRadioBtnChange.bind(this)}
/>
<span className="throughputInputRadioBtnLabel">Manual</span>
</Stack>
{this.state.isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small">
Provision maximum RU/s required by this resource. Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
capacity calculator
</Link>
.
</Text>
<Stack horizontal>
<Text variant="small" style={{ lineHeight: "20px" }}>
Max RU/s
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}>
<Icon iconName="InfoSolid" className="panelInfoIcon" />
</TooltipHost>
</Stack>
<TextField
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
field: { fontSize: 12 },
}}
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.minAutoPilotThroughput}
value={this.state.throughput.toString()}
aria-label="Max request units per second"
required={true}
/>
<Text variant="small">
Your {this.props.isDatabase ? "database" : "container"} throughput will automatically scale from{" "}
<b>
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "}
{this.state.throughput} RU/s
</b>{" "}
based on usage.
</Text>
</Stack>
)}
{!this.state.isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text variant="small">
Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
capacity calculator
</Link>
.
</Text>
<TooltipHost
directionalHint={DirectionalHint.topLeftEdge}
content={
this.props.showFreeTierExceedThroughputTooltip &&
this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
: undefined
}
>
<TextField
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
field: { fontSize: 12 },
}}
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
step={100}
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
value={this.state.throughput.toString()}
aria-label="Max request units per second"
required={true}
/>
</TooltipHost>
</Stack>
)}
<CostEstimateText requestUnits={this.state.throughput} isAutoscale={this.state.isAutoscaleSelected} />
{this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
<Stack horizontal verticalAlign="start">
<Checkbox
checked={this.state.isCostAcknowledged}
styles={{
checkbox: { width: 12, height: 12 },
label: { padding: 0, margin: "4px 4px 0 0" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
this.setState({ isCostAcknowledged: isChecked });
this.props.onCostAcknowledgeChange(isChecked);
}}
/>
<Text variant="small" style={{ lineHeight: "20px" }}>
{this.getCostAcknowledgeText()}
</Text>
</Stack>
)}
</div>
);
}
private getThroughputLabelText(): string {
if (this.state.isAutoscaleSelected) {
return AutoPilotUtils.getAutoPilotHeaderText();
}
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
return this.state.isAutoscaleSelected
? AutoPilotUtils.getAutoPilotHeaderText()
: `Throughput (${minRU} - ${maxRU} RU/s)`;
}
private onThroughputValueChange(newInput: string): void {
const newThroughput = parseInt(newInput);
this.setState({ throughput: newThroughput });
this.props.setThroughputValue(newThroughput);
}
private getAutoScaleTooltip(): string {
return `After the first ${AutoPilotUtils.getStorageBasedOnUserInput(
this.state.throughput
)} GB of data stored, the max
RU/s will be automatically upgraded based on the new storage value.`;
}
private getCostAcknowledgeText(): string {
const databaseAccount = userContext.databaseAccount;
if (!databaseAccount || !databaseAccount.properties) {
return "";
}
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
return PricingUtils.getEstimatedSpendAcknowledgeString(
this.state.throughput,
userContext.portalEnv,
numberOfRegions,
multimasterEnabled,
this.state.isAutoscaleSelected
);
}
private onAutoscaleRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.checked && !this.state.isAutoscaleSelected) {
this.setState({ isAutoscaleSelected: true, throughput: AutoPilotUtils.minAutoPilotThroughput });
this.props.setIsAutoscale(true);
}
}
private onManualRadioBtnChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.checked && this.state.isAutoscaleSelected) {
this.setState({
isAutoscaleSelected: false,
throughput: SharedConstants.CollectionCreation.DefaultCollectionRUs400,
});
this.props.setIsAutoscale(false);
this.props.setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
}
}
}
interface CostEstimateTextProps {
requestUnits: number;
isAutoscale: boolean;
}
const CostEstimateText: React.FunctionComponent<CostEstimateTextProps> = (props: CostEstimateTextProps) => {
const { requestUnits, isAutoscale } = props;
const databaseAccount = userContext.databaseAccount;
if (!databaseAccount || !databaseAccount.properties) {
return <></>;
}
const serverId: string = userContext.portalEnv;
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
const hourlyPrice: number = PricingUtils.computeRUUsagePriceHourly({
serverId,
requestUnits,
numberOfRegions,
multimasterEnabled,
isAutoscale,
});
const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
const currency: string = PricingUtils.getPriceCurrency(serverId);
const currencySign: string = PricingUtils.getCurrencySign(serverId);
const multiplier = PricingUtils.getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale
? PricingUtils.getAutoscalePricePerRu(serverId, multiplier) * multiplier
: PricingUtils.getPricePerRu(serverId) * multiplier;
if (isAutoscale) {
return (
<Text variant="small">
Estimated monthly cost ({currency}):{" "}
<b>
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU)
</Text>
);
}
return (
<Text variant="small">
Cost ({currency}):{" "}
<b>
{currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)} monthly{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU)
<br />
<em>{PricingUtils.estimatedCostDisclaimer}</em>
</Text>
);
};

View File

@@ -48,6 +48,7 @@ import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import { NotebookUtil } from "./Notebook/NotebookUtil";
import AddCollectionPane from "./Panes/AddCollectionPane";
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
import AddDatabasePane from "./Panes/AddDatabasePane";
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
@@ -163,8 +164,6 @@ export default class Explorer {
public isAccountReady: ko.Observable<boolean>;
public canSaveQueries: ko.Computed<boolean>;
public features: ko.Observable<any>;
public serverId: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>;
public queriesClient: QueriesClient;
public tableDataClient: TableDataClient;
public splitter: Splitter;
@@ -186,11 +185,6 @@ export default class Explorer {
public selectedCollectionId: ko.Computed<string>;
public isLeftPaneExpanded: ko.Observable<boolean>;
public selectedNode: ko.Observable<ViewModels.TreeNode>;
/**
* @deprecated
* Use a local loading state and spinner instead. Using a global isRefreshing state causes problems.
* */
public isRefreshingExplorer: ko.Observable<boolean>;
private resourceTree: ResourceTreeAdapter;
// Resource Token
@@ -198,9 +192,8 @@ export default class Explorer {
public resourceTokenCollectionId: ko.Observable<string>;
public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>;
public resourceTokenPartitionKey: ko.Observable<string>;
public isAuthWithResourceToken: ko.Observable<boolean>;
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
public resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
// Tabs
public isTabsContentExpanded: ko.Observable<boolean>;
@@ -301,22 +294,6 @@ export default class Explorer {
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
let firstInitialization = true;
this.isRefreshingExplorer = ko.observable<boolean>(true);
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
if (!isRefreshing && firstInitialization) {
// set focus on first element
firstInitialization = false;
try {
document.getElementById("createNewContainerCommandButton").parentElement.parentElement.focus();
} catch (e) {
Logger.logWarning(
"getElementById('createNewContainerCommandButton') failed to find element",
"Explorer/this.isRefreshingExplorer.subscribe"
);
}
}
});
this.isAccountReady = ko.observable<boolean>(false);
this._isInitializingNotebooks = false;
this.arcadiaToken = ko.observable<string>();
@@ -337,7 +314,9 @@ export default class Explorer {
this.isSynapseLinkUpdating = ko.observable<boolean>(false);
this.isAccountReady.subscribe(async (isAccountReady: boolean) => {
if (isAccountReady) {
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
this.arcadiaWorkspaces = ko.observableArray();
@@ -348,9 +327,9 @@ export default class Explorer {
Promise.all([this._refreshNotebooksEnabledStateForAccount(), this._refreshSparkEnabledStateForAccount()]).then(
async () => {
this.isNotebookEnabled(
!this.isAuthWithResourceToken() &&
userContext.authType !== AuthType.ResourceToken &&
((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) ||
this.isFeatureEnabled(Constants.Features.enableNotebooks))
userContext.features.enableNotebooks)
);
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
@@ -372,7 +351,7 @@ export default class Explorer {
this.isSparkEnabledForAccount() &&
this.arcadiaWorkspaces() &&
this.arcadiaWorkspaces().length > 0) ||
this.isFeatureEnabled(Constants.Features.enableSpark)
userContext.features.enableSpark
);
if (this.isSparkEnabled()) {
appInsights.trackEvent(
@@ -396,26 +375,20 @@ export default class Explorer {
});
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
this.features = ko.observable();
this.serverId = ko.observable<string>();
this.queriesClient = new QueriesClient(this);
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
this.resourceTokenDatabaseId = ko.observable<string>();
this.resourceTokenCollectionId = ko.observable<string>();
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
this.resourceTokenPartitionKey = ko.observable<string>();
this.isAuthWithResourceToken = ko.observable<boolean>(false);
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
this.canExceedMaximumValue = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
);
this.canExceedMaximumValue = ko.computed<boolean>(() => userContext.features.canExceedMaximumValue);
this.isSchemaEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSchema));
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
this.isAutoscaleDefaultEnabled = ko.observable<boolean>(false);
@@ -495,7 +468,7 @@ export default class Explorer {
});
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
if (this.isFeatureEnabled(Constants.Features.enableFixedCollectionWithSharedThroughput)) {
if (userContext.features.enableFixedCollectionWithSharedThroughput) {
return true;
}
@@ -554,9 +527,7 @@ export default class Explorer {
() =>
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
);
this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
);
this.isRightPanelV2Enabled = ko.computed<boolean>(() => userContext.features.enableRightPanelV2);
this.defaultExperience.subscribe((defaultExperience: string) => {
if (
defaultExperience &&
@@ -907,42 +878,29 @@ export default class Explorer {
});
// Override notebook server parameters from URL parameters
const featureSubcription = this.features.subscribe((features) => {
const serverInfo = this.notebookServerInfo();
if (this.isFeatureEnabled(Constants.Features.notebookServerUrl)) {
serverInfo.notebookServerEndpoint = features[Constants.Features.notebookServerUrl];
}
if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) {
this.notebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl,
authToken: userContext.features.notebookServerToken,
});
}
if (this.isFeatureEnabled(Constants.Features.notebookServerToken)) {
serverInfo.authToken = features[Constants.Features.notebookServerToken];
}
this.notebookServerInfo(serverInfo);
this.notebookServerInfo.valueHasMutated();
if (userContext.features.notebookBasePath) {
this.notebookBasePath(userContext.features.notebookBasePath);
}
if (this.isFeatureEnabled(Constants.Features.notebookBasePath)) {
this.notebookBasePath(features[Constants.Features.notebookBasePath]);
}
if (this.isFeatureEnabled(Constants.Features.livyEndpoint)) {
this.sparkClusterConnectionInfo({
userName: undefined,
password: undefined,
endpoints: [
{
endpoint: features[Constants.Features.livyEndpoint],
kind: DataModels.SparkClusterEndpointKind.Livy,
},
],
});
this.sparkClusterConnectionInfo.valueHasMutated();
}
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
updateUserContext({ useSDKOperations: true });
}
featureSubcription.dispose();
});
if (userContext.features.livyEndpoint) {
this.sparkClusterConnectionInfo({
userName: undefined,
password: undefined,
endpoints: [
{
endpoint: userContext.features.livyEndpoint,
kind: DataModels.SparkClusterEndpointKind.Livy,
},
],
});
}
}
public openEnableSynapseLinkDialog(): void {
@@ -1026,20 +984,6 @@ export default class Explorer {
return this.selectedNode() == null;
}
public isFeatureEnabled(feature: string): boolean {
const features = this.features();
if (!features) {
return false;
}
if (feature in features && features[feature]) {
return true;
}
return false;
}
public logConsoleData(consoleData: ConsoleData): void {
this.setNotificationConsoleData(consoleData);
}
@@ -1086,7 +1030,6 @@ export default class Explorer {
}
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
this.isRefreshingExplorer(true);
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
dataExplorerArea: Constants.Areas.ResourceTree,
});
@@ -1116,22 +1059,19 @@ export default class Explorer {
this.deleteDatabasesFromList(deltaDatabases.toDelete);
this.selectedNode(currentlySelectedNode);
this._setLoadingStatusText("Fetching containers...");
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
.then(
() => {
this._setLoadingStatusText("Successfully fetched containers.");
deferred.resolve();
},
(reason) => {
this._setLoadingStatusText("Failed to fetch containers.");
deferred.reject(reason);
}
)
.finally(() => this.isRefreshingExplorer(false));
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then(
() => {
this._setLoadingStatusText("Successfully fetched containers.");
deferred.resolve();
},
(reason) => {
this._setLoadingStatusText("Failed to fetch containers.");
deferred.reject(reason);
}
);
},
(error) => {
this._setLoadingStatusText("Failed to fetch databases.");
this.isRefreshingExplorer(false);
deferred.reject(error);
const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
@@ -1191,8 +1131,9 @@ export default class Explorer {
description: "Refresh button clicked",
dataExplorerArea: Constants.Areas.ResourceTree,
});
this.isRefreshingExplorer(true);
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases();
userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases();
this.refreshNotebookList();
};
@@ -1285,12 +1226,12 @@ export default class Explorer {
throw error;
} finally {
// Overwrite with feature flags
if (this.isFeatureEnabled(Constants.Features.notebookServerUrl)) {
connectionInfo.notebookServerEndpoint = this.features()[Constants.Features.notebookServerUrl];
if (userContext.features.notebookServerUrl) {
connectionInfo.notebookServerEndpoint = userContext.features.notebookServerUrl;
}
if (this.isFeatureEnabled(Constants.Features.notebookServerToken)) {
connectionInfo.authToken = this.features()[Constants.Features.notebookServerToken];
if (userContext.features.notebookServerToken) {
connectionInfo.authToken = userContext.features.notebookServerToken;
}
this.notebookServerInfo(connectionInfo);
@@ -1440,16 +1381,12 @@ export default class Explorer {
if (inputs.defaultCollectionThroughput) {
this.collectionCreationDefaults = inputs.defaultCollectionThroughput;
}
this.features(inputs.features);
this.serverId(inputs.serverId ?? Constants.ServerIds.productionPortal);
this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType ?? SharedConstants.CollectionCreation.DefaultSubscriptionType);
this.hasWriteAccess(inputs.hasWriteAccess ?? true);
if (inputs.addCollectionDefaultFlight) {
this.flight(inputs.addCollectionDefaultFlight);
}
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription ?? false);
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken ?? false);
this.setFeatureFlagsFromFlights(inputs.flights);
TelemetryProcessor.traceSuccess(
Action.LoadDatabaseAccount,
@@ -1530,9 +1467,9 @@ export default class Explorer {
public isRunningOnNationalCloud(): boolean {
return (
this.serverId() === Constants.ServerIds.blackforest ||
this.serverId() === Constants.ServerIds.fairfax ||
this.serverId() === Constants.ServerIds.mooncake
userContext.portalEnv === "blackforest" ||
userContext.portalEnv === "fairfax" ||
userContext.portalEnv === "mooncake"
);
}
@@ -2397,10 +2334,12 @@ export default class Explorer {
public onNewCollectionClicked(): void {
if (this.isPreferredApiCassandra()) {
this.cassandraAddCollectionPane.open();
} else if (userContext.features.enableReactPane) {
this.openAddCollectionPanel();
} else {
this.addCollectionPane.open(this.selectedDatabaseId());
document.getElementById("linkAddCollection").focus();
}
document.getElementById("linkAddCollection").focus();
}
private refreshCommandBarButtons(): void {
@@ -2529,7 +2468,7 @@ export default class Explorer {
}
public openDeleteCollectionConfirmationPane(): void {
this.isFeatureEnabled(Constants.Features.enableKOPanel)
userContext.features.enableKOPanel
? this.deleteCollectionConfirmationPane.open()
: this.openSidePanel(
"Delete Collection",
@@ -2540,4 +2479,16 @@ export default class Explorer {
/>
);
}
public async openAddCollectionPanel(): Promise<void> {
await this.loadDatabaseOffers();
this.openSidePanel(
"New Collection",
<AddCollectionPanel
explorer={this}
closePanel={() => this.closeSidePanel()}
openNotificationConsole={() => this.expandConsole()}
/>
);
}
}

View File

@@ -4,11 +4,8 @@
* - inspired from gremlin-javascript for nodejs: https://github.com/jbmusso/gremlin-javascript
* - tested on cosmosdb gremlin server
* - only supports sessionless gremlin requests
* - Relies on text-encoding polyfill (github.com/inexorabletash/text-encoding) for TextEncoder/TextDecoder on IE, Edge.
*/
import { TextEncoder, TextDecoder } from "text-encoding";
export interface GremlinSimpleClientParameters {
endpoint: string; // The websocket endpoint
user: string;

View File

@@ -1,107 +0,0 @@
/**
* This adapter is responsible to render the React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import * as ko from "knockout";
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import * as React from "react";
import { StyleConstants } from "../../../Common/Constants";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil";
export interface CommandBarComponentProps {
isNotebookTabActive: boolean;
tabsButtons: CommandButtonComponentProps[];
}
export const CommandBarComponent: React.FunctionComponent = ({ isNotebookTabActive, tabsButtons }: CommandBarComponentProps) {
constructor(props: CommandBarComponentProps) {
super(props);
this.state = {
isNotebookTabActive: false
}
this.container = container;
this.tabsButtons = [];
// this.isNotebookTabActive = ko.computed(() =>
// container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
// );
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
container.isPreferredApiTable,
container.isPreferredApiMongoDB,
container.isPreferredApiDocumentDB,
container.isPreferredApiCassandra,
container.isPreferredApiGraph,
container.deleteCollectionText,
container.deleteDatabaseText,
container.addCollectionText,
container.addDatabaseText,
container.isDatabaseNodeOrNoneSelected,
container.isDatabaseNodeSelected,
container.isNoneSelected,
container.isResourceTokenCollectionNodeSelected,
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
container.databaseAccount,
this.isNotebookTabActive,
container.isServerlessEnabled,
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now());
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.tabsButtons = buttons;
this.triggerRender();
}
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
const contextButtons = (this.tabsButtons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (this.tabsButtons && this.tabsButtons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (props.isNotebookTabActive) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);
}
return (
<React.Fragment>
<div className="commandBarContainer">
<CommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
</React.Fragment>
);
}

View File

@@ -0,0 +1,110 @@
/**
* This adapter is responsible to render the React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import * as ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { StyleConstants } from "../../../Common/Constants";
import * as CommandBarUtil from "./CommandBarUtil";
import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: Explorer;
private tabsButtons: CommandButtonComponentProps[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: Explorer) {
this.container = container;
this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(() =>
container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
);
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
container.isPreferredApiTable,
container.isPreferredApiMongoDB,
container.isPreferredApiDocumentDB,
container.isPreferredApiCassandra,
container.isPreferredApiGraph,
container.deleteCollectionText,
container.deleteDatabaseText,
container.addCollectionText,
container.addDatabaseText,
container.isDatabaseNodeOrNoneSelected,
container.isDatabaseNodeSelected,
container.isNoneSelected,
container.isResourceTokenCollectionNodeSelected,
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
container.databaseAccount,
this.isNotebookTabActive,
container.isServerlessEnabled,
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now());
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.tabsButtons = buttons;
this.triggerRender();
}
public renderComponent(): JSX.Element {
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
const contextButtons = (this.tabsButtons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (this.tabsButtons && this.tabsButtons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (this.isNotebookTabActive()) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);
}
return (
<React.Fragment>
<div className="commandBarContainer">
<CommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
</React.Fragment>
);
}
private triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -1,8 +1,10 @@
import * as ko from "knockout";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import { AuthType } from "../../../AuthType";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import NotebookManager from "../../Notebook/NotebookManager";
import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import NotebookManager from "../../Notebook/NotebookManager";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: Explorer;
@@ -13,7 +15,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
@@ -53,7 +54,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
@@ -118,7 +118,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
@@ -199,7 +198,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
@@ -281,7 +279,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
@@ -340,12 +337,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
beforeAll(() => {
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(true);
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
updateUserContext({
authType: AuthType.ResourceToken,
});
});
it("should only show New SQL Query and Open Query buttons", () => {

View File

@@ -1,37 +1,38 @@
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import * as Constants from "../../../Common/Constants";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import AddTriggerIcon from "../../../../images/AddTrigger.svg";
import AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import GitHubIcon from "../../../../images/github.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import GitHubIcon from "../../../../images/github.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { Areas } from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext";
import Explorer from "../../Explorer";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as React from "react";
import Explorer from "../../Explorer";
import { OpenFullScreen } from "../../OpenFullScreen";
let counter = 0;
export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
if (container.isAuthWithResourceToken()) {
if (userContext.authType === AuthType.ResourceToken) {
return createStaticCommandBarButtonsForResourceToken(container);
}

View File

@@ -1,4 +1,4 @@
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer, from } from "rxjs";
import { webSocket } from "rxjs/webSocket";
import { StateObservable } from "redux-observable";
import { ofType } from "redux-observable";
@@ -944,6 +944,39 @@ const traceNotebookKernelEpic = (
);
};
const resetCellStatusOnExecuteCanceledEpic = (
action$: Observable<actions.ExecuteCanceled>,
state$: StateObservable<AppState>
): Observable<actions.UpdateCellStatus> => {
return action$.pipe(
ofType(actions.EXECUTE_CANCELED),
mergeMap((action) => {
const contentRef = action.payload.contentRef;
const model = state$.value.core.entities.contents.byRef.get(contentRef).model;
let busyCellIds: string[] = [];
if (model.type === "notebook") {
const cellMap = model.transient.get("cellMap");
if (cellMap) {
for (const entry of cellMap.toArray()) {
const cellId = entry[0];
const status = model.transient.getIn(["cellMap", cellId, "status"]);
if (status === "busy") {
busyCellIds.push(cellId);
}
}
}
}
return from(busyCellIds).pipe(
map((busyCellId) => {
return actions.updateCellStatus({ id: busyCellId, contentRef, status: undefined });
})
);
})
);
};
export const allEpics = [
addInitialCodeCellEpic,
focusInitialCodeCellEpic,
@@ -960,4 +993,5 @@ export const allEpics = [
traceNotebookTelemetryEpic,
traceNotebookInfoEpic,
traceNotebookKernelEpic,
resetCellStatusOnExecuteCanceledEpic,
];

View File

@@ -1,22 +1,22 @@
import * as _ from "underscore";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import editable from "../../Common/EditableUtility";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
import { createCollection } from "../../Common/dataAccess/createCollection";
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as ViewModels from "../../Contracts/ViewModels";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
isPreferredApiTable: ko.Computed<boolean>;
@@ -49,7 +49,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
public throughputDatabase: ViewModels.Editable<number>;
public isPreferredApiTable: ko.Computed<boolean>;
public partitionKeyPlaceholder: ko.Computed<string>;
public isTryCosmosDBSubscription: ko.Computed<boolean>;
public isTryCosmosDBSubscription: ko.Observable<boolean>;
public maxThroughputRU: ko.Observable<number>;
public minThroughputRU: ko.Observable<number>;
public throughputRangeText: ko.Computed<string>;
@@ -186,7 +186,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId: string = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -200,23 +199,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (!this.isSharedAutoPilotSelected()) {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
} else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
@@ -240,7 +244,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId: string = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -254,28 +257,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (!this.isAutoPilotSelected()) {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.throughputMultiPartition(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.throughputMultiPartition(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
} else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.autoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
);
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.autoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
@@ -285,9 +288,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
return estimatedSpend;
});
this.isTryCosmosDBSubscription = ko.pureComputed<boolean>(() => {
return (this.container && this.container.isTryCosmosDBSubscription()) || false;
});
this.isTryCosmosDBSubscription = ko.observable<boolean>(userContext.isTryCosmosDBSubscription || false);
this.isTryCosmosDBSubscription.subscribe((isTryCosmosDB: boolean) => {
if (!!isTryCosmosDB) {
@@ -298,7 +299,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.canRequestSupport = ko.pureComputed(() => {
if (
configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() &&
!userContext.isTryCosmosDBSubscription &&
configContext.platform !== Platform.Portal
) {
const offerThroughput: number = this._getThroughput();
@@ -489,7 +490,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(
this.container.serverId(),
userContext.portalEnv,
this.isFreeTierAccount(),
this.container.isFirstResourceCreated(),
this.container.defaultExperience(),
@@ -993,7 +994,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.container.openEnableSynapseLinkDialog();
}
public ttl90DaysEnabled: () => boolean = () => this.container.isFeatureEnabled(Constants.Features.ttl90Days);
public ttl90DaysEnabled: () => boolean = () => userContext.features.ttl90Days;
public isValid(): boolean {
// TODO add feature flag that disables validation for customers with custom accounts
@@ -1201,7 +1202,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (this.isAnalyticalStorageOn()) {
// TODO: always default to 90 days once the backend hotfix is deployed
return this.container.isFeatureEnabled(Constants.Features.ttl90Days)
return userContext.features.ttl90Days
? Constants.AnalyticalStorageTtl.Days90
: Constants.AnalyticalStorageTtl.Infinite;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,19 @@
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import editable from "../../Common/EditableUtility";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ContextualPaneBase } from "./ContextualPaneBase";
import * as Constants from "../../Common/Constants";
import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { configContext, Platform } from "../../ConfigContext";
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as ViewModels from "../../Contracts/ViewModels";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { ContextualPaneBase } from "./ContextualPaneBase";
export default class AddDatabasePane extends ContextualPaneBase {
public defaultExperience: ko.Computed<string>;
@@ -122,7 +122,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -134,10 +133,15 @@ export default class AddDatabasePane extends ContextualPaneBase {
let estimatedSpendAcknowledge: string;
let estimatedSpend: string;
if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -145,13 +149,13 @@ export default class AddDatabasePane extends ContextualPaneBase {
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.maxAutoPilotThroughputSet(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.maxAutoPilotThroughputSet(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -165,7 +169,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.canRequestSupport = ko.pureComputed(() => {
if (
configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() &&
!userContext.isTryCosmosDBSubscription &&
configContext.platform !== Platform.Portal
) {
const offerThroughput: number = this.throughput();
@@ -239,7 +243,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(
this.container.serverId(),
userContext.portalEnv,
this.isFreeTierAccount(),
this.container.isFirstResourceCreated(),
this.container.defaultExperience(),

View File

@@ -1,21 +1,21 @@
import * as _ from "underscore";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { HashMap } from "../../Common/HashMap";
import { configContext, Platform } from "../../ConfigContext";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as ViewModels from "../../Contracts/ViewModels";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
export default class CassandraAddCollectionPane extends ContextualPaneBase {
public createTableQuery: ko.Observable<string>;
@@ -127,7 +127,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -139,10 +138,15 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string;
let estimatedDedicatedSpendAcknowledge: string;
if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -150,13 +154,13 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.selectedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.selectedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
@@ -172,7 +176,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -183,10 +186,15 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string;
let estimatedSharedSpendAcknowledge: string;
if (!this.isSharedAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster);
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.keyspaceThroughput(),
userContext.portalEnv,
regions,
multimaster
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.keyspaceThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
@@ -194,13 +202,13 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
@@ -215,7 +223,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
});
this.canRequestSupport = ko.pureComputed(() => {
if (configContext.platform !== Platform.Emulator && !this.container.isTryCosmosDBSubscription()) {
if (configContext.platform !== Platform.Emulator && !userContext.isTryCosmosDBSubscription) {
const offerThroughput: number = this.throughput();
return offerThroughput <= 100000;
}

View File

@@ -133,7 +133,7 @@ describe("Delete Collection Confirmation Pane", () => {
.simulate("change", { target: { value: selectedCollectionId } });
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
wrapper.find("#sidePanelOkButton").hostNodes().simulate("click");
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
wrapper.unmount();
@@ -154,7 +154,7 @@ describe("Delete Collection Confirmation Pane", () => {
.simulate("change", { target: { value: feedbackText } });
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
wrapper.find("#sidePanelOkButton").hostNodes().simulate("click");
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
expect(deleteCollection).toHaveBeenCalledWith(databaseId, selectedCollectionId);
const deleteFeedback = new DeleteFeedback(

View File

@@ -1,20 +1,19 @@
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as React from "react";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { PanelFooterComponent } from "./PanelFooterComponent";
import { Collection } from "../../Contracts/ViewModels";
import { Text, TextField } from "office-ui-fabric-react";
import { userContext } from "../../UserContext";
import * as React from "react";
import { Areas } from "../../Common/Constants";
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { PanelErrorComponent, PanelErrorProps } from "./PanelErrorComponent";
import DeleteFeedback from "../../Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { Collection } from "../../Contracts/ViewModels";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
import { PanelFooterComponent } from "./PanelFooterComponent";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
import { PanelLoadingScreen } from "./PanelLoadingScreen";
export interface DeleteCollectionConfirmationPanelProps {
explorer: Explorer;
closePanel: () => void;
@@ -44,8 +43,8 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
render(): JSX.Element {
return (
<div className="panelContentContainer">
<PanelErrorComponent {...this.getPanelErrorProps()} />
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
<PanelInfoErrorComponent {...this.getPanelErrorProps()} />
<div className="panelMainContent">
<div className="confirmDeleteInput">
<span className="mandatoryStar">* </span>
@@ -79,18 +78,16 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
</div>
)}
</div>
<PanelFooterComponent buttonLabel="OK" onOKButtonClicked={() => this.submit()} />
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" hidden={!this.state.isExecuting}>
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div>
</div>
<PanelFooterComponent buttonLabel="OK" />
{this.state.isExecuting && <PanelLoadingScreen />}
</form>
);
}
private getPanelErrorProps(): PanelErrorProps {
private getPanelErrorProps(): PanelInfoErrorProps {
if (this.state.formError) {
return {
isWarning: false,
messageType: "error",
message: this.state.formError,
showErrorDetails: true,
openNotificationConsole: this.props.openNotificationConsole,
@@ -98,7 +95,7 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
}
return {
isWarning: true,
messageType: "warning",
showErrorDetails: false,
message:
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
@@ -109,9 +106,10 @@ export class DeleteCollectionConfirmationPanel extends React.Component<
return this.props.explorer.isLastCollection() && !this.props.explorer.isSelectedDatabaseShared();
}
public async submit(): Promise<void> {
const collection = this.props.explorer.findSelectedCollection();
public async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
event.preventDefault();
const collection = this.props.explorer.findSelectedCollection();
if (!collection || this.inputCollectionName !== collection.id()) {
const errorMessage = "Input collection name does not match the selected collection";
this.setState({ formError: errorMessage });

View File

@@ -1,12 +1,58 @@
@import "../../../less/Common/Constants";
.panelContentContainer {
.panelFormWrapper {
display: flex;
flex-direction: column;
height: 100%;
.panelMainContent {
flex-grow: 1;
padding: 0 34px;
margin: 20px 0;
overflow: auto;
& > * {
margin-bottom: @DefaultSpace;
& > * {
margin-bottom: @SmallSpace;
}
}
.panelInfoIcon {
font-size: @mediumFontSize;
width: @mediumFontSize;
margin: auto 0 auto @SmallSpace;
color: @InfoIconColor;
cursor: default;
vertical-align: middle;
}
.panelTextBold {
font-weight: 600;
line-height: 20px;
}
.panelTextField {
font-size: @mediumFontSize;
border: 1px solid #605e5c;
color: #000;
padding: 4px 10px;
width: @newCollectionPaneInputWidth;
}
.panelRadioBtn {
margin: 0;
}
.panelRadioBtnLabel {
font-size: @mediumFontSize;
padding: 0 @LargeSpace 0 @SmallSpace;
}
.collapsibleSection {
margin-bottom: 0;
}
}
}
@@ -16,26 +62,30 @@
font-weight: 400;
}
.panelWarningErrorContainer {
.panelInfoErrorContainer {
background-color: @BaseLow;
padding: @DefaultSpace;
display: inline-flex;
margin-bottom: 24px;
margin: 20px 34px 0 34px;
.panelWarningIcon {
i {
font-size: @WarningErrorIconSize;
width: @WarningErrorIconSize;
margin: auto 0 auto @SmallSpace;
margin-left: @SmallSpace;
}
.panelWarningIcon {
color: @WarningIconColor;
}
.panelErrorIcon {
font-size: @WarningErrorIconSize;
width: @WarningErrorIconSize;
margin: auto 0 auto @SmallSpace;
color: @ErrorIconColor;
}
.panelLargeInfoIcon {
color: @InfoIconColor;
}
.panelWarningErrorDetailsLinkContainer {
display: flex;
flex-direction: column;
@@ -48,10 +98,19 @@
}
}
.panelFooter button {
height: 30px;
.panelFooter {
padding: 20px 34px;
border-top: solid 1px #bbbbbb;
& button {
height: 30px;
}
}
.deleteCollectionFeedback {
margin-top: 12px;
}
.panelGroupSpacing > * {
margin-bottom: @SmallSpace;
}

View File

@@ -9,10 +9,30 @@ export interface PanelContainerProps {
closePanel: () => void;
}
export class PanelContainerComponent extends React.Component<PanelContainerProps> {
export interface PanelContainerState {
height: string;
}
export class PanelContainerComponent extends React.Component<PanelContainerProps, PanelContainerState> {
private static readonly consoleHeaderHeight = 32;
private static readonly consoleContentHeight = 220;
constructor(props: PanelContainerProps) {
super(props);
this.state = {
height: this.getPanelHeight(),
};
}
componentDidMount(): void {
window.addEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
}
componentWillUnmount(): void {
window.removeEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
}
render(): JSX.Element {
if (!this.props.panelContent) {
return <></>;
@@ -30,8 +50,10 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
headerClassName="panelHeader"
styles={{
navigation: { borderBottom: "1px solid #cccccc" },
content: { padding: "24px 34px 20px 34px", height: "100%" },
content: { padding: 0, height: "100%" },
scrollableContent: { height: "100%" },
header: { padding: "0 0 8px 34px" },
commands: { marginTop: 8 },
}}
style={{ height: this.getPanelHeight() }}
>

View File

@@ -1,29 +0,0 @@
import React from "react";
import { Icon, Text } from "office-ui-fabric-react";
export interface PanelErrorProps {
message: string;
isWarning: boolean;
showErrorDetails: boolean;
openNotificationConsole?: () => void;
}
export const PanelErrorComponent: React.FunctionComponent<PanelErrorProps> = (props: PanelErrorProps): JSX.Element => (
<div className="panelWarningErrorContainer">
{props.isWarning ? (
<Icon iconName="WarningSolid" className="panelWarningIcon" />
) : (
<Icon iconName="StatusErrorFull" className="panelErrorIcon" />
)}
<span className="panelWarningErrorDetailsLinkContainer">
<Text className="panelWarningErrorMessage" variant="small">
{props.message}
</Text>
{props.showErrorDetails && (
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
More details
</a>
)}
</span>
</div>
);

View File

@@ -3,13 +3,12 @@ import { PrimaryButton } from "office-ui-fabric-react";
export interface PanelFooterProps {
buttonLabel: string;
onOKButtonClicked: () => void;
}
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
props: PanelFooterProps
): JSX.Element => (
<div className="panelFooter">
<PrimaryButton id="sidePanelOkButton" text={props.buttonLabel} onClick={() => props.onOKButtonClicked()} />
<PrimaryButton type="submit" id="sidePanelOkButton" text={props.buttonLabel} />
</div>
);

View File

@@ -0,0 +1,45 @@
import React from "react";
import { Icon, Link, Stack, Text } from "office-ui-fabric-react";
export interface PanelInfoErrorProps {
message: string;
messageType: string;
showErrorDetails: boolean;
link?: string;
linkText?: string;
openNotificationConsole?: () => void;
}
export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProps> = (
props: PanelInfoErrorProps
): JSX.Element => {
let icon: JSX.Element;
if (props.messageType === "error") {
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" />;
} else if (props.messageType === "warning") {
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" />;
} else if (props.messageType === "info") {
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" />;
}
return (
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="start">
{icon}
<span className="panelWarningErrorDetailsLinkContainer">
<Text className="panelWarningErrorMessage" variant="small">
{props.message}{" "}
{props.link && props.linkText && (
<Link target="_blank" href={props.link}>
{props.linkText}
</Link>
)}
</Text>
{props.showErrorDetails && (
<a className="paneErrorLink" role="link" onClick={props.openNotificationConsole}>
More details
</a>
)}
</span>
</Stack>
);
};

View File

@@ -0,0 +1,8 @@
import React from "react";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
export const PanelLoadingScreen: React.FunctionComponent = () => (
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div>
);

View File

@@ -16,9 +16,15 @@ exports[`PaneContainerComponent test should be resize if notification console is
}
styles={
Object {
"commands": Object {
"marginTop": 8,
},
"content": Object {
"height": "100%",
"padding": "24px 34px 20px 34px",
"padding": 0,
},
"header": Object {
"padding": "0 0 8px 34px",
},
"navigation": Object {
"borderBottom": "1px solid #cccccc",
@@ -52,9 +58,15 @@ exports[`PaneContainerComponent test should render with panel content and header
}
styles={
Object {
"commands": Object {
"marginTop": 8,
},
"content": Object {
"height": "100%",
"padding": "24px 34px 20px 34px",
"padding": 0,
},
"header": Object {
"padding": "0 0 8px 34px",
},
"navigation": Object {
"borderBottom": "1px solid #cccccc",

View File

@@ -1,25 +1,26 @@
/**
* Accordion top class
*/
import * as React from "react";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import { Link } from "office-ui-fabric-react/lib/Link";
import * as React from "react";
import AddDatabaseIcon from "../../../images/AddDatabase.svg";
import NewQueryIcon from "../../../images/AddSqlQuery_16x16.svg";
import NewStoredProcedureIcon from "../../../images/AddStoredProcedure.svg";
import OpenQueryIcon from "../../../images/BrowseQuery.svg";
import NewContainerIcon from "../../../images/Hero-new-container.svg";
import NewNotebookIcon from "../../../images/Hero-new-notebook.svg";
import NewQueryIcon from "../../../images/AddSqlQuery_16x16.svg";
import OpenQueryIcon from "../../../images/BrowseQuery.svg";
import NewStoredProcedureIcon from "../../../images/AddStoredProcedure.svg";
import ScaleAndSettingsIcon from "../../../images/Scale_15x15.svg";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import AddDatabaseIcon from "../../../images/AddDatabase.svg";
import SampleIcon from "../../../images/Hero-sample.svg";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import ScaleAndSettingsIcon from "../../../images/Scale_15x15.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext";
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
import CollectionIcon from "../../../images/tree-collection.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
export interface SplashScreenItem {
iconSrc: string;
@@ -220,7 +221,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
private createCommonTaskItems(): SplashScreenItem[] {
const items: SplashScreenItem[] = [];
if (this.container.isAuthWithResourceToken()) {
if (userContext.authType === AuthType.ResourceToken) {
return items;
}

View File

@@ -1,22 +1,21 @@
import * as ko from "knockout";
import * as _ from "underscore";
import Q from "q";
import * as _ from "underscore";
import { Areas } from "../../../Common/Constants";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { CassandraTableKey, CassandraAPIDataClient } from "../TableDataClient";
import DataTableViewModel from "./DataTableViewModel";
import * as DataTableUtilities from "./DataTableUtilities";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import * as Constants from "../Constants";
import { getQuotedCqlIdentifier } from "../CqlUtilities";
import * as Entities from "../Entities";
import { CassandraAPIDataClient, CassandraTableKey } from "../TableDataClient";
import * as TableEntityProcessor from "../TableEntityProcessor";
import * as Utilities from "../Utilities";
import * as DataTableUtilities from "./DataTableUtilities";
import DataTableViewModel from "./DataTableViewModel";
import TableCommands from "./TableCommands";
import TableEntityCache from "./TableEntityCache";
import * as Constants from "../Constants";
import { Areas } from "../../../Common/Constants";
import * as Utilities from "../Utilities";
import * as Entities from "../Entities";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import * as TableEntityProcessor from "../TableEntityProcessor";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as ViewModels from "../../../Contracts/ViewModels";
interface IListTableEntitiesSegmentedResult extends Entities.IListTableEntitiesResult {
ExceedMaximumRetries?: boolean;
@@ -354,8 +353,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
itemB = new Date(<string>(<any>rowB[col])._);
break;
default:
itemA = <string>(<any>rowA[col])._.toLowerCase();
itemB = <string>(<any>rowB[col])._.toLowerCase();
itemA = <string>(<any>rowA[col])._?.toLowerCase();
itemB = <string>(<any>rowB[col])._?.toLowerCase();
}
var compareResult: number = itemA < itemB ? -1 : itemA > itemB ? 1 : 0;
if (compareResult !== 0) {

View File

@@ -1,17 +1,17 @@
import * as ko from "knockout";
import * as CustomTimestampHelper from "./CustomTimestampHelper";
import { getQuotedCqlIdentifier } from "../CqlUtilities";
import QueryClauseViewModel from "./QueryClauseViewModel";
import ClauseGroup from "./ClauseGroup";
import ClauseGroupViewModel from "./ClauseGroupViewModel";
import QueryViewModel from "./QueryViewModel";
import { KeyCodes } from "../../../Common/Constants";
import * as Constants from "../Constants";
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
import * as DateTimeUtilities from "./DateTimeUtilities";
import { getQuotedCqlIdentifier } from "../CqlUtilities";
import * as DataTableUtilities from "../DataTable/DataTableUtilities";
import TableEntityListViewModel from "../DataTable/TableEntityListViewModel";
import * as TableEntityProcessor from "../TableEntityProcessor";
import * as Utilities from "../Utilities";
import { KeyCodes } from "../../../Common/Constants";
import ClauseGroup from "./ClauseGroup";
import ClauseGroupViewModel from "./ClauseGroupViewModel";
import * as CustomTimestampHelper from "./CustomTimestampHelper";
import * as DateTimeUtilities from "./DateTimeUtilities";
import QueryClauseViewModel from "./QueryClauseViewModel";
import QueryViewModel from "./QueryViewModel";
export default class QueryBuilderViewModel {
/* Labels */
@@ -182,7 +182,7 @@ export default class QueryBuilderViewModel {
value = `["${TableEntityProcessor.keyProperties.PartitionKey}"]`;
filterString = filterString.concat(filterString === "SELECT" ? " c" : ", c");
} else if (value === Constants.EntityKeyNames.RowKey) {
value = `["${TableEntityProcessor.keyProperties.Id2}"]`;
value = `["${TableEntityProcessor.keyProperties.Id}"]`;
filterString = filterString.concat(filterString === "SELECT" ? " c" : ", c");
} else {
if (value === Constants.EntityKeyNames.Timestamp) {

View File

@@ -1,6 +1,6 @@
import * as ViewModels from "../../Contracts/ViewModels";
import * as Entities from "./Entities";
import * as Constants from "./Constants";
import * as Entities from "./Entities";
import * as DateTimeUtilities from "./QueryBuilder/DateTimeUtilities";
// For use exclusively with Tables API.
@@ -36,7 +36,7 @@ export function convertDocumentsToEntities(documents: any[]): Entities.ITableEnt
let results: Entities.ITableEntityForTablesAPI[] = [];
documents &&
documents.forEach((document) => {
if (!document.hasOwnProperty(keyProperties.PartitionKey) || !document.hasOwnProperty(keyProperties.Id2)) {
if (!document.hasOwnProperty(keyProperties.PartitionKey) || !document.hasOwnProperty(keyProperties.Id)) {
//Document does not match the current required format for Tables, so we ignore it
return; // The rest of the key properties should be guaranteed as DocumentDB properties
}

View File

@@ -26,8 +26,10 @@ import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
import { updateDocument } from "../../Common/dataAccess/updateDocument";
import { deleteConflict } from "../../Common/dataAccess/deleteConflict";
import { queryConflicts } from "../../Common/dataAccess/queryConflicts";
import template from "./ConflictsTab.html";
export default class ConflictsTab extends TabsBase {
public static readonly component = { name: "conflicts-tab", template };
public selectedConflictId: ko.Observable<ConflictId>;
public selectedConflictContent: ViewModels.Editable<string>;
public selectedConflictCurrent: ViewModels.Editable<string>;

View File

@@ -17,6 +17,7 @@ import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer";
import template from "./DatabaseSettingsTab.html";
import TabsBase from "./TabsBase";
const updateThroughputBeyondLimitWarningMessage: string = `
@@ -44,6 +45,7 @@ const throughputApplyLongDelayMessage = (isAutoscale: boolean, throughput: numbe
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
export default class DatabaseSettingsTab extends TabsBase implements ViewModels.WaitsForTemplate {
public static readonly component = { name: "database-settings-tab", template };
// editables
public isAutoPilotSelected: ViewModels.Editable<boolean>;
public throughput: ViewModels.Editable<number>;
@@ -136,7 +138,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
return "";
}
const serverId = this.container.serverId();
const regions =
(account &&
account.properties &&
@@ -150,14 +151,14 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.autoPilotThroughput(),
serverId,
userContext.portalEnv,
regions,
multimaster
);
@@ -402,7 +403,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this._setBaseline();
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
} catch (error) {
this.container.isRefreshingExplorer(false);
this.isExecutionError(true);
console.error(error);
const errorMessage = getErrorMessage(error);

View File

@@ -37,8 +37,10 @@ import { readDocument } from "../../Common/dataAccess/readDocument";
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
import { updateDocument } from "../../Common/dataAccess/updateDocument";
import { createDocument } from "../../Common/dataAccess/createDocument";
import template from "./DocumentsTab.html";
export default class DocumentsTab extends TabsBase {
public static readonly component = { name: "documents-tab", template };
public selectedDocumentId: ko.Observable<DocumentId>;
public selectedDocumentContent: ViewModels.Editable<string>;
public initialDocumentContent: ko.Observable<string>;
@@ -880,8 +882,6 @@ export default class DocumentsTab extends TabsBase {
buttons.push(DocumentsTab._createUploadButton(this.collection.container));
}
const features = this.collection.container.features() || {};
return buttons;
}

View File

@@ -6,6 +6,7 @@ import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { JunoClient, IGalleryItem } from "../../Juno/JunoClient";
import template from "./GalleryTab.html";
interface GalleryTabOptions extends ViewModels.TabOptions {
account: DatabaseAccount;
@@ -21,6 +22,7 @@ interface GalleryTabOptions extends ViewModels.TabOptions {
* Notebook gallery tab
*/
export default class GalleryTab extends TabsBase {
public static readonly component = { name: "gallery-tab", template };
private container: Explorer;
private galleryAndNotebookViewerComponentProps: GalleryAndNotebookViewerComponentProps;
public galleryAndNotebookViewerComponentAdapter: GalleryAndNotebookViewerComponentAdapter;
@@ -36,7 +38,7 @@ export default class GalleryTab extends TabsBase {
galleryItem: options.galleryItem,
isFavorite: options.isFavorite,
selectedTab: options.selectedTab,
sortBy: SortBy.MostViewed,
sortBy: SortBy.MostRecent,
searchText: undefined,
};
this.galleryAndNotebookViewerComponentAdapter = new GalleryAndNotebookViewerComponentAdapter(

View File

@@ -10,6 +10,7 @@ import GraphStylingPane from "../Panes/GraphStylingPane";
import NewVertexPane from "../Panes/NewVertexPane";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import template from "./GraphTab.html";
export interface GraphIconMap {
[key: string]: { data: string; format: string };
@@ -36,6 +37,7 @@ interface GraphTabOptions extends ViewModels.TabOptions {
}
export default class GraphTab extends TabsBase {
public static readonly component = { name: "graph-tab", template };
// Graph default configuration
public static readonly DEFAULT_NODE_CAPTION = "id";
private static readonly LINK_COLOR = "#aaa";

View File

@@ -1,426 +0,0 @@
<div
class="tab-pane active tabdocuments flexContainer"
data-bind="
attr:{
id: tabId
},
visible: isActive"
role="tabpanel"
>
<!-- Documents Tab Command Bar - Start -->
<div class="contentdiv">
<div class="tabCommandButton documentMenu">
<!-- New Document - Start -->
<span
class="commandButton"
data-bind="
click: onNewDocumentClick,
visible: newDocumentButton.visible() && newDocumentButton.enabled()"
>
<img class="commandIcon" src="/createDoc.svg" />New Document
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: newDocumentButton.visible() && !newDocumentButton.enabled()"
>
<img class="commandIcon" src="/createDoc-disabled.svg" />New Document
</span>
<!-- New Document - End -->
<!-- New Query - Start -->
<span
class="commandButton"
data-bind="
visible: !$root.isPreferredApiMongoDB,
click: collection.onNewQueryClick"
>
<img class="commandIcon" src="/AddSqlQuery_16x16.svg" /> New SQL Query
</span>
<span
class="commandButton"
data-bind="
visible: $root.isPreferredApiMongoDB,
click: collection.onNewMongoQueryClick"
>
<img class="commandIcon" src="/AddSqlQuery_16x16.svg" /> New Query
</span>
<!-- New Query - End -->
<!-- Save New - Start -->
<span
class="commandButton"
data-bind="
click: onSaveNewDocumentClick,
visible: saveNewDocumentButton.visible() && saveNewDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-cosmos.svg" />Save
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: saveNewDocumentButton.visible() && !saveNewDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-disabled.svg" />Save
</span>
<!-- Save New - End -->
<!-- Discard New - Start -->
<span
class="commandButton"
data-bind="
click: onRevertNewDocumentClick,
visible: discardNewDocumentChangesButton.visible() && discardNewDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard.svg" />Discard
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: discardNewDocumentChangesButton.visible() && !discardNewDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard-disabled.svg" />Discard
</span>
<!-- Discard New - End -->
<!-- Save Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onSaveExisitingDocumentClick,
visible: saveExisitingDocumentButton.visible() && saveExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-cosmos.svg" />Update
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: saveExisitingDocumentButton.visible() && !saveExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/save-disabled.svg" />Update
</span>
<!-- Save Exisiting - End -->
<!-- Discard Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onRevertExisitingDocumentClick,
visible: discardExisitingDocumentChangesButton.visible() && discardExisitingDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard.svg" />Discard
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: discardExisitingDocumentChangesButton.visible() && !discardExisitingDocumentChangesButton.enabled()"
>
<img class="imgiconwidth" src="/discard-disabled.svg" />Discard
</span>
<!-- Discard Exisiting - End -->
<!-- Delete Exisiting - Start -->
<span
class="commandButton"
data-bind="
click: onDeleteExisitingDocumentClick,
visible: deleteExisitingDocumentButton.visible() && deleteExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/delete.svg" />Delete
</span>
<span
class="commandButton tabCommandDisabled"
data-bind="
visible: deleteExisitingDocumentButton.visible() && !deleteExisitingDocumentButton.enabled()"
>
<img class="imgiconwidth" src="/delete-disabled.svg" />Delete
</span>
<!-- Delete Exisiting - End -->
</div>
</div>
<script type="text/html" id="toolbarItemTemplate">
<!-- ko if: type === "action" -->
<div class="toolbar-group" data-bind="visible: visible">
<button
class="toolbar-group-button"
data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled"
>
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "toggle" -->
<div class="toolbar-group" data-bind="visible: visible">
<button
class="toolbar-group-button toggle-button"
data-bind="hasFocus: focused, attr: {id: id, title: title}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled"
>
<div class="toolbar-group-button-icon" data-bind="css: { 'toggle-checked': checked }">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
<!-- /ko -->
<!-- ko if: type === "dropdown" -->
<div class="toolbar-group" data-bind="visible: visible">
<div class="dropdown" data-bind="attr: {id: (id + '-dropdown')}">
<button
role="menu"
class="toolbar-group-button"
data-bind="hasFocus: focused, attr: {id: id, title: title, 'aria-label': displayName}, event: { mousedown: mouseDown, keydown: keyDown, keyup: keyUp }, enable: enabled"
>
<div class="toolbar-group-button-icon">
<div class="toolbar_icon" data-bind="icon: icon"></div>
</div>
<span data-bind="text: displayName"></span>
</button>
</div>
</div>
<!-- /ko -->
<!-- ko if: type === "separator" -->
<div class="toolbar-group vertical-separator" data-bind="visible: visible"></div>
<!-- /ko -->
</script>
<!-- Documents Tab Command Bar - End -->
<!-- ko if: false -->
<!-- Messagebox Ok Cancel- Start -->
<div class="messagebox-background">
<div class="messagebox">
<h2 class="messagebox-title">Title</h2>
<div class="messagebox-text" tabindex="0">Text</div>
<div class="messagebox-buttons">
<div class="messagebox-buttons-container">
<button value="ok" class="messagebox-button-primary">Ok</button>
<button value="cancel" class="messagebox-button-default">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Messagebox OK Cancel - End -->
<!-- /ko -->
<!-- Filter - Start -->
<div class="filterdivs">
<!-- Read-only Filter - Start -->
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && !$root.isPreferredApiMongoDB()"
>
SELECT * FROM c
<span
data-bind="
text: appliedFilter"
></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<div
class="filterDocCollapsed"
data-bind="
visible: !isFilterExpanded() && $root.isPreferredApiMongoDB()"
>
<span
data-bind="
visible: appliedFilter().length > 0"
>Filter :
</span>
<span
data-bind="
visible: !appliedFilter().length > 0"
>No filter applied</span
>
<span
data-bind="
text: appliedFilter"
></span>
<button
class="filterbtnstyle queryButton"
data-bind="
click: onShowFilterClick"
>
Edit Filter
</button>
</div>
<!-- Read-only Filter - End -->
<!-- Editable Filter - start -->
<div
class="filterDocExpanded"
data-bind="
visible: isFilterExpanded"
>
<div>
<div>
<span
class="filterspan"
data-bind="
visible: !$root.isPreferredApiMongoDB()"
>
SELECT * FROM c
</span>
<input
type="text"
list="filtersList"
class="querydropdown"
title="Type a query predicate or choose one from the list."
data-bind="
attr:{
placeholder:$root.isPreferredApiMongoDB()?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
},
textInput: filterContent"
/>
<datalist
id="filtersList"
data-bind="
foreach: lastFilterContents"
>
<option
data-bind="
value: $data"
></option>
</datalist>
<span class="filterbuttonpad">
<button
class="filterbtnstyle queryButton"
data-bind="
click: refreshDocumentsGrid,
enable: applyFilterButton.enabled"
>
Apply Filter
</button>
</span>
<span
class="filterclose"
data-bind="
click: onHideFilterClick"
>
<img src="/close-black.svg" style="height: 14px; width: 14px" />
</span>
</div>
</div>
</div>
<!-- Editable Filter - End -->
</div>
<!-- Filter - End -->
<!-- Ids and Editor - Start -->
<div>
<div class="row rowoverride documentsTabGridAndEditor">
<div class="documentsGridHeaderContainer documentsContainer">
<!-- ko if: !partitionKeyProperty -->
<table>
<tbody>
<tr>
<!-- ko if: $root.isPreferredApiMongoDB -->
<td class="documentsGridHeader">_id</td>
<!-- /ko -->
<!-- ko if: !$root.isPreferredApiMongoDB() -->
<td class="documentsGridHeader">id</td>
<!-- /ko -->
<td class="refreshColHeader">
<img class="refreshcol" src="/refresh-cosmos.svg" data-bind="click: refreshDocumentsGrid" />
</td>
</tr>
</tbody>
</table>
<!-- /ko -->
<!-- ko if: partitionKeyProperty -->
<table>
<tbody>
<tr>
<td class="documentsGridHeader fixedWidthHeader">_id</td>
<td
class="documentsGridHeader documentsGridPartition"
data-bind="
attr: {
title: partitionKeyPropertyHeader
},
text: partitionKeyPropertyHeader"
></td>
<td class="refreshColHeader">
<img class="refreshcol" src="/refresh-cosmos.svg" data-bind="click: refreshDocumentsGrid" />
</td>
</tr>
</tbody>
</table>
<!-- /ko -->
</div>
<!-- Document Ids - Start -->
<div
class="tabdocuments scrollable"
data-bind="
attr: {
id: documentContentsGridId,
tabindex: collection.documentIds().length <= 0 ? -1 : 0
},
style: { height: dataContentsGridScrollHeight },
event: { keydown: accessibleDocumentList.onKeyDown }"
>
<table class="table can-select table-hover dataTable">
<tbody id="tbodycontent">
<!-- ko foreach: documentIds -->
<tr
class="pointer accessibleListElement"
data-bind="
click: $data.click,
css: {
gridRowSelected: $parent.selectedDocumentId && $parent.selectedDocumentId() && $parent.selectedDocumentId().rid === $data.rid,
gridRowHighlighted: $parent.accessibleDocumentList.currentItem() && $parent.accessibleDocumentList.currentItem().rid === $data.rid
}"
>
<td style="width: 82px">
<a
data-bind="
text: $data.id, attr: { title: $data.id }"
></a>
</td>
<!-- ko if: $data.partitionKeyProperty -->
<td><a data-bind="text: $data.partitionKeyValue, attr: { title: $data.partitionKeyValue }"></a></td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
<div class="loadMore">
<a role="button" data-bind="click: loadNextPage, event: { keypress: onLoadMoreKeyInput }" tabindex="0"
>Load more</a
>
</div>
</div>
<!-- Document Ids - End -->
<!-- Editor - Start -->
<div id="divcontent" style="float: left; width: calc(100% - 200px)">
<div
style="height: 100vh; border-left: 1px solid #d6d7d8; float: initial; display: flow-root !important"
data-bind="
attr: {
id: documentEditorId
},
css: {
mongoDocumentEditor:$root.isPreferredApiMongoDB()
}"
></div>
</div>
<!-- Editor - End -->
</div>
</div>
<!-- Ids and Editor - End -->
</div>

View File

@@ -5,8 +5,10 @@ import QueryTab from "./QueryTab";
import * as HeadersUtility from "../../Common/HeadersUtility";
import { queryIterator } from "../../Common/MongoProxyClient";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
import template from "./MongoQueryTab.html";
export default class MongoQueryTab extends QueryTab {
public static readonly component = { name: "mongo-query-tab", template };
public collection: ViewModels.Collection;
constructor(options: ViewModels.QueryTabOptions) {

View File

@@ -1,20 +1,20 @@
import * as Constants from "../../Common/Constants";
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import Q from "q";
import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as Constants from "../../Common/Constants";
import { HashMap } from "../../Common/HashMap";
import { configContext, Platform } from "../../ConfigContext";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import { configContext, Platform } from "../../ConfigContext";
import template from "./MongoShellTab.html";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import TabsBase from "./TabsBase";
export default class MongoShellTab extends TabsBase {
public static readonly component = { name: "mongo-shell-tab", template };
public url: ko.Computed<string>;
private _container: Explorer;
private _runtimeEndpoint: string;
@@ -33,7 +33,7 @@ export default class MongoShellTab extends TabsBase {
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
let baseUrl = "/content/mongoshell/dist/";
if (this._container.serverId() === "localhost") {
if (userContext.portalEnv === "localhost") {
baseUrl = "/content/mongoshell/";
}
@@ -85,10 +85,10 @@ export default class MongoShellTab extends TabsBase {
}
private handleReadyMessage(event: MessageEvent, shellIframe: HTMLIFrameElement) {
if (typeof event.data["data"] !== "string") {
if (typeof event.data["kind"] !== "string") {
return;
}
if (event.data.data !== "ready") {
if (event.data.kind !== "ready") {
return;
}

View File

@@ -33,6 +33,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
import { toJS, stringifyNotebook } from "@nteract/commutable";
import { appInsights } from "../../Shared/appInsights";
import { userContext } from "../../UserContext";
import template from "./NotebookV2Tab.html";
export interface NotebookTabOptions extends ViewModels.TabOptions {
account: DataModels.DatabaseAccount;
@@ -42,6 +43,7 @@ export interface NotebookTabOptions extends ViewModels.TabOptions {
}
export default class NotebookTabV2 extends TabsBase {
public static readonly component = { name: "notebookv2-tab", template };
private static clientManager: NotebookClientV2;
private container: Explorer;
public notebookPath: ko.Observable<string>;

View File

@@ -10,6 +10,7 @@ import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import template from "./NotebookViewerTab.html";
interface NotebookViewerTabOptions extends ViewModels.TabOptions {
account: DatabaseAccount;
@@ -38,6 +39,7 @@ class NotebookViewerComponentAdapter implements ReactAdapter {
}
export default class NotebookViewerTab extends TabsBase {
public static readonly component = { name: "notebook-viewer-tab", template };
private container: Explorer;
public notebookUrl: string;

View File

@@ -17,6 +17,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
import { queryDocumentsPage } from "../../Common/dataAccess/queryDocumentsPage";
import template from "./QueryTab.html";
enum ToggleState {
Result,
@@ -24,6 +25,7 @@ enum ToggleState {
}
export default class QueryTab extends TabsBase implements ViewModels.WaitsForTemplate {
public static readonly component = { name: "query-tab", template };
public queryEditorId: string;
public executeQueryButton: ViewModels.Button;
public fetchNextPageButton: ViewModels.Button;

View File

@@ -15,9 +15,11 @@ import EditEntityIcon from "../../../images/Edit-entity.svg";
import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg";
import Explorer from "../Explorer";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import template from "./QueryTablesTab.html";
// Will act as table explorer class
export default class QueryTablesTab extends TabsBase {
public static readonly component = { name: "tables-query-tab", template };
public collection: ViewModels.Collection;
public tableEntityListViewModel = ko.observable<TableEntityListViewModel>();
public queryViewModel = ko.observable<QueryViewModel>();

View File

@@ -9,8 +9,10 @@ import * as Constants from "../../Common/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import template from "./SettingsTabV2.html";
export class SettingsTabV2 extends TabsBase {
public static readonly component = { name: "collection-settings-tab-v2", template };
public settingsComponentAdapter: SettingsComponentAdapter;
constructor(options: ViewModels.TabOptions) {
@@ -87,6 +89,7 @@ export class CollectionSettingsTabV2 extends SettingsTabV2 {
}
export class DatabaseSettingsTabV2 extends SettingsTabV2 {
public static readonly component = { name: "database-settings-tab-v2", template };
private notificationRead: ko.Observable<boolean>;
private notification: DataModels.Notification;

View File

@@ -14,6 +14,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
import StoredProcedure from "../Tree/StoredProcedure";
import ScriptTabBase from "./ScriptTabBase";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import template from "./StoredProcedureTab.html";
enum ToggleState {
Result = "result",
@@ -21,6 +22,7 @@ enum ToggleState {
}
export default class StoredProcedureTab extends ScriptTabBase {
public static readonly component = { name: "stored-procedure-tab", template };
public collection: ViewModels.Collection;
public node: StoredProcedure;
public executeResultsEditorId: string;

View File

@@ -1,186 +0,0 @@
import DocumentsTabTemplate from "./DocumentsTab.html";
import ConflictsTabTemplate from "./ConflictsTab.html";
import GraphTabTemplate from "./GraphTab.html";
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
import TerminalTabTemplate from "./TerminalTab.html";
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
import MongoQueryTabTemplate from "./MongoQueryTab.html";
import MongoShellTabTemplate from "./MongoShellTab.html";
import QueryTabTemplate from "./QueryTab.html";
import QueryTablesTabTemplate from "./QueryTablesTab.html";
import SettingsTabV2Template from "./SettingsTabV2.html";
import DatabaseSettingsTabTemplate from "./DatabaseSettingsTab.html";
import StoredProcedureTabTemplate from "./StoredProcedureTab.html";
import TriggerTabTemplate from "./TriggerTab.html";
import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html";
import GalleryTabTemplate from "./GalleryTab.html";
import NotebookViewerTabTemplate from "./NotebookViewerTab.html";
import TabsManagerTemplate from "./TabsManager.html";
export class TabComponent {
constructor(data: any) {
return data.data;
}
}
export class TabsManager {
constructor() {
return {
viewModel: TabComponent,
template: TabsManagerTemplate,
};
}
}
export class DocumentsTab {
constructor() {
return {
viewModel: TabComponent,
template: DocumentsTabTemplate,
};
}
}
export class ConflictsTab {
constructor() {
return {
viewModel: TabComponent,
template: ConflictsTabTemplate,
};
}
}
export class GraphTab {
constructor() {
return {
viewModel: TabComponent,
template: GraphTabTemplate,
};
}
}
export class NotebookV2Tab {
constructor() {
return {
viewModel: TabComponent,
template: NotebookV2TabTemplate,
};
}
}
export class TerminalTab {
constructor() {
return {
viewModel: TabComponent,
template: TerminalTabTemplate,
};
}
}
export class MongoDocumentsTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoDocumentsTabTemplate,
};
}
}
export class MongoQueryTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoQueryTabTemplate,
};
}
}
export class MongoShellTab {
constructor() {
return {
viewModel: TabComponent,
template: MongoShellTabTemplate,
};
}
}
export class QueryTab {
constructor() {
return {
viewModel: TabComponent,
template: QueryTabTemplate,
};
}
}
export class QueryTablesTab {
constructor() {
return {
viewModel: TabComponent,
template: QueryTablesTabTemplate,
};
}
}
export class SettingsTabV2 {
constructor() {
return {
viewModel: TabComponent,
template: SettingsTabV2Template,
};
}
}
export class DatabaseSettingsTab {
constructor() {
return {
viewModel: TabComponent,
template: DatabaseSettingsTabTemplate,
};
}
}
export class StoredProcedureTab {
constructor() {
return {
viewModel: TabComponent,
template: StoredProcedureTabTemplate,
};
}
}
export class TriggerTab {
constructor() {
return {
viewModel: TabComponent,
template: TriggerTabTemplate,
};
}
}
export class UserDefinedFunctionTab {
constructor() {
return {
viewModel: TabComponent,
template: UserDefinedFunctionTabTemplate,
};
}
}
export class GalleryTab {
constructor() {
return {
viewModel: TabComponent,
template: GalleryTabTemplate,
};
}
}
export class NotebookViewerTab {
constructor() {
return {
viewModel: TabComponent,
template: NotebookViewerTabTemplate,
};
}
}

View File

@@ -13,6 +13,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
// TODO: Use specific actions for logging telemetry data
export default class TabsBase extends WaitsForTemplateViewModel {
public static readonly component = { name: "tab", template: "" };
public closeTabButton: ViewModels.Button;
public node: ViewModels.TreeNode;
public collection: ViewModels.CollectionBase;

View File

@@ -78,77 +78,9 @@
<div class="tabPanesContainer">
<!-- ko foreach: openedTabs -->
<div class="tabs-container" data-bind="visible: $data.isActive">
<!-- ko if: $data.tabKind === 0 -->
<documents-tab params="{data: $data}"></documents-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 1 -->
<settings-tab params="{data: $data}"></settings-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 2 -->
<stored-procedure-tab params="{data: $data}"></stored-procedure-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 3 -->
<user-defined-function-tab params="{data: $data}"></user-defined-function-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 4 -->
<trigger-tab params="{data: $data}"></trigger-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 5 -->
<query-tab params="{data: $data}"></query-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 6 -->
<graph-tab params="{data: $data}"></graph-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 9 -->
<tables-query-tab class="flexContainer" params="{data: $data}"></tables-query-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 10 -->
<mongo-shell-tab params="{data: $data}"></mongo-shell-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 11 -->
<database-settings-tab params="{data: $data}"></database-settings-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 12 -->
<conflicts-tab params="{data: $data}"></conflicts-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 14 -->
<terminal-tab params="{data: $data}"></terminal-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 15 -->
<notebookv2-tab params="{data: $data}"></notebookv2-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 16 -->
<spark-master-tab params="{data: $data}"></spark-master-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 17 -->
<gallery-tab params="{data: $data}"></gallery-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 18 -->
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 20 -->
<collection-settings-tab-v2 params="{data: $data}"></collection-settings-tab-v2>
<!-- /ko -->
<!-- ko if: $data.tabKind === 21 -->
<database-settings-tab-v2 params="{data: $data}"></database-settings-tab-v2>
<!-- /ko -->
<span
data-bind="class: $data.constructor.component.name, component: { name: $data.constructor.component.name, params: $data }"
></span>
</div>
<!-- /ko -->
</div>

View File

@@ -1,6 +1,5 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsManagerTemplate from "./TabsManager.html";
import Explorer from "../Explorer";
import TabsBase from "./TabsBase";
@@ -82,14 +81,3 @@ export class TabsManager {
return this.activeTab() && this.activeTab().tabKind === tabKind;
}
}
function TabsManagerWrapperViewModel(params: { data: TabsManager }) {
return params.data;
}
export function TabsManagerKOComponent(): unknown {
return {
viewModel: TabsManagerWrapperViewModel,
template: TabsManagerTemplate,
};
}

View File

@@ -7,6 +7,7 @@ import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { NotebookTerminalComponent } from "../Controls/Notebook/NotebookTerminalComponent";
import Explorer from "../Explorer";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import template from "./TerminalTab.html";
export interface TerminalTabOptions extends ViewModels.TabOptions {
account: DataModels.DatabaseAccount;
@@ -38,6 +39,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
}
export default class TerminalTab extends TabsBase {
public static readonly component = { name: "terminal-tab", template };
private container: Explorer;
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;

View File

@@ -9,8 +9,10 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Trigger from "../Tree/Trigger";
import ScriptTabBase from "./ScriptTabBase";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import template from "./TriggerTab.html";
export default class TriggerTab extends ScriptTabBase {
public static readonly component = { name: "trigger-tab", template };
public collection: ViewModels.Collection;
public node: Trigger;
public triggerType: ViewModels.Editable<string>;

View File

@@ -8,8 +8,10 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import UserDefinedFunction from "../Tree/UserDefinedFunction";
import ScriptTabBase from "./ScriptTabBase";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import template from "./UserDefinedFunctionTab.html";
export default class UserDefinedFunctionTab extends ScriptTabBase {
public static readonly component = { name: "user-defined-function-tab", template };
public collection: ViewModels.Collection;
public node: UserDefinedFunction;
constructor(options: ViewModels.ScriptTabOption) {

View File

@@ -4,18 +4,24 @@ import * as _ from "underscore";
import UploadWorker from "worker-loader!../../workers/upload";
import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants";
import { createDocument } from "../../Common/dataAccess/createDocument";
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
import { readTriggers } from "../../Common/dataAccess/readTriggers";
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
import Explorer from "../Explorer";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
import ConflictsTab from "../Tabs/ConflictsTab";
@@ -32,12 +38,6 @@ import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction";
import { configContext, Platform } from "../../ConfigContext";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { createDocument } from "../../Common/dataAccess/createDocument";
export default class Collection implements ViewModels.Collection {
public nodeKind: string;
@@ -1200,7 +1200,6 @@ export default class Collection implements ViewModels.Collection {
public async loadOffer(): Promise<void> {
if (!this.container.isServerlessEnabled() && !this.offer()) {
this.container.isRefreshingExplorer(true);
const startKey: number = TelemetryProcessor.traceStart(Action.LoadOffers, {
databaseName: this.databaseId,
collectionName: this.id(),
@@ -1237,8 +1236,6 @@ export default class Collection implements ViewModels.Collection {
startKey
);
throw error;
} finally {
this.container.isRefreshingExplorer(false);
}
}
}

View File

@@ -58,9 +58,7 @@ export default class Database implements ViewModels.Database {
});
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
const useDatabaseSettingsTabV1: boolean = this.container.isFeatureEnabled(
Constants.Features.enableDatabaseSettingsTabV1
);
const useDatabaseSettingsTabV1 = userContext.features.enableDatabaseSettingsTabV1;
const tabKind: ViewModels.CollectionTabKind = useDatabaseSettingsTabV1
? ViewModels.CollectionTabKind.DatabaseSettings
: ViewModels.CollectionTabKind.DatabaseSettingsV2;

View File

@@ -3,13 +3,14 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import Explorer from "../Explorer";
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
import TabsBase from "../Tabs/TabsBase";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
function sample(prefix) {
@@ -56,7 +57,7 @@ export default class StoredProcedure {
this.rid = data._rid;
this.id = ko.observable(data.id);
this.body = ko.observable(data.body as string);
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
this.isExecuteEnabled = userContext.features.executeSproc;
}
public static create(source: ViewModels.Collection, event: MouseEvent) {

View File

@@ -27,7 +27,7 @@ const onInit = async () => {
const props: GalleryAndNotebookViewerComponentProps = {
junoClient: new JunoClient(),
selectedTab: galleryViewerProps.selectedTab || GalleryTab.PublicGallery,
sortBy: galleryViewerProps.sortBy || SortBy.MostViewed,
sortBy: galleryViewerProps.sortBy || SortBy.MostRecent,
searchText: galleryViewerProps.searchText,
};

View File

@@ -1,7 +1,7 @@
import { Octokit } from "@octokit/rest";
import { HttpStatusCodes } from "../Common/Constants";
import * as Logger from "../Common/Logger";
import UrlUtility from "../Common/UrlUtility";
import * as UrlUtility from "../Common/UrlUtility";
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
import { getErrorMessage } from "../Common/ErrorHandlingUtils";

View File

@@ -8,7 +8,7 @@ import * as Logger from "../Common/Logger";
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
import { GitHubClient, IGitHubFile, IGitHubResponse } from "./GitHubClient";
import * as GitHubUtils from "../Utils/GitHubUtils";
import UrlUtility from "../Common/UrlUtility";
import * as UrlUtility from "../Common/UrlUtility";
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export interface GitHubContentProviderParams {

View File

@@ -1,5 +0,0 @@
Number.isInteger =
Number.isInteger ||
function(value) {
return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
};

View File

@@ -27,8 +27,8 @@
"Enable DB level throughput": "Enable Database Level Throughput",
"Database Throughput": "Database Throughput",
"UpdateInProgressMessage": "Data is being updated",
"UpdateCompletedMessageTitle":"Update succeeded",
"UpdateCompletedMessageText": "Data updation completed.",
"UpdateCompletedMessageTitle": "Update succeeded",
"UpdateCompletedMessageText": "Data update completed.",
"SubmissionMessageSuccessTitle": "Update started",
"SubmissionMessageForNewRegionText": "Data update started. Region changed.",
"SubmissionMessageForSameRegionText": "Data update started. Region not changed.",
@@ -37,6 +37,56 @@
"OnSaveFailureMessage": "Data save operation not currently permitted."
},
"SqlX": {
"DedicatedGatewayDescription": "Provision a dedicated gateway cluster for your Azure Cosmos DB account. A dedicated gateway is compute that is a front-end to data in your Azure Cosmos DB account. Your dedicated gateway automatically includes the integrated cache, which can improve read performance. ",
"DedicatedGateway": "Dedicated Gateway",
"Enable": "Enable",
"Disable": "Disable",
"LearnAboutDedicatedGateway": "Learn more about dedicated gateway.",
"DeprovisioningDetailsText": "Learn more about deprovisioning the dedicated gateway.",
"DedicatedGatewayPricing": "Learn more about dedicated gateway pricing",
"SKUs": "SKUs",
"SKUsPlaceHolder": "Select SKUs",
"NumberOfInstances": "Number of instances",
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
"CreateMessage": "Dedicated gateway resource is being created.",
"CreateInitializeTitle": "Provisioning resource",
"CreateInitializeMessage": "Dedicated gateway resource will be provisioned.",
"CreateSuccessTitle": "Resource provisioned",
"CreateSuccesseMessage": "Dedicated gateway resource provisioned.",
"CreateFailureTitle": "Failed to provision resource",
"CreateFailureMessage": "Dedicated gateway resource provisioning failed.",
"UpdateMessage": "Dedicated gateway resource is being updated.",
"UpdateInitializeTitle": "Updating resource",
"UpdateInitializeMessage": "Dedicated gateway resource will be updated.",
"UpdateSuccessTitle": "Resource updated",
"UpdateSuccesseMessage": "Dedicated gateway resource updated.",
"UpdateFailureTitle": "Failed to update resource",
"UpdateFailureMessage": "Dedicated gateway resource updation failed.",
"DeleteMessage": "Dedicated gateway resource is being deleted.",
"DeleteInitializeTitle": "Deleting resource",
"DeleteInitializeMessage": "Dedicated gateway resource will be deleted.",
"DeleteSuccessTitle": "Resource deleted",
"DeleteSuccesseMessage": "Dedicated gateway resource deleted.",
"DeleteFailureTitle": "Failed to delete resource",
"DeleteFailureMessage": "Dedicated gateway resource deletion failed.",
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment",
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
"NoValue": "",
"SKUDetails": "SKU Details: ",
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
"CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory",
"Cost": "Cost",
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
"ConnectionString": "Connection String",
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
"KeysBlade": "the keys blade",
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
}
}
}

View File

@@ -1,18 +1,8 @@
// CSS Dependencies
import "abort-controller/polyfill";
import "babel-polyfill";
import "bootstrap/dist/css/bootstrap.css";
import "es6-object-assign/auto";
import "es6-symbol/implement";
import "object.entries/auto";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import "promise-polyfill/src/polyfill";
import "promise.prototype.finally/auto";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "url-polyfill/url-polyfill.min";
import "webcrypto-liner/build/webcrypto-liner.shim.min";
import "whatwg-fetch";
import "../externals/jquery-ui.min.css";
import "../externals/jquery-ui.min.js";
import "../externals/jquery-ui.structure.min.css";
@@ -37,6 +27,7 @@ import "../less/TableStyles/EntityEditor.less";
import "../less/TableStyles/fulldatatables.less";
import "../less/TableStyles/queryBuilder.less";
import "../less/tree.less";
import { AuthType } from "./AuthType";
import "./Explorer/Controls/Accordion/AccordionComponent.less";
import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less";
import { Dialog, DialogProps } from "./Explorer/Controls/Dialog";
@@ -44,11 +35,11 @@ import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
import "./Explorer/Controls/ThroughputInput/ThroughputInput.less";
import "./Explorer/Controls/TreeComponent/treeComponent.less";
import { ExplorerParams } from "./Explorer/Explorer";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Graph/NewVertexComponent/newVertexComponent.less";
import { CommandBarComponent } from "./Explorer/Menus/CommandBar/CommandBarComponent";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
@@ -60,13 +51,12 @@ import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen";
import "./Explorer/SplashScreen/SplashScreen.less";
import "./Explorer/Tabs/QueryTab.less";
import { useConfig } from "./hooks/useConfig";
import { useExplorerState } from "./hooks/useExplorerState";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { useSidePanel } from "./hooks/useSidePanel";
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
import "./Libs/is-integer-polyfill";
import "./Libs/jquery";
import "./Shared/appInsights";
import { userContext } from "./UserContext";
initializeIcons();
@@ -101,8 +91,6 @@ const App: React.FunctionComponent = () => {
const config = useConfig();
const explorer = useKnockoutExplorer(config?.platform, explorerParams);
const { commandBarProperties } = useExplorerState(explorer);
if (!explorer) {
return <LoadingExplorer />;
}
@@ -111,7 +99,7 @@ const App: React.FunctionComponent = () => {
<div className="flexContainer">
<div id="divExplorer" className="flexContainer hideOverflows" style={{ display: "none" }}>
{/* Main Command Bar - Start */}
<CommandBarComponent {...commandBarProperties} />
<div data-bind="react: commandBarComponentAdapter" />
{/* Collections Tree and Tabs - Begin */}
<div className="resourceTreeAndTabs">
{/* Collections Tree - Start */}
@@ -158,11 +146,11 @@ const App: React.FunctionComponent = () => {
</div>
</div>
</div>
<div
style={{ overflowY: "auto" }}
data-bind="if: isAuthWithResourceToken(), react:resourceTreeForResourceToken"
/>
<div style={{ overflowY: "auto" }} data-bind="if: !isAuthWithResourceToken(), react:resourceTree" />
{userContext.authType === AuthType.ResourceToken ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTreeForResourceToken" />
) : (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
)}
</div>
{/* Collections Window - End */}
</div>
@@ -212,18 +200,12 @@ const App: React.FunctionComponent = () => {
{/* Splitter - End */}
</div>
{/* Collections Tree - End */}
<div
className="connectExplorerContainer"
data-bind="visible: !isRefreshingExplorer() && tabsManager.openedTabs().length === 0"
>
<div className="connectExplorerContainer" data-bind="visible: tabsManager.openedTabs().length === 0">
<form className="connectExplorerFormContainer">
<SplashScreen explorer={explorer} />
</form>
</div>
<div
className="tabsManagerContainer"
data-bind='component: { name: "tabs-manager", params: {data: tabsManager} }'
/>
<div className="tabsManagerContainer" data-bind='component: { name: "tabs-manager", params: tabsManager }' />
</div>
{/* Collections Tree and Tabs - End */}
<div

View File

@@ -1,17 +1,22 @@
import { extractFeatures } from "./extractFeatures";
describe("extractFeatures", () => {
it("correctly detects feature flags", () => {
// Search containing non-features, with Camelcase keys and uri encoded values
const params = new URLSearchParams(
"?platform=Hosted&feature.notebookserverurl=https%3A%2F%2Flocalhost%3A10001%2F12345%2Fnotebook&feature.notebookServerToken=token&feature.enablenotebooks=true&key=mykey"
);
it("correctly detects feature flags in a case insensitive manner", () => {
const url = "https://localhost:10001/12345/notebook";
const token = "super secret";
const notebooksEnabled = false;
const params = new URLSearchParams({
platform: "Hosted",
"feature.NOTEBOOKSERVERURL": url,
"feature.NoTeBooKServerToken": token,
"feature.NotAFeature": "nope",
"feature.ENABLEnotebooks": notebooksEnabled.toString(),
});
const features = extractFeatures(params);
expect(features).toEqual({
notebookserverurl: "https://localhost:10001/12345/notebook",
notebookservertoken: "token",
enablenotebooks: "true",
});
expect(features.notebookServerUrl).toBe(url);
expect(features.notebookServerToken).toBe(token);
expect(features.enableNotebooks).toBe(notebooksEnabled);
});
});

View File

@@ -1,14 +1,56 @@
export function extractFeatures(params?: URLSearchParams): { [key: string]: string } {
params = params || new URLSearchParams(window.parent.location.search);
const featureParamRegex = /feature.(.*)/i;
const features: { [key: string]: string } = {};
params.forEach((value: string, param: string) => {
if (featureParamRegex.test(param)) {
const matches: string[] = param.match(featureParamRegex);
if (matches.length > 0) {
features[matches[1].toLowerCase()] = value;
}
}
});
return features;
export type Features = {
readonly canExceedMaximumValue: boolean;
readonly cosmosdb: boolean;
readonly enableChangeFeedPolicy: boolean;
readonly enableDatabaseSettingsTabV1: boolean;
readonly enableFixedCollectionWithSharedThroughput: boolean;
readonly enableKOPanel: boolean;
readonly enableNotebooks: boolean;
readonly enableReactPane: boolean;
readonly enableRightPanelV2: boolean;
readonly enableSchema: boolean;
readonly enableSDKoperations: boolean;
readonly enableSpark: boolean;
readonly enableTtl: boolean;
readonly executeSproc: boolean;
readonly hostedDataExplorer: boolean;
readonly livyEndpoint?: string;
readonly notebookBasePath?: string;
readonly notebookServerToken?: string;
readonly notebookServerUrl?: string;
readonly selfServeType?: string;
readonly showMinRUSurvey: boolean;
readonly ttl90Days: boolean;
};
export function extractFeatures(params?: URLSearchParams): Features {
params = params || new URLSearchParams(window.location.search);
const downcased = new URLSearchParams();
params.forEach((value, key) => downcased.append(key.toLocaleLowerCase(), value));
const get = (key: string) => downcased.get("feature." + key.toLocaleLowerCase()) ?? undefined;
return {
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
cosmosdb: "true" === get("cosmosdb"),
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
enableDatabaseSettingsTabV1: "true" === get("enabledbsettingsv1"),
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
enableKOPanel: "true" === get("enablekopanel"),
enableNotebooks: "true" === get("enablenotebooks"),
enableReactPane: "true" === get("enablereactpane"),
enableRightPanelV2: "true" === get("enablerightpanelv2"),
enableSchema: "true" === get("enableschema"),
enableSDKoperations: "true" === get("enablesdkoperations"),
enableSpark: "true" === get("enablespark"),
enableTtl: "true" === get("enablettl"),
executeSproc: "true" === get("dataexplorerexecutesproc"),
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
livyEndpoint: get("livyendpoint"),
notebookBasePath: get("notebookbasepath"),
notebookServerToken: get("notebookservertoken"),
notebookServerUrl: get("notebookserverurl"),
selfServeType: get("selfservetype"),
showMinRUSurvey: "true" === get("showminrusurvey"),
ttl90Days: "true" === get("ttl90days"),
};
}

View File

@@ -3,7 +3,7 @@ import { HttpStatusCodes } from "../Common/Constants";
import { IResourceProviderClient, IResourceProviderRequestOptions } from "./IResourceProviderClient";
import { OperationStatus } from "../Contracts/DataModels";
import { TokenProviderFactory } from "../TokenProviders/TokenProviderFactory";
import UrlUtility from "../Common/UrlUtility";
import * as UrlUtility from "../Common/UrlUtility";
export class ResourceProviderClient<T> implements IResourceProviderClient<T> {
private httpClient: HttpClient;

View File

@@ -1,9 +1,8 @@
import crossroads from "crossroads";
import hasher from "hasher";
import * as _ from "underscore";
import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import crossroads from "crossroads";
import hasher from "hasher";
import ScriptTabBase from "../Explorer/Tabs/ScriptTabBase";
import TabsBase from "../Explorer/Tabs/TabsBase";
@@ -398,14 +397,7 @@ export class TabRouteHandler {
private _executeActionHelper(action: () => void): void {
const explorer = window.dataExplorer;
if (!!explorer && (explorer.isRefreshingExplorer() || !explorer.isAccountReady())) {
const refreshSubscription = explorer.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
if (!isRefreshing) {
action();
refreshSubscription.dispose();
}
});
} else {
if (explorer && explorer.isAccountReady()) {
action();
}
}

View File

@@ -1,4 +1,4 @@
import { PropertyInfo, OnChange, Values, IsDisplayable, RefreshOptions } from "../Decorators";
import { IsDisplayable, OnChange, PropertyInfo, RefreshOptions, Values } from "../Decorators";
import {
ChoiceItem,
Description,
@@ -12,14 +12,14 @@ import {
SmartUiInput,
} from "../SelfServeTypes";
import {
getMaxCollectionThroughput,
getMaxDatabaseThroughput,
getMinCollectionThroughput,
getMinDatabaseThroughput,
initialize,
onRefreshSelfServeExample,
Regions,
update,
initialize,
getMinDatabaseThroughput,
getMaxDatabaseThroughput,
getMinCollectionThroughput,
getMaxCollectionThroughput,
} from "./SelfServeExample.rp";
const regionDropdownItems: ChoiceItem[] = [
@@ -203,11 +203,7 @@ export default class SelfServeExample extends SelfServeBaseClass {
public initialize = async (): Promise<Map<string, SmartUiInput>> => {
const initializeResponse = await initialize();
const defaults = new Map<string, SmartUiInput>();
const currentRegionText = `current region selected is ${initializeResponse.regions}`;
defaults.set("currentRegionText", {
value: { textTKey: currentRegionText, type: DescriptionType.Text } as Description,
hidden: false,
});
defaults.set("currentRegionText", undefined);
defaults.set("regions", { value: initializeResponse.regions });
defaults.set("enableLogging", { value: initializeResponse.enableLogging });
const accountName = initializeResponse.accountName;

View File

@@ -1,17 +1,17 @@
import { Spinner, SpinnerSize } from "office-ui-fabric-react";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import * as React from "react";
import ReactDOM from "react-dom";
import { sendMessage } from "../Common/MessageHandler";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import { sendReadyMessage } from "../Common/MessageHandler";
import { configContext, updateConfigContext } from "../ConfigContext";
import { SelfServeFrameInputs } from "../Contracts/ViewModels";
import { updateUserContext } from "../UserContext";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import "./SelfServe.less";
import { SelfServeComponent } from "./SelfServeComponent";
import { SelfServeDescriptor } from "./SelfServeTypes";
import { SelfServeType } from "./SelfServeUtils";
import { SelfServeFrameInputs } from "../Contracts/ViewModels";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import { configContext, updateConfigContext } from "../ConfigContext";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import { updateUserContext } from "../UserContext";
import "./SelfServe.less";
import { Spinner, SpinnerSize } from "office-ui-fabric-react";
initializeIcons();
const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDescriptor> => {
@@ -89,4 +89,4 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
ReactDOM.render(renderSpinner(), document.getElementById("selfServeContent"));
window.addEventListener("message", handleMessage, false);
sendMessage("ready");
sendReadyMessage();

View File

@@ -1,34 +1,36 @@
import React from "react";
import { TFunction } from "i18next";
import {
CommandBar,
ICommandBarItemProps,
IStackTokens,
MessageBar,
MessageBarType,
Separator,
Spinner,
SpinnerSize,
Stack,
} from "office-ui-fabric-react";
import promiseRetry, { AbortError } from "p-retry";
import React from "react";
import { Translation } from "react-i18next";
import * as _ from "underscore";
import { sendMessage } from "../Common/MessageHandler";
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
import "../i18n";
import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles";
import {
AnyDisplay,
Node,
BooleanInput,
ChoiceInput,
DescriptionDisplay,
InputType,
Node,
NumberInput,
RefreshResult,
SelfServeDescriptor,
SmartUiInput,
DescriptionDisplay,
StringInput,
NumberInput,
BooleanInput,
ChoiceInput,
} from "./SelfServeTypes";
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
import { Translation } from "react-i18next";
import { TFunction } from "i18next";
import "../i18n";
import { sendMessage } from "../Common/MessageHandler";
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
import promiseRetry, { AbortError } from "p-retry";
interface SelfServeNotification {
message: string;
@@ -127,7 +129,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
this.props.descriptor.inputNames.map((inputName) => {
let initialValue = initialValues.get(inputName);
if (!initialValue) {
initialValue = { value: undefined, hidden: false };
initialValue = { value: undefined, hidden: false, disabled: false };
}
currentValues = currentValues.set(inputName, initialValue);
baselineValues = baselineValues.set(inputName, initialValue);
@@ -311,34 +313,41 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
this.performSave();
};
public isDiscardButtonDisabled = (): boolean => {
if (this.state.isSaving) {
return true;
}
public isInputModified = (): boolean => {
for (const key of this.state.currentValues.keys()) {
const currentValue = JSON.stringify(this.state.currentValues.get(key));
const baselineValue = JSON.stringify(this.state.baselineValues.get(key));
const currentValue = this.state.currentValues.get(key);
if (currentValue && currentValue.hidden === undefined) {
currentValue.hidden = false;
}
if (currentValue && currentValue.disabled === undefined) {
currentValue.disabled = false;
}
if (currentValue !== baselineValue) {
return false;
const baselineValue = this.state.baselineValues.get(key);
if (baselineValue && baselineValue.hidden === undefined) {
baselineValue.hidden = false;
}
if (baselineValue && baselineValue.disabled === undefined) {
baselineValue.disabled = false;
}
if (!_.isEqual(currentValue, baselineValue)) {
return true;
}
}
return true;
return false;
};
public isRefreshing = (): boolean => {
return this.state.isSaving || this.state.isInitializing || this.state.refreshResult?.isUpdateInProgress;
};
public isDiscardButtonDisabled = (): boolean => {
return this.isRefreshing() || !this.isInputModified();
};
public isSaveButtonDisabled = (): boolean => {
if (this.state.hasErrors || this.state.isSaving) {
return true;
}
for (const key of this.state.currentValues.keys()) {
const currentValue = JSON.stringify(this.state.currentValues.get(key));
const baselineValue = JSON.stringify(this.state.baselineValues.get(key));
if (currentValue !== baselineValue) {
return false;
}
}
return true;
return this.state.hasErrors || this.isRefreshing() || !this.isInputModified();
};
private performRefresh = async (): Promise<void> => {
@@ -397,7 +406,6 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
key: "save",
text: this.getCommonTranslation("Save"),
iconProps: { iconName: "Save" },
split: true,
disabled: this.isSaveButtonDisabled(),
onClick: () => this.onSaveButtonClick(),
},
@@ -405,21 +413,21 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
key: "discard",
text: this.getCommonTranslation("Discard"),
iconProps: { iconName: "Undo" },
split: true,
disabled: this.isDiscardButtonDisabled(),
onClick: () => {
this.discard();
},
buttonStyles: commandBarItemStyles,
},
{
key: "refresh",
text: this.getCommonTranslation("Refresh"),
disabled: this.state.isInitializing,
iconProps: { iconName: "Refresh" },
split: true,
onClick: () => {
this.onRefreshClicked();
},
buttonStyles: commandBarItemStyles,
},
];
};
@@ -432,7 +440,6 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
};
public render(): JSX.Element {
const containerStackTokens: IStackTokens = { childrenGap: 5 };
if (this.state.compileErrorMessage) {
return <MessageBar messageBarType={MessageBarType.error}>{this.state.compileErrorMessage}</MessageBar>;
}
@@ -445,13 +452,13 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
return (
<div style={{ overflowX: "auto" }}>
<Stack tokens={containerStackTokens} styles={{ root: { padding: 10 } }}>
<CommandBar styles={{ root: { paddingLeft: 0 } }} items={this.getCommandBarItems()} />
<Stack tokens={containerStackTokens}>
<Stack.Item>
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
<Separator styles={separatorStyles} />
</Stack.Item>
{this.state.isInitializing ? (
<Spinner
size={SpinnerSize.large}
styles={{ root: { textAlign: "center", justifyContent: "center", width: "100%", height: "100%" } }}
/>
<Spinner size={SpinnerSize.large} />
) : (
<>
{this.state.notification && (

View File

@@ -0,0 +1,20 @@
import { IButtonStyles, ICommandBarStyles, ISeparatorStyles, IStackTokens } from "office-ui-fabric-react";
import { StyleConstants } from "../Common/Constants";
export const commandBarItemStyles: IButtonStyles = { root: { paddingLeft: 20 } };
export const commandBarStyles: ICommandBarStyles = { root: { paddingLeft: 0 } };
export const containerStackTokens: IStackTokens = { childrenGap: 5, padding: 10 };
export const separatorStyles: Partial<ISeparatorStyles> = {
root: {
selectors: {
"::before": {
background: StyleConstants.BaseMedium,
},
},
padding: 0,
height: 1,
},
};

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