diff --git a/.eslintignore b/.eslintignore index 050bbc0dc..f72026dc5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -54,7 +54,6 @@ src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts src/Explorer/Controls/InputTypeahead/InputTypeahead.ts src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts -src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts src/Explorer/Controls/ThroughputInput/ThroughputInputComponent.ts src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts src/Explorer/Controls/Toolbar/IToolbarAction.ts @@ -109,10 +108,8 @@ src/Explorer/Notebook/NotebookUtil.ts src/Explorer/OpenActions.test.ts src/Explorer/OpenActions.ts src/Explorer/OpenActionsStubs.ts -src/Explorer/Panes/AddCollectionPane.test.ts -src/Explorer/Panes/AddCollectionPane.ts -src/Explorer/Panes/AddDatabasePane.test.ts src/Explorer/Panes/AddDatabasePane.ts +src/Explorer/Panes/AddDatabasePane.test.ts src/Explorer/Panes/BrowseQueriesPane.ts src/Explorer/Panes/CassandraAddCollectionPane.ts src/Explorer/Panes/ContextualPaneBase.ts diff --git a/less/documentDB.less b/less/documentDB.less index 97a7be384..e58206818 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -1757,7 +1757,7 @@ input::-webkit-calendar-picker-indicator { cursor: pointer; } -.contextual-pane .paneMainContent { +.paneMainContent { flex: 1; padding-left: 34px; padding-right: 34px; diff --git a/package-lock.json b/package-lock.json index 6a7ee0ce2..0c3fee1e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2109,52 +2109,56 @@ } } }, - "@fluentui/date-time-utilities": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-7.9.1.tgz", - "integrity": "sha512-o8iU1VIY+QsqVRWARKiky29fh4KR1xaKSgMClXIi65qkt8EDDhjmlzL0KVDEoDA2GWukwb/1PpaVCWDg4v3cUQ==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "tslib": "^1.10.0" - } - }, "@fluentui/dom-utilities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-1.1.2.tgz", - "integrity": "sha512-XqPS7l3YoMwxdNlaYF6S2Mp0K3FmVIOIy2K3YkMc+eRxu9wFK6emr2Q/3rBhtG5u/On37NExRT7/5CTLnoi9gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.1.0.tgz", + "integrity": "sha512-DMr0uH4EtyXgdpVLyvWq60YtWN38jx22rtdsEIbbBNYcFgcl3rRa7M8p/rnaw/k/KWX35H40AYga1SM6Zgpyww==", "requires": { - "@uifabric/set-version": "^7.0.24", - "tslib": "^1.10.0" + "@fluentui/set-version": "^8.1.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } } }, "@fluentui/font-icons-mdl2": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.0.5.tgz", - "integrity": "sha512-/2Qx/LCZH+rupbhzZ+WKCrw2XK3J2wj3pjMPLTLj/6rnmouA7yywFDiHIAseQXPhY34nzRVX4x3zEjz3RMWvmA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.1.0.tgz", + "integrity": "sha512-U0nAsv/vULZ4ezHDw0umk4mijSot9BNDXl0dZ4ZatxLBr8JZkNgTDowBZ9aEyWuFukZ6Lf0V/eEPIeJULrUDfw==", "requires": { - "@fluentui/set-version": "^8.0.3", - "@fluentui/style-utilities": "^8.0.5", - "tslib": "^1.10.0" + "@fluentui/set-version": "^8.1.0", + "@fluentui/style-utilities": "^8.1.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } } }, "@fluentui/foundation-legacy": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.0.5.tgz", - "integrity": "sha512-bHODfopWfT4HfBvXFCyfKxi7/BrIVgKUvQ4X3L2GXUZv6jjQMvLPWZ1u8n2tdp91ch1KSWnBXklGaK/nyK1M5w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.1.0.tgz", + "integrity": "sha512-GC6MkfcBbfqltgKe0hi4Wq0DTj8UxSFUdoOG9QQDLjIjI1r+L935ba0x91phqB9nptJCp+5TjkTtz7Q1lJ97Tw==", "requires": { - "@fluentui/merge-styles": "^8.0.4", - "@fluentui/set-version": "^8.0.3", - "@fluentui/style-utilities": "^8.0.5", - "@fluentui/utilities": "^8.0.5", - "tslib": "^1.10.0" - } - }, - "@fluentui/keyboard-key": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.2.17.tgz", - "integrity": "sha512-iT1bU56rKrKEOfODoW6fScY11qj3iaYrZ+z11T6fo5+TDm84UGkkXjLXJTE57ZJzg0/gbccHQWYv+chY7bJN8Q==", - "requires": { - "tslib": "^1.10.0" + "@fluentui/merge-styles": "^8.1.0", + "@fluentui/set-version": "^8.1.0", + "@fluentui/style-utilities": "^8.1.0", + "@fluentui/utilities": "^8.1.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } } }, "@fluentui/merge-styles": { @@ -2174,23 +2178,23 @@ } }, "@fluentui/react": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.10.1.tgz", - "integrity": "sha512-J6wCfkkCzJ2Z9K6KVCrshpDDnhh5smKEGChsxMkLRoXYeAlVUMiS9lLg93PW+AbY4NfG+tNO2Ug8itNKpUmlYw==", + "version": "8.14.3", + "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.14.3.tgz", + "integrity": "sha512-Gp4VBtZk5h5kXpu1vU+KvTvcRaMleD5Yl7c5XLmRVyEfofgw1Sd+M7h+1aQkwHPxvl9h7ebaJce3PbJ+xuE7Ag==", "requires": { - "@fluentui/date-time-utilities": "^8.0.2", - "@fluentui/font-icons-mdl2": "^8.0.4", - "@fluentui/foundation-legacy": "^8.0.4", - "@fluentui/merge-styles": "^8.0.3", - "@fluentui/react-focus": "^8.0.7", - "@fluentui/react-hooks": "^8.1.2", - "@fluentui/react-window-provider": "^2.0.2", - "@fluentui/set-version": "^8.0.2", - "@fluentui/style-utilities": "^8.0.4", - "@fluentui/theme": "^2.0.4", - "@fluentui/utilities": "^8.0.4", + "@fluentui/date-time-utilities": "^8.1.0", + "@fluentui/font-icons-mdl2": "^8.1.0", + "@fluentui/foundation-legacy": "^8.1.0", + "@fluentui/merge-styles": "^8.1.0", + "@fluentui/react-focus": "^8.1.0", + "@fluentui/react-hooks": "^8.2.0", + "@fluentui/react-window-provider": "^2.1.0", + "@fluentui/set-version": "^8.1.0", + "@fluentui/style-utilities": "^8.1.0", + "@fluentui/theme": "^2.1.0", + "@fluentui/utilities": "^8.1.0", "@microsoft/load-themed-styles": "^1.10.26", - "tslib": "^1.10.0" + "tslib": "^2.1.0" }, "dependencies": { "@fluentui/date-time-utilities": { @@ -2200,13 +2204,6 @@ "requires": { "@fluentui/set-version": "^8.1.0", "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - } } }, "@fluentui/react-window-provider": { @@ -2216,14 +2213,12 @@ "requires": { "@fluentui/set-version": "^8.1.0", "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - } } + }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" } } }, @@ -2240,15 +2235,6 @@ "tslib": "^2.1.0" }, "dependencies": { - "@fluentui/dom-utilities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.1.0.tgz", - "integrity": "sha512-DMr0uH4EtyXgdpVLyvWq60YtWN38jx22rtdsEIbbBNYcFgcl3rRa7M8p/rnaw/k/KWX35H40AYga1SM6Zgpyww==", - "requires": { - "@fluentui/set-version": "^8.1.0", - "tslib": "^2.1.0" - } - }, "@fluentui/keyboard-key": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.3.0.tgz", @@ -2257,17 +2243,6 @@ "tslib": "^2.1.0" } }, - "@fluentui/utilities": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.1.0.tgz", - "integrity": "sha512-/yHnDkrIlyn/Jy3XWccNRyuujQDgUxz44OQDEiMSko50S/L7cVeWdIzG/CiIsCnKAgU4/QyzRo40Wdy3rdM8ag==", - "requires": { - "@fluentui/dom-utilities": "^2.1.0", - "@fluentui/merge-styles": "^8.1.0", - "@fluentui/set-version": "^8.1.0", - "tslib": "^2.1.0" - } - }, "tslib": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", @@ -2276,24 +2251,29 @@ } }, "@fluentui/react-hooks": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.1.3.tgz", - "integrity": "sha512-6iWYjfqXvEhHZoY4nmN6APoIdxwNxzVxXOJgq1Oo1nbR10E7Va7mz41gvlFAgg8OmH7mGpdSztlVj6TVl/TY2g==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.2.0.tgz", + "integrity": "sha512-FnmtkDurjnLXN/VssBnQQ19RGY3mUh+rLiYa4VRBWRIh1JTs7o6DGCu+IijvvFNzTHvsMgL/O3v/2UjXShO6uQ==", "requires": { - "@fluentui/react-window-provider": "^2.0.3", - "@fluentui/set-version": "^8.0.3", - "@fluentui/utilities": "^8.0.5", - "tslib": "^1.10.0" + "@fluentui/react-window-provider": "^2.1.0", + "@fluentui/set-version": "^8.1.0", + "@fluentui/utilities": "^8.1.0", + "tslib": "^2.1.0" }, "dependencies": { "@fluentui/react-window-provider": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.0.3.tgz", - "integrity": "sha512-eefkmzLJbYjVHtEtGbOwReyeYG+2bDefxdmvPRSVFlcRFCnasTvshuZj3xkaKSu5fsGzGw+f+ab3hDgLSETvbg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.1.0.tgz", + "integrity": "sha512-LcNni1utHiXiCu8EbXL42o118yNRAWKX15qKd0iyMqcUg5RplOdWuaniohXv2gsmdNB0l3F5Tnujgayy0xPlvQ==", "requires": { - "@fluentui/set-version": "^8.0.3", - "tslib": "^1.10.0" + "@fluentui/set-version": "^8.1.0", + "tslib": "^2.1.0" } + }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" } } }, @@ -2325,26 +2305,6 @@ "tslib": "^2.1.0" }, "dependencies": { - "@fluentui/dom-utilities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.1.0.tgz", - "integrity": "sha512-DMr0uH4EtyXgdpVLyvWq60YtWN38jx22rtdsEIbbBNYcFgcl3rRa7M8p/rnaw/k/KWX35H40AYga1SM6Zgpyww==", - "requires": { - "@fluentui/set-version": "^8.1.0", - "tslib": "^2.1.0" - } - }, - "@fluentui/utilities": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.1.0.tgz", - "integrity": "sha512-/yHnDkrIlyn/Jy3XWccNRyuujQDgUxz44OQDEiMSko50S/L7cVeWdIzG/CiIsCnKAgU4/QyzRo40Wdy3rdM8ag==", - "requires": { - "@fluentui/dom-utilities": "^2.1.0", - "@fluentui/merge-styles": "^8.1.0", - "@fluentui/set-version": "^8.1.0", - "tslib": "^2.1.0" - } - }, "tslib": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", @@ -2363,26 +2323,6 @@ "tslib": "^2.1.0" }, "dependencies": { - "@fluentui/dom-utilities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.1.0.tgz", - "integrity": "sha512-DMr0uH4EtyXgdpVLyvWq60YtWN38jx22rtdsEIbbBNYcFgcl3rRa7M8p/rnaw/k/KWX35H40AYga1SM6Zgpyww==", - "requires": { - "@fluentui/set-version": "^8.1.0", - "tslib": "^2.1.0" - } - }, - "@fluentui/utilities": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.1.0.tgz", - "integrity": "sha512-/yHnDkrIlyn/Jy3XWccNRyuujQDgUxz44OQDEiMSko50S/L7cVeWdIzG/CiIsCnKAgU4/QyzRo40Wdy3rdM8ag==", - "requires": { - "@fluentui/dom-utilities": "^2.1.0", - "@fluentui/merge-styles": "^8.1.0", - "@fluentui/set-version": "^8.1.0", - "tslib": "^2.1.0" - } - }, "tslib": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", @@ -2391,24 +2331,20 @@ } }, "@fluentui/utilities": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.0.5.tgz", - "integrity": "sha512-epqvqV4YjKcbgShLhDk/y2Xa+6sUTBXry/BrMPNVwNOqdj2HCf4eCY+a1emFnytMpIVysfYZ6glIx9MNQ8JgNg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.1.0.tgz", + "integrity": "sha512-/yHnDkrIlyn/Jy3XWccNRyuujQDgUxz44OQDEiMSko50S/L7cVeWdIzG/CiIsCnKAgU4/QyzRo40Wdy3rdM8ag==", "requires": { - "@fluentui/dom-utilities": "^2.0.3", - "@fluentui/merge-styles": "^8.0.4", - "@fluentui/set-version": "^8.0.3", - "tslib": "^1.10.0" + "@fluentui/dom-utilities": "^2.1.0", + "@fluentui/merge-styles": "^8.1.0", + "@fluentui/set-version": "^8.1.0", + "tslib": "^2.1.0" }, "dependencies": { - "@fluentui/dom-utilities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.0.3.tgz", - "integrity": "sha512-RaUoEYd12TeEjvKLBtfXDZmjreaV6qIfBB7G+QCuBpv5FF7TjhrGK3WDZ7oaG0dkdegAx+ecyUFyOfoBRPJLkg==", - "requires": { - "@fluentui/set-version": "^8.0.3", - "tslib": "^1.10.0" - } + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" } } }, @@ -3090,9 +3026,9 @@ } }, "react": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", - "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -3258,9 +3194,9 @@ }, "dependencies": { "react": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", - "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -3546,9 +3482,9 @@ } }, "@microsoft/load-themed-styles": { - "version": "1.10.146", - "resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.146.tgz", - "integrity": "sha512-qQZ4J58J2VMe/XRpr2YRDusQB9uRBJ1SjJB76x7uH94t9hqxjVVxn2qL99Bl+ERbfrACZ9peGn2uamt4ponqZQ==" + "version": "1.10.168", + "resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.168.tgz", + "integrity": "sha512-AnIjt1R+6P73ZQ0r/Djzij+dsizfvz0yj7spuCH6exEnpGgbsdPe0cdNrXPHVnaeJriHfefvhVRWGO7D8sMF9A==" }, "@nodelib/fs.scandir": { "version": "2.1.4", @@ -3574,18 +3510,18 @@ } }, "@npmcli/move-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.0.tgz", - "integrity": "sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", "requires": { "mkdirp": "^1.0.4", - "rimraf": "^2.7.1" + "rimraf": "^3.0.2" }, "dependencies": { "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } @@ -6078,413 +6014,6 @@ } } }, - "@uifabric/azure-themes": { - "version": "7.7.32", - "resolved": "https://registry.npmjs.org/@uifabric/azure-themes/-/azure-themes-7.7.32.tgz", - "integrity": "sha512-S7mym3EQ/WjRu0+VHLv6HP06BvaszaEl1jXvI8IiP19aWn+AYWEtdB0BjTA8/SUc1PXrRWG/wZgucOgLvKncDA==", - "requires": { - "@fluentui/theme": "^1.7.4", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "office-ui-fabric-react": "^7.168.2", - "tslib": "^1.10.0" - }, - "dependencies": { - "@fluentui/react-focus": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.17.6.tgz", - "integrity": "sha512-JkLWNDe567lhvbnIhbYv9nUWYDIVN06utc3krs0UZBI+A0YZtQmftBtY0ghXo4PSjgozZocdu9sYkkgZOgyRLg==", - "requires": { - "@fluentui/keyboard-key": "^0.2.12", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@fluentui/react-window-provider": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.2.tgz", - "integrity": "sha512-fGSgL3Vp/+6t1Ysfz21FWZmqsU+iFVxOigvHnm5uKVyyRPwtaabv/F6kQ2y5isLMI2YmJaUd2i0cDJKu8ggrvw==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "tslib": "^1.10.0" - } - }, - "@fluentui/theme": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-1.7.4.tgz", - "integrity": "sha512-o4eo7lstLxxXl1g2RR9yz18Yt8yjQO/LbQuZjsiAfv/4Bf0CRnb+3j1F7gxIdBWAchKj9gzaMpIFijfI98pvYQ==", - "requires": { - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@uifabric/foundation": { - "version": "7.9.26", - "resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.9.26.tgz", - "integrity": "sha512-1FLTb+jlH/Tuel2L9wT/zLl5ZW6W4Lbjrs5VUVjv81vWxzznvPnTf8+Ew0qkzaH7xDuMNMl7okswhV0IfJyheg==", - "requires": { - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@uifabric/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": { - "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" - } - }, - "office-ui-fabric-react": { - "version": "7.168.2", - "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.168.2.tgz", - "integrity": "sha512-ssN6/6K4Z/PdT2ExE1q61B34w6LqfQrqTvrlq/NfSvDk7USxXvP3+rd7HQAUrynSsWx2MnNeYt23d34sSHLCKw==", - "requires": { - "@fluentui/date-time-utilities": "^7.9.1", - "@fluentui/react-focus": "^7.17.6", - "@fluentui/react-window-provider": "^1.0.2", - "@microsoft/load-themed-styles": "^1.10.26", - "@uifabric/foundation": "^7.9.26", - "@uifabric/icons": "^7.5.23", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/react-hooks": "^7.14.0", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "prop-types": "^15.7.2", - "tslib": "^1.10.0" - } - } - } - }, - "@uifabric/icons": { - "version": "7.5.23", - "resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.5.23.tgz", - "integrity": "sha512-eIvUbH0EWgFgdfgFfINgqS2ZVZTyJ/9n5nR4bmcyAe75wsKxm4ser4WIT9IvaBF6+HFVfjUF/v6+VMD7y2LBng==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "tslib": "^1.10.0" - }, - "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/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": { - "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/merge-styles": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/@uifabric/merge-styles/-/merge-styles-7.19.2.tgz", - "integrity": "sha512-kTlhwglDqwVgIaJq+0yXgzi65plGhmFcPrfme/rXUGMJZoU+qlGT5jXj5d3kuI59p6VB8jWEg9DAxHozhYeu0g==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "tslib": "^1.10.0" - } - }, - "@uifabric/react-cards": { - "version": "0.109.110", - "resolved": "https://registry.npmjs.org/@uifabric/react-cards/-/react-cards-0.109.110.tgz", - "integrity": "sha512-x/X0+u7uWr/fv98HxLzI9K0eC0LXnzGV4PjspnMqj48r7Bkbzm6qNorXxWQDeq9LPuhHCuf0DyHrIn+umvGG4Q==", - "requires": { - "@microsoft/load-themed-styles": "^1.10.26", - "@uifabric/azure-themes": "^7.1.37", - "@uifabric/file-type-icons": "^7.3.13", - "@uifabric/foundation": "^7.7.33", - "@uifabric/set-version": "^7.0.15", - "@uifabric/styling": "^7.13.7", - "@uifabric/theme-samples": "^7.0.102", - "@uifabric/utilities": "^7.23.0", - "office-ui-fabric-react": "^7.121.10", - "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/file-type-icons": { - "version": "7.6.30", - "resolved": "https://registry.npmjs.org/@uifabric/file-type-icons/-/file-type-icons-7.6.30.tgz", - "integrity": "sha512-NpZ5kKFxmT60EvHzKSD9XpKYDLBCJDZw3ae3WhW4k2z67RKo5yOZ76hzN9y3UWW3E0qMw9r2+zFF7JukjQYHtw==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.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/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": { - "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/set-version": { - "version": "7.0.24", - "resolved": "https://registry.npmjs.org/@uifabric/set-version/-/set-version-7.0.24.tgz", - "integrity": "sha512-t0Pt21dRqdC707/ConVJC0WvcQ/KF7tKLU8AZY7YdjgJpMHi1c0C427DB4jfUY19I92f60LOQyhJ4efH+KpFEg==", - "requires": { - "tslib": "^1.10.0" - } - }, - "@uifabric/theme-samples": { - "version": "7.2.34", - "resolved": "https://registry.npmjs.org/@uifabric/theme-samples/-/theme-samples-7.2.34.tgz", - "integrity": "sha512-olraurYMgXOA3ytPcm8oBcXj/eWZ04pAtpENkHQVujR6Ok4qqka1neVchTW7IBUgw+X+Rl/ampT7sqq77h2JYw==", - "requires": { - "@fluentui/theme": "^1.7.4", - "@uifabric/set-version": "^7.0.24", - "@uifabric/variants": "^7.2.35", - "office-ui-fabric-react": "^7.168.2", - "tslib": "^1.10.0" - }, - "dependencies": { - "@fluentui/react-focus": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.17.6.tgz", - "integrity": "sha512-JkLWNDe567lhvbnIhbYv9nUWYDIVN06utc3krs0UZBI+A0YZtQmftBtY0ghXo4PSjgozZocdu9sYkkgZOgyRLg==", - "requires": { - "@fluentui/keyboard-key": "^0.2.12", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@fluentui/react-window-provider": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.2.tgz", - "integrity": "sha512-fGSgL3Vp/+6t1Ysfz21FWZmqsU+iFVxOigvHnm5uKVyyRPwtaabv/F6kQ2y5isLMI2YmJaUd2i0cDJKu8ggrvw==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "tslib": "^1.10.0" - } - }, - "@fluentui/theme": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-1.7.4.tgz", - "integrity": "sha512-o4eo7lstLxxXl1g2RR9yz18Yt8yjQO/LbQuZjsiAfv/4Bf0CRnb+3j1F7gxIdBWAchKj9gzaMpIFijfI98pvYQ==", - "requires": { - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@uifabric/foundation": { - "version": "7.9.26", - "resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.9.26.tgz", - "integrity": "sha512-1FLTb+jlH/Tuel2L9wT/zLl5ZW6W4Lbjrs5VUVjv81vWxzznvPnTf8+Ew0qkzaH7xDuMNMl7okswhV0IfJyheg==", - "requires": { - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@uifabric/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": { - "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" - } - }, - "office-ui-fabric-react": { - "version": "7.168.2", - "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.168.2.tgz", - "integrity": "sha512-ssN6/6K4Z/PdT2ExE1q61B34w6LqfQrqTvrlq/NfSvDk7USxXvP3+rd7HQAUrynSsWx2MnNeYt23d34sSHLCKw==", - "requires": { - "@fluentui/date-time-utilities": "^7.9.1", - "@fluentui/react-focus": "^7.17.6", - "@fluentui/react-window-provider": "^1.0.2", - "@microsoft/load-themed-styles": "^1.10.26", - "@uifabric/foundation": "^7.9.26", - "@uifabric/icons": "^7.5.23", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/react-hooks": "^7.14.0", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "prop-types": "^15.7.2", - "tslib": "^1.10.0" - } - } - } - }, - "@uifabric/variants": { - "version": "7.2.35", - "resolved": "https://registry.npmjs.org/@uifabric/variants/-/variants-7.2.35.tgz", - "integrity": "sha512-pK9n0QM3wYuE/f7scOhkW0jsSx1BPU6RePeuMJcoriCgK6zeN66p5ySwh+UhjuEtoGaSfVxeFve02hWNTfO8vg==", - "requires": { - "@fluentui/theme": "^1.7.4", - "@uifabric/set-version": "^7.0.24", - "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/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" - } - } - } - }, "@ungap/url-search-params": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@ungap/url-search-params/-/url-search-params-0.2.2.tgz", @@ -7135,6 +6664,14 @@ "inherits": "^2.0.1", "minimalistic-assert": "^1.0.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": { @@ -7454,6 +6991,11 @@ "regenerator-runtime": "^0.11.0" }, "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": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", @@ -7644,9 +7186,9 @@ "dev": true }, "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", "dev": true }, "body-parser": { @@ -7861,14 +7403,6 @@ "requires": { "bn.js": "^5.0.0", "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": { @@ -7888,12 +7422,6 @@ "safe-buffer": "^5.2.0" }, "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": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -8002,9 +7530,9 @@ "dev": true }, "cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "version": "15.0.6", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.6.tgz", + "integrity": "sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w==", "requires": { "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", @@ -8020,7 +7548,7 @@ "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", - "ssri": "^8.0.0", + "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" }, @@ -8348,7 +7876,8 @@ "chownr": { "version": "1.1.4", "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": { "version": "1.0.2", @@ -8837,22 +8366,9 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "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": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", @@ -8889,6 +8405,14 @@ "requires": { "bn.js": "^4.1.0", "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": { @@ -10048,6 +9572,14 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.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": { @@ -10326,18 +9858,26 @@ "integrity": "sha512-vhGNxT87PdZA6Ak4E0QhArwGzNcSPUwSN7n9wCFLeBlY2NNuuiwguQuQIp7P5oB65PLJ892yKcHiqz1xLWeiug==" }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "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": { @@ -11106,9 +10646,9 @@ "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==" }, "eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", + "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", "dev": true, "requires": { "original": "^1.0.0" @@ -12377,9 +11917,9 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", - "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -19374,6 +18914,14 @@ "requires": { "bn.js": "^4.0.0", "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": { @@ -20446,109 +19994,6 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, - "office-ui-fabric-react": { - "version": "7.164.2", - "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.164.2.tgz", - "integrity": "sha512-qx8WbSXDJbcfq7MdJujUSAkiaYTQIQBkz4pb03r9LncwlrcnVx+thgKn8yztb2g0FlP0z5ONHv1wufrOHYxnHQ==", - "requires": { - "@fluentui/date-time-utilities": "^7.9.1", - "@fluentui/react-focus": "^7.17.6", - "@fluentui/react-window-provider": "^1.0.2", - "@microsoft/load-themed-styles": "^1.10.26", - "@uifabric/foundation": "^7.9.26", - "@uifabric/icons": "^7.5.23", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/react-hooks": "^7.13.12", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "prop-types": "^15.7.2", - "tslib": "^1.10.0" - }, - "dependencies": { - "@fluentui/react-focus": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-7.17.6.tgz", - "integrity": "sha512-JkLWNDe567lhvbnIhbYv9nUWYDIVN06utc3krs0UZBI+A0YZtQmftBtY0ghXo4PSjgozZocdu9sYkkgZOgyRLg==", - "requires": { - "@fluentui/keyboard-key": "^0.2.12", - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@fluentui/react-window-provider": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-1.0.2.tgz", - "integrity": "sha512-fGSgL3Vp/+6t1Ysfz21FWZmqsU+iFVxOigvHnm5uKVyyRPwtaabv/F6kQ2y5isLMI2YmJaUd2i0cDJKu8ggrvw==", - "requires": { - "@uifabric/set-version": "^7.0.24", - "tslib": "^1.10.0" - } - }, - "@fluentui/theme": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-1.7.4.tgz", - "integrity": "sha512-o4eo7lstLxxXl1g2RR9yz18Yt8yjQO/LbQuZjsiAfv/4Bf0CRnb+3j1F7gxIdBWAchKj9gzaMpIFijfI98pvYQ==", - "requires": { - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@uifabric/foundation": { - "version": "7.9.26", - "resolved": "https://registry.npmjs.org/@uifabric/foundation/-/foundation-7.9.26.tgz", - "integrity": "sha512-1FLTb+jlH/Tuel2L9wT/zLl5ZW6W4Lbjrs5VUVjv81vWxzznvPnTf8+Ew0qkzaH7xDuMNMl7okswhV0IfJyheg==", - "requires": { - "@uifabric/merge-styles": "^7.19.2", - "@uifabric/set-version": "^7.0.24", - "@uifabric/styling": "^7.19.0", - "@uifabric/utilities": "^7.33.5", - "tslib": "^1.10.0" - } - }, - "@uifabric/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": { - "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" - } - } - } - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -20693,7 +20138,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -21023,9 +20467,9 @@ } }, "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -21596,6 +21040,14 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "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": { @@ -25620,6 +25072,12 @@ "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": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", diff --git a/package.json b/package.json index d3973f815..540165904 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@azure/ms-rest-nodeauth": "3.0.7", "@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-decorators": "7.12.12", - "@fluentui/react": "8.10.1", + "@fluentui/react": "8.14.3", "@jupyterlab/services": "6.0.2", "@jupyterlab/terminal": "3.0.3", "@microsoft/applicationinsights-web": "2.6.1", @@ -43,7 +43,6 @@ "@testing-library/jest-dom": "5.11.9", "@types/mkdirp": "1.0.1", "@types/node-fetch": "2.5.7", - "@uifabric/react-cards": "0.109.110", "applicationinsights": "1.8.0", "bootstrap": "3.4.1", "canvas": "file:./canvas", diff --git a/src/Common/Tooltip/InfoTooltip.tsx b/src/Common/Tooltip/InfoTooltip.tsx new file mode 100644 index 000000000..c16e22ce8 --- /dev/null +++ b/src/Common/Tooltip/InfoTooltip.tsx @@ -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 ( + + + + + + ); +}; diff --git a/src/Common/Tooltip/Tooltip.tsx b/src/Common/Tooltip/Tooltip.tsx deleted file mode 100644 index ee7b69425..000000000 --- a/src/Common/Tooltip/Tooltip.tsx +++ /dev/null @@ -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 = { root: { display: "inline-block" } }; - -export interface TooltipProps { - children: string; -} -export const Tooltip: React.FunctionComponent = ({ children }: TooltipProps) => { - const tooltipId = useId("tooltip"); - - return children ? ( - - - More information - - - ) : ( - <> - ); -}; diff --git a/src/Common/Upload/Upload.tsx b/src/Common/Upload/Upload.tsx index 217469ee9..7116f3835 100644 --- a/src/Common/Upload/Upload.tsx +++ b/src/Common/Upload/Upload.tsx @@ -2,7 +2,7 @@ import { Image, Stack, TextField } from "@fluentui/react"; import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react"; import FolderIcon from "../../../images/folder_16x16.svg"; import * as Constants from "../Constants"; -import { Tooltip } from "../Tooltip/Tooltip"; +import { InfoTooltip } from "../Tooltip/InfoTooltip"; interface UploadProps { label: string; @@ -51,7 +51,7 @@ export const Upload: FunctionComponent = ({ return (
{label} - {tooltip} + {tooltip && {tooltip}} { expect(ko.components.isRegistered("json-editor")).toBe(true); }); - it("should registeradd-collection-pane component", () => { - expect(ko.components.isRegistered("add-collection-pane")).toBe(true); - }); - it("should register graph-styling-pane component", () => { expect(ko.components.isRegistered("graph-styling-pane")).toBe(true); }); diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index 0ec97eb49..ddf9bb991 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -19,7 +19,7 @@ ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); // Panes + ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); -ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); diff --git a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx index d212a344b..e520dabad 100644 --- a/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/Cards/GalleryCardComponent.tsx @@ -1,20 +1,23 @@ import { BaseButton, Button, - FontWeights, + DocumentCard, + DocumentCardActivity, + DocumentCardDetails, + DocumentCardPreview, + DocumentCardTitle, Icon, IconButton, - Image, + IDocumentCardPreviewProps, + IDocumentCardStyles, ImageFit, Link, - Persona, Separator, Spinner, SpinnerSize, Text, TooltipHost, } from "@fluentui/react"; -import { Card } from "@uifabric/react-cards"; import React, { FunctionComponent, useState } from "react"; import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg"; import { IGalleryItem } from "../../../../Juno/JunoClient"; @@ -48,7 +51,6 @@ export const GalleryCardComponent: FunctionComponent const CARD_WIDTH = 256; const cardImageHeight = 144; const cardDescriptionMaxChars = 80; - const cardItemGapBig = 10; const cardItemGapSmall = 8; const cardDeleteSpinnerHeight = 360; const smallTextLineHeight = 18; @@ -64,9 +66,9 @@ export const GalleryCardComponent: FunctionComponent const dateString = new Date(data.created).toLocaleString("default", options); const cardTitle = FileSystemUtil.stripExtension(data.name, "ipynb"); - const renderTruncatedDescription = (): string => { - let truncatedDescription = data.description.substr(0, cardDescriptionMaxChars); - if (data.description.length > cardDescriptionMaxChars) { + const renderTruncated = (text: string, totalLength: number): string => { + let truncatedDescription = text.substr(0, totalLength); + if (text.length > totalLength) { truncatedDescription = `${truncatedDescription} ...`; } return truncatedDescription; @@ -120,42 +122,35 @@ export const GalleryCardComponent: FunctionComponent event.preventDefault(); activate(); }; - + const DocumentCardActivityPeople = [{ name: data.author, profileImageSrc: data.isSample && CosmosDBLogo }]; + const previewProps: IDocumentCardPreviewProps = { + previewImages: [ + { + previewImageSrc: data.thumbnailUrl, + imageFit: ImageFit.cover, + width: CARD_WIDTH, + height: cardImageHeight, + }, + ], + }; + const cardStyles: IDocumentCardStyles = { + root: { display: "inline-block", marginRight: 20, width: CARD_WIDTH }, + }; return ( - handlerOnClick(event, onClick)} - > + {isDeletingPublishedNotebook && ( - - - + )} {!isDeletingPublishedNotebook && ( <> - - - - - - {`${cardTitle} - - - - + + + + {data.tags ? ( data.tags.map((tag, index, array) => ( @@ -167,43 +162,22 @@ export const GalleryCardComponent: FunctionComponent
)}
- - - {cardTitle} - - - - {renderTruncatedDescription()} - - - + + + {data.views !== undefined && generateIconText("RedEye", data.views.toString())} {data.downloads !== undefined && generateIconText("Download", data.downloads.toString())} {data.favorites !== undefined && generateIconText("Heart", data.favorites.toString())} -
- + {cardButtonsVisible && ( - + - + {isFavorite !== undefined && generateIconButtonWithTooltip( isFavorite ? "HeartFill" : "Heart", @@ -222,10 +196,10 @@ export const GalleryCardComponent: FunctionComponent ) )} - + )} )} -
+ ); }; diff --git a/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap b/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap index 17f8fd5cf..a4cdc6ef5 100644 --- a/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap +++ b/src/Explorer/Controls/NotebookGallery/Cards/__snapshots__/GalleryCardComponent.test.tsx.snap @@ -1,59 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`GalleryCardComponent renders 1`] = ` - - - - - - name cover image - - + /> + + - + + - name - - - description - - - - + + - + - - + + `; diff --git a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx index 37e96aaa7..f1733a141 100644 --- a/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookGallery/GalleryViewerComponent.tsx @@ -34,7 +34,6 @@ import { CodeOfConductComponent } from "./CodeOfConductComponent"; import "./GalleryViewerComponent.less"; import { InfoComponent } from "./InfoComponent/InfoComponent"; -const CARD_WIDTH = 256; export interface GalleryViewerComponentProps { container?: Explorer; junoClient: JunoClient; @@ -87,7 +86,7 @@ export class GalleryViewerComponent extends React.Component { if (itemIndex === 0) { - this.columnCount = Math.floor(visibleRect.width / CARD_WIDTH) || this.columnCount; + this.columnCount = Math.floor(visibleRect.width / GalleryViewerComponent.CARD_WIDTH) || this.columnCount; this.rowCount = GalleryViewerComponent.rowsPerPage; } @@ -672,7 +671,7 @@ export class GalleryViewerComponent extends React.Component +
); diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 671725d4f..9eb6d8b7e 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -75,89 +75,6 @@ exports[`SettingsComponent renders 1`] = ` "upsellMessageAriaLabel": [Function], "visible": [Function], }, - AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -226,89 +143,6 @@ exports[`SettingsComponent renders 1`] = ` ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], - "addCollectionPane": AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, "addCollectionText": [Function], "addDatabasePane": AddDatabasePane { "autoPilotUsageCost": [Function], @@ -1353,89 +1187,6 @@ exports[`SettingsComponent renders 1`] = ` "upsellMessageAriaLabel": [Function], "visible": [Function], }, - AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -1504,89 +1255,6 @@ exports[`SettingsComponent renders 1`] = ` ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], - "addCollectionPane": AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, "addCollectionText": [Function], "addDatabasePane": AddDatabasePane { "autoPilotUsageCost": [Function], @@ -2644,89 +2312,6 @@ exports[`SettingsComponent renders 1`] = ` "upsellMessageAriaLabel": [Function], "visible": [Function], }, - AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -2795,89 +2380,6 @@ exports[`SettingsComponent renders 1`] = ` ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], - "addCollectionPane": AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, "addCollectionText": [Function], "addDatabasePane": AddDatabasePane { "autoPilotUsageCost": [Function], @@ -3922,89 +3424,6 @@ exports[`SettingsComponent renders 1`] = ` "upsellMessageAriaLabel": [Function], "visible": [Function], }, - AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -4073,89 +3492,6 @@ exports[`SettingsComponent renders 1`] = ` ], "_refreshSparkEnabledStateForAccount": [Function], "_resetNotebookWorkspace": [Function], - "addCollectionPane": AddCollectionPane { - "_isSynapseLinkEnabled": [Function], - "autoPilotThroughput": [Function], - "autoPilotUsageCost": [Function], - "canConfigureThroughput": [Function], - "canExceedMaximumValue": [Function], - "canRequestSupport": [Function], - "collectionId": [Function], - "collectionIdTitle": [Function], - "collectionWithThroughputInShared": [Function], - "collectionWithThroughputInSharedTitle": [Function], - "container": [Circular], - "costsVisible": [Function], - "databaseCreateNew": [Function], - "databaseCreateNewShared": [Function], - "databaseHasSharedOffer": [Function], - "databaseId": [Function], - "databaseIds": [Function], - "dedicatedRequestUnitsUsageCost": [Function], - "displayCollectionThroughput": [Function], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "formWarnings": [Function], - "freeTierExceedThroughputTooltip": [Function], - "id": "addcollectionpane", - "isAnalyticalStorageOn": [Function], - "isAutoPilotSelected": [Function], - "isEnableMongoCapabilityEnabled": [Function], - "isExecuting": [Function], - "isFixedStorageSelected": [Function], - "isFreeTierAccount": [Function], - "isNonTableApi": [Function], - "isPreferredApiTable": [Function], - "isSharedAutoPilotSelected": [Function], - "isSynapseLinkSupported": [Function], - "isSynapseLinkUpdating": [Function], - "isTemplateReady": [Function], - "isTryCosmosDBSubscription": [Function], - "isUnlimitedStorageSelected": [Function], - "largePartitionKey": [Function], - "lowerCasePartitionKeyName": [Function], - "maxCollectionsReached": [Function], - "maxCollectionsReachedMessage": [Function], - "maxThroughputRU": [Function], - "minThroughputRU": [Function], - "onMoreDetailsKeyPress": [Function], - "partitionKey": [Function], - "partitionKeyName": [Function], - "partitionKeyPattern": [Function], - "partitionKeyPlaceholder": [Function], - "partitionKeyTitle": [Function], - "partitionKeyVisible": [Function], - "requestUnitsUsageCost": [Function], - "ruToolTipText": [Function], - "sharedAutoPilotThroughput": [Function], - "sharedThroughputRangeText": [Function], - "shouldCreateMongoWildcardIndex": [Function], - "shouldUseDatabaseThroughput": [Function], - "showAnalyticalStore": [Function], - "showEnableSynapseLink": [Function], - "showIndexingOptionsForSharedThroughput": [Function], - "showUpsellMessage": [Function], - "storage": [Function], - "throughputDatabase": [Function], - "throughputMultiPartition": [Function], - "throughputRangeText": [Function], - "throughputSinglePartition": [Function], - "throughputSpendAck": [Function], - "throughputSpendAckText": [Function], - "throughputSpendAckVisible": [Function], - "title": [Function], - "ttl90DaysEnabled": [Function], - "uniqueKeys": [Function], - "uniqueKeysPlaceholder": [Function], - "uniqueKeysVisible": [Function], - "upsellAnchorText": [Function], - "upsellAnchorUrl": [Function], - "upsellMessage": [Function], - "upsellMessageAriaLabel": [Function], - "useIndexingForSharedThroughput": [Function], - "visible": [Function], - }, "addCollectionText": [Function], "addDatabasePane": AddDatabasePane { "autoPilotUsageCost": [Function], diff --git a/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.test.tsx b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.test.tsx new file mode 100644 index 000000000..6c2c3875f --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.test.tsx @@ -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(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx new file mode 100644 index 000000000..51aaae619 --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx @@ -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 = ({ + 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 = PricingUtils.estimatedCostDisclaimer; + + if (isAutoscale) { + return ( + + Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "} + + {currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "} + {currencySign + calculateEstimateNumber(monthlyPrice)}{" "} + + ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "} + RU/s, {currencySign + pricePerRu}/RU) + + ); + } + + return ( + + Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "} + + {currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "} + {currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "} + {currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "} + + ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "} + {currencySign + pricePerRu}/RU) + + ); +}; diff --git a/src/Explorer/Controls/ThroughputInput/CostEstimateText/__snapshots__/CostEstimateText.test.tsx.snap b/src/Explorer/Controls/ThroughputInput/CostEstimateText/__snapshots__/CostEstimateText.test.tsx.snap new file mode 100644 index 000000000..cd4efbdc6 --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/CostEstimateText/__snapshots__/CostEstimateText.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CostEstimateText Pane should render Default properly 1`] = ``; diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx new file mode 100644 index 000000000..7b59ae003 --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx @@ -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(); + }); + + 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"); + }); +}); diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 323cea83b..30971dc84 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -1,11 +1,14 @@ -import { Checkbox, DirectionalHint, Icon, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react"; -import React from "react"; +import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react"; +import React, { FunctionComponent, useState } from "react"; import * as Constants from "../../../Common/Constants"; +import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import * as SharedConstants from "../../../Shared/Constants"; import { userContext } from "../../../UserContext"; import { getCollectionName } from "../../../Utils/APITypeUtils"; import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; import * as PricingUtils from "../../../Utils/PricingUtils"; +import { CostEstimateText } from "./CostEstimateText/CostEstimateText"; +import "./ThroughputInput.less"; export interface ThroughputInputProps { isDatabase: boolean; @@ -14,176 +17,25 @@ export interface ThroughputInputProps { setThroughputValue: (throughput: number) => void; setIsAutoscale: (isAutoscale: boolean) => void; onCostAcknowledgeChange: (isAcknowledged: boolean) => void; + isAutoscaleSelected?: boolean; + throughput?: number; } -export interface ThroughputInputState { - isAutoscaleSelected: boolean; - throughput: number; - isCostAcknowledged: boolean; - throughputError: string; -} - -export class ThroughputInput extends React.Component { - constructor(props: ThroughputInputProps) { - super(props); - - this.state = { - isAutoscaleSelected: true, - throughput: AutoPilotUtils.minAutoPilotThroughput, - isCostAcknowledged: false, - throughputError: undefined, - }; - - this.props.setThroughputValue(AutoPilotUtils.minAutoPilotThroughput); - this.props.setIsAutoscale(true); - } - - render(): JSX.Element { - return ( -
- - - - {this.getThroughputLabelText()} - - - - - - - - - Autoscale - - - Manual - - - {this.state.isAutoscaleSelected && ( - - - Estimate your required RU/s with  - - capacity calculator - - . - - - - - {this.props.isDatabase ? "Database" : getCollectionName()} max RU/s - - - - - - - this.onThroughputValueChange(newInput)} - step={AutoPilotUtils.autoPilotIncrementStep} - min={AutoPilotUtils.minAutoPilotThroughput} - value={this.state.throughput.toString()} - aria-label="Max request units per second" - errorMessage={this.state.throughputError} - /> - - - Your {this.props.isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will - automatically scale from{" "} - - {AutoPilotUtils.getMinRUsBasedOnUserInput(this.state.throughput)} RU/s (10% of max RU/s) -{" "} - {this.state.throughput} RU/s - {" "} - based on usage. - - - )} - - {!this.state.isAutoscaleSelected && ( - - - Estimate your required RU/s with  - - capacity calculator - - . - - - SharedConstants.CollectionCreation.DefaultCollectionRUs400 - ? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s." - : undefined - } - > - this.onThroughputValueChange(newInput)} - step={100} - min={SharedConstants.CollectionCreation.DefaultCollectionRUs400} - max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity} - value={this.state.throughput.toString()} - aria-label="Max request units per second" - required={true} - errorMessage={this.state.throughputError} - /> - - - )} - - - - {this.state.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && ( - - - , isChecked: boolean) => { - this.setState({ isCostAcknowledged: isChecked }); - this.props.onCostAcknowledgeChange(isChecked); - }} - /> - - {this.getCostAcknowledgeText()} - - - )} -
- ); - } - - private getThroughputLabelText(): string { +export const ThroughputInput: FunctionComponent = ({ + isDatabase, + showFreeTierExceedThroughputTooltip, + setThroughputValue, + setIsAutoscale, + isSharded, + isAutoscaleSelected = true, + throughput = AutoPilotUtils.minAutoPilotThroughput, + onCostAcknowledgeChange, +}: ThroughputInputProps) => { + const [isCostAcknowledged, setIsCostAcknowledged] = useState(false); + const [throughputError, setThroughputError] = useState(""); + const getThroughputLabelText = (): string => { let throughputHeaderText: string; - if (this.state.isAutoscaleSelected) { + if (isAutoscaleSelected) { throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase(); } else { const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString(); @@ -192,29 +44,26 @@ export class ThroughputInput extends React.Component { 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" }); + setThroughputValue(newThroughput); + if (!isSharded && newThroughput > 10000) { + setThroughputError("Unsharded collections support up to 10,000 RUs"); } else { - this.setState({ throughputError: undefined }); + setThroughputError(""); } - } + }; - private getAutoScaleTooltip(): string { + 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.`; - } + }; - private getCostAcknowledgeText(): string { - const { databaseAccount } = userContext; + const getCostAcknowledgeText = (): string => { + const databaseAccount = userContext.databaseAccount; if (!databaseAccount || !databaseAccount.properties) { return ""; } @@ -223,98 +72,157 @@ export class ThroughputInput extends React.Component): void { - if (event.target.checked && !this.state.isAutoscaleSelected) { - this.setState({ isAutoscaleSelected: true, throughput: AutoPilotUtils.minAutoPilotThroughput }); - this.props.setIsAutoscale(true); + const handleOnChangeMode = (event: React.ChangeEvent, mode: string): void => { + if (mode === "Autoscale") { + setThroughputValue(AutoPilotUtils.minAutoPilotThroughput); + setIsAutoscale(true); + } else { + setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400); + setIsAutoscale(false); } - } - - private onManualRadioBtnChange(event: React.ChangeEvent): 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 = (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 = ( - - - - ); - - if (isAutoscale) { - return ( - - Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "} - - {currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice / 10)} -{" "} - {currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)}{" "} - - ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "} - RU/s, {currencySign + pricePerRu}/RU) - - ); - } + }; return ( - - Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "} - - {currencySign + PricingUtils.calculateEstimateNumber(hourlyPrice)} hourly /{" "} - {currencySign + PricingUtils.calculateEstimateNumber(dailyPrice)} daily /{" "} - {currencySign + PricingUtils.calculateEstimateNumber(monthlyPrice)} monthly{" "} - - ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "} - {currencySign + pricePerRu}/RU) - +
+ + + + {getThroughputLabelText()} + + {PricingUtils.getRuToolTipText()} + + + + handleOnChangeMode(e, "Autoscale")} + /> + Autoscale + + handleOnChangeMode(e, "Manual")} + /> + Manual + + + {isAutoscaleSelected && ( + + + Estimate your required RU/s with{" "} + + capacity calculator + + . + + + + + {isDatabase ? "Database" : getCollectionName()} Max RU/s + + {getAutoScaleTooltip()} + + + onThroughputValueChange(newInput)} + step={AutoPilotUtils.autoPilotIncrementStep} + min={AutoPilotUtils.minAutoPilotThroughput} + value={throughput.toString()} + aria-label="Max request units per second" + required={true} + errorMessage={throughputError} + /> + + + Your {isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will automatically scale + from{" "} + + {AutoPilotUtils.getMinRUsBasedOnUserInput(throughput)} RU/s (10% of max RU/s) - {throughput} RU/s + {" "} + based on usage. + + + )} + + {!isAutoscaleSelected && ( + + + Estimate your required RU/s with  + + capacity calculator + + . + + + SharedConstants.CollectionCreation.DefaultCollectionRUs400 + ? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s." + : undefined + } + > + onThroughputValueChange(newInput)} + step={100} + min={SharedConstants.CollectionCreation.DefaultCollectionRUs400} + max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity} + value={throughput.toString()} + aria-label="Max request units per second" + required={true} + errorMessage={throughputError} + /> + + + )} + + + + {throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && ( + + , isChecked: boolean) => { + setIsCostAcknowledged(isChecked); + onCostAcknowledgeChange(isChecked); + }} + /> + + {getCostAcknowledgeText()} + + + )} +
); }; diff --git a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap new file mode 100644 index 000000000..a564978b4 --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap @@ -0,0 +1,1989 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ThroughputInput Pane should render Default properly 1`] = ` + +
+ +
+ + *  + + + + Container throughput (autoscale) + + + + + + +
+ + + +  + + + +
+
+
+
+
+
+
+ +
+ + + Autoscale + + + + Manual + +
+
+ +
+ + + Estimate your required RU/s with + + + + + capacity calculator + + + + . + + + +
+ + + Container + Max RU/s + + + + + + +
+ + + +  + + + +
+
+
+
+
+
+
+ + +
+
+
+ +
+
+
+
+
+ + + Your + container + throughput will automatically scale from + + + 400 + RU/s (10% of max RU/s) - + 4000 + RU/s + + + based on usage. + + +
+
+ +
+
+`; diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 223ab657c..fe73a7a49 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -31,7 +31,7 @@ import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants" import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import { ArcadiaResourceManager } from "../SparkClusterManager/ArcadiaResourceManager"; import { updateUserContext, userContext } from "../UserContext"; -import { getCollectionName } from "../Utils/APITypeUtils"; +import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { stringToBlob } from "../Utils/BlobUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; @@ -49,9 +49,9 @@ import { NotebookContentItem, NotebookContentItemType } from "./Notebook/Noteboo import type NotebookManager from "./Notebook/NotebookManager"; import type { NotebookPaneContent } from "./Notebook/NotebookManager"; import { NotebookUtil } from "./Notebook/NotebookUtil"; -import AddCollectionPane from "./Panes/AddCollectionPane"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import AddDatabasePane from "./Panes/AddDatabasePane"; +import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane"; import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane"; import { ContextualPaneBase } from "./Panes/ContextualPaneBase"; @@ -150,7 +150,6 @@ export default class Explorer { // Contextual panes public addDatabasePane: AddDatabasePane; - public addCollectionPane: AddCollectionPane; public graphStylingPane: GraphStylingPane; public cassandraAddCollectionPane: CassandraAddCollectionPane; private gitHubClient: GitHubClient; @@ -412,14 +411,6 @@ export default class Explorer { container: this, }); - this.addCollectionPane = new AddCollectionPane({ - isPreferredApiTable: ko.computed(() => userContext.apiType === "Tables"), - id: "addcollectionpane", - visible: ko.observable(false), - - container: this, - }); - this.graphStylingPane = new GraphStylingPane({ id: "graphstylingpane", visible: ko.observable(false), @@ -442,12 +433,7 @@ export default class Explorer { } }); - this._panes = [ - this.addDatabasePane, - this.addCollectionPane, - this.graphStylingPane, - this.cassandraAddCollectionPane, - ]; + this._panes = [this.addDatabasePane, this.graphStylingPane, this.cassandraAddCollectionPane]; this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); this.isTabsContentExpanded = ko.observable(false); @@ -471,11 +457,6 @@ export default class Explorer { this.collectionTreeNodeAltText("Container"); this.deleteCollectionText("Delete Container"); this.deleteDatabaseText("Delete Database"); - this.addCollectionPane.title("Add Container"); - this.addCollectionPane.collectionIdTitle("Container id"); - this.addCollectionPane.collectionWithThroughputInSharedTitle( - "Provision dedicated throughput for this container" - ); this.refreshTreeTitle("Refresh containers"); break; case "Mongo": @@ -485,11 +466,6 @@ export default class Explorer { this.collectionTreeNodeAltText("Collection"); this.deleteCollectionText("Delete Collection"); this.deleteDatabaseText("Delete Database"); - this.addCollectionPane.title("Add Collection"); - this.addCollectionPane.collectionIdTitle("Collection id"); - this.addCollectionPane.collectionWithThroughputInSharedTitle( - "Provision dedicated throughput for this collection" - ); this.refreshTreeTitle("Refresh collections"); break; case "Gremlin": @@ -499,9 +475,6 @@ export default class Explorer { this.deleteDatabaseText("Delete Database"); this.collectionTitle("Gremlin API"); this.collectionTreeNodeAltText("Graph"); - this.addCollectionPane.title("Add Graph"); - this.addCollectionPane.collectionIdTitle("Graph id"); - this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph"); this.refreshTreeTitle("Refresh graphs"); break; case "Tables": @@ -511,9 +484,6 @@ export default class Explorer { this.deleteDatabaseText("Delete Database"); this.collectionTitle("Azure Table API"); this.collectionTreeNodeAltText("Table"); - this.addCollectionPane.title("Add Table"); - this.addCollectionPane.collectionIdTitle("Table id"); - this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table"); this.refreshTreeTitle("Refresh tables"); this.tableDataClient = new TablesAPIDataClient(); break; @@ -524,9 +494,6 @@ export default class Explorer { this.deleteDatabaseText("Delete Keyspace"); this.collectionTitle("Cassandra API"); this.collectionTreeNodeAltText("Table"); - this.addCollectionPane.title("Add Table"); - this.addCollectionPane.collectionIdTitle("Table id"); - this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table"); this.refreshTreeTitle("Refresh tables"); this.tableDataClient = new CassandraAPIDataClient(); break; @@ -1845,9 +1812,6 @@ export default class Explorer { public onNewCollectionClicked(databaseId?: string): void { if (userContext.apiType === "Cassandra") { this.cassandraAddCollectionPane.open(); - } else if (userContext.features.enableKOPanel) { - this.addCollectionPane.open(this.selectedDatabaseId()); - document.getElementById("linkAddCollection").focus(); } else { this.openAddCollectionPanel(databaseId); } @@ -1961,7 +1925,7 @@ export default class Explorer { public openDeleteDatabaseConfirmationPane(): void { this.openSidePanel( - "Delete Database", + "Delete " + getDatabaseName(), ); + this.openSidePanel("Upload " + getUploadName(), ); } public openSettingPane(): void { this.openSidePanel( - "Settings", + "Setting", this.expandConsole()} closePanel={this.closeSidePanel} /> ); } @@ -2005,6 +1969,21 @@ export default class Explorer { /> ); } + public openAddDatabasePane(): void { + if (userContext.features.enableKOPanel) { + this.addDatabasePane.open(); + document.getElementById("linkAddDatabase").focus(); + } else { + this.openSidePanel( + "Add " + getDatabaseName(), + + ); + } + } public openBrowseQueriesPanel(): void { this.openSidePanel("Open Saved Queries", ); @@ -2021,7 +2000,7 @@ export default class Explorer { public openUploadFilePanel(parent?: NotebookContentItem): void { parent = parent || this.resourceTree.myNotebooksContentRoot; this.openSidePanel( - "Upload File", + "Upload file to notebook server", this.expandConsole()} closePanel={this.closeSidePanel} diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 8f0715988..b5040d8a6 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -266,8 +266,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps { iconSrc: AddDatabaseIcon, iconAlt: label, onCommandClick: () => { - container.addDatabasePane.open(); - document.getElementById("linkAddDatabase").focus(); + container.openAddDatabasePane(); }, commandButtonLabel: label, ariaLabel: label, diff --git a/src/Explorer/Panes/AddCollectionPane.html b/src/Explorer/Panes/AddCollectionPane.html deleted file mode 100644 index 2d0cfe8ba..000000000 --- a/src/Explorer/Panes/AddCollectionPane.html +++ /dev/null @@ -1,602 +0,0 @@ -
-
-
- -
-
- -
-
- - -
- -
- -
-
- - diff --git a/src/Explorer/Panes/AddCollectionPane.test.ts b/src/Explorer/Panes/AddCollectionPane.test.ts deleted file mode 100644 index 3e36abe1f..000000000 --- a/src/Explorer/Panes/AddCollectionPane.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as Constants from "../../Common/Constants"; -import { DatabaseAccount } from "../../Contracts/DataModels"; -import { updateUserContext } from "../../UserContext"; -import Explorer from "../Explorer"; -import AddCollectionPane from "./AddCollectionPane"; - -const mockDatabaseAccount: DatabaseAccount = { - id: "mock", - kind: "DocumentDB", - location: "", - name: "mock", - properties: { - documentEndpoint: "", - cassandraEndpoint: "", - gremlinEndpoint: "", - tableEndpoint: "", - enableFreeTier: false, - }, - type: undefined, -}; - -const mockFreeTierDatabaseAccount: DatabaseAccount = { - id: "mock", - kind: "DocumentDB", - location: "", - name: "mock", - properties: { - documentEndpoint: "", - cassandraEndpoint: "", - gremlinEndpoint: "", - tableEndpoint: "", - enableFreeTier: true, - }, - type: undefined, -}; - -describe("Add Collection Pane", () => { - describe("isValid()", () => { - it("should be true if graph API and partition key is not /id nor /label", () => { - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableGremlin" }], - }, - } as DatabaseAccount, - }); - const explorer = new Explorer(); - const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; - addCollectionPane.partitionKey("/blah"); - expect(addCollectionPane.isValid()).toBe(true); - }); - - it("should be false if graph API and partition key is /id or /label", () => { - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableGremlin" }], - }, - } as DatabaseAccount, - }); - const explorer = new Explorer(); - const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; - addCollectionPane.partitionKey("/id"); - expect(addCollectionPane.isValid()).toBe(false); - - addCollectionPane.partitionKey("/label"); - expect(addCollectionPane.isValid()).toBe(false); - }); - - it("should be true for any non-graph API with /id or /label partition key", () => { - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableCassandra" }], - }, - } as DatabaseAccount, - }); - const explorer = new Explorer(); - const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; - - addCollectionPane.partitionKey("/id"); - expect(addCollectionPane.isValid()).toBe(true); - - addCollectionPane.partitionKey("/label"); - expect(addCollectionPane.isValid()).toBe(true); - }); - - it("should display free tier text in upsell messaging", () => { - updateUserContext({ databaseAccount: mockFreeTierDatabaseAccount }); - const explorer = new Explorer(); - const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; - expect(addCollectionPane.isFreeTierAccount()).toBe(true); - expect(addCollectionPane.upsellMessage()).toContain("With free tier"); - expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation); - expect(addCollectionPane.upsellAnchorText()).toBe("Learn more"); - }); - - it("should display standard texr in upsell messaging", () => { - updateUserContext({ databaseAccount: mockDatabaseAccount }); - const explorer = new Explorer(); - const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; - expect(addCollectionPane.isFreeTierAccount()).toBe(false); - expect(addCollectionPane.upsellMessage()).toContain("Start at"); - expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing); - expect(addCollectionPane.upsellAnchorText()).toBe("More details"); - }); - }); -}); diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts deleted file mode 100644 index 17aef98fa..000000000 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ /dev/null @@ -1,1201 +0,0 @@ -import * as ko from "knockout"; -import * as _ from "underscore"; -import * as Constants from "../../Common/Constants"; -import { createCollection } from "../../Common/dataAccess/createCollection"; -import editable from "../../Common/EditableUtility"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; -import { configContext, Platform } from "../../ConfigContext"; -import * as DataModels from "../../Contracts/DataModels"; -import { SubscriptionType } from "../../Contracts/SubscriptionType"; -import * as ViewModels from "../../Contracts/ViewModels"; -import * as AddCollectionUtility from "../../Shared/AddCollectionUtility"; -import * as SharedConstants from "../../Shared/Constants"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { userContext } from "../../UserContext"; -import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; -import { isCapabilityEnabled } from "../../Utils/CapabilityUtils"; -import * as PricingUtils from "../../Utils/PricingUtils"; -import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent"; -import { ContextualPaneBase } from "./ContextualPaneBase"; - -export interface AddCollectionPaneOptions extends ViewModels.PaneOptions { - isPreferredApiTable: ko.Computed; - databaseId?: string; - databaseSelfLink?: string; -} - -export default class AddCollectionPane extends ContextualPaneBase { - public defaultExperience: ko.Computed; - public databaseIds: ko.ObservableArray; - public collectionId: ko.Observable; - public collectionIdTitle: ko.Observable; - public databaseId: ko.Observable; - public databaseCreateNew: ko.Observable; - public collectionWithThroughputInSharedTitle: ko.Observable; - public collectionWithThroughputInShared: ko.Observable; - public databaseCreateNewShared: ko.Observable; - public databaseHasSharedOffer: ko.Observable; - public formErrorsDetails: ko.Observable; - public formWarnings: ko.Observable; - public partitionKey: ko.Observable; - public partitionKeyName: ko.Computed; - public lowerCasePartitionKeyName: ko.Computed; - public partitionKeyVisible: ko.Computed; - public partitionKeyPattern: ko.Computed; - public partitionKeyTitle: ko.Computed; - public storage: ko.Observable; - public throughputSinglePartition: ViewModels.Editable; - public throughputMultiPartition: ViewModels.Editable; - public throughputDatabase: ViewModels.Editable; - public isPreferredApiTable: ko.Computed; - public partitionKeyPlaceholder: ko.Computed; - public isTryCosmosDBSubscription: ko.Observable; - public maxThroughputRU: ko.Observable; - public minThroughputRU: ko.Observable; - public throughputRangeText: ko.Computed; - public sharedThroughputRangeText: ko.Computed; - public throughputSpendAckText: ko.Observable; - public throughputSpendAck: ko.Observable; - public throughputSpendAckVisible: ko.Computed; - public maxCollectionsReached: ko.Computed; - public maxCollectionsReachedMessage: ko.Observable; - public requestUnitsUsageCost: ko.Computed; - public dedicatedRequestUnitsUsageCost: ko.Computed; - public canRequestSupport: ko.PureComputed; - public largePartitionKey: ko.Observable = ko.observable(false); - public useIndexingForSharedThroughput: ko.Observable = ko.observable(true); - public costsVisible: ko.PureComputed; - public uniqueKeysVisible: ko.Computed; - public uniqueKeys: ko.ObservableArray; - public uniqueKeysPlaceholder: ko.Computed; - public upsellMessage: ko.PureComputed; - public upsellMessageAriaLabel: ko.PureComputed; - public upsellAnchorUrl: ko.PureComputed; - public upsellAnchorText: ko.PureComputed; - public debugstring: ko.Computed; - public displayCollectionThroughput: ko.Computed; - public isAutoPilotSelected: ko.Observable; - public isSharedAutoPilotSelected: ko.Observable; - public autoPilotThroughput: ko.Observable; - public sharedAutoPilotThroughput: ko.Observable; - public autoPilotUsageCost: ko.Computed; - public shouldUseDatabaseThroughput: ko.Computed; - public isFreeTierAccount: ko.Computed; - public showIndexingOptionsForSharedThroughput: ko.Computed; - public showAnalyticalStore: ko.Computed; - public showEnableSynapseLink: ko.Computed; - public isSynapseLinkSupported: ko.Computed; - public isAnalyticalStorageOn: ko.Observable; - public isSynapseLinkUpdating: ko.Computed; - public canExceedMaximumValue: ko.PureComputed; - public ruToolTipText: ko.Computed; - public freeTierExceedThroughputTooltip: ko.Computed; - public canConfigureThroughput: ko.PureComputed; - public showUpsellMessage: ko.PureComputed; - public shouldCreateMongoWildcardIndex: ko.Observable; - - private _isSynapseLinkEnabled: ko.Computed; - private isEnableMongoCapabilityEnabled: ko.Observable; - - constructor(options: AddCollectionPaneOptions) { - super(options); - this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); - this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); - this.formWarnings = ko.observable(); - this.collectionId = ko.observable(); - this.databaseId = ko.observable(); - this.databaseCreateNew = ko.observable(true); - this.databaseCreateNewShared = ko.observable(this.getSharedThroughputDefault()); - this.collectionWithThroughputInShared = ko.observable(false); - this.databaseIds = ko.observableArray(); - this.uniqueKeys = ko.observableArray(); - this.isSharedAutoPilotSelected = ko.observable(); - this.isAutoPilotSelected = ko.observable(); - - if (this.container) { - this.container.databases.subscribe((newDatabases: ViewModels.Database[]) => { - this._onDatabasesChange(newDatabases); - }); - this._onDatabasesChange(this.container.databases()); - } - - this.isPreferredApiTable = options.isPreferredApiTable; - this.partitionKey = ko.observable(); - this.partitionKey.subscribe((newPartitionKey: string) => { - if (userContext.apiType === "Mongo" || !newPartitionKey || newPartitionKey[0] === "/") { - return; - } - - this.partitionKey(`/${newPartitionKey}`); - }); - this.partitionKey.extend({ rateLimit: 100 }); - this.partitionKeyPattern = ko.pureComputed(() => { - if (userContext.apiType === "Gremlin") { - return "^/[^/]*"; - } - return ".*"; - }); - this.partitionKeyTitle = ko.pureComputed(() => { - if (userContext.apiType === "Gremlin") { - return "May not use composite partition key"; - } - return ""; - }); - - this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); - - this.storage = ko.observable(); - this.throughputSinglePartition = editable.observable(); - this.throughputMultiPartition = editable.observable(); - this.throughputDatabase = editable.observable(); - this.collectionIdTitle = ko.observable(); - this.collectionWithThroughputInSharedTitle = ko.observable(); - this.maxThroughputRU = ko.observable(); - this.minThroughputRU = ko.observable(); - this.throughputSpendAckText = ko.observable(); - this.throughputSpendAck = ko.observable(false); - this.maxCollectionsReachedMessage = ko.observable(); - this.databaseHasSharedOffer = ko.observable(true); - this.throughputRangeText = ko.pureComputed(() => { - if (this.isAutoPilotSelected()) { - return AutoPilotUtils.getAutoPilotHeaderText(); - } - return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; - }); - this.sharedThroughputRangeText = ko.pureComputed(() => { - if (this.isSharedAutoPilotSelected()) { - return AutoPilotUtils.getAutoPilotHeaderText(); - } - return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; - }); - - this.databaseId(options.databaseId); - - this.requestUnitsUsageCost = ko.computed(() => { - const offerThroughput: number = this._getThroughput(); - if ( - offerThroughput < this.minThroughputRU() || - (offerThroughput > this.maxThroughputRU() && !this.canExceedMaximumValue()) - ) { - return ""; - } - - const { databaseAccount: account } = userContext; - if (!account) { - return ""; - } - - const regions = - (account && - account.properties && - account.properties.readLocations && - account.properties.readLocations.length) || - 1; - const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; - - let throughputSpendAckText: string; - let estimatedSpend: string; - if (!this.isSharedAutoPilotSelected()) { - throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( - offerThroughput, - userContext.portalEnv, - regions, - multimaster, - this.isSharedAutoPilotSelected() - ); - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - offerThroughput, - userContext.portalEnv, - regions, - multimaster - ); - } else { - throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( - this.sharedAutoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster, - this.isSharedAutoPilotSelected() - ); - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.sharedAutoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster - ); - } - // TODO: change throughputSpendAckText to be a computed value, instead of having this side effect - this.throughputSpendAckText(throughputSpendAckText); - return estimatedSpend; - }); - - this.dedicatedRequestUnitsUsageCost = ko.computed(() => { - const offerThroughput: number = this._getThroughput(); - if ( - offerThroughput < this.minThroughputRU() || - (offerThroughput > this.maxThroughputRU() && !this.canExceedMaximumValue()) - ) { - return ""; - } - - const { databaseAccount: account } = userContext; - if (!account) { - return ""; - } - - const regions = - (account && - account.properties && - account.properties.readLocations && - account.properties.readLocations.length) || - 1; - const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; - - let throughputSpendAckText: string; - let estimatedSpend: string; - if (!this.isAutoPilotSelected()) { - throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( - this.throughputMultiPartition(), - userContext.portalEnv, - regions, - multimaster, - this.isAutoPilotSelected() - ); - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - this.throughputMultiPartition(), - userContext.portalEnv, - regions, - multimaster - ); - } else { - throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( - this.autoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster, - this.isAutoPilotSelected() - ); - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.autoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster - ); - } - // TODO: change throughputSpendAckText to be a computed value, instead of having this side effect - this.throughputSpendAckText(throughputSpendAckText); - return estimatedSpend; - }); - - this.isTryCosmosDBSubscription = ko.observable(userContext.isTryCosmosDBSubscription || false); - - this.isTryCosmosDBSubscription.subscribe((isTryCosmosDB: boolean) => { - if (!!isTryCosmosDB) { - this.resetData(); - } - }); - - this.canRequestSupport = ko.pureComputed(() => { - if ( - configContext.platform !== Platform.Emulator && - !userContext.isTryCosmosDBSubscription && - configContext.platform !== Platform.Portal - ) { - const offerThroughput: number = this._getThroughput(); - return offerThroughput <= 100000; - } - - return false; - }); - - this.costsVisible = ko.pureComputed(() => { - return configContext.platform !== Platform.Emulator; - }); - - this.maxCollectionsReached = ko.computed(() => { - if (!this.isTryCosmosDBSubscription()) { - return false; - } - - const currentCollections = this.container - .databases() - .map((db: ViewModels.Database) => { - if (db.collections() && "length" in db.collections()) { - return db.collections().length; - } - - return 0; - }) - .reduce((totalCollections: number, collections: number) => { - return totalCollections + collections; - }, 0); - - const maxCollections = Constants.TryCosmosExperience.collectionsPerAccount; - - if (currentCollections >= maxCollections) { - let typeOfContainer = "collection"; - if (userContext.apiType === "Gremlin" || userContext.apiType === "Tables") { - typeOfContainer = "container"; - } - - this.maxCollectionsReachedMessage( - `You cannot create more than ${maxCollections} ${typeOfContainer}(s) during the Try Cosmos DB trial period.` - ); - return true; - } - - return false; - }); - - this.storage.subscribe(() => { - if (this.isFixedStorageSelected()) { - this.isAutoPilotSelected(false); - this.partitionKey(""); - } - this._updateThroughputLimitByStorage(); - }); - - // TODO: Create derived classes for Tables and Mongo to replace the If statements below - this.partitionKeyName = ko.computed(() => { - if (userContext.apiType === "Mongo") { - return "Shard key"; - } - - return "Partition key"; - }); - - this.lowerCasePartitionKeyName = ko.computed(() => this.partitionKeyName().toLowerCase()); - - this.partitionKeyPlaceholder = ko.computed(() => { - if (userContext.apiType === "Mongo") { - return "e.g., address.zipCode"; - } - - if (userContext.apiType === "Gremlin") { - return "e.g., /address"; - } - - return "e.g., /address/zipCode"; - }); - - this.uniqueKeysPlaceholder = ko.pureComputed(() => { - if (userContext.apiType === "Mongo") { - return "Comma separated paths e.g. firstName,address.zipCode"; - } - - return "Comma separated paths e.g. /firstName,/address/zipCode"; - }); - - this.uniqueKeysVisible = ko.pureComputed(() => { - if (userContext.apiType === "SQL") { - return true; - } - - return false; - }); - - this.partitionKeyVisible = ko.computed(() => { - if (this.container == null || userContext.apiType === "Tables") { - return false; - } - - if (userContext.apiType === "Mongo" && !this.isUnlimitedStorageSelected() && this.databaseHasSharedOffer()) { - return false; - } - - if (!this.isUnlimitedStorageSelected() && !this.databaseHasSharedOffer()) { - return false; - } - - return true; - }); - - this.throughputSpendAckVisible = ko.pureComputed(() => { - const autoscaleThroughput = this.autoPilotThroughput() * 1; - if (this.isAutoPilotSelected()) { - return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - } - const selectedThroughput: number = this._getThroughput(); - const maxRU: number = this.maxThroughputRU && this.maxThroughputRU(); - - const isMaxRUGreaterThanDefault: boolean = maxRU > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - const isThroughputSetGreaterThanDefault: boolean = - selectedThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; - - if (this.canExceedMaximumValue()) { - return isThroughputSetGreaterThanDefault; - } - - return isThroughputSetGreaterThanDefault && isMaxRUGreaterThanDefault; - }); - - this.databaseCreateNew.subscribe((createNew: boolean) => { - if (!createNew) { - this.databaseCreateNewShared(this.getSharedThroughputDefault()); - } - }); - - this.databaseId.subscribe((selectedDatabaseId: string) => { - if (!selectedDatabaseId) { - return; - } - - if (!this.databaseCreateNew()) { - const selectedDatabase: ViewModels.Database = this.container - .databases() - .find((database: ViewModels.Database) => database.id() === selectedDatabaseId); - this.databaseHasSharedOffer(!!selectedDatabase?.offer()); - } - }); - - this.databaseCreateNewShared.subscribe((useShared: boolean) => { - this._updateThroughputLimitByStorage(); - this.databaseHasSharedOffer(useShared); - }); - - this.isAutoPilotSelected = ko.observable(false); - this.isSharedAutoPilotSelected = ko.observable(false); - this.autoPilotThroughput = ko.observable(AutoPilotUtils.minAutoPilotThroughput); - this.sharedAutoPilotThroughput = ko.observable(AutoPilotUtils.minAutoPilotThroughput); - this.autoPilotUsageCost = ko.pureComputed(() => { - const autoPilot = this._getAutoPilot(); - if (!autoPilot) { - return ""; - } - const isDatabaseThroughput: boolean = this.databaseCreateNewShared(); - return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput); - }); - - this.resetData(); - - this.freeTierExceedThroughputTooltip = ko.pureComputed(() => - this.isFreeTierAccount() && !this.container.isFirstResourceCreated() - ? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s." - : "" - ); - - this.upsellMessage = ko.pureComputed(() => { - return PricingUtils.getUpsellMessage( - userContext.portalEnv, - this.isFreeTierAccount(), - this.container.isFirstResourceCreated(), - true - ); - }); - - this.upsellMessageAriaLabel = ko.pureComputed(() => { - return `${this.upsellMessage()}. Click ${this.isFreeTierAccount() ? "to learn more" : "for more details"}`; - }); - - this.upsellAnchorUrl = ko.pureComputed(() => { - return this.isFreeTierAccount() ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing; - }); - - this.upsellAnchorText = ko.pureComputed(() => { - return this.isFreeTierAccount() ? "Learn more" : "More details"; - }); - - this.displayCollectionThroughput = ko.computed(() => { - const createNewDatabase = this.databaseCreateNew(); - const useExisitingDatabaseWithThroughput = !this.databaseCreateNew() && this.databaseHasSharedOffer(); - const useExisitingDatabaseWithoutThroughput = !this.databaseCreateNew() && !this.databaseHasSharedOffer(); - const provisionDatabaseThroughputIsChecked = this.databaseCreateNewShared(); - const provisionDedicatedThroughputForContainerIsChecked = this.collectionWithThroughputInShared(); - - if (createNewDatabase && provisionDatabaseThroughputIsChecked) { - return false; - } - - if (createNewDatabase && !provisionDatabaseThroughputIsChecked) { - return true; - } - - if (useExisitingDatabaseWithThroughput && !provisionDedicatedThroughputForContainerIsChecked) { - return false; - } - - if (useExisitingDatabaseWithThroughput && provisionDedicatedThroughputForContainerIsChecked) { - return true; - } - - if (useExisitingDatabaseWithoutThroughput) { - return true; - } - - return false; - }); - - this.isFreeTierAccount = ko.computed(() => { - return userContext?.databaseAccount?.properties?.enableFreeTier; - }); - - this.showUpsellMessage = ko.pureComputed(() => { - if (this.container.isServerlessEnabled()) { - return false; - } - - if ( - this.isFreeTierAccount() && - !this.databaseCreateNew() && - this.databaseHasSharedOffer() && - !this.collectionWithThroughputInShared() - ) { - return false; - } - - return true; - }); - - this.showIndexingOptionsForSharedThroughput = ko.computed(() => { - const newDatabaseWithSharedOffer = this.databaseCreateNew() && this.databaseCreateNewShared(); - const existingDatabaseWithSharedOffer = !this.databaseCreateNew() && this.databaseHasSharedOffer(); - - if ((newDatabaseWithSharedOffer || existingDatabaseWithSharedOffer) && this.isFreeTierAccount()) { - return true; - } - - return false; - }); - - this.shouldUseDatabaseThroughput = ko.computed(() => { - // new database with shared offer - if (this.databaseCreateNew() && this.databaseCreateNewShared()) { - return true; - } - - // existing database with shared offer and not provisioning collection level throughput - if (!this.databaseCreateNew() && this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared()) { - return true; - } - - return false; - }); - - this.isSynapseLinkSupported = ko.computed(() => { - if (configContext.platform === Platform.Emulator) { - return false; - } - - if (this.container.isServerlessEnabled()) { - return false; - } - - if (userContext.apiType === "SQL") { - return true; - } - - if (userContext.apiType === "Mongo") { - return true; - } - - if (userContext.apiType === "Cassandra" && this.container.hasStorageAnalyticsAfecFeature()) { - return true; - } - - return false; - }); - - this._isSynapseLinkEnabled = ko.computed(() => { - const databaseAccount = userContext?.databaseAccount || ({} as DataModels.DatabaseAccount); - const properties = databaseAccount.properties || ({} as DataModels.DatabaseAccountExtendedProperties); - - // TODO: remove check for capability once all accounts have been migrated - const capabilities = properties.capabilities || ([] as DataModels.Capability[]); - if (capabilities.some((capability) => capability.name === Constants.CapabilityNames.EnableStorageAnalytics)) { - return true; - } - - const enableAnalyticalStorage: boolean = properties.enableAnalyticalStorage; - if (enableAnalyticalStorage) { - return true; - } - - return false; - }); - - this.showEnableSynapseLink = ko.computed(() => { - return this.isSynapseLinkSupported() && !this._isSynapseLinkEnabled(); - }); - - this.showAnalyticalStore = ko.computed(() => { - return this.isSynapseLinkSupported() && this._isSynapseLinkEnabled(); - }); - - this.isAnalyticalStorageOn = ko.observable(this._isSynapseLinkEnabled()); - - this._isSynapseLinkEnabled.subscribe((isSynapseLinkEnabled: boolean) => { - this.isAnalyticalStorageOn(isSynapseLinkEnabled); - }); - - this.isSynapseLinkUpdating = ko.computed(() => this.container.isSynapseLinkUpdating()); - - this.useIndexingForSharedThroughput.subscribe((value) => { - TelemetryProcessor.traceMark(Action.ModifyOptionForThroughputWithSharedDatabase, { - changedSelectedValueTo: value ? ActionModifiers.IndexAll : ActionModifiers.NoIndex, - }); - }); - - this.isEnableMongoCapabilityEnabled = ko.observable(isCapabilityEnabled("EnableMongo")); - - this.shouldCreateMongoWildcardIndex = ko.observable(this.container.isMongoIndexingEnabled()); - } - - public getSharedThroughputDefault(): boolean { - const { subscriptionType } = userContext; - if (subscriptionType === SubscriptionType.EA || this.container.isServerlessEnabled()) { - return false; - } - - return true; - } - - public onMoreDetailsKeyPress = (source: any, event: KeyboardEvent): boolean => { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.showErrorDetails(); - return false; - } - return true; - }; - - public async open(databaseId?: string) { - super.open(); - // TODO: Figure out if a database level partition split is about to happen once shared throughput read is available - this.formWarnings(""); - this.databaseCreateNewShared(this.getSharedThroughputDefault()); - this.shouldCreateMongoWildcardIndex(this.container.isMongoIndexingEnabled()); - if (!this.container.isServerlessEnabled()) { - this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - } - if (this.isPreferredApiTable() && !databaseId) { - databaseId = SharedConstants.CollectionCreation.TablesAPIDefaultDatabase; - } - - this.databaseCreateNew(!databaseId); - this.collectionWithThroughputInShared(false); - this.databaseId(databaseId); - - const addCollectionPaneOpenMessage = { - collection: ko.toJS({ - id: this.collectionId(), - storage: this.storage(), - offerThroughput: this._getThroughput(), - partitionKey: this.partitionKey(), - databaseId: this.databaseId(), - }), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", - throughput: this._getThroughput(), - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - }; - - await this.container.loadDatabaseOffers(); - this._onDatabasesChange(this.container.databases()); - this._setFocus(); - - TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage); - } - - private transferFocus(elementIdToKeepVisible: string, elementIdToFocus: string): void { - document.getElementById(elementIdToKeepVisible).style.visibility = "visible"; - document.getElementById(elementIdToFocus).focus(); - } - - private onFocusOut(_: any, event: any): void { - event.target.parentElement.style.visibility = ""; - } - - private onMouseOut(_: any, event: any): void { - event.target.style.visibility = ""; - } - - private onKeyDown(previousActiveElementId: string, _: any, event: KeyboardEvent): boolean { - if (event.shiftKey && event.keyCode == Constants.KeyCodes.Tab) { - document.getElementById(previousActiveElementId).focus(); - return false; - } else { - // Execute default action - return true; - } - } - - private isMongo(): boolean { - return userContext.apiType === "Mongo"; - } - - private _onDatabasesChange(newDatabaseIds: ViewModels.Database[]) { - this.databaseIds(newDatabaseIds?.map((database: ViewModels.Database) => database.id())); - } - - private _computeOfferThroughput(): number { - if (!this.canConfigureThroughput()) { - return undefined; - } - - // return undefined if autopilot is selected for the new database/collection - if (this.databaseCreateNew()) { - // database is shared and autopilot is sleected for the database - if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) { - return undefined; - } - // database is not shared and autopilot is selected for the collection - if (!this.databaseCreateNewShared() && this.isAutoPilotSelected()) { - return undefined; - } - } - - return this._getThroughput(); - } - - public submit() { - if (!this.isValid()) { - return; - } - - if (userContext.apiType === "Tables") { - // Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk' - this.databaseId(SharedConstants.CollectionCreation.TablesAPIDefaultDatabase); - this.partitionKey("/'$pk'"); - } - - let partitionKeyPath: string = this.partitionKey(); - const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this._getUniqueKeyPolicy(); - const offerThroughput: number = this._computeOfferThroughput(); - - let partitionKeyVersion: number = this.largePartitionKey() ? 2 : undefined; - let partitionKey: DataModels.PartitionKey = partitionKeyPath.trim() - ? { - paths: [partitionKeyPath], - kind: Constants.BackendDefaults.partitionKeyKind, - version: partitionKeyVersion, - } - : null; - const autoPilot: DataModels.AutoPilotCreationSettings = this._getAutoPilot(); - - const addCollectionPaneStartMessage = { - database: ko.toJS({ - id: this.databaseId(), - new: this.databaseCreateNew(), - shared: this.databaseHasSharedOffer(), - }), - offerThroughput: offerThroughput, - offerAutopilot: autoPilot, - collection: ko.toJS({ - id: this.collectionId(), - storage: this.storage(), - partitionKey, - uniqueKeyPolicy, - collectionWithThroughputInShared: this.collectionWithThroughputInShared(), - }), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", - throughput: offerThroughput, - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - useIndexingForSharedThroughput: this.useIndexingForSharedThroughput(), - }; - const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage); - - let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId(); - let collectionId: string = this.collectionId().trim(); - - let indexingPolicy: DataModels.IndexingPolicy; - let createMongoWildcardIndex: boolean; - // todo - remove mongo indexing policy ticket # 616274 - if (userContext.apiType === "Mongo") { - createMongoWildcardIndex = this.shouldCreateMongoWildcardIndex(); - } else if (this.showIndexingOptionsForSharedThroughput()) { - if (this.useIndexingForSharedThroughput()) { - indexingPolicy = SharedConstants.IndexingPolicies.AllPropertiesIndexed; - } else { - indexingPolicy = SharedConstants.IndexingPolicies.SharedDatabaseDefault; - } - } else { - indexingPolicy = SharedConstants.IndexingPolicies.AllPropertiesIndexed; - } - - this.formErrors(""); - this.isExecuting(true); - - const databaseLevelThroughput: boolean = this.databaseCreateNew() - ? this.databaseCreateNewShared() - : this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(); - const autoPilotMaxThroughput: number = databaseLevelThroughput - ? this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput() - : this.isAutoPilotSelected() && this.autoPilotThroughput(); - const createCollectionParams: DataModels.CreateCollectionParams = { - createNewDatabase: this.databaseCreateNew(), - collectionId, - databaseId, - databaseLevelThroughput, - offerThroughput, - analyticalStorageTtl: this._getAnalyticalStorageTtl(), - autoPilotMaxThroughput, - indexingPolicy, - partitionKey, - uniqueKeyPolicy, - createMongoWildcardIndex, - }; - - createCollection(createCollectionParams).then( - () => { - this.isExecuting(false); - this.close(); - this.container.refreshAllDatabases(); - const addCollectionPaneSuccessMessage = { - database: ko.toJS({ - id: this.databaseId(), - new: this.databaseCreateNew(), - shared: this.databaseHasSharedOffer(), - }), - offerThroughput, - collection: ko.toJS({ - id: this.collectionId(), - storage: this.storage(), - partitionKey, - uniqueKeyPolicy, - collectionWithThroughputInShared: this.collectionWithThroughputInShared(), - }), - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", - throughput: offerThroughput, - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - }; - TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey); - this.resetData(); - this.container.refreshAllDatabases(); - }, - (error: any) => { - this.isExecuting(false); - const errorMessage: string = getErrorMessage(error); - this.formErrors(errorMessage); - this.formErrorsDetails(errorMessage); - const addCollectionPaneFailedMessage = { - database: ko.toJS({ - id: this.databaseId(), - new: this.databaseCreateNew(), - shared: this.databaseHasSharedOffer(), - }), - offerThroughput: offerThroughput, - collection: { - id: this.collectionId(), - storage: this.storage(), - partitionKey, - uniqueKeyPolicy, - collectionWithThroughputInShared: this.collectionWithThroughputInShared(), - }, - subscriptionType: userContext.subscriptionType, - subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", - throughput: offerThroughput, - flight: userContext.addCollectionFlight, - }, - dataExplorerArea: Constants.Areas.ContextualPane, - error: errorMessage, - errorStack: getErrorStack(error), - }; - TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey); - } - ); - } - - public resetData() { - this.collectionId(""); - this.databaseId(""); - this.partitionKey(""); - this.throughputSpendAck(false); - if (!this.container.isServerlessEnabled()) { - this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); - } - this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); - this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); - - this.shouldCreateMongoWildcardIndex = ko.observable(this.container.isMongoIndexingEnabled()); - - this.uniqueKeys([]); - this.useIndexingForSharedThroughput(true); - - const defaultStorage = this.container.collectionCreationDefaults.storage; - this.storage(defaultStorage); - - const defaultThroughput = this.container.collectionCreationDefaults.throughput; - this.throughputSinglePartition(defaultThroughput.fixed); - this.throughputMultiPartition( - AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container) - ); - - this.throughputDatabase(defaultThroughput.shared); - this.databaseCreateNew(true); - this.databaseHasSharedOffer(this.getSharedThroughputDefault()); - this.collectionWithThroughputInShared(false); - this.databaseCreateNewShared(this.getSharedThroughputDefault()); - if (this.isTryCosmosDBSubscription()) { - this._resetDataForTryCosmosDB(); - } - - this.largePartitionKey(false); - - this._updateThroughputLimitByStorage(); - super.resetData(); - } - - public isNonTableApi = (): boolean => { - return userContext.apiType !== "Tables"; - }; - - public isUnlimitedStorageSelected = (): boolean => { - return this.storage() === Constants.BackendDefaults.multiPartitionStorageInGb; - }; - - public isFixedStorageSelected = (): boolean => { - return this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb; - }; - - public onStorageOptionsKeyDown(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.RightArrow) { - this.storage(Constants.BackendDefaults.multiPartitionStorageInGb); - return false; - } - - if (event.keyCode === Constants.KeyCodes.LeftArrow) { - this.storage(Constants.BackendDefaults.singlePartitionStorageInGb); - return false; - } - - return true; - } - - public onEnableSynapseLinkButtonClicked() { - this.container.openEnableSynapseLinkDialog(); - } - - public ttl90DaysEnabled: () => boolean = () => userContext.features.ttl90Days; - - public isValid(): boolean { - // TODO add feature flag that disables validation for customers with custom accounts - if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) { - const autoPilot = this._getAutoPilot(); - if ( - !autoPilot || - !autoPilot.maxThroughput || - !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) - ) { - this.formErrors( - `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` - ); - return false; - } - } - - const throughput = this._getThroughput(); - if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) { - this.formErrors(`Please acknowledge the estimated daily spend.`); - return false; - } - - if (userContext.apiType === "Gremlin" && (this.partitionKey() === "/id" || this.partitionKey() === "/label")) { - this.formErrors("/id and /label as partition keys are not allowed for graph."); - return false; - } - - const autoscaleThroughput = this.autoPilotThroughput() * 1; - - if ( - this.isAutoPilotSelected() && - autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !this.throughputSpendAck() - ) { - this.formErrors(`Please acknowledge the estimated monthly spend.`); - return false; - } - - return true; - } - - private _setFocus() { - // Autofocus is enabled on AddCollectionPane based on the preferred API - if (userContext.apiType === "Tables") { - const focusTableId = document.getElementById("containerId"); - focusTableId && focusTableId.focus(); - return; - } - - if (this.databaseCreateNew()) { - const focusDatabaseId = document.getElementById("databaseId"); - focusDatabaseId && focusDatabaseId.focus(); - return; - } - - const focusExistingDatabaseId = document.getElementById("containerId"); - focusExistingDatabaseId && focusExistingDatabaseId.focus(); - } - - private _getThroughput(): number { - let throughput: number = - this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb - ? this.throughputSinglePartition() - : this.throughputMultiPartition(); - if (this.databaseHasSharedOffer()) { - if (this.collectionWithThroughputInShared()) { - throughput = this.throughputMultiPartition(); - } else { - throughput = this.throughputDatabase(); - } - } - - return isNaN(throughput) ? 0 : Number(throughput); - } - - private _getAutoPilot(): DataModels.AutoPilotCreationSettings { - if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()) { - return { - maxThroughput: this.sharedAutoPilotThroughput() * 1, - }; - } - if (this.isAutoPilotSelected() && this.autoPilotThroughput()) { - return { - maxThroughput: this.autoPilotThroughput() * 1, - }; - } - - return undefined; - } - - private _calculateNumberOfPartitions(): number { - // Note: this will not validate properly on accounts that have been set up for custom partitioning, - // but there is no way to know the number of partitions for that case. - return this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb - ? SharedConstants.CollectionCreation.NumberOfPartitionsInFixedCollection - : SharedConstants.CollectionCreation.NumberOfPartitionsInUnlimitedCollection; - } - - private _convertShardKeyToPartitionKey(shardKey: string): string { - if (!shardKey) { - return shardKey; - } - - const shardKeyParts = shardKey.split("."); - let partitionKey = shardKeyParts.join("/"); - - if (partitionKey[0] !== "/") { - partitionKey = "/" + partitionKey; - } - return partitionKey; - } - - private _resetDataForTryCosmosDB() { - this.storage(Constants.BackendDefaults.multiPartitionStorageInGb); - this.throughputSinglePartition(Constants.TryCosmosExperience.defaultRU); - this.throughputDatabase(SharedConstants.CollectionCreation.DefaultCollectionRUs400); - } - - private _updateThroughputLimitByStorage() { - if (this.databaseCreateNewShared()) { - this._updateThroughputLimitByDatabase(); - } else { - this._updateThroughputLimitByCollectionStorage(); - } - } - - private _updateThroughputLimitByCollectionStorage() { - const storage = this.storage(); - const minThroughputRU = - storage === SharedConstants.CollectionCreation.storage10Gb - ? SharedConstants.CollectionCreation.DefaultCollectionRUs400 - : this.container.collectionCreationDefaults.throughput.unlimitedmin; - - let maxThroughputRU; - if (this.isTryCosmosDBSubscription()) { - maxThroughputRU = Constants.TryCosmosExperience.maxRU; - } else { - maxThroughputRU = - storage === SharedConstants.CollectionCreation.storage10Gb - ? SharedConstants.CollectionCreation.DefaultCollectionRUs10K - : this.container.collectionCreationDefaults.throughput.unlimitedmax; - } - - this.minThroughputRU(minThroughputRU); - this.maxThroughputRU(maxThroughputRU); - } - - private _updateThroughputLimitByDatabase() { - const defaultThruoghput = this.container.collectionCreationDefaults.throughput; - this.maxThroughputRU(defaultThruoghput.unlimitedmax); - this.minThroughputRU(defaultThruoghput.unlimitedmin); - } - - /** - * Obtains the UniqueKeyPolicy and applies transformations for Mongo APIs - */ - private _getUniqueKeyPolicy(): DataModels.UniqueKeyPolicy { - let transform = (value: string) => { - return value; - }; - if (userContext.apiType === "Mongo") { - transform = (value: string) => { - return this._convertShardKeyToPartitionKey(value); - }; - } - - return this._parseUniqueIndexes(transform); - } - - /** - * Obtains the current added unique keys and applies cleaning, removing spaces and empty entries - * @param transform Transformation process for each detected key - */ - private _parseUniqueIndexes(transform: (value: string) => string): DataModels.UniqueKeyPolicy { - if (this.uniqueKeys().length === 0) { - return null; - } - - const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = { uniqueKeys: [] }; - this.uniqueKeys().forEach((uniqueIndexPaths: DynamicListItem) => { - const uniqueIndexPathValue: string = uniqueIndexPaths.value(); - if (!!uniqueIndexPathValue && uniqueIndexPathValue.length > 0) { - const validPaths: string[] = _.filter( - uniqueIndexPathValue.split(","), - (path: string) => !!path && path.length > 0 - ); - const cleanedUpPaths: string[] = validPaths.map((path: string) => { - return transform(path.trim()); - }); - if (cleanedUpPaths.length > 0) { - const uniqueKey: DataModels.UniqueKey = { paths: cleanedUpPaths }; - uniqueKeyPolicy.uniqueKeys.push(uniqueKey); - } - } - }); - - return uniqueKeyPolicy; - } - - private _getAnalyticalStorageTtl(): number { - if (!this.showAnalyticalStore()) { - return undefined; - } - - if (this.isAnalyticalStorageOn()) { - // TODO: always default to 90 days once the backend hotfix is deployed - return userContext.features.ttl90Days - ? Constants.AnalyticalStorageTtl.Days90 - : Constants.AnalyticalStorageTtl.Infinite; - } - - return Constants.AnalyticalStorageTtl.Disabled; - } -} diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index 20205c223..25c7732e5 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -210,6 +210,8 @@ export class AddCollectionPanel extends React.Component (this.newDatabaseThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)} @@ -435,6 +437,8 @@ export class AddCollectionPanel extends React.Component (this.collectionThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)} diff --git a/src/Explorer/Panes/AddDatabasePane.html b/src/Explorer/Panes/AddDatabasePane.html index 0166ab7cb..2a598ade6 100644 --- a/src/Explorer/Panes/AddDatabasePane.html +++ b/src/Explorer/Panes/AddDatabasePane.html @@ -1,4 +1,4 @@ -
+
@@ -126,31 +126,31 @@

diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.test.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.test.tsx new file mode 100644 index 000000000..4206eb723 --- /dev/null +++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.test.tsx @@ -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(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx new file mode 100644 index 000000000..5911b8f80 --- /dev/null +++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx @@ -0,0 +1,341 @@ +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 = ({ + 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(""); + 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(getSharedThroughputDefault); + const [formErrorsDetails, setFormErrorsDetails] = useState(); + const [formErrors, setFormErrors] = useState(""); + + const [isAutoPilotSelected, setIsAutoPilotSelected] = useState(container.isAutoscaleDefaultEnabled()); + + const throughputDefaults = container.collectionCreationDefaults.throughput; + const [throughput, setThroughput] = useState( + isAutoPilotSelected ? AutoPilotUtils.minAutoPilotThroughput : throughputDefaults.shared + ); + + const [throughputSpendAck, setThroughputSpendAck] = useState(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(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, newValue?: string) => { + setDatabaseId(newValue || ""); + }, + [] + ); + + const props: RightPaneFormProps = { + expandConsole: container.expandConsole, + formError: formErrors, + formErrorDetail: formErrorsDetails, + isExecuting, + submitButtonText: "OK", + onSubmit, + }; + + return ( + +

+ {showUpsellMessage && formErrors === "" && ( + + )} +
+
+

+ * + {databaseIdLabel} + {databaseIdTooltipText} +

+ + + +
+ setDatabaseCreateNewShared(!databaseCreateNewShared)} + />{" "} + {databaseLevelThroughputTooltipText} +
+ {databaseCreateNewShared && ( +
+ setThroughput(throughput)} + setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)} + onCostAcknowledgeChange={(isAcknowledged: boolean) => setThroughputSpendAck(isAcknowledged)} + /> + + {canRequestSupport() && ( +

+ + Contact support{" "} + + for more than {throughputDefaults.unlimitedmax?.toLocaleString()} RU/s. +

+ )} +
+ )} +
+
+
+ + ); +}; diff --git a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap new file mode 100644 index 000000000..aa5566040 --- /dev/null +++ b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap @@ -0,0 +1,103 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AddDatabasePane Pane should render Default properly 1`] = ` + +
+ +
+
+

+ + * + + + Database id + + + A database is a logical container of one or more collections + +

+ +
+ + + + Provisioned throughput at the database level will be shared across all collections within the database. + +
+
+ +
+
+
+
+
+`; diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap index a445e6fe4..29d835e45 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap @@ -1105,7 +1105,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect >