Migrate Add Database Panel to React (#597)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
parent
7efa8ca58f
commit
9d5c9d6296
|
@ -54,7 +54,6 @@ src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts
|
||||||
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
src/Explorer/Controls/InputTypeahead/InputTypeahead.ts
|
||||||
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts
|
||||||
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts
|
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts
|
||||||
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts
|
||||||
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
src/Explorer/Controls/Toolbar/IToolbarAction.ts
|
||||||
|
@ -111,8 +110,8 @@ src/Explorer/OpenActions.ts
|
||||||
src/Explorer/OpenActionsStubs.ts
|
src/Explorer/OpenActionsStubs.ts
|
||||||
src/Explorer/Panes/AddCollectionPane.test.ts
|
src/Explorer/Panes/AddCollectionPane.test.ts
|
||||||
src/Explorer/Panes/AddCollectionPane.ts
|
src/Explorer/Panes/AddCollectionPane.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
|
||||||
src/Explorer/Panes/AddDatabasePane.ts
|
src/Explorer/Panes/AddDatabasePane.ts
|
||||||
|
src/Explorer/Panes/AddDatabasePane.test.ts
|
||||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
src/Explorer/Panes/BrowseQueriesPane.ts
|
||||||
src/Explorer/Panes/CassandraAddCollectionPane.ts
|
src/Explorer/Panes/CassandraAddCollectionPane.ts
|
||||||
src/Explorer/Panes/ContextualPaneBase.ts
|
src/Explorer/Panes/ContextualPaneBase.ts
|
||||||
|
|
|
@ -1757,7 +1757,7 @@ input::-webkit-calendar-picker-indicator {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contextual-pane .paneMainContent {
|
.paneMainContent {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
|
|
|
@ -2297,6 +2297,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@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/set-version": {
|
"@fluentui/set-version": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.1.0.tgz",
|
||||||
|
@ -3090,9 +3099,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "17.0.1",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||||
"integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
|
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
|
@ -3258,9 +3267,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": "17.0.1",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||||
"integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
|
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
|
@ -3574,18 +3583,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@npmcli/move-file": {
|
"@npmcli/move-file": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
|
||||||
"integrity": "sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ==",
|
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"rimraf": "^2.7.1"
|
"rimraf": "^3.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "2.7.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
}
|
}
|
||||||
|
@ -6193,6 +6202,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@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": {
|
"@uifabric/icons": {
|
||||||
"version": "7.5.23",
|
"version": "7.5.23",
|
||||||
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.5.23.tgz",
|
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.5.23.tgz",
|
||||||
|
@ -6327,6 +6348,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@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": {
|
"@uifabric/set-version": {
|
||||||
"version": "7.0.24",
|
"version": "7.0.24",
|
||||||
"resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.24.tgz",
|
"resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.24.tgz",
|
||||||
|
@ -6335,6 +6367,32 @@
|
||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@uifabric/styling": {
|
||||||
|
"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.4",
|
||||||
|
"@microsoft/load-themed-styles": "^1.10.26",
|
||||||
|
"@uifabric/merge-styles": "^7.19.2",
|
||||||
|
"@uifabric/set-version": "^7.0.24",
|
||||||
|
"@uifabric/utilities": "^7.33.5",
|
||||||
|
"tslib": "^1.10.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@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/theme-samples": {
|
"@uifabric/theme-samples": {
|
||||||
"version": "7.2.34",
|
"version": "7.2.34",
|
||||||
"resolved": "https://registry.npmjs.org/@uifabric/theme-samples/-/theme-samples-7.2.34.tgz",
|
"resolved": "https://registry.npmjs.org/@uifabric/theme-samples/-/theme-samples-7.2.34.tgz",
|
||||||
|
@ -6380,42 +6438,6 @@
|
||||||
"tslib": "^1.10.0"
|
"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/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/styling": {
|
|
||||||
"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.4",
|
|
||||||
"@microsoft/load-themed-styles": "^1.10.26",
|
|
||||||
"@uifabric/merge-styles": "^7.19.2",
|
|
||||||
"@uifabric/set-version": "^7.0.24",
|
|
||||||
"@uifabric/utilities": "^7.33.5",
|
|
||||||
"tslib": "^1.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@uifabric/utilities": {
|
"@uifabric/utilities": {
|
||||||
"version": "7.33.5",
|
"version": "7.33.5",
|
||||||
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-7.33.5.tgz",
|
"resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-7.33.5.tgz",
|
||||||
|
@ -6429,9 +6451,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"office-ui-fabric-react": {
|
"office-ui-fabric-react": {
|
||||||
"version": "7.168.2",
|
"version": "7.170.0",
|
||||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.168.2.tgz",
|
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.170.0.tgz",
|
||||||
"integrity": "sha512-ssN6/6K4Z/PdT2ExE1q61B34w6LqfQrqTvrlq/NfSvDk7USxXvP3+rd7HQAUrynSsWx2MnNeYt23d34sSHLCKw==",
|
"integrity": "sha512-N348H5dS56oMrnKeZP1p7h2o+lO9wUY9YEHiVZ0FYpB8gmRwgJVq8/d2hSfZEgQH14IMbhdLYNE8RFziYyHFsw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@fluentui/date-time-utilities": "^7.9.1",
|
"@fluentui/date-time-utilities": "^7.9.1",
|
||||||
"@fluentui/react-focus": "^7.17.6",
|
"@fluentui/react-focus": "^7.17.6",
|
||||||
|
@ -6450,6 +6472,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@uifabric/variants": {
|
"@uifabric/variants": {
|
||||||
"version": "7.2.35",
|
"version": "7.2.35",
|
||||||
"resolved": "https://registry.npmjs.org/@uifabric/variants/-/variants-7.2.35.tgz",
|
"resolved": "https://registry.npmjs.org/@uifabric/variants/-/variants-7.2.35.tgz",
|
||||||
|
@ -6470,18 +6504,6 @@
|
||||||
"@uifabric/utilities": "^7.33.5",
|
"@uifabric/utilities": "^7.33.5",
|
||||||
"tslib": "^1.10.0"
|
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7135,6 +7157,14 @@
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.0",
|
||||||
"safer-buffer": "^2.1.0"
|
"safer-buffer": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"assert": {
|
"assert": {
|
||||||
|
@ -7436,6 +7466,11 @@
|
||||||
"regenerator-runtime": "^0.11.0"
|
"regenerator-runtime": "^0.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.6.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
||||||
|
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
|
||||||
|
},
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.11.1",
|
"version": "0.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||||
|
@ -7626,9 +7661,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
|
@ -7843,14 +7878,6 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^5.0.0",
|
"bn.js": "^5.0.0",
|
||||||
"randombytes": "^2.0.1"
|
"randombytes": "^2.0.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"bn.js": {
|
|
||||||
"version": "5.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
|
||||||
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserify-sign": {
|
"browserify-sign": {
|
||||||
|
@ -7870,12 +7897,6 @@
|
||||||
"safe-buffer": "^5.2.0"
|
"safe-buffer": "^5.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
|
||||||
"version": "5.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
|
||||||
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
|
@ -7984,9 +8005,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "15.0.5",
|
"version": "15.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.6.tgz",
|
||||||
"integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==",
|
"integrity": "sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@npmcli/move-file": "^1.0.1",
|
"@npmcli/move-file": "^1.0.1",
|
||||||
"chownr": "^2.0.0",
|
"chownr": "^2.0.0",
|
||||||
|
@ -8002,7 +8023,7 @@
|
||||||
"p-map": "^4.0.0",
|
"p-map": "^4.0.0",
|
||||||
"promise-inflight": "^1.0.1",
|
"promise-inflight": "^1.0.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"ssri": "^8.0.0",
|
"ssri": "^8.0.1",
|
||||||
"tar": "^6.0.2",
|
"tar": "^6.0.2",
|
||||||
"unique-filename": "^1.1.1"
|
"unique-filename": "^1.1.1"
|
||||||
},
|
},
|
||||||
|
@ -8330,7 +8351,8 @@
|
||||||
"chownr": {
|
"chownr": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"chrome-trace-event": {
|
"chrome-trace-event": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -8819,21 +8841,8 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||||
},
|
|
||||||
"p-limit": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
|
||||||
"requires": {
|
|
||||||
"p-try": "^2.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
"core-js": {
|
|
||||||
"version": "2.6.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
|
||||||
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
|
|
||||||
},
|
},
|
||||||
"core-js-compat": {
|
"core-js-compat": {
|
||||||
"version": "3.8.3",
|
"version": "3.8.3",
|
||||||
|
@ -8871,6 +8880,14 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.1.0",
|
"bn.js": "^4.1.0",
|
||||||
"elliptic": "^6.5.3"
|
"elliptic": "^6.5.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create-file-webpack": {
|
"create-file-webpack": {
|
||||||
|
@ -10024,6 +10041,14 @@
|
||||||
"bn.js": "^4.1.0",
|
"bn.js": "^4.1.0",
|
||||||
"miller-rabin": "^4.0.0",
|
"miller-rabin": "^4.0.0",
|
||||||
"randombytes": "^2.0.0"
|
"randombytes": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dir-glob": {
|
"dir-glob": {
|
||||||
|
@ -10302,18 +10327,26 @@
|
||||||
"integrity": "sha512-vhGNxT87PdZA6Ak4E0QhArwGzNcSPUwSN7n9wCFLeBlY2NNuuiwguQuQIp7P5oB65PLJ892yKcHiqz1xLWeiug=="
|
"integrity": "sha512-vhGNxT87PdZA6Ak4E0QhArwGzNcSPUwSN7n9wCFLeBlY2NNuuiwguQuQIp7P5oB65PLJ892yKcHiqz1xLWeiug=="
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.3",
|
"version": "6.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.11.9",
|
||||||
"brorand": "^1.0.1",
|
"brorand": "^1.1.0",
|
||||||
"hash.js": "^1.0.0",
|
"hash.js": "^1.0.0",
|
||||||
"hmac-drbg": "^1.0.0",
|
"hmac-drbg": "^1.0.1",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.4",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.1",
|
||||||
"minimalistic-crypto-utils": "^1.0.0"
|
"minimalistic-crypto-utils": "^1.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"emitter-listener": {
|
"emitter-listener": {
|
||||||
|
@ -11045,9 +11078,9 @@
|
||||||
"integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg=="
|
"integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg=="
|
||||||
},
|
},
|
||||||
"eventsource": {
|
"eventsource": {
|
||||||
"version": "1.0.7",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
|
||||||
"integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
|
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"original": "^1.0.0"
|
"original": "^1.0.0"
|
||||||
|
@ -12316,9 +12349,9 @@
|
||||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
||||||
},
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "11.0.2",
|
"version": "11.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
|
||||||
"integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==",
|
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"array-union": "^2.1.0",
|
"array-union": "^2.1.0",
|
||||||
"dir-glob": "^3.0.1",
|
"dir-glob": "^3.0.1",
|
||||||
|
@ -19298,6 +19331,14 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.0.0",
|
"bn.js": "^4.0.0",
|
||||||
"brorand": "^1.0.1"
|
"brorand": "^1.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mime": {
|
"mime": {
|
||||||
|
@ -20617,7 +20658,6 @@
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-try": "^2.0.0"
|
"p-try": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -20947,9 +20987,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2": {
|
"pbkdf2": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
|
||||||
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
|
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"create-hash": "^1.1.2",
|
"create-hash": "^1.1.2",
|
||||||
|
@ -21520,6 +21560,14 @@
|
||||||
"parse-asn1": "^5.0.0",
|
"parse-asn1": "^5.0.0",
|
||||||
"randombytes": "^2.0.1",
|
"randombytes": "^2.0.1",
|
||||||
"safe-buffer": "^5.1.2"
|
"safe-buffer": "^5.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
|
@ -25544,6 +25592,12 @@
|
||||||
"y18n": "^4.0.0"
|
"y18n": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chownr": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Icon, TooltipHost } from "@fluentui/react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export interface TooltipProps {
|
||||||
|
children: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InfoTooltip: React.FunctionComponent = ({ children }: TooltipProps) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<TooltipHost content={children}>
|
||||||
|
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" />
|
||||||
|
</TooltipHost>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,24 +0,0 @@
|
||||||
import { ITooltipHostStyles, TooltipHost } from "@fluentui/react";
|
|
||||||
import { useId } from "@fluentui/react-hooks";
|
|
||||||
import * as React from "react";
|
|
||||||
import InfoBubble from "../../../images/info-bubble.svg";
|
|
||||||
|
|
||||||
const calloutProps = { gapSpace: 0 };
|
|
||||||
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: "inline-block" } };
|
|
||||||
|
|
||||||
export interface TooltipProps {
|
|
||||||
children: string;
|
|
||||||
}
|
|
||||||
export const Tooltip: React.FunctionComponent = ({ children }: TooltipProps) => {
|
|
||||||
const tooltipId = useId("tooltip");
|
|
||||||
|
|
||||||
return children ? (
|
|
||||||
<span>
|
|
||||||
<TooltipHost content={children} id={tooltipId} calloutProps={calloutProps} styles={hostStyles}>
|
|
||||||
<img className="infoImg" src={InfoBubble} alt="More information" />
|
|
||||||
</TooltipHost>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -2,7 +2,7 @@ import { Image, Stack, TextField } from "@fluentui/react";
|
||||||
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
|
||||||
import FolderIcon from "../../../images/folder_16x16.svg";
|
import FolderIcon from "../../../images/folder_16x16.svg";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import { Tooltip } from "../Tooltip/Tooltip";
|
import { InfoTooltip } from "../Tooltip/InfoTooltip";
|
||||||
|
|
||||||
interface UploadProps {
|
interface UploadProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -51,7 +51,7 @@ export const Upload: FunctionComponent<UploadProps> = ({
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="renewUploadItemsHeader">{label}</span>
|
<span className="renewUploadItemsHeader">{label}</span>
|
||||||
<Tooltip>{tooltip}</Tooltip>
|
{tooltip && <InfoTooltip>{tooltip}</InfoTooltip>}
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
|
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -19,6 +19,7 @@ ko.components.register("dynamic-list", DynamicListComponent);
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
|
|
||||||
// Panes
|
// Panes
|
||||||
|
|
||||||
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
||||||
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
|
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
|
||||||
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
|
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { CostEstimateText } from "./CostEstimateText";
|
||||||
|
const props = {
|
||||||
|
requestUnits: 5,
|
||||||
|
isAutoscale: false,
|
||||||
|
};
|
||||||
|
describe("CostEstimateText Pane", () => {
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const wrapper = shallow(<CostEstimateText {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { Text } from "@fluentui/react";
|
||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip";
|
||||||
|
import * as SharedConstants from "../../../../Shared/Constants";
|
||||||
|
import { userContext } from "../../../../UserContext";
|
||||||
|
import {
|
||||||
|
calculateEstimateNumber,
|
||||||
|
computeRUUsagePriceHourly,
|
||||||
|
getAutoscalePricePerRu,
|
||||||
|
getCurrencySign,
|
||||||
|
getMultimasterMultiplier,
|
||||||
|
getPriceCurrency,
|
||||||
|
getPricePerRu,
|
||||||
|
} from "../../../../Utils/PricingUtils";
|
||||||
|
|
||||||
|
interface CostEstimateTextProps {
|
||||||
|
requestUnits: number;
|
||||||
|
isAutoscale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
|
||||||
|
requestUnits,
|
||||||
|
isAutoscale,
|
||||||
|
}: CostEstimateTextProps) => {
|
||||||
|
const { databaseAccount } = userContext;
|
||||||
|
if (!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 = computeRUUsagePriceHourly({
|
||||||
|
serverId,
|
||||||
|
requestUnits,
|
||||||
|
numberOfRegions,
|
||||||
|
multimasterEnabled,
|
||||||
|
isAutoscale,
|
||||||
|
});
|
||||||
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
|
const monthlyPrice: number = hourlyPrice * SharedConstants.hoursInAMonth;
|
||||||
|
const currency: string = getPriceCurrency(serverId);
|
||||||
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
|
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
|
const pricePerRu = isAutoscale
|
||||||
|
? getAutoscalePricePerRu(serverId, multiplier) * multiplier
|
||||||
|
: getPricePerRu(serverId) * multiplier;
|
||||||
|
|
||||||
|
const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>PricingUtils.estimatedCostDisclaimer</InfoTooltip>;
|
||||||
|
|
||||||
|
if (isAutoscale) {
|
||||||
|
return (
|
||||||
|
<Text variant="small">
|
||||||
|
Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
||||||
|
<b>
|
||||||
|
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
|
||||||
|
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
|
||||||
|
</b>
|
||||||
|
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
|
||||||
|
RU/s, {currencySign + pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text variant="small">
|
||||||
|
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
||||||
|
<b>
|
||||||
|
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
|
||||||
|
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
|
||||||
|
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
||||||
|
</b>
|
||||||
|
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
|
||||||
|
{currencySign + pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CostEstimateText Pane should render Default properly 1`] = `<Fragment />`;
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { ThroughputInput } from "./ThroughputInput";
|
||||||
|
const props = {
|
||||||
|
isDatabase: false,
|
||||||
|
showFreeTierExceedThroughputTooltip: true,
|
||||||
|
isSharded: false,
|
||||||
|
setThroughputValue: () => jest.fn(),
|
||||||
|
setIsAutoscale: () => jest.fn(),
|
||||||
|
onCostAcknowledgeChange: () => jest.fn(),
|
||||||
|
};
|
||||||
|
describe("ThroughputInput Pane", () => {
|
||||||
|
let wrapper: ReactWrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mount(<ThroughputInput {...props} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("test Autoscale Mode select", () => {
|
||||||
|
wrapper.setProps({ isAutoscaleSelected: true });
|
||||||
|
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toBe(
|
||||||
|
"Estimate your required RU/s with capacity calculator."
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[aria-label="maxRUDescription"]').at(0).text()).toContain("Max RU/s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("test Manual Mode select", () => {
|
||||||
|
wrapper.setProps({ isAutoscaleSelected: false });
|
||||||
|
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toContain("Estimate your required RU/s with");
|
||||||
|
expect(wrapper.find('[aria-label="capacityLink"]').at(0).text()).toContain("capacity calculator");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,11 +1,14 @@
|
||||||
import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||||
import React from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||||
|
import { CostEstimateText } from "./CostEstimateText/CostEstimateText";
|
||||||
|
import "./ThroughputInput.less";
|
||||||
|
|
||||||
export interface ThroughputInputProps {
|
export interface ThroughputInputProps {
|
||||||
isDatabase: boolean;
|
isDatabase: boolean;
|
||||||
|
@ -14,84 +17,128 @@ export interface ThroughputInputProps {
|
||||||
setThroughputValue: (throughput: number) => void;
|
setThroughputValue: (throughput: number) => void;
|
||||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||||
|
isAutoscaleSelected?: boolean;
|
||||||
|
throughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThroughputInputState {
|
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||||
isAutoscaleSelected: boolean;
|
isDatabase,
|
||||||
throughput: number;
|
showFreeTierExceedThroughputTooltip,
|
||||||
isCostAcknowledged: boolean;
|
setThroughputValue,
|
||||||
throughputError: string;
|
setIsAutoscale,
|
||||||
|
isSharded,
|
||||||
|
isAutoscaleSelected = true,
|
||||||
|
throughput = AutoPilotUtils.minAutoPilotThroughput,
|
||||||
|
onCostAcknowledgeChange,
|
||||||
|
}: ThroughputInputProps) => {
|
||||||
|
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||||
|
const [throughputError, setThroughputError] = useState<string>("");
|
||||||
|
const getThroughputLabelText = (): string => {
|
||||||
|
let throughputHeaderText: string;
|
||||||
|
if (isAutoscaleSelected) {
|
||||||
|
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
||||||
|
} else {
|
||||||
|
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
||||||
|
const maxRU: string = userContext.isTryCosmosDBSubscription
|
||||||
|
? Constants.TryCosmosExperience.maxRU.toLocaleString()
|
||||||
|
: "unlimited";
|
||||||
|
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
||||||
}
|
}
|
||||||
|
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
||||||
export class ThroughputInput extends React.Component<ThroughputInputProps, ThroughputInputState> {
|
|
||||||
constructor(props: ThroughputInputProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isAutoscaleSelected: true,
|
|
||||||
throughput: AutoPilotUtils.minAutoPilotThroughput,
|
|
||||||
isCostAcknowledged: false,
|
|
||||||
throughputError: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
const onThroughputValueChange = (newInput: string): void => {
|
||||||
this.props.setIsAutoscale(true);
|
const newThroughput = parseInt(newInput);
|
||||||
|
setThroughputValue(newThroughput);
|
||||||
|
if (!isSharded && newThroughput > 10000) {
|
||||||
|
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||||
|
} else {
|
||||||
|
setThroughputError("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAutoScaleTooltip = (): string => {
|
||||||
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
|
return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCostAcknowledgeText = (): string => {
|
||||||
|
const databaseAccount = userContext.databaseAccount;
|
||||||
|
if (!databaseAccount || !databaseAccount.properties) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
const numberOfRegions: number = databaseAccount.properties.readLocations?.length || 1;
|
||||||
|
const multimasterEnabled: boolean = databaseAccount.properties.enableMultipleWriteLocations;
|
||||||
|
|
||||||
|
return PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||||
|
throughput,
|
||||||
|
userContext.portalEnv,
|
||||||
|
numberOfRegions,
|
||||||
|
multimasterEnabled,
|
||||||
|
isAutoscaleSelected
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
||||||
|
if (mode === "Autoscale") {
|
||||||
|
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
|
setIsAutoscale(true);
|
||||||
|
} else {
|
||||||
|
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
|
setIsAutoscale(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="throughputInputContainer throughputInputSpacing">
|
<div className="throughputInputContainer throughputInputSpacing">
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
|
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
|
||||||
{this.getThroughputLabelText()}
|
{getThroughputLabelText()}
|
||||||
</Text>
|
</Text>
|
||||||
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={PricingUtils.getRuToolTipText()}>
|
<InfoTooltip>{PricingUtils.getRuToolTipText()}</InfoTooltip>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label="Autoscale mode"
|
aria-label="Autoscale mode"
|
||||||
checked={this.state.isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onAutoscaleRadioBtnChange.bind(this)}
|
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||||
/>
|
/>
|
||||||
<span className="throughputInputRadioBtnLabel">Autoscale</span>
|
<span className="throughputInputRadioBtnLabel">Autoscale</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label="Manual mode"
|
aria-label="Manual mode"
|
||||||
checked={!this.state.isAutoscaleSelected}
|
checked={!isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onManualRadioBtnChange.bind(this)}
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
/>
|
/>
|
||||||
<span className="throughputInputRadioBtnLabel">Manual</span>
|
<span className="throughputInputRadioBtnLabel">Manual</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{this.state.isAutoscaleSelected && (
|
{isAutoscaleSelected && (
|
||||||
<Stack className="throughputInputSpacing">
|
<Stack className="throughputInputSpacing">
|
||||||
<Text variant="small">
|
<Text variant="small" aria-label="ruDescription">
|
||||||
Estimate your required RU/s with
|
Estimate your required RU/s with{" "}
|
||||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="ruDescription">
|
||||||
capacity calculator
|
capacity calculator
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
|
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription">
|
||||||
{this.props.isDatabase ? "Database" : getCollectionName()} max RU/s
|
{isDatabase ? "Database" : getCollectionName()} Max RU/s
|
||||||
</Text>
|
</Text>
|
||||||
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={this.getAutoScaleTooltip()}>
|
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -100,31 +147,31 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
|
||||||
fieldGroup: { width: 300, height: 27 },
|
fieldGroup: { width: 300, height: 27 },
|
||||||
field: { fontSize: 12 },
|
field: { fontSize: 12 },
|
||||||
}}
|
}}
|
||||||
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
|
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
min={AutoPilotUtils.minAutoPilotThroughput}
|
min={AutoPilotUtils.minAutoPilotThroughput}
|
||||||
value={this.state.throughput.toString()}
|
value={throughput.toString()}
|
||||||
aria-label="Max request units per second"
|
aria-label="Max request units per second"
|
||||||
errorMessage={this.state.throughputError}
|
required={true}
|
||||||
|
errorMessage={throughputError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text variant="small">
|
<Text variant="small">
|
||||||
Your {this.props.isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will
|
Your {isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will automatically scale
|
||||||
automatically scale from{" "}
|
from{" "}
|
||||||
<b>
|
<b>
|
||||||
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "}
|
{AutoPilotUtils.getMinRUsBasedOnUserInput(throughput)} RU/s (10% of max RU/s) - {throughput} RU/s
|
||||||
{this.state.throughput} RU/s
|
|
||||||
</b>{" "}
|
</b>{" "}
|
||||||
based on usage.
|
based on usage.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!this.state.isAutoscaleSelected && (
|
{!isAutoscaleSelected && (
|
||||||
<Stack className="throughputInputSpacing">
|
<Stack className="throughputInputSpacing">
|
||||||
<Text variant="small">
|
<Text variant="small" aria-label="ruDescription">
|
||||||
Estimate your required RU/s with
|
Estimate your required RU/s with
|
||||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="capacityLink">
|
||||||
capacity calculator
|
capacity calculator
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
|
@ -133,8 +180,8 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.topLeftEdge}
|
directionalHint={DirectionalHint.topLeftEdge}
|
||||||
content={
|
content={
|
||||||
this.props.showFreeTierExceedThroughputTooltip &&
|
showFreeTierExceedThroughputTooltip &&
|
||||||
this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
||||||
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
@ -145,178 +192,39 @@ export class ThroughputInput extends React.Component<ThroughputInputProps, Throu
|
||||||
fieldGroup: { width: 300, height: 27 },
|
fieldGroup: { width: 300, height: 27 },
|
||||||
field: { fontSize: 12 },
|
field: { fontSize: 12 },
|
||||||
}}
|
}}
|
||||||
onChange={(event, newInput?: string) => this.onThroughputValueChange(newInput)}
|
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||||
step={100}
|
step={100}
|
||||||
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
||||||
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
||||||
value={this.state.throughput.toString()}
|
value={throughput.toString()}
|
||||||
aria-label="Max request units per second"
|
aria-label="Max request units per second"
|
||||||
required={true}
|
required={true}
|
||||||
errorMessage={this.state.throughputError}
|
errorMessage={throughputError}
|
||||||
/>
|
/>
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CostEstimateText requestUnits={this.state.throughput} isAutoscale={this.state.isAutoscaleSelected} />
|
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
|
||||||
|
|
||||||
{this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
|
{throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
|
||||||
<Stack horizontal verticalAlign="start">
|
<Stack horizontal verticalAlign="start">
|
||||||
<span className="mandatoryStar">* </span>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={this.state.isCostAcknowledged}
|
checked={isCostAcknowledged}
|
||||||
styles={{
|
styles={{
|
||||||
checkbox: { width: 12, height: 12 },
|
checkbox: { width: 12, height: 12 },
|
||||||
label: { padding: 0, margin: "4px 4px 0 0" },
|
label: { padding: 0, margin: "4px 4px 0 0" },
|
||||||
}}
|
}}
|
||||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => {
|
||||||
this.setState({ isCostAcknowledged: isChecked });
|
setIsCostAcknowledged(isChecked);
|
||||||
this.props.onCostAcknowledgeChange(isChecked);
|
onCostAcknowledgeChange(isChecked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text variant="small" style={{ lineHeight: "20px" }}>
|
<Text variant="small" style={{ lineHeight: "20px" }}>
|
||||||
{this.getCostAcknowledgeText()}
|
{getCostAcknowledgeText()}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
private getThroughputLabelText(): string {
|
|
||||||
let throughputHeaderText: string;
|
|
||||||
if (this.state.isAutoscaleSelected) {
|
|
||||||
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
|
|
||||||
} else {
|
|
||||||
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
|
|
||||||
const maxRU: string = userContext.isTryCosmosDBSubscription
|
|
||||||
? Constants.TryCosmosExperience.maxRU.toLocaleString()
|
|
||||||
: "unlimited";
|
|
||||||
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${this.props.isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onThroughputValueChange(newInput: string): void {
|
|
||||||
const newThroughput = parseInt(newInput);
|
|
||||||
this.setState({ throughput: newThroughput });
|
|
||||||
this.props.setThroughputValue(newThroughput);
|
|
||||||
|
|
||||||
if (!this.props.isSharded && newThroughput > 10000) {
|
|
||||||
this.setState({ throughputError: "Unsharded collections support up to 10,000 RUs" });
|
|
||||||
} else {
|
|
||||||
this.setState({ throughputError: undefined });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getAutoScaleTooltip(): string {
|
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
|
||||||
return `Set the max RU/s to the highest RU/s you want your ${collectionName} to scale to. The ${collectionName} will scale between 10% of max RU/s to the max RU/s based on usage.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCostAcknowledgeText(): string {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
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;
|
|
||||||
if (!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;
|
|
||||||
|
|
||||||
const iconWithEstimatedCostDisclaimer: JSX.Element = (
|
|
||||||
<TooltipHost
|
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
content={PricingUtils.estimatedCostDisclaimer}
|
|
||||||
styles={{ root: { verticalAlign: "bottom" } }}
|
|
||||||
>
|
|
||||||
<Icon iconName="Info" className="panelInfoIcon" />
|
|
||||||
</TooltipHost>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isAutoscale) {
|
|
||||||
return (
|
|
||||||
<Text variant="small">
|
|
||||||
Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
|
||||||
<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">
|
|
||||||
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
|
|
||||||
<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)
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -31,7 +31,7 @@ import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
|
import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
import { getCollectionName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
||||||
|
@ -52,6 +52,7 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import AddCollectionPane from "./Panes/AddCollectionPane";
|
import AddCollectionPane from "./Panes/AddCollectionPane";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import AddDatabasePane from "./Panes/AddDatabasePane";
|
import AddDatabasePane from "./Panes/AddDatabasePane";
|
||||||
|
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
|
@ -1961,7 +1962,7 @@ export default class Explorer {
|
||||||
|
|
||||||
public openDeleteDatabaseConfirmationPane(): void {
|
public openDeleteDatabaseConfirmationPane(): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Delete Database",
|
"Delete " + getDatabaseName(),
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
explorer={this}
|
explorer={this}
|
||||||
openNotificationConsole={this.expandConsole}
|
openNotificationConsole={this.expandConsole}
|
||||||
|
@ -1972,12 +1973,12 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public openUploadItemsPanePane(): void {
|
public openUploadItemsPanePane(): void {
|
||||||
this.openSidePanel("Upload", <UploadItemsPane explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Upload " + getUploadName(), <UploadItemsPane explorer={this} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openSettingPane(): void {
|
public openSettingPane(): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Settings",
|
"Setting",
|
||||||
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
|
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2005,6 +2006,21 @@ export default class Explorer {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public openAddDatabasePane(): void {
|
||||||
|
if (userContext.features.enableKOPanel) {
|
||||||
|
this.addDatabasePane.open();
|
||||||
|
document.getElementById("linkAddDatabase").focus();
|
||||||
|
} else {
|
||||||
|
this.openSidePanel(
|
||||||
|
"Add " + getDatabaseName(),
|
||||||
|
<AddDatabasePanel
|
||||||
|
explorer={this}
|
||||||
|
openNotificationConsole={this.expandConsole}
|
||||||
|
closePanel={this.closeSidePanel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public openBrowseQueriesPanel(): void {
|
public openBrowseQueriesPanel(): void {
|
||||||
this.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} closePanel={this.closeSidePanel} />);
|
||||||
|
@ -2021,7 +2037,7 @@ export default class Explorer {
|
||||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Upload File",
|
"Upload file to notebook server",
|
||||||
<UploadFilePane
|
<UploadFilePane
|
||||||
expandConsole={() => this.expandConsole()}
|
expandConsole={() => this.expandConsole()}
|
||||||
closePanel={this.closeSidePanel}
|
closePanel={this.closeSidePanel}
|
||||||
|
|
|
@ -266,8 +266,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
container.addDatabasePane.open();
|
container.openAddDatabasePane();
|
||||||
document.getElementById("linkAddDatabase").focus();
|
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
|
|
|
@ -213,6 +213,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
||||||
}
|
}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
|
isAutoscaleSelected={this.isNewDatabaseAutoscale}
|
||||||
|
throughput={this.newDatabaseThroughput}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
|
@ -442,6 +444,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
||||||
}
|
}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
|
isAutoscaleSelected={this.isCollectionAutoscale}
|
||||||
|
throughput={this.collectionThroughput}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
||||||
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
||||||
<div class="contextual-pane" data-bind="attr: { id: id }">
|
<div class="contextual-pane" data-bind="attr: { id: id }">
|
||||||
<!-- Add database form -- Start -->
|
<!-- Add database form -- Start -->
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { AddDatabasePanel } from "./AddDatabasePanel";
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
explorer: new Explorer(),
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
openNotificationConsole: (): void => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("AddDatabasePane Pane", () => {
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const wrapper = shallow(<AddDatabasePanel {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,342 @@
|
||||||
|
import { Checkbox, Text, TextField } from "@fluentui/react";
|
||||||
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
|
||||||
|
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 { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
|
export interface AddDatabasePaneProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
openNotificationConsole: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||||
|
explorer: container,
|
||||||
|
closePanel,
|
||||||
|
openNotificationConsole,
|
||||||
|
}: AddDatabasePaneProps) => {
|
||||||
|
const { subscriptionType } = userContext;
|
||||||
|
const getSharedThroughputDefault = !(subscriptionType === SubscriptionType.EA || container.isServerlessEnabled());
|
||||||
|
const _isAutoPilotSelectedAndWhatTier = (): DataModels.AutoPilotCreationSettings => {
|
||||||
|
if (isAutoPilotSelected && maxAutoPilotThroughputSet) {
|
||||||
|
return {
|
||||||
|
maxThroughput: maxAutoPilotThroughputSet * 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
|
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
||||||
|
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
||||||
|
const databaseIdLabel: string = isCassandraAccount ? "Keyspace id" : "Database id";
|
||||||
|
const databaseIdPlaceHolder: string = isCassandraAccount ? "Type a new keyspace id" : "Type a new database id";
|
||||||
|
|
||||||
|
const [databaseId, setDatabaseId] = useState<string>("");
|
||||||
|
const databaseIdTooltipText = `A ${
|
||||||
|
isCassandraAccount ? "keyspace" : "database"
|
||||||
|
} is a logical container of one or more ${isCassandraAccount ? "tables" : "collections"}`;
|
||||||
|
|
||||||
|
const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
||||||
|
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(getSharedThroughputDefault);
|
||||||
|
const [formErrorsDetails, setFormErrorsDetails] = useState<string>();
|
||||||
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
|
|
||||||
|
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(container.isAutoscaleDefaultEnabled());
|
||||||
|
|
||||||
|
const throughputDefaults = container.collectionCreationDefaults.throughput;
|
||||||
|
const [throughput, setThroughput] = useState<number>(
|
||||||
|
isAutoPilotSelected ? AutoPilotUtils.minAutoPilotThroughput : throughputDefaults.shared
|
||||||
|
);
|
||||||
|
|
||||||
|
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const canRequestSupport = () => {
|
||||||
|
if (
|
||||||
|
configContext.platform !== Platform.Emulator &&
|
||||||
|
!userContext.isTryCosmosDBSubscription &&
|
||||||
|
configContext.platform !== Platform.Portal
|
||||||
|
) {
|
||||||
|
const offerThroughput: number = throughput;
|
||||||
|
return offerThroughput <= 100000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
const upsellMessage: string = PricingUtils.getUpsellMessage(
|
||||||
|
userContext.portalEnv,
|
||||||
|
isFreeTierAccount,
|
||||||
|
container.isFirstResourceCreated(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const upsellAnchorUrl: string = isFreeTierAccount ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
|
||||||
|
|
||||||
|
const upsellAnchorText: string = isFreeTierAccount ? "Learn more" : "More details";
|
||||||
|
const maxAutoPilotThroughputSet = AutoPilotUtils.minAutoPilotThroughput;
|
||||||
|
|
||||||
|
const canConfigureThroughput = !container.isServerlessEnabled();
|
||||||
|
const showUpsellMessage = () => {
|
||||||
|
if (container.isServerlessEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFreeTierAccount) {
|
||||||
|
return databaseCreateNewShared;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDatabaseCreateNewShared(getSharedThroughputDefault);
|
||||||
|
}, [subscriptionType]);
|
||||||
|
|
||||||
|
const addDatabasePaneMessage = {
|
||||||
|
database: {
|
||||||
|
id: databaseId,
|
||||||
|
shared: databaseCreateNewShared,
|
||||||
|
},
|
||||||
|
subscriptionType: SubscriptionType[subscriptionType],
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const addDatabasePaneOpenMessage = {
|
||||||
|
subscriptionType: SubscriptionType[subscriptionType],
|
||||||
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
|
defaultsCheck: {
|
||||||
|
throughput: throughput,
|
||||||
|
flight: userContext.addCollectionFlight,
|
||||||
|
},
|
||||||
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
|
};
|
||||||
|
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (!_isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offerThroughput: number = _computeOfferThroughput();
|
||||||
|
|
||||||
|
const addDatabasePaneStartMessage = {
|
||||||
|
...addDatabasePaneMessage,
|
||||||
|
offerThroughput,
|
||||||
|
};
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
|
||||||
|
setFormErrors("");
|
||||||
|
setIsExecuting(true);
|
||||||
|
|
||||||
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
|
databaseId: addDatabasePaneStartMessage.database.id,
|
||||||
|
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||||
|
};
|
||||||
|
if (isAutoPilotSelected) {
|
||||||
|
createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.offerThroughput;
|
||||||
|
} else {
|
||||||
|
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDatabase(createDatabaseParams).then(
|
||||||
|
() => {
|
||||||
|
_onCreateDatabaseSuccess(offerThroughput, startKey);
|
||||||
|
},
|
||||||
|
(error: string) => {
|
||||||
|
_onCreateDatabaseFailure(error, offerThroughput, startKey);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
|
||||||
|
setIsExecuting(false);
|
||||||
|
closePanel();
|
||||||
|
container.refreshAllDatabases();
|
||||||
|
const addDatabasePaneSuccessMessage = {
|
||||||
|
...addDatabasePaneMessage,
|
||||||
|
offerThroughput,
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _onCreateDatabaseFailure = (error: string, offerThroughput: number, startKey: number): void => {
|
||||||
|
setIsExecuting(false);
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormErrors(errorMessage);
|
||||||
|
setFormErrorsDetails(errorMessage);
|
||||||
|
const addDatabasePaneFailedMessage = {
|
||||||
|
...addDatabasePaneMessage,
|
||||||
|
offerThroughput,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
};
|
||||||
|
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _getThroughput = (): number => {
|
||||||
|
return isNaN(throughput) ? 0 : Number(throughput);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _computeOfferThroughput = (): number => {
|
||||||
|
if (!canConfigureThroughput) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _getThroughput();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _isValid = (): boolean => {
|
||||||
|
// TODO add feature flag that disables validation for customers with custom accounts
|
||||||
|
if (isAutoPilotSelected) {
|
||||||
|
const autoPilot = _isAutoPilotSelectedAndWhatTier();
|
||||||
|
if (
|
||||||
|
!autoPilot ||
|
||||||
|
!autoPilot.maxThroughput ||
|
||||||
|
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||||
|
) {
|
||||||
|
setFormErrors(
|
||||||
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const throughput = _getThroughput();
|
||||||
|
|
||||||
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated daily spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoscaleThroughput = maxAutoPilotThroughputSet * 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isAutoPilotSelected &&
|
||||||
|
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||||
|
!throughputSpendAck
|
||||||
|
) {
|
||||||
|
setFormErrors(`Please acknowledge the estimated monthly spend.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleonChangeDBId = React.useCallback(
|
||||||
|
(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
|
||||||
|
setDatabaseId(newValue || "");
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
expandConsole: container.expandConsole,
|
||||||
|
formError: formErrors,
|
||||||
|
formErrorDetail: formErrorsDetails,
|
||||||
|
isExecuting,
|
||||||
|
submitButtonText: "OK",
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RightPaneForm {...props}>
|
||||||
|
<div className="paneContentContainer" role="dialog" aria-labelledby="databaseTitle">
|
||||||
|
{showUpsellMessage && formErrors === "" && (
|
||||||
|
<PanelInfoErrorComponent
|
||||||
|
message={upsellMessage}
|
||||||
|
messageType="info"
|
||||||
|
showErrorDetails={false}
|
||||||
|
openNotificationConsole={openNotificationConsole}
|
||||||
|
link={upsellAnchorUrl}
|
||||||
|
linkText={upsellAnchorText}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="paneMainContent">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span className="mandatoryStar">*</span>
|
||||||
|
<Text variant="small">{databaseIdLabel}</Text>
|
||||||
|
<InfoTooltip>{databaseIdTooltipText}</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
id="database-id"
|
||||||
|
type="text"
|
||||||
|
aria-required="true"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
size={40}
|
||||||
|
aria-label={databaseIdLabel}
|
||||||
|
placeholder={databaseIdPlaceHolder}
|
||||||
|
value={databaseId}
|
||||||
|
onChange={handleonChangeDBId}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="databaseProvision"
|
||||||
|
aria-label="New database provision support"
|
||||||
|
style={{ display: "block ruby" }}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
title="Provision shared throughput"
|
||||||
|
styles={{
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, alignItems: "center" },
|
||||||
|
}}
|
||||||
|
label="Provision throughput"
|
||||||
|
checked={databaseCreateNewShared}
|
||||||
|
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
|
||||||
|
/>{" "}
|
||||||
|
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
{databaseCreateNewShared && (
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
||||||
|
isDatabase={true}
|
||||||
|
isSharded={databaseCreateNewShared}
|
||||||
|
isAutoscaleSelected={isAutoPilotSelected}
|
||||||
|
throughput={throughput}
|
||||||
|
setThroughputValue={(throughput: number) => setThroughput(throughput)}
|
||||||
|
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
||||||
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => setThroughputSpendAck(isAcknowledged)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{canRequestSupport() && (
|
||||||
|
<p>
|
||||||
|
<a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request">
|
||||||
|
Contact support{" "}
|
||||||
|
</a>
|
||||||
|
for more than <span>{throughputDefaults.unlimitedmax?.toLocaleString()} </span> RU/s.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||||
|
<RightPaneForm
|
||||||
|
expandConsole={[Function]}
|
||||||
|
formError=""
|
||||||
|
isExecuting={false}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
submitButtonText="OK"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-labelledby="databaseTitle"
|
||||||
|
className="paneContentContainer"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<PanelInfoErrorComponent
|
||||||
|
link="https://aka.ms/azure-cosmos-db-pricing"
|
||||||
|
linkText="More details"
|
||||||
|
message="Start at $24/mo per database, multiple containers included"
|
||||||
|
messageType="info"
|
||||||
|
openNotificationConsole={[Function]}
|
||||||
|
showErrorDetails={false}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="paneMainContent"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
className="mandatoryStar"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
<Text
|
||||||
|
variant="small"
|
||||||
|
>
|
||||||
|
Database id
|
||||||
|
</Text>
|
||||||
|
<InfoTooltip>
|
||||||
|
A database is a logical container of one or more collections
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
aria-label="Database id"
|
||||||
|
aria-required="true"
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus={true}
|
||||||
|
id="database-id"
|
||||||
|
onChange={[Function]}
|
||||||
|
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||||
|
placeholder="Type a new database id"
|
||||||
|
size={40}
|
||||||
|
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-label="New database provision support"
|
||||||
|
className="databaseProvision"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"display": "block ruby",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledCheckboxBase
|
||||||
|
checked={true}
|
||||||
|
label="Provision throughput"
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"checkbox": Object {
|
||||||
|
"height": 12,
|
||||||
|
"width": 12,
|
||||||
|
},
|
||||||
|
"label": Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"padding": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="Provision shared throughput"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InfoTooltip>
|
||||||
|
Provisioned throughput at the database level will be shared across all collections within the database.
|
||||||
|
</InfoTooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ThroughputInput
|
||||||
|
isAutoscaleSelected={false}
|
||||||
|
isDatabase={true}
|
||||||
|
isSharded={true}
|
||||||
|
onCostAcknowledgeChange={[Function]}
|
||||||
|
setIsAutoscale={[Function]}
|
||||||
|
setThroughputValue={[Function]}
|
||||||
|
throughput={400}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
|
`;
|
|
@ -17,7 +17,6 @@ export class AddDatabasePaneComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddCollectionPaneComponent {
|
export class AddCollectionPaneComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,12 +9,6 @@ export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
|
||||||
buttonLabel,
|
buttonLabel,
|
||||||
}: PanelFooterProps): JSX.Element => (
|
}: PanelFooterProps): JSX.Element => (
|
||||||
<div className="panelFooter">
|
<div className="panelFooter">
|
||||||
<PrimaryButton
|
<PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
|
||||||
type="submit"
|
|
||||||
id="sidePanelOkButton"
|
|
||||||
text={buttonLabel}
|
|
||||||
ariaLabel={buttonLabel}
|
|
||||||
data-testid="submit"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,19 +22,19 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
||||||
}: PanelInfoErrorProps): JSX.Element => {
|
}: PanelInfoErrorProps): JSX.Element => {
|
||||||
let icon: JSX.Element;
|
let icon: JSX.Element;
|
||||||
if (messageType === "error") {
|
if (messageType === "error") {
|
||||||
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" data-testid="errorIcon" />;
|
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" aria-label="error" />;
|
||||||
} else if (messageType === "warning") {
|
} else if (messageType === "warning") {
|
||||||
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" data-testid="warningIcon" />;
|
icon = <Icon iconName="WarningSolid" className="panelWarningIcon" aria-label="warning" />;
|
||||||
} else if (messageType === "info") {
|
} else if (messageType === "info") {
|
||||||
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" data-testid="InfoIcon" />;
|
icon = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label="Infomation" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
formError && (
|
formError && (
|
||||||
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="start">
|
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
|
||||||
{icon}
|
{icon}
|
||||||
<span className="panelWarningErrorDetailsLinkContainer">
|
<span className="panelWarningErrorDetailsLinkContainer">
|
||||||
<Text className="panelWarningErrorMessage" variant="small" data-testid="panelmessage">
|
<Text className="panelWarningErrorMessage" variant="small" aria-label="message">
|
||||||
{message}
|
{message}
|
||||||
{link && linkText && (
|
{link && linkText && (
|
||||||
<Link target="_blank" href={link}>
|
<Link target="_blank" href={link}>
|
||||||
|
|
|
@ -3,48 +3,38 @@ import { mount, ReactWrapper } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { RightPaneForm } from "./RightPaneForm";
|
import { RightPaneForm } from "./RightPaneForm";
|
||||||
|
|
||||||
const onClose = jest.fn();
|
|
||||||
const onSubmit = jest.fn();
|
const onSubmit = jest.fn();
|
||||||
const expandConsole = jest.fn();
|
const expandConsole = jest.fn();
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
closePanel: (): void => undefined,
|
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: "",
|
formError: "",
|
||||||
formErrorDetail: "",
|
formErrorDetail: "",
|
||||||
id: "loadQueryPane",
|
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
title: "Load Query Pane",
|
|
||||||
submitButtonText: "Load",
|
submitButtonText: "Load",
|
||||||
onClose,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Load Query Pane", () => {
|
describe("Right Pane Form", () => {
|
||||||
let wrapper: ReactWrapper;
|
let wrapper: ReactWrapper;
|
||||||
|
|
||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
wrapper = mount(<RightPaneForm {...props} />);
|
wrapper = mount(<RightPaneForm {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it("should call close method click cancel icon", () => {
|
|
||||||
render(<RightPaneForm {...props} />);
|
|
||||||
fireEvent.click(screen.getByTestId("closePaneBtn"));
|
|
||||||
expect(onClose).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it("should call submit method enter in form", () => {
|
it("should call submit method enter in form", () => {
|
||||||
render(<RightPaneForm {...props} />);
|
render(<RightPaneForm {...props} />);
|
||||||
fireEvent.click(screen.getByTestId("submit"));
|
fireEvent.click(screen.getByLabelText("Load"));
|
||||||
expect(onSubmit).toHaveBeenCalled();
|
expect(onSubmit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("should call submit method click on submit button", () => {
|
it("should call submit method click on submit button", () => {
|
||||||
render(<RightPaneForm {...props} />);
|
render(<RightPaneForm {...props} />);
|
||||||
fireEvent.click(screen.getByTestId("submit"));
|
fireEvent.click(screen.getByLabelText("Load"));
|
||||||
expect(onSubmit).toHaveBeenCalled();
|
expect(onSubmit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("should render error in header", () => {
|
it("should render error in header", () => {
|
||||||
render(<RightPaneForm {...props} formError="file already Exist" />);
|
render(<RightPaneForm {...props} formError="file already Exist" />);
|
||||||
expect(screen.getByTestId("errorIcon")).toBeDefined();
|
expect(screen.getByLabelText("error")).toBeDefined();
|
||||||
expect(screen.getByTestId("panelmessage").innerHTML).toEqual("file already Exist");
|
expect(screen.getByLabelText("message").innerHTML).toEqual("file already Exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { IconButton } from "@fluentui/react";
|
|
||||||
import React, { FunctionComponent, ReactNode } from "react";
|
import React, { FunctionComponent, ReactNode } from "react";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import { PanelFooterComponent } from "../PanelFooterComponent";
|
import { PanelFooterComponent } from "../PanelFooterComponent";
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
|
||||||
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||||
|
@ -9,12 +7,9 @@ export interface RightPaneFormProps {
|
||||||
expandConsole: () => void;
|
expandConsole: () => void;
|
||||||
formError: string;
|
formError: string;
|
||||||
formErrorDetail: string;
|
formErrorDetail: string;
|
||||||
id: string;
|
|
||||||
isExecuting: boolean;
|
isExecuting: boolean;
|
||||||
onClose: () => void;
|
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
submitButtonText: string;
|
submitButtonText: string;
|
||||||
title: string;
|
|
||||||
isSubmitButtonHidden?: boolean;
|
isSubmitButtonHidden?: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -23,51 +18,16 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError,
|
formError,
|
||||||
formErrorDetail,
|
formErrorDetail,
|
||||||
id,
|
|
||||||
isExecuting,
|
isExecuting,
|
||||||
onClose,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
submitButtonText,
|
submitButtonText,
|
||||||
title,
|
|
||||||
isSubmitButtonHidden = false,
|
isSubmitButtonHidden = false,
|
||||||
children,
|
children,
|
||||||
}: RightPaneFormProps) => {
|
}: RightPaneFormProps) => {
|
||||||
const getPanelHeight = (): number => {
|
|
||||||
const notificationConsoleElement: HTMLElement = document.getElementById("explorerNotificationConsole");
|
|
||||||
return window.innerHeight - $(notificationConsoleElement).height();
|
|
||||||
};
|
|
||||||
|
|
||||||
const panelHeight: number = getPanelHeight();
|
|
||||||
|
|
||||||
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSubmit();
|
onSubmit();
|
||||||
};
|
};
|
||||||
const renderPanelHeader = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="firstdivbg headerline">
|
|
||||||
<span id="databaseTitle" role="heading" aria-level={2}>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="Close pane"
|
|
||||||
title="Close pane"
|
|
||||||
data-testid="closePaneBtn"
|
|
||||||
onClick={onClose}
|
|
||||||
tabIndex={0}
|
|
||||||
className="closePaneBtn"
|
|
||||||
iconProps={{ iconName: "Cancel" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
||||||
if (event.keyCode === KeyCodes.Escape) {
|
|
||||||
onClose();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const panelInfoErrorProps: PanelInfoErrorProps = {
|
const panelInfoErrorProps: PanelInfoErrorProps = {
|
||||||
messageType: "error",
|
messageType: "error",
|
||||||
|
@ -78,19 +38,13 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div tabIndex={-1} onKeyDown={onKeyDown}>
|
<>
|
||||||
<div className="contextual-pane-out" onClick={onClose}></div>
|
|
||||||
<div className="contextual-pane" id={id} style={{ height: panelHeight }} onKeyDown={onKeyDown}>
|
|
||||||
<div className="panelContentWrapper">
|
|
||||||
{renderPanelHeader()}
|
|
||||||
<PanelInfoErrorComponent {...panelInfoErrorProps} />
|
<PanelInfoErrorComponent {...panelInfoErrorProps} />
|
||||||
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
||||||
{children}
|
{children}
|
||||||
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
|
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
{isExecuting && <PanelLoadingScreen />}
|
{isExecuting && <PanelLoadingScreen />}
|
||||||
</div>
|
</>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,13 @@
|
||||||
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
|
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
|
||||||
import React, { FunctionComponent, MouseEvent, useState } from "react";
|
import React, { FunctionComponent, MouseEvent, useState } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Tooltip } from "../../../Common/Tooltip/Tooltip";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
||||||
import * as StringUtility from "../../../Shared/StringUtility";
|
import * as StringUtility from "../../../Shared/StringUtility";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import {
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
GenericRightPaneComponent,
|
|
||||||
GenericRightPaneProps,
|
|
||||||
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
|
||||||
|
|
||||||
export interface SettingsPaneProps {
|
export interface SettingsPaneProps {
|
||||||
expandConsole: () => void;
|
expandConsole: () => void;
|
||||||
|
@ -105,15 +102,12 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
setGraphAutoVizDisabled(option.key);
|
setGraphAutoVizDisabled(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
const genericPaneProps: RightPaneFormProps = {
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
formErrorDetail: "",
|
formErrorDetail: "",
|
||||||
id: "settingspane",
|
|
||||||
isExecuting,
|
isExecuting,
|
||||||
title: "Setting",
|
|
||||||
submitButtonText: "Apply",
|
submitButtonText: "Apply",
|
||||||
onClose: () => closePanel(),
|
|
||||||
onSubmit: () => handlerOnSubmit(undefined),
|
onSubmit: () => handlerOnSubmit(undefined),
|
||||||
};
|
};
|
||||||
const pageOptionList: IChoiceGroupOption[] = [
|
const pageOptionList: IChoiceGroupOption[] = [
|
||||||
|
@ -130,17 +124,17 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
{shouldShowQueryPageOptions && (
|
{shouldShowQueryPageOptions && (
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart pageOptionsPart">
|
<div className="settingsSectionPart pageOptionsPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Page options
|
Page options
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
||||||
query results per page.
|
query results per page.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<ChoiceGroup selectedKey={pageOption} options={pageOptionList} onChange={handleOnPageOptionChange} />
|
<ChoiceGroup selectedKey={pageOption} options={pageOptionList} onChange={handleOnPageOptionChange} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,7 +143,7 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
<div className="tabcontent">
|
<div className="tabcontent">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Query results per page
|
Query results per page
|
||||||
<Tooltip>Enter the number of query results that should be shown per page.</Tooltip>
|
<InfoTooltip>Enter the number of query results that should be shown per page.</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinButton
|
<SpinButton
|
||||||
|
@ -176,10 +170,10 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Enable cross-partition query
|
Enable cross-partition query
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Send more than one request while executing a query. More than one request is necessary if the query is
|
Send more than one request while executing a query. More than one request is necessary if the query is
|
||||||
not scoped to single partition key value.
|
not scoped to single partition key value.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -199,11 +193,11 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Max degree of parallelism
|
Max degree of parallelism
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Gets or sets the number of concurrent operations run client side during parallel query execution. A
|
Gets or sets the number of concurrent operations run client side during parallel query execution. A
|
||||||
positive property value limits the number of concurrent operations to the set value. If it is set to
|
positive property value limits the number of concurrent operations to the set value. If it is set to
|
||||||
less than 0, the system automatically decides the number of concurrent operations to run.
|
less than 0, the system automatically decides the number of concurrent operations to run.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinButton
|
<SpinButton
|
||||||
|
@ -227,10 +221,10 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Display Gremlin query results as:
|
Display Gremlin query results as:
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as
|
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as
|
||||||
JSON.
|
JSON.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ChoiceGroup
|
<ChoiceGroup
|
||||||
|
@ -249,6 +243,6 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Settings Pane should render Default properly 1`] = `
|
exports[`Settings Pane should render Default properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
formErrorDetail=""
|
||||||
id="settingspane"
|
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Apply"
|
submitButtonText="Apply"
|
||||||
title="Setting"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
|
@ -25,9 +22,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Page options
|
Page options
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledChoiceGroup
|
<StyledChoiceGroup
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
@ -56,9 +53,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Query results per page
|
Query results per page
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Enter the number of query results that should be shown per page.
|
Enter the number of query results that should be shown per page.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledSpinButton
|
<StyledSpinButton
|
||||||
ariaLabel="Custom query items per page"
|
ariaLabel="Custom query items per page"
|
||||||
|
@ -85,9 +82,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Enable cross-partition query
|
Enable cross-partition query
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
|
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
ariaLabel="Enable cross partition query"
|
ariaLabel="Enable cross partition query"
|
||||||
|
@ -114,9 +111,9 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Max degree of parallelism
|
Max degree of parallelism
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
|
Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledSpinButton
|
<StyledSpinButton
|
||||||
ariaLabel="Max degree of parallelism"
|
ariaLabel="Max degree of parallelism"
|
||||||
|
@ -148,20 +145,17 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Settings Pane should render Gremlin properly 1`] = `
|
exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||||
<GenericRightPaneComponent
|
<RightPaneForm
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
formErrorDetail=""
|
||||||
id="settingspane"
|
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Apply"
|
submitButtonText="Apply"
|
||||||
title="Setting"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
|
@ -176,9 +170,9 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
>
|
>
|
||||||
Display Gremlin query results as:
|
Display Gremlin query results as:
|
||||||
<Tooltip>
|
<InfoTooltip>
|
||||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as JSON.
|
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as JSON.
|
||||||
</Tooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<StyledChoiceGroup
|
<StyledChoiceGroup
|
||||||
aria-label="Graph Auto-visualization"
|
aria-label="Graph Auto-visualization"
|
||||||
|
@ -214,5 +208,5 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GenericRightPaneComponent>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -15,9 +15,6 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
||||||
closePanel,
|
closePanel,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
}: UploadFilePanelProps) => {
|
}: UploadFilePanelProps) => {
|
||||||
const title = "Upload file to notebook server";
|
|
||||||
const submitButtonLabel = "Upload";
|
|
||||||
const selectFileInputLabel = "Select file to upload";
|
|
||||||
const extensions: string = undefined; //ex. ".ipynb"
|
const extensions: string = undefined; //ex. ".ipynb"
|
||||||
const errorMessage = "Could not upload file";
|
const errorMessage = "Could not upload file";
|
||||||
const inProgressMessage = "Uploading file to notebook server";
|
const inProgressMessage = "Uploading file to notebook server";
|
||||||
|
@ -39,11 +36,8 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const file: File = files.item(0);
|
const file: File = files.item(0);
|
||||||
// const id: string = logConsoleProgress(
|
|
||||||
// `${inProgressMessage}: ${file.name}`
|
|
||||||
// );
|
|
||||||
|
|
||||||
logConsoleProgress(`${inProgressMessage}: ${file.name}`);
|
const clearMessage = logConsoleProgress(`${inProgressMessage}: ${file.name}`);
|
||||||
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
|
@ -61,7 +55,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
// clearInProgressMessageWithId(id);
|
clearMessage();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,18 +86,15 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
|
||||||
expandConsole,
|
expandConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
formErrorDetail: formErrorsDetails,
|
formErrorDetail: formErrorsDetails,
|
||||||
id: "uploadFilePane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title,
|
submitButtonText: "Upload",
|
||||||
submitButtonText: submitButtonLabel,
|
|
||||||
onClose: closePanel,
|
|
||||||
onSubmit: submit,
|
onSubmit: submit,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
<Upload label={selectFileInputLabel} accept={extensions} onUpload={updateSelectedFiles} />
|
<Upload label="Select file to upload" accept={extensions} onUpload={updateSelectedFiles} />
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@flu
|
||||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
||||||
import { Upload } from "../../../Common/Upload/Upload";
|
import { Upload } from "../../../Common/Upload/Upload";
|
||||||
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
|
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { getErrorMessage } from "../../Tables/Utilities";
|
import { getErrorMessage } from "../../Tables/Utilities";
|
||||||
|
@ -10,23 +9,9 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
|
||||||
|
|
||||||
export interface UploadItemsPaneProps {
|
export interface UploadItemsPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTitle = (): string => {
|
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explorer }: UploadItemsPaneProps) => {
|
||||||
if (userContext.apiType === "Cassandra" || userContext.apiType === "Tables") {
|
|
||||||
return "Upload Tables";
|
|
||||||
} else if (userContext.apiType === "Gremlin") {
|
|
||||||
return "Upload Graph";
|
|
||||||
} else {
|
|
||||||
return "Upload Items";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
|
|
||||||
explorer,
|
|
||||||
closePanel,
|
|
||||||
}: UploadItemsPaneProps) => {
|
|
||||||
const [files, setFiles] = useState<FileList>();
|
const [files, setFiles] = useState<FileList>();
|
||||||
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
@ -71,11 +56,8 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({
|
||||||
expandConsole: () => explorer.expandConsole(),
|
expandConsole: () => explorer.expandConsole(),
|
||||||
formError,
|
formError,
|
||||||
formErrorDetail,
|
formErrorDetail,
|
||||||
id: "uploaditemspane",
|
|
||||||
isExecuting: isExecuting,
|
isExecuting: isExecuting,
|
||||||
title: getTitle(),
|
|
||||||
submitButtonText: "Upload",
|
submitButtonText: "Upload",
|
||||||
onClose: closePanel,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,8 @@ exports[`Upload Items Pane should render Default properly 1`] = `
|
||||||
expandConsole={[Function]}
|
expandConsole={[Function]}
|
||||||
formError=""
|
formError=""
|
||||||
formErrorDetail=""
|
formErrorDetail=""
|
||||||
id="uploaditemspane"
|
|
||||||
onClose={[Function]}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="Upload"
|
submitButtonText="Upload"
|
||||||
title="Upload Items"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="paneMainContent"
|
className="paneMainContent"
|
||||||
|
|
|
@ -1288,20 +1288,20 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
<Stack
|
<Stack
|
||||||
className="panelInfoErrorContainer"
|
className="panelInfoErrorContainer"
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
verticalAlign="start"
|
verticalAlign="center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-Stack panelInfoErrorContainer css-202"
|
className="ms-Stack panelInfoErrorContainer css-202"
|
||||||
>
|
>
|
||||||
<StyledIconBase
|
<StyledIconBase
|
||||||
|
aria-label="warning"
|
||||||
className="panelWarningIcon"
|
className="panelWarningIcon"
|
||||||
data-testid="warningIcon"
|
|
||||||
iconName="WarningSolid"
|
iconName="WarningSolid"
|
||||||
key=".0:$.0"
|
key=".0:$.0"
|
||||||
>
|
>
|
||||||
<IconBase
|
<IconBase
|
||||||
|
aria-label="warning"
|
||||||
className="panelWarningIcon"
|
className="panelWarningIcon"
|
||||||
data-testid="warningIcon"
|
|
||||||
iconName="WarningSolid"
|
iconName="WarningSolid"
|
||||||
styles={[Function]}
|
styles={[Function]}
|
||||||
theme={
|
theme={
|
||||||
|
@ -1579,10 +1579,10 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-hidden={true}
|
aria-label="warning"
|
||||||
className="panelWarningIcon root-204"
|
className="panelWarningIcon root-204"
|
||||||
data-icon-name="WarningSolid"
|
data-icon-name="WarningSolid"
|
||||||
data-testid="warningIcon"
|
role="img"
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
|
@ -1593,13 +1593,13 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
key=".0:$.1"
|
key=".0:$.1"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
|
aria-label="message"
|
||||||
className="panelWarningErrorMessage"
|
className="panelWarningErrorMessage"
|
||||||
data-testid="panelmessage"
|
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
aria-label="message"
|
||||||
className="panelWarningErrorMessage css-205"
|
className="panelWarningErrorMessage css-205"
|
||||||
data-testid="panelmessage"
|
|
||||||
>
|
>
|
||||||
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.
|
||||||
</span>
|
</span>
|
||||||
|
@ -2303,14 +2303,12 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-testid="submit"
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-testid="submit"
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
|
@ -2590,7 +2588,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-testid="submit"
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
@ -2872,7 +2869,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-testid="submit"
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
@ -3155,7 +3151,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-testid="submit"
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
@ -4012,7 +4007,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-218"
|
className="ms-Button ms-Button--primary root-218"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-testid="submit"
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|
|
@ -292,7 +292,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
title: this.container.addDatabaseText(),
|
title: this.container.addDatabaseText(),
|
||||||
description: null,
|
description: null,
|
||||||
onClick: () => this.container.addDatabasePane.open(),
|
onClick: () => this.container.openAddDatabasePane(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
|
||||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||||
import "./Explorer/Controls/ThroughputInput/ThroughputInput.less";
|
|
||||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||||
import { ExplorerParams } from "./Explorer/Explorer";
|
import { ExplorerParams } from "./Explorer/Explorer";
|
||||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||||
|
|
|
@ -28,3 +28,30 @@ export const getCollectionName = (isPlural?: boolean): string => {
|
||||||
|
|
||||||
return collectionName;
|
return collectionName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDatabaseName = (): string => {
|
||||||
|
const { apiType } = userContext;
|
||||||
|
switch (apiType) {
|
||||||
|
case "SQL":
|
||||||
|
case "Mongo":
|
||||||
|
case "Gremlin":
|
||||||
|
case "Tables":
|
||||||
|
return "Database";
|
||||||
|
case "Cassandra":
|
||||||
|
return "Keyspace";
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown API type: ${apiType}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUploadName = (): string => {
|
||||||
|
switch (userContext.apiType) {
|
||||||
|
case "Cassandra":
|
||||||
|
case "Tables":
|
||||||
|
return "Tables";
|
||||||
|
case "Gremlin":
|
||||||
|
return "Graph";
|
||||||
|
default:
|
||||||
|
return "Items";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@ test("Mongo CRUD", async () => {
|
||||||
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
await safeClick(explorer, `.nodeItem >> text=${databaseId}`);
|
||||||
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
await safeClick(explorer, `.nodeItem >> text=${containerId}`);
|
||||||
// Create indexing policy
|
// Create indexing policy
|
||||||
await safeClick(explorer, ".nodeItem >> text=Settings");
|
await safeClick(explorer, ".nodeItem >> text=Setting");
|
||||||
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
await explorer.click('button[role="tab"]:has-text("Indexing Policy")');
|
||||||
await explorer.click('[aria-label="Index Field Name 0"]');
|
await explorer.click('[aria-label="Index Field Name 0"]');
|
||||||
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
await explorer.fill('[aria-label="Index Field Name 0"]', "foo");
|
||||||
|
|
Loading…
Reference in New Issue