diff --git a/.eslintignore b/.eslintignore index 06150b48a..a2012a696 100644 --- a/.eslintignore +++ b/.eslintignore @@ -133,7 +133,6 @@ src/Explorer/Panes/Tables/AddTableEntityPane.ts src/Explorer/Panes/Tables/EditTableEntityPane.ts src/Explorer/Panes/Tables/EntityPropertyViewModel.ts src/Explorer/Panes/Tables/QuerySelectPane.ts -src/Explorer/Panes/Tables/TableColumnOptionsPane.ts src/Explorer/Panes/Tables/TableEntityPane.ts src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts diff --git a/package-lock.json b/package-lock.json index 2a10c0f28..3c3ad623d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38078,73 +38078,67 @@ "@testing-library/dom": "^7.28.1" } }, - "@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true - }, - "@types/applicationinsights-js": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/applicationinsights-js/-/applicationinsights-js-1.0.7.tgz", - "integrity": "sha512-oIqnhcvhz0NRJ+vSN56leWsHyUmz7bySe5f88ukwPhXlNzfTB3JyZ6/bw4+B8zjSktBoYxR9Pzf0t18XwRiwHw==", - "dev": true - }, - "@types/aria-query": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", - "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==", - "dev": true - }, - "@types/asap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/asap/-/asap-2.0.0.tgz", - "integrity": "sha512-upIS0Gt9Mc8eEpCbYMZ1K8rhNosfKUtimNcINce+zLwJF5UpM3Vv7yz3S5l/1IX+DxTa8lTkUjqynvjRXyJzsg==" - }, - "@types/babel__core": { - "version": "7.1.14", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", - "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", + "@microsoft/applicationinsights-analytics-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.6.1.tgz", + "integrity": "sha512-wRH67jZTPy6SP54ygAQsFXu5ZnfzGOoMkA6ll1pkhSC4hij7Cvzumr4PYkr2gIPBPxD354sLOjBL/lmOgBTc5g==", "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@microsoft/applicationinsights-common": "2.6.1", + "@microsoft/applicationinsights-core-js": "2.6.1", + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" } }, - "@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", + "@microsoft/applicationinsights-channel-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.6.1.tgz", + "integrity": "sha512-oatERKW3eAjVNm5ej2NpRvBCCtPtiINIKxLiWVHv2SC2rzGrUnxNWFlUJojRT+u5ZXXsB51Ktw9gx8iHexnDVw==", "requires": { - "@babel/types": "^7.0.0" + "@microsoft/applicationinsights-common": "2.6.1", + "@microsoft/applicationinsights-core-js": "2.6.1", + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" } }, - "@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", + "@microsoft/applicationinsights-common": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.6.1.tgz", + "integrity": "sha512-kV/dI9UwTew3mq+3F3Tkl2dBn2C4+FOOG3S5cS7/SssVsUlvLrWXBrOOxfIJ7+EjQQ6ijcqlDus4Nz7Ms2Kk2Q==", "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@microsoft/applicationinsights-core-js": "2.6.1", + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" } }, - "@types/babel__traverse": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", - "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", + "@microsoft/applicationinsights-core-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.6.1.tgz", + "integrity": "sha512-SFnTx48BGkmz6P9GvXFeIZ641vnfrKo0hB74Hp9GR7UE3hqIDuomIBQS17unnh05T5w3PWKlDCGNpiCbJV6kzQ==", "requires": { - "@babel/types": "^7.3.0" + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" } }, - "@types/cheerio": { - "version": "0.22.28", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.28.tgz", - "integrity": "sha512-ehUMGSW5IeDxJjbru4awKYMlKGmo1wSSGUVqXtYwlgmUM8X1a0PZttEIm6yEY7vHsY/hh6iPnklF213G0UColw==", - "dev": true, + "@microsoft/applicationinsights-dependencies-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.6.1.tgz", + "integrity": "sha512-Y7R6BjzyB6NrkIBTT6FJrr2jKr8MiLxy264b1xUQHjry3Bwu+fsPJ7EETV61dkOEHs6IlWxAtikNCv8f3zQydw==", "requires": { - "@types/node": "*" + "@microsoft/applicationinsights-common": "2.6.1", + "@microsoft/applicationinsights-core-js": "2.6.1", + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" + } + }, + "@microsoft/applicationinsights-properties-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.6.1.tgz", + "integrity": "sha512-TQmKp9/j0yMGjUHBatRQ41E4S/1yTkJImh8csZxc4waMQmV9ITkNrDaC3bIPcrw+ASepI3QfP7sry+n7nvYLFw==", + "requires": { + "@microsoft/applicationinsights-common": "2.6.1", + "@microsoft/applicationinsights-core-js": "2.6.1", + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" } }, "@types/codemirror": { @@ -38153,20 +38147,96 @@ "integrity": "sha512-OMtPqg2wFOEcNeVga+m+UXpYJw8ugISPCQOtShdFUho/k91Ms1oWOozoDT1I87Phv6IdwLfMLtIOahh1tO1cJQ==", "dev": true }, - "@types/crossroads": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/crossroads/-/crossroads-0.0.30.tgz", - "integrity": "sha512-1Ze8YTD5tFWeM5CtqAiJ348tDDYBOGDdLid4rnnTLYDU5sJgEjMknLx4g5yxWDHCr51nSl4Z9M/ZAEqTtyEMVA==", - "dev": true, + "@microsoft/applicationinsights-web": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-2.6.1.tgz", + "integrity": "sha512-jKF6hpPq3pzLqLSVxTMh7HU9tr/SPOFJN12pFYpNMU/rAMs/fgZsmFG/oBAzYTZxNilDTFljoblB66J9X+K6RQ==", "requires": { - "@types/signals": "*" + "@microsoft/applicationinsights-analytics-js": "2.6.1", + "@microsoft/applicationinsights-channel-js": "2.6.1", + "@microsoft/applicationinsights-common": "2.6.1", + "@microsoft/applicationinsights-core-js": "2.6.1", + "@microsoft/applicationinsights-dependencies-js": "2.6.1", + "@microsoft/applicationinsights-properties-js": "2.6.1", + "@microsoft/applicationinsights-shims": "1.0.3", + "@microsoft/dynamicproto-js": "^1.1.1" } }, - "@types/d3": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.9.2.tgz", - "integrity": "sha512-eqiXmREP+cZhngaPalnJ1M88tXO74GyQB8TFd3h20e4zf5NOwLVGGlHU2ZoQ1e+rcdbfboSSwxffoQPIxEh55w==", - "dev": true, + "@microsoft/dynamicproto-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.1.tgz", + "integrity": "sha512-SPY1PlXmg4FbJVItVdhDV+zigajPtSI8oZPrsKBEIzAF4FROuwWIq5C+RAF8VJshBqphcvU8Eoh4DrUEZOB31g==", + "requires": { + "findup-sync": "^4.0.0", + "nopt": "^5.0.0", + "pify": "^2.3.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@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==" + }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "requires": { "@types/d3-array": "^1", "@types/d3-axis": "*", @@ -40732,10 +40802,18 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, - "buffer-from": { + "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } }, "buffer-indexof": { "version": "1.1.1", diff --git a/package.json b/package.json index 330e2b43d..c007e33d3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@babel/plugin-proposal-decorators": "7.12.12", "@jupyterlab/services": "6.0.2", "@jupyterlab/terminal": "3.0.3", - "@microsoft/applicationinsights-web": "2.5.9", + "@microsoft/applicationinsights-web": "2.6.1", "@nteract/commutable": "7.4.2", "@nteract/connected-components": "6.8.2", "@nteract/core": "15.1.0", diff --git a/src/Common/Logger.ts b/src/Common/Logger.ts index 1e34609df..b53226d6a 100644 --- a/src/Common/Logger.ts +++ b/src/Common/Logger.ts @@ -1,7 +1,7 @@ -import { sendMessage } from "./MessageHandler"; -import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts"; -import { appInsights } from "../Shared/appInsights"; import { SeverityLevel } from "@microsoft/applicationinsights-web"; +import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts"; +import { trackTrace } from "../Shared/appInsights"; +import { sendMessage } from "./MessageHandler"; // TODO: Move to a separate Diagnostics folder // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -46,7 +46,7 @@ function _logEntry(entry: Diagnostics.LogEntry): void { return SeverityLevel.Information; } })(entry.level); - appInsights.trackTrace({ message: entry.message, severityLevel }, { area: entry.area }); + trackTrace({ message: entry.message, severityLevel }, { area: entry.area }); } function _generateLogEntry( diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index a325ba0be..264e88eab 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -10,7 +10,6 @@ import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleCompo import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import * as PaneComponents from "./Panes/PaneComponents"; import ConflictsTab from "./Tabs/ConflictsTab"; -import DatabaseSettingsTab from "./Tabs/DatabaseSettingsTab"; import DocumentsTab from "./Tabs/DocumentsTab"; import GalleryTab from "./Tabs/GalleryTab"; import GraphTab from "./Tabs/GraphTab"; @@ -53,7 +52,6 @@ ko.components.register("tabs-manager", { template: TabsManagerTemplate }); TerminalTab, GalleryTab, NotebookViewerTab, - DatabaseSettingsTab, DatabaseSettingsTabV2, ].forEach(({ component: { name, template } }) => ko.components.register(name, { template })); @@ -68,7 +66,6 @@ ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVerte ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); -ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent()); ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent()); diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 3c007097a..e98a362a4 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -220,31 +220,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -607,7 +582,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -629,7 +603,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -782,32 +755,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], @@ -1026,31 +973,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -1413,7 +1335,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -1435,7 +1356,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -1588,32 +1508,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], @@ -1845,31 +1739,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -2232,7 +2101,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -2254,7 +2122,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -2407,32 +2274,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], @@ -2651,31 +2492,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -3038,7 +2854,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -3060,7 +2875,6 @@ exports[`SettingsComponent renders 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -3213,32 +3027,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index c86f40bb4..6402216c1 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -19,13 +19,12 @@ import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter" import { configContext, Platform } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; import { MessageTypes } from "../Contracts/ExplorerContracts"; -import { SubscriptionType } from "../Contracts/SubscriptionType"; import * as ViewModels from "../Contracts/ViewModels"; import { IGalleryItem } from "../Juno/JunoClient"; import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager"; import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory"; import { RouteHandler } from "../RouteHandlers/RouteHandler"; -import { appInsights } from "../Shared/appInsights"; +import { trackEvent } from "../Shared/appInsights"; import * as SharedConstants from "../Shared/Constants"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { ExplorerSettings } from "../Shared/ExplorerSettings"; @@ -67,7 +66,6 @@ import { StringInputPane } from "./Panes/StringInputPane"; import AddTableEntityPane from "./Panes/Tables/AddTableEntityPane"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane"; -import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane"; import { UploadFilePane } from "./Panes/UploadFilePane"; import { UploadItemsPane } from "./Panes/UploadItemsPane"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; @@ -98,10 +96,6 @@ export interface ExplorerParams { } export default class Explorer { - public flight: ko.Observable = ko.observable( - SharedConstants.CollectionCreation.DefaultAddCollectionDefaultFlight - ); - public addCollectionText: ko.Observable; public addDatabaseText: ko.Observable; public collectionTitle: ko.Observable; @@ -109,7 +103,6 @@ export default class Explorer { public deleteDatabaseText: ko.Observable; public collectionTreeNodeAltText: ko.Observable; public refreshTreeTitle: ko.Observable; - public hasWriteAccess: ko.Observable; public collapsedResourceTreeWidth: number = ExplorerMetrics.CollapsedResourceTreeWidth; /** @@ -118,11 +111,6 @@ export default class Explorer { * */ public databaseAccount: ko.Observable; public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults; - /** - * @deprecated - * Use userContext.subscriptionType instead - * */ - public subscriptionType: ko.Observable; /** * @deprecated * Use userContext.apiType instead @@ -205,7 +193,6 @@ export default class Explorer { public graphStylingPane: GraphStylingPane; public addTableEntityPane: AddTableEntityPane; public editTableEntityPane: EditTableEntityPane; - public tableColumnOptionsPane: TableColumnOptionsPane; public querySelectPane: QuerySelectPane; public newVertexPane: NewVertexPane; public cassandraAddCollectionPane: CassandraAddCollectionPane; @@ -272,7 +259,6 @@ export default class Explorer { }); this.addCollectionText = ko.observable("New Collection"); this.addDatabaseText = ko.observable("New Database"); - this.hasWriteAccess = ko.observable(true); this.collectionTitle = ko.observable("Collections"); this.collectionTreeNodeAltText = ko.observable("Collection"); this.deleteCollectionText = ko.observable("Delete Collection"); @@ -280,7 +266,6 @@ export default class Explorer { this.refreshTreeTitle = ko.observable("Refresh collections"); this.databaseAccount = ko.observable(); - this.subscriptionType = ko.observable(SharedConstants.CollectionCreation.DefaultSubscriptionType); this.isAccountReady = ko.observable(false); this._isInitializingNotebooks = false; this.arcadiaToken = ko.observable(); @@ -341,7 +326,7 @@ export default class Explorer { userContext.features.enableSpark ); if (this.isSparkEnabled()) { - appInsights.trackEvent( + trackEvent( { name: "LoadedWithSparkEnabled" }, { subscriptionId: userContext.subscriptionId, @@ -572,13 +557,6 @@ export default class Explorer { container: this, }); - this.tableColumnOptionsPane = new TableColumnOptionsPane({ - id: "tablecolumnoptionspane", - visible: ko.observable(false), - - container: this, - }); - this.querySelectPane = new QuerySelectPane({ id: "queryselectpane", visible: ko.observable(false), @@ -622,7 +600,6 @@ export default class Explorer { this.graphStylingPane, this.addTableEntityPane, this.editTableEntityPane, - this.tableColumnOptionsPane, this.querySelectPane, this.newVertexPane, this.cassandraAddCollectionPane, @@ -1283,11 +1260,6 @@ export default class Explorer { this.collectionCreationDefaults = inputs.defaultCollectionThroughput; } this.databaseAccount(databaseAccount); - this.subscriptionType(inputs.subscriptionType ?? SharedConstants.CollectionCreation.DefaultSubscriptionType); - this.hasWriteAccess(inputs.hasWriteAccess ?? true); - if (inputs.addCollectionDefaultFlight) { - this.flight(inputs.addCollectionDefaultFlight); - } this.setFeatureFlagsFromFlights(inputs.flights); TelemetryProcessor.traceSuccess( Action.LoadDatabaseAccount, diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index 74d1071ca..611a1a95c 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -105,10 +105,6 @@ export default class AddCollectionPane extends ContextualPaneBase { this.databaseId = ko.observable(); this.databaseCreateNew = ko.observable(true); this.databaseCreateNewShared = ko.observable(this.getSharedThroughputDefault()); - this.container.subscriptionType && - this.container.subscriptionType.subscribe((subscriptionType) => { - this.databaseCreateNewShared(this.getSharedThroughputDefault()); - }); this.collectionWithThroughputInShared = ko.observable(false); this.databaseIds = ko.observableArray(); this.uniqueKeys = ko.observableArray(); @@ -478,9 +474,6 @@ export default class AddCollectionPane extends ContextualPaneBase { }); this.resetData(); - this.container.flight.subscribe(() => { - this.resetData(); - }); this.freeTierExceedThroughputTooltip = ko.pureComputed(() => this.isFreeTierAccount() && !this.container.isFirstResourceCreated() @@ -659,7 +652,7 @@ export default class AddCollectionPane extends ContextualPaneBase { } public getSharedThroughputDefault(): boolean { - const subscriptionType = this.container.subscriptionType && this.container.subscriptionType(); + const subscriptionType = userContext.subscriptionType; if (subscriptionType === SubscriptionType.EA || this.container.isServerlessEnabled()) { return false; } @@ -701,12 +694,12 @@ export default class AddCollectionPane extends ContextualPaneBase { partitionKey: this.partitionKey(), databaseId: this.databaseId(), }), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", throughput: this._getThroughput(), - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; @@ -805,12 +798,12 @@ export default class AddCollectionPane extends ContextualPaneBase { uniqueKeyPolicy, collectionWithThroughputInShared: this.collectionWithThroughputInShared(), }), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", throughput: offerThroughput, - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, useIndexingForSharedThroughput: this.useIndexingForSharedThroughput(), @@ -877,12 +870,12 @@ export default class AddCollectionPane extends ContextualPaneBase { uniqueKeyPolicy, collectionWithThroughputInShared: this.collectionWithThroughputInShared(), }), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", throughput: offerThroughput, - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; @@ -909,12 +902,12 @@ export default class AddCollectionPane extends ContextualPaneBase { uniqueKeyPolicy, collectionWithThroughputInShared: this.collectionWithThroughputInShared(), }, - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u", throughput: offerThroughput, - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, error: errorMessage, diff --git a/src/Explorer/Panes/AddDatabasePane.test.ts b/src/Explorer/Panes/AddDatabasePane.test.ts new file mode 100644 index 000000000..711a9cfc1 --- /dev/null +++ b/src/Explorer/Panes/AddDatabasePane.test.ts @@ -0,0 +1,105 @@ +import * as Constants from "../../Common/Constants"; +import { DatabaseAccount } from "../../Contracts/DataModels"; +import { SubscriptionType } from "../../Contracts/SubscriptionType"; +import { updateUserContext } from "../../UserContext"; +import Explorer from "../Explorer"; +import AddDatabasePane from "./AddDatabasePane"; + +describe("Add Database Pane", () => { + describe("getSharedThroughputDefault()", () => { + let explorer: Explorer; + const mockDatabaseAccount: DatabaseAccount = { + id: "mock", + kind: "DocumentDB", + location: "", + name: "mock", + properties: { + documentEndpoint: "", + cassandraEndpoint: "", + gremlinEndpoint: "", + tableEndpoint: "", + enableFreeTier: false, + }, + type: undefined, + tags: [], + }; + + const mockFreeTierDatabaseAccount: DatabaseAccount = { + id: "mock", + kind: "DocumentDB", + location: "", + name: "mock", + properties: { + documentEndpoint: "", + cassandraEndpoint: "", + gremlinEndpoint: "", + tableEndpoint: "", + enableFreeTier: true, + }, + type: undefined, + tags: [], + }; + + beforeEach(() => { + explorer = new Explorer(); + }); + + it("should be true if subscription type is Benefits", () => { + updateUserContext({ + subscriptionType: SubscriptionType.Benefits, + }); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); + }); + + it("should be false if subscription type is EA", () => { + updateUserContext({ + subscriptionType: SubscriptionType.EA, + }); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.getSharedThroughputDefault()).toBe(false); + }); + + it("should be true if subscription type is Free", () => { + updateUserContext({ + subscriptionType: SubscriptionType.Free, + }); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); + }); + + it("should be true if subscription type is Internal", () => { + updateUserContext({ + subscriptionType: SubscriptionType.Internal, + }); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); + }); + + it("should be true if subscription type is PAYG", () => { + updateUserContext({ + subscriptionType: SubscriptionType.PAYG, + }); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); + }); + + it("should display free tier text in upsell messaging", () => { + explorer.databaseAccount(mockFreeTierDatabaseAccount); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.isFreeTierAccount()).toBe(true); + expect(addDatabasePane.upsellMessage()).toContain("With free tier"); + expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation); + expect(addDatabasePane.upsellAnchorText()).toBe("Learn more"); + }); + + it("should display standard texr in upsell messaging", () => { + explorer.databaseAccount(mockDatabaseAccount); + const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; + expect(addDatabasePane.isFreeTierAccount()).toBe(false); + expect(addDatabasePane.upsellMessage()).toContain("Start at"); + expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing); + expect(addDatabasePane.upsellAnchorText()).toBe("More details"); + }); + }); +}); diff --git a/src/Explorer/Panes/AddDatabasePane.ts b/src/Explorer/Panes/AddDatabasePane.ts new file mode 100644 index 000000000..dff1f08ff --- /dev/null +++ b/src/Explorer/Panes/AddDatabasePane.ts @@ -0,0 +1,461 @@ +import * as ko from "knockout"; +import * as Constants from "../../Common/Constants"; +import { createDatabase } from "../../Common/dataAccess/createDatabase"; +import editable from "../../Common/EditableUtility"; +import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; +import { configContext, Platform } from "../../ConfigContext"; +import * as DataModels from "../../Contracts/DataModels"; +import { SubscriptionType } from "../../Contracts/SubscriptionType"; +import * as ViewModels from "../../Contracts/ViewModels"; +import * as SharedConstants from "../../Shared/Constants"; +import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; +import { userContext } from "../../UserContext"; +import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; +import * as PricingUtils from "../../Utils/PricingUtils"; +import { ContextualPaneBase } from "./ContextualPaneBase"; + +export default class AddDatabasePane extends ContextualPaneBase { + public defaultExperience: ko.Computed; + public databaseIdLabel: ko.Computed; + public databaseIdPlaceHolder: ko.Computed; + public databaseId: ko.Observable; + public databaseIdTooltipText: ko.Computed; + public databaseLevelThroughputTooltipText: ko.Computed; + public databaseCreateNewShared: ko.Observable; + public formErrorsDetails: ko.Observable; + public throughput: ViewModels.Editable; + public maxThroughputRU: ko.Observable; + public minThroughputRU: ko.Observable; + public maxThroughputRUText: ko.PureComputed; + public throughputRangeText: ko.Computed; + public throughputSpendAckText: ko.Observable; + public throughputSpendAck: ko.Observable; + public throughputSpendAckVisible: ko.Computed; + public requestUnitsUsageCost: ko.Computed; + public canRequestSupport: ko.PureComputed; + public costsVisible: ko.PureComputed; + public upsellMessage: ko.PureComputed; + public upsellMessageAriaLabel: ko.PureComputed; + public upsellAnchorUrl: ko.PureComputed; + public upsellAnchorText: ko.PureComputed; + public isAutoPilotSelected: ko.Observable; + public maxAutoPilotThroughputSet: ko.Observable; + public autoPilotUsageCost: ko.Computed; + public canExceedMaximumValue: ko.PureComputed; + public ruToolTipText: ko.Computed; + public freeTierExceedThroughputTooltip: ko.Computed; + public isFreeTierAccount: ko.Computed; + public canConfigureThroughput: ko.PureComputed; + public showUpsellMessage: ko.PureComputed; + + constructor(options: ViewModels.PaneOptions) { + super(options); + this.title((this.container && this.container.addDatabaseText()) || "New Database"); + this.databaseId = ko.observable(); + this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); + this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); + + this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); + + // TODO 388844: get defaults from parent frame + this.databaseCreateNewShared = ko.observable(this.getSharedThroughputDefault()); + + this.databaseIdLabel = ko.computed(() => + this.container.isPreferredApiCassandra() ? "Keyspace id" : "Database id" + ); + + this.databaseIdPlaceHolder = ko.computed(() => + this.container.isPreferredApiCassandra() ? "Type a new keyspace id" : "Type a new database id" + ); + + this.databaseIdTooltipText = ko.computed(() => { + const isCassandraAccount: boolean = this.container.isPreferredApiCassandra(); + return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${ + isCassandraAccount ? "tables" : "collections" + }`; + }); + this.databaseLevelThroughputTooltipText = ko.computed(() => { + const isCassandraAccount: boolean = this.container.isPreferredApiCassandra(); + const databaseLabel: string = isCassandraAccount ? "keyspace" : "database"; + const collectionsLabel: string = isCassandraAccount ? "tables" : "collections"; + return `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`; + }); + + this.throughput = editable.observable(); + this.maxThroughputRU = ko.observable(); + this.minThroughputRU = ko.observable(); + this.throughputSpendAckText = ko.observable(); + this.throughputSpendAck = ko.observable(false); + this.isAutoPilotSelected = ko.observable(false); + this.maxAutoPilotThroughputSet = ko.observable(AutoPilotUtils.minAutoPilotThroughput); + this.autoPilotUsageCost = ko.pureComputed(() => { + const autoPilot = this._isAutoPilotSelectedAndWhatTier(); + if (!autoPilot) { + return ""; + } + return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */); + }); + this.throughputRangeText = ko.pureComputed(() => { + if (this.isAutoPilotSelected()) { + return AutoPilotUtils.getAutoPilotHeaderText(); + } + return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; + }); + + this.requestUnitsUsageCost = ko.computed(() => { + const offerThroughput: number = this.throughput(); + if ( + offerThroughput < this.minThroughputRU() || + (offerThroughput > this.maxThroughputRU() && !this.canExceedMaximumValue()) + ) { + return ""; + } + + const account = this.container.databaseAccount(); + 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 estimatedSpendAcknowledge: string; + let estimatedSpend: string; + if (!this.isAutoPilotSelected()) { + estimatedSpend = PricingUtils.getEstimatedSpendHtml( + offerThroughput, + userContext.portalEnv, + regions, + multimaster + ); + estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( + offerThroughput, + userContext.portalEnv, + regions, + multimaster, + this.isAutoPilotSelected() + ); + } else { + estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( + this.maxAutoPilotThroughputSet(), + userContext.portalEnv, + regions, + multimaster + ); + estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( + this.maxAutoPilotThroughputSet(), + userContext.portalEnv, + regions, + multimaster, + this.isAutoPilotSelected() + ); + } + // TODO: change throughputSpendAckText to be a computed value, instead of having this side effect + this.throughputSpendAckText(estimatedSpendAcknowledge); + return estimatedSpend; + }); + + this.canRequestSupport = ko.pureComputed(() => { + if ( + configContext.platform !== Platform.Emulator && + !userContext.isTryCosmosDBSubscription && + configContext.platform !== Platform.Portal + ) { + const offerThroughput: number = this.throughput(); + return offerThroughput <= 100000; + } + + return false; + }); + + this.isFreeTierAccount = ko.computed(() => { + const databaseAccount = this.container && this.container.databaseAccount && this.container.databaseAccount(); + const isFreeTierAccount = + databaseAccount && databaseAccount.properties && databaseAccount.properties.enableFreeTier; + return isFreeTierAccount; + }); + + this.showUpsellMessage = ko.pureComputed(() => { + if (this.container.isServerlessEnabled()) { + return false; + } + + if (this.isFreeTierAccount()) { + return this.databaseCreateNewShared(); + } + + return true; + }); + + this.maxThroughputRUText = ko.pureComputed(() => { + return this.maxThroughputRU().toLocaleString(); + }); + + this.costsVisible = ko.pureComputed(() => { + return configContext.platform !== Platform.Emulator; + }); + + this.throughputSpendAckVisible = ko.pureComputed(() => { + const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; + if (this.isAutoPilotSelected()) { + return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; + } + + const selectedThroughput: number = this.throughput(); + 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.databaseCreateNewShared.subscribe((useShared: boolean) => { + this._updateThroughputLimitByDatabase(); + }); + + 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(), + this.container.defaultExperience(), + false + ); + }); + + 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"; + }); + } + + 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 open() { + super.open(); + this.resetData(); + const addDatabasePaneOpenMessage = { + subscriptionType: userContext.subscriptionType, + subscriptionQuotaId: userContext.quotaId, + defaultsCheck: { + throughput: this.throughput(), + flight: userContext.addCollectionFlight, + }, + dataExplorerArea: Constants.Areas.ContextualPane, + }; + const focusElement = document.getElementById("database-id"); + focusElement && focusElement.focus(); + TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage); + } + + public submit() { + if (!this._isValid()) { + return; + } + + const offerThroughput: number = this._computeOfferThroughput(); + + const addDatabasePaneStartMessage = { + database: ko.toJS({ + id: this.databaseId(), + shared: this.databaseCreateNewShared(), + }), + offerThroughput, + subscriptionType: userContext.subscriptionType, + subscriptionQuotaId: userContext.quotaId, + defaultsCheck: { + flight: userContext.addCollectionFlight, + }, + dataExplorerArea: Constants.Areas.ContextualPane, + }; + const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage); + this.formErrors(""); + this.isExecuting(true); + + const createDatabaseParams: DataModels.CreateDatabaseParams = { + databaseId: addDatabasePaneStartMessage.database.id, + databaseLevelThroughput: addDatabasePaneStartMessage.database.shared, + }; + + if (this.isAutoPilotSelected()) { + createDatabaseParams.autoPilotMaxThroughput = this.maxAutoPilotThroughputSet(); + } else { + createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput; + } + + createDatabase(createDatabaseParams).then( + (database: DataModels.Database) => { + this._onCreateDatabaseSuccess(offerThroughput, startKey); + }, + (error: any) => { + this._onCreateDatabaseFailure(error, offerThroughput, startKey); + } + ); + } + + public resetData() { + this.databaseId(""); + this.databaseCreateNewShared(this.getSharedThroughputDefault()); + this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled()); + this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput); + this._updateThroughputLimitByDatabase(); + this.throughputSpendAck(false); + super.resetData(); + } + + public getSharedThroughputDefault(): boolean { + const subscriptionType = userContext.subscriptionType; + + if (subscriptionType === SubscriptionType.EA || this.container.isServerlessEnabled()) { + return false; + } + + return true; + } + + private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void { + this.isExecuting(false); + this.close(); + this.container.refreshAllDatabases(); + const addDatabasePaneSuccessMessage = { + database: ko.toJS({ + id: this.databaseId(), + shared: this.databaseCreateNewShared(), + }), + offerThroughput: offerThroughput, + subscriptionType: userContext.subscriptionType, + subscriptionQuotaId: userContext.quotaId, + defaultsCheck: { + flight: userContext.addCollectionFlight, + }, + dataExplorerArea: Constants.Areas.ContextualPane, + }; + TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey); + this.resetData(); + } + + private _onCreateDatabaseFailure(error: any, offerThroughput: number, startKey: number): void { + this.isExecuting(false); + const errorMessage = getErrorMessage(error); + this.formErrors(errorMessage); + this.formErrorsDetails(errorMessage); + const addDatabasePaneFailedMessage = { + database: ko.toJS({ + id: this.databaseId(), + shared: this.databaseCreateNewShared(), + }), + offerThroughput: offerThroughput, + subscriptionType: userContext.subscriptionType, + subscriptionQuotaId: userContext.quotaId, + defaultsCheck: { + flight: userContext.addCollectionFlight, + }, + dataExplorerArea: Constants.Areas.ContextualPane, + error: errorMessage, + errorStack: getErrorStack(error), + }; + TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey); + } + + private _getThroughput(): number { + const throughput: number = this.throughput(); + return isNaN(throughput) ? 0 : Number(throughput); + } + + private _computeOfferThroughput(): number { + if (!this.canConfigureThroughput()) { + return undefined; + } + + if (this.isAutoPilotSelected()) { + return undefined; + } + + return this._getThroughput(); + } + + private _isValid(): boolean { + // TODO add feature flag that disables validation for customers with custom accounts + if (this.isAutoPilotSelected()) { + const autoPilot = this._isAutoPilotSelectedAndWhatTier(); + 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; + } + + const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; + + if ( + this.isAutoPilotSelected() && + autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && + !this.throughputSpendAck() + ) { + this.formErrors(`Please acknowledge the estimated monthly spend.`); + return false; + } + + return true; + } + + private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings { + if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) { + return { + maxThroughput: this.maxAutoPilotThroughputSet() * 1, + }; + } + return undefined; + } + + private _updateThroughputLimitByDatabase() { + const throughputDefaults = this.container.collectionCreationDefaults.throughput; + this.throughput(throughputDefaults.shared); + this.maxThroughputRU(throughputDefaults.unlimitedmax); + this.minThroughputRU(throughputDefaults.unlimitedmin); + } +} diff --git a/src/Explorer/Panes/AddDatabasePane/index.tsx b/src/Explorer/Panes/AddDatabasePane/index.tsx index b53e44d2d..56b53f940 100644 --- a/src/Explorer/Panes/AddDatabasePane/index.tsx +++ b/src/Explorer/Panes/AddDatabasePane/index.tsx @@ -118,7 +118,7 @@ export const AddDatabasePane: FunctionComponent = ({ subscriptionType: SubscriptionType[subscriptionType], subscriptionQuotaId: userContext.quotaId, defaultsCheck: { - flight: container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; @@ -129,7 +129,7 @@ export const AddDatabasePane: FunctionComponent = ({ subscriptionQuotaId: userContext.quotaId, defaultsCheck: { throughput: throughput, - flight: container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; diff --git a/src/Explorer/Panes/CassandraAddCollectionPane.ts b/src/Explorer/Panes/CassandraAddCollectionPane.ts index 44cf83a7f..10d644723 100644 --- a/src/Explorer/Panes/CassandraAddCollectionPane.ts +++ b/src/Explorer/Panes/CassandraAddCollectionPane.ts @@ -5,7 +5,6 @@ import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils" import { HashMap } from "../../Common/HashMap"; 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"; @@ -117,10 +116,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase { this.resetData(); - this.container.flight.subscribe(() => { - this.resetData(); - }); - this.requestUnitsUsageCostDedicated = ko.computed(() => { const account = this.container.databaseAccount(); if (!account) { @@ -306,12 +301,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase { partitionKey: "", databaseId: this.keyspaceId(), }), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: "u", throughput: this.throughput(), - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; @@ -358,12 +353,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase { hasDedicatedThroughput: this.dedicateTableThroughput(), }), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: "u", throughput: this.throughput(), - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, toCreateKeyspace: toCreateKeyspace, @@ -402,12 +397,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase { hasDedicatedThroughput: this.dedicateTableThroughput(), }), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: "u", throughput: this.throughput(), - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, toCreateKeyspace: toCreateKeyspace, @@ -430,12 +425,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase { hasDedicatedThroughput: this.dedicateTableThroughput(), }, keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), - subscriptionType: SubscriptionType[this.container.subscriptionType()], + subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: "u", throughput: this.throughput(), - flight: this.container.flight(), + flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, toCreateKeyspace: toCreateKeyspace, diff --git a/src/Explorer/Panes/PaneComponents.ts b/src/Explorer/Panes/PaneComponents.ts index ef7c29f77..d49ef49d6 100644 --- a/src/Explorer/Panes/PaneComponents.ts +++ b/src/Explorer/Panes/PaneComponents.ts @@ -7,7 +7,6 @@ import GraphStylingPaneTemplate from "./GraphStylingPane.html"; import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html"; import StringInputPaneTemplate from "./StringInputPane.html"; import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html"; -import TableColumnOptionsPaneTemplate from "./Tables/TableColumnOptionsPane.html"; import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html"; import TableQuerySelectPaneTemplate from "./Tables/TableQuerySelectPane.html"; @@ -71,15 +70,6 @@ export class TableEditEntityPaneComponent { } } -export class TableColumnOptionsPaneComponent { - constructor() { - return { - viewModel: PaneComponent, - template: TableColumnOptionsPaneTemplate, - }; - } -} - export class TableQuerySelectPaneComponent { constructor() { return { diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap index d5efabebf..2acc9a3d2 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap @@ -196,31 +196,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -583,7 +558,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -605,7 +579,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -758,32 +731,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], @@ -1090,31 +1037,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -1477,7 +1399,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -1499,7 +1420,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -1652,32 +1572,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], diff --git a/src/Explorer/Panes/Tables/TableColumnOptionsPane.html b/src/Explorer/Panes/Tables/TableColumnOptionsPane.html deleted file mode 100644 index a0e289976..000000000 --- a/src/Explorer/Panes/Tables/TableColumnOptionsPane.html +++ /dev/null @@ -1,78 +0,0 @@ -
-
-
- -
-
- -
- Column Options -
- Close -
-
- -
-
Choose the columns and the order in which you want to display them in the table.
-
-
- - - - Move down - - - Move up - -
-
-
-
    -
  • - - -
  • -
-
-
-
- -
- -
-
-
-
-
-
-
- -
-
diff --git a/src/Explorer/Panes/Tables/TableColumnOptionsPane.ts b/src/Explorer/Panes/Tables/TableColumnOptionsPane.ts deleted file mode 100644 index 0da321ab2..000000000 --- a/src/Explorer/Panes/Tables/TableColumnOptionsPane.ts +++ /dev/null @@ -1,195 +0,0 @@ -import * as ko from "knockout"; -import * as ViewModels from "../../../Contracts/ViewModels"; -import * as DataTableOperations from "../../Tables/DataTable/DataTableOperations"; -import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel"; -import { ContextualPaneBase } from "../ContextualPaneBase"; -import _ from "underscore"; - -/** - * Represents an item shown in the available columns. - * columnName: the name of the column. - * selected: indicate whether user wants to display the column in the table. - * order: the order in the initial table. E.g., - * Order array of initial table: I = [0, 1, 2, 3, 4, 5, 6, 7, 8] <----> {prop0, prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8} - * Order array of current table: C = [0, 1, 2, 6, 7, 3, 4, 5, 8] <----> {prop0, prop1, prop2, prop6, prop7, prop3, prop4, prop5, prop8} - * if order = 6, then this column will be the one with column name prop6 - * index: index in the observable array, this used for selection and moving up/down. - */ -interface IColumnOption { - columnName: ko.Observable; - selected: ko.Observable; - order: number; - index: number; -} - -export interface IColumnSetting { - columnNames: string[]; - visible?: boolean[]; - order?: number[]; -} - -export class TableColumnOptionsPane extends ContextualPaneBase { - public titleLabel: string = "Column Options"; - public instructionLabel: string = "Choose the columns and the order in which you want to display them in the table."; - public availableColumnsLabel: string = "Available Columns"; - public moveUpLabel: string = "Move Up"; - public moveDownLabel: string = "Move Down"; - public noColumnSelectedWarning: string = "At least one column should be selected."; - - public columnOptions: ko.ObservableArray; - public allSelected: ko.Computed; - public anyColumnSelected: ko.Computed; - public canSelectAll: ko.Computed; - public canMoveUp: ko.Observable; - public canMoveDown: ko.Observable; - - public tableViewModel: TableEntityListViewModel; - public parameters: IColumnSetting; - - private selectedColumnOption: IColumnOption = null; - - constructor(options: ViewModels.PaneOptions) { - super(options); - - this.columnOptions = ko.observableArray(); - this.anyColumnSelected = ko.computed(() => { - return _.some(this.columnOptions(), (value: IColumnOption) => { - return value.selected(); - }); - }); - - this.canSelectAll = ko.computed(() => { - return _.some(this.columnOptions(), (value: IColumnOption) => { - return !value.selected(); - }); - }); - - this.canMoveUp = ko.observable(false); - this.canMoveDown = ko.observable(false); - - this.allSelected = ko.pureComputed({ - read: () => { - return !this.canSelectAll(); - }, - write: (value) => { - if (value) { - this.selectAll(); - } else { - this.clearAll(); - } - }, - owner: this, - }); - } - - public submit() { - var newColumnSetting = this.getParameters(); - DataTableOperations.reorderColumns(this.tableViewModel.table, newColumnSetting.order).then(() => { - DataTableOperations.filterColumns(this.tableViewModel.table, newColumnSetting.visible); - this.visible(false); - }); - } - public open() { - this.setDisplayedColumns(this.parameters.columnNames, this.parameters.order, this.parameters.visible); - super.open(); - } - - private getParameters(): IColumnSetting { - var newColumnSettings: IColumnSetting = { - columnNames: [], - order: [], - visible: [], - }; - this.columnOptions().map((value: IColumnOption) => { - newColumnSettings.columnNames.push(value.columnName()); - newColumnSettings.order.push(value.order); - newColumnSettings.visible.push(value.selected()); - }); - return newColumnSettings; - } - - public setDisplayedColumns(columnNames: string[], order: number[], visible: boolean[]): void { - var options: IColumnOption[] = order.map((value: number, index: number) => { - var columnOption: IColumnOption = { - columnName: ko.observable(columnNames[index]), - order: value, - selected: ko.observable(visible[index]), - index: index, - }; - return columnOption; - }); - this.columnOptions(options); - } - - public selectAll(): void { - const columnOptions = this.columnOptions && this.columnOptions(); - columnOptions && - columnOptions.forEach((value: IColumnOption) => { - value.selected(true); - }); - } - - public clearAll(): void { - const columnOptions = this.columnOptions && this.columnOptions(); - columnOptions && - columnOptions.forEach((value: IColumnOption) => { - value.selected(false); - }); - - if (columnOptions && columnOptions.length > 0) { - columnOptions[0].selected(true); - } - } - - public moveUp(): void { - if (this.selectedColumnOption) { - var currentSelectedIndex: number = this.selectedColumnOption.index; - var swapTargetIndex: number = currentSelectedIndex - 1; - //Debug.assert(currentSelectedIndex > 0); - - this.swapColumnOption(this.columnOptions(), swapTargetIndex, currentSelectedIndex); - this.selectTargetItem($(`div.column-options li:eq(${swapTargetIndex})`), this.columnOptions()[swapTargetIndex]); - } - } - - public moveDown(): void { - if (this.selectedColumnOption) { - var currentSelectedIndex: number = this.selectedColumnOption.index; - var swapTargetIndex: number = currentSelectedIndex + 1; - //Debug.assert(currentSelectedIndex < (this.columnOptions().length - 1)); - - this.swapColumnOption(this.columnOptions(), swapTargetIndex, currentSelectedIndex); - this.selectTargetItem($(`div.column-options li:eq(${swapTargetIndex})`), this.columnOptions()[swapTargetIndex]); - } - } - - public handleClick = (data: IColumnOption, event: KeyboardEvent): boolean => { - this.selectTargetItem($(event.currentTarget), data); - return true; - }; - - private selectTargetItem($target: JQuery, targetColumn: IColumnOption): void { - this.selectedColumnOption = targetColumn; - - this.canMoveUp(targetColumn.index !== 0); - this.canMoveDown(targetColumn.index !== this.columnOptions().length - 1); - - $(".list-item.selected").removeClass("selected"); - $target.addClass("selected"); - } - - private swapColumnOption(options: IColumnOption[], indexA: number, indexB: number): void { - var tempColumnName: string = options[indexA].columnName(); - var tempSelected: boolean = options[indexA].selected(); - var tempOrder: number = options[indexA].order; - - options[indexA].columnName(options[indexB].columnName()); - options[indexB].columnName(tempColumnName); - - options[indexA].selected(options[indexB].selected()); - options[indexB].selected(tempSelected); - - options[indexA].order = options[indexB].order; - options[indexB].order = tempOrder; - } -} diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap index f6145f2d6..c684734f7 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap @@ -196,31 +196,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -583,7 +558,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -605,7 +579,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -758,32 +731,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 43085da2b..edf002606 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -197,31 +197,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "title": [Function], "visible": [Function], }, - TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, QuerySelectPane { "allSelected": [Function], "anyColumnSelected": [Function], @@ -584,7 +559,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "title": [Function], "visible": [Function], }, - "flight": [Function], "graphStylingPane": GraphStylingPane { "container": [Circular], "firstFieldHasFocus": [Function], @@ -606,7 +580,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "visible": [Function], }, "hasStorageAnalyticsAfecFeature": [Function], - "hasWriteAccess": [Function], "isAccountReady": [Function], "isAutoscaleDefaultEnabled": [Function], "isCopyNotebookPaneEnabled": [Function], @@ -763,32 +736,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "title": [Function], "visible": [Function], }, - "subscriptionType": [Function], - "tableColumnOptionsPane": TableColumnOptionsPane { - "allSelected": [Function], - "anyColumnSelected": [Function], - "availableColumnsLabel": "Available Columns", - "canMoveDown": [Function], - "canMoveUp": [Function], - "canSelectAll": [Function], - "columnOptions": [Function], - "container": [Circular], - "firstFieldHasFocus": [Function], - "formErrors": [Function], - "formErrorsDetails": [Function], - "handleClick": [Function], - "id": "tablecolumnoptionspane", - "instructionLabel": "Choose the columns and the order in which you want to display them in the table.", - "isExecuting": [Function], - "isTemplateReady": [Function], - "moveDownLabel": "Move Down", - "moveUpLabel": "Move Up", - "noColumnSelectedWarning": "At least one column should be selected.", - "selectedColumnOption": null, - "title": [Function], - "titleLabel": "Column Options", - "visible": [Function], - }, "tabsManager": TabsManager { "activeTab": [Function], "openedTabs": [Function], diff --git a/src/Explorer/Tables/DataTable/TableCommands.ts b/src/Explorer/Tables/DataTable/TableCommands.ts index c52c296d3..a84dc9008 100644 --- a/src/Explorer/Tables/DataTable/TableCommands.ts +++ b/src/Explorer/Tables/DataTable/TableCommands.ts @@ -1,12 +1,8 @@ -import _ from "underscore"; import Q from "q"; -import * as DataTableUtilities from "./DataTableUtilities"; -import * as DataTableOperations from "./DataTableOperations"; -import TableEntityListViewModel from "./TableEntityListViewModel"; -import * as Entities from "../Entities"; -import * as ViewModels from "../../../Contracts/ViewModels"; -import * as TableColumnOptionsPane from "../../Panes/Tables/TableColumnOptionsPane"; import Explorer from "../../Explorer"; +import * as Entities from "../Entities"; +import * as DataTableUtilities from "./DataTableUtilities"; +import TableEntityListViewModel from "./TableEntityListViewModel"; export default class TableCommands { // Command Ids @@ -92,64 +88,6 @@ export default class TableCommands { return null; } - public customizeColumnsCommand(viewModel: TableEntityListViewModel): Q.Promise { - var table: DataTables.DataTable = viewModel.table; - var displayedColumnNames: string[] = DataTableOperations.getDataTableHeaders(table); - var columnsCount: number = displayedColumnNames.length; - var currentOrder: number[] = DataTableOperations.getInitialOrder(columnsCount); - //Debug.assert(!!table && !!currentOrder && displayedColumnNames.length === currentOrder.length); - - var currentSettings: boolean[]; - try { - currentSettings = currentOrder.map((value: number, index: number) => { - return table.column(index).visible(); - }); - } catch (err) { - // Error - } - - let parameters: TableColumnOptionsPane.IColumnSetting = { - columnNames: displayedColumnNames, - order: currentOrder, - visible: currentSettings, - }; - - this._container.tableColumnOptionsPane.tableViewModel = viewModel; - this._container.tableColumnOptionsPane.parameters = parameters; - this._container.tableColumnOptionsPane.open(); - return null; - } - - public reorderColumnsBasedOnSelectedEntities(viewModel: TableEntityListViewModel): Q.Promise { - var selected = viewModel.selected(); - if (!selected || !selected.length) { - return null; - } - - var table = viewModel.table; - var currentColumnNames: string[] = DataTableOperations.getDataTableHeaders(table); - var headersCount: number = currentColumnNames.length; - - var headersUnion: string[] = DataTableUtilities.getPropertyIntersectionFromTableEntities( - selected, - viewModel.queryTablesTab.container.isPreferredApiCassandra() - ); - - // An array with elements representing indexes of selected entities' header union out of initial headers. - var orderOfLeftHeaders: number[] = headersUnion.map((item: string) => currentColumnNames.indexOf(item)); - - // An array with elements representing initial order of the table. - var initialOrder: number[] = DataTableOperations.getInitialOrder(headersCount); - - // An array with elements representing indexes of headers not present in selected entities' header union. - var orderOfRightHeaders: number[] = _.difference(initialOrder, orderOfLeftHeaders); - - // This will be the target order, with headers in selected entities on the left while others on the right, both in the initial order, respectively. - var targetOrder: number[] = orderOfLeftHeaders.concat(orderOfRightHeaders); - - return DataTableOperations.reorderColumns(table, targetOrder); - } - public resetColumns(viewModel: TableEntityListViewModel): void { viewModel.reloadTable(); } diff --git a/src/Explorer/Tabs/DatabaseSettingsTab.html b/src/Explorer/Tabs/DatabaseSettingsTab.html deleted file mode 100644 index 3f770cd7c..000000000 --- a/src/Explorer/Tabs/DatabaseSettingsTab.html +++ /dev/null @@ -1,85 +0,0 @@ -
-
-
-
- Info - -
-
- Warning - -
-
-
-
-
- Scale -
-
- Info - With free tier, you'll get the first 400 RU/s and 5 GB of storage in this account for free. To keep your - account free, keep the total RU/s across all resources in the account to 400 RU/s. - - Learn more. - -
-
- - - -
-

- Learn more about minimum throughput - here. -

-

- - Contact support - for more than RU/s -

-

- Use Data Explorer from Azure Portal to request more than RU/s -

-
-
-
-
diff --git a/src/Explorer/Tabs/DatabaseSettingsTab.ts b/src/Explorer/Tabs/DatabaseSettingsTab.ts deleted file mode 100644 index 02f65c6e1..000000000 --- a/src/Explorer/Tabs/DatabaseSettingsTab.ts +++ /dev/null @@ -1,489 +0,0 @@ -import * as ko from "knockout"; -import Q from "q"; -import DiscardIcon from "../../../images/discard.svg"; -import SaveIcon from "../../../images/save-cosmos.svg"; -import * as Constants from "../../Common/Constants"; -import { updateOffer } from "../../Common/dataAccess/updateOffer"; -import editable from "../../Common/EditableUtility"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; -import { configContext, Platform } from "../../ConfigContext"; -import * as DataModels from "../../Contracts/DataModels"; -import * as ViewModels from "../../Contracts/ViewModels"; -import * as SharedConstants from "../../Shared/Constants"; -import { Action } 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 { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; -import Explorer from "../Explorer"; -import template from "./DatabaseSettingsTab.html"; -import TabsBase from "./TabsBase"; - -const updateThroughputBeyondLimitWarningMessage: string = ` -You are about to request an increase in throughput beyond the pre-allocated capacity. -The service will scale out and increase throughput for the selected database. -This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`; - -const updateThroughputDelayedApplyWarningMessage: string = ` -You are about to request an increase in throughput beyond the pre-allocated capacity. -This operation will take some time to complete.`; - -const currentThroughput: (isAutoscale: boolean, throughput: number) => string = (isAutoscale, throughput) => - isAutoscale - ? `Current autoscale throughput: ${Math.round(throughput / 10)} - ${throughput} RU/s` - : `Current manual throughput: ${throughput} RU/s`; - -const throughputApplyShortDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) => - `A request to increase the throughput is currently in progress. - This operation will take some time to complete.
- Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`; - -const throughputApplyLongDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) => - `A request to increase the throughput is currently in progress. - This operation will take 1-3 business days to complete. View the latest status in Notifications.
- Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`; - -export default class DatabaseSettingsTab extends TabsBase implements ViewModels.WaitsForTemplate { - public static readonly component = { name: "database-settings-tab", template }; - // editables - public isAutoPilotSelected: ViewModels.Editable; - public throughput: ViewModels.Editable; - public autoPilotThroughput: ViewModels.Editable; - public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor; - - public saveSettingsButton: ViewModels.Button; - public discardSettingsChangesButton: ViewModels.Button; - - public canRequestSupport: ko.PureComputed; - public canThroughputExceedMaximumValue: ko.Computed; - public costsVisible: ko.Computed; - public displayedError: ko.Observable; - public isFreeTierAccount: ko.Computed; - public isTemplateReady: ko.Observable; - public minRUAnotationVisible: ko.Computed; - public minRUs: ko.Observable; - public maxRUs: ko.Observable; - public maxRUsText: ko.PureComputed; - public maxRUThroughputInputLimit: ko.Computed; - public notificationStatusInfo: ko.Observable; - public pendingNotification: ko.Observable; - public requestUnitsUsageCost: ko.PureComputed; - public autoscaleCost: ko.PureComputed; - public shouldShowNotificationStatusPrompt: ko.Computed; - public shouldDisplayPortalUsePrompt: ko.Computed; - public shouldShowStatusBar: ko.Computed; - public throughputTitle: ko.PureComputed; - public throughputAriaLabel: ko.PureComputed; - public autoPilotUsageCost: ko.PureComputed; - public warningMessage: ko.Computed; - public canExceedMaximumValue: ko.PureComputed; - public overrideWithAutoPilotSettings: ko.Computed; - public overrideWithProvisionedThroughputSettings: ko.Computed; - public testId: string; - public throughputAutoPilotRadioId: string; - public throughputProvisionedRadioId: string; - public throughputModeRadioName: string; - public freeTierExceedThroughputWarning: ko.Computed; - - private _hasProvisioningTypeChanged: ko.Computed; - private _wasAutopilotOriginallySet: ko.Observable; - private _offerReplacePending: ko.Observable; - private container: Explorer; - - constructor(options: ViewModels.TabOptions) { - super(options); - - this.container = options.node && (options.node as ViewModels.Database).container; - this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); - - // html element ids - this.testId = `scaleSettingThroughputValue${this.tabId}`; - this.throughputAutoPilotRadioId = `editContainerThroughput-autoPilotRadio${this.tabId}`; - this.throughputProvisionedRadioId = `editContainerThroughput-manualRadio${this.tabId}`; - this.throughputModeRadioName = `throughputModeRadio${this.tabId}`; - - this.throughput = editable.observable(); - this._wasAutopilotOriginallySet = ko.observable(false); - this.isAutoPilotSelected = editable.observable(false); - this.autoPilotThroughput = editable.observable(); - - const autoscaleMaxThroughput = this.database?.offer()?.autoscaleMaxThroughput; - if (autoscaleMaxThroughput) { - if (AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) { - this._wasAutopilotOriginallySet(true); - this.isAutoPilotSelected(true); - this.autoPilotThroughput(autoscaleMaxThroughput); - } - } - - this._hasProvisioningTypeChanged = ko.pureComputed(() => { - if (this._wasAutopilotOriginallySet() !== this.isAutoPilotSelected()) { - return true; - } - return false; - }); - - this.autoPilotUsageCost = ko.pureComputed(() => { - const autoPilot = this.autoPilotThroughput(); - if (!autoPilot) { - return ""; - } - return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */); - }); - - this.requestUnitsUsageCost = ko.pureComputed(() => { - const account = userContext.databaseAccount; - 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 estimatedSpend: string; - if (!this.isAutoPilotSelected()) { - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - // if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set... - this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(), - userContext.portalEnv, - regions, - multimaster - ); - } else { - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.autoPilotThroughput(), - userContext.portalEnv, - regions, - multimaster - ); - } - return estimatedSpend; - }); - - this.costsVisible = ko.computed(() => { - return configContext.platform !== Platform.Emulator; - }); - - this.shouldDisplayPortalUsePrompt = ko.pureComputed(() => configContext.platform === Platform.Hosted); - this.canThroughputExceedMaximumValue = ko.pureComputed( - () => configContext.platform === Platform.Portal && !this.container.isRunningOnNationalCloud() - ); - this.canRequestSupport = ko.pureComputed(() => { - if ( - configContext.platform === Platform.Emulator || - configContext.platform === Platform.Hosted || - this.canThroughputExceedMaximumValue() - ) { - return false; - } - - return true; - }); - - this.overrideWithAutoPilotSettings = ko.pureComputed(() => { - return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet(); - }); - - this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => { - return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet(); - }); - - this.minRUs = ko.observable( - this.database.offer()?.minimumThroughput || this.container.collectionCreationDefaults.throughput.unlimitedmin - ); - - this.minRUAnotationVisible = ko.computed(() => { - return PricingUtils.isLargerThanDefaultMinRU(this.minRUs()); - }); - - this.maxRUs = ko.observable(this.container.collectionCreationDefaults.throughput.unlimitedmax); - - this.maxRUThroughputInputLimit = ko.pureComputed(() => { - if (configContext.platform === Platform.Hosted) { - return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million; - } - - return this.maxRUs(); - }); - - this.maxRUsText = ko.pureComputed(() => { - return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million.toLocaleString(); - }); - - this.throughputTitle = ko.pureComputed(() => { - if (this.isAutoPilotSelected()) { - return AutoPilotUtils.getAutoPilotHeaderText(); - } - - return `Throughput (${this.minRUs().toLocaleString()} - unlimited RU/s)`; - }); - - this.throughputAriaLabel = ko.pureComputed(() => { - return this.throughputTitle() + this.requestUnitsUsageCost(); - }); - this.pendingNotification = ko.observable(); - this._offerReplacePending = ko.observable(!!this.database.offer()?.offerReplacePending); - this.notificationStatusInfo = ko.observable(""); - this.shouldShowNotificationStatusPrompt = ko.computed(() => this.notificationStatusInfo().length > 0); - this.warningMessage = ko.computed(() => { - if (this.overrideWithProvisionedThroughputSettings()) { - return AutoPilotUtils.manualToAutoscaleDisclaimer; - } - - const offer = this.database.offer(); - if (offer?.offerReplacePending) { - const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput; - return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id()); - } - - if ( - this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && - this.canThroughputExceedMaximumValue() - ) { - return updateThroughputBeyondLimitWarningMessage; - } - - if (this.throughput() > this.maxRUs()) { - return updateThroughputDelayedApplyWarningMessage; - } - - if (this.pendingNotification()) { - const matches: string[] = this.pendingNotification().description.match("Throughput update for (.*) RU/s"); - const throughput: number = matches.length > 1 && Number(matches[1]); - - if (throughput) { - return throughputApplyLongDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id()); - } - } - - return ""; - }); - - this.warningMessage.subscribe((warning: string) => { - if (warning.length > 0) { - this.notificationStatusInfo(""); - } - }); - - this.shouldShowStatusBar = ko.computed( - () => this.shouldShowNotificationStatusPrompt() || (this.warningMessage && this.warningMessage().length > 0) - ); - - this.displayedError = ko.observable(""); - - this._setBaseline(); - - this.saveSettingsButton = { - enabled: ko.computed(() => { - if (this._hasProvisioningTypeChanged()) { - return true; - } - - if (this._offerReplacePending && this._offerReplacePending()) { - return false; - } - - const isAutoPilot = this.isAutoPilotSelected(); - const isManual = !this.isAutoPilotSelected(); - if (isAutoPilot) { - if (!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) { - return false; - } - if (this.isAutoPilotSelected.editableIsDirty()) { - return true; - } - if (this.autoPilotThroughput.editableIsDirty()) { - return true; - } - } - if (isManual) { - if (!this.throughput()) { - return false; - } - - if (this.throughput() < this.minRUs()) { - return false; - } - - if ( - !this.canThroughputExceedMaximumValue() && - this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million - ) { - return false; - } - - if (this.throughput.editableIsDirty()) { - return true; - } - - if (this.isAutoPilotSelected.editableIsDirty()) { - return true; - } - } - - return false; - }), - - visible: ko.computed(() => { - return true; - }), - }; - - this.discardSettingsChangesButton = { - enabled: ko.computed(() => { - if (this.throughput.editableIsDirty()) { - return true; - } - if (this.isAutoPilotSelected.editableIsDirty()) { - return true; - } - if (this.autoPilotThroughput.editableIsDirty()) { - return true; - } - - return false; - }), - - visible: ko.computed(() => { - return true; - }), - }; - - this.isTemplateReady = ko.observable(false); - - this.isFreeTierAccount = ko.computed(() => { - const databaseAccount = userContext.databaseAccount; - return databaseAccount?.properties?.enableFreeTier; - }); - - this.freeTierExceedThroughputWarning = ko.computed(() => - this.isFreeTierAccount() - ? "Billing will apply if you provision more than 400 RU/s of manual throughput, or if the resource scales beyond 400 RU/s with autoscale." - : "" - ); - - this._buildCommandBarOptions(); - } - - public onSaveClick = async (): Promise => { - this.isExecutionError(false); - - this.isExecuting(true); - - const startKey: number = TelemetryProcessor.traceStart(Action.UpdateSettings, { - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle(), - }); - - try { - const updateOfferParams: DataModels.UpdateOfferParams = { - databaseId: this.database.id(), - currentOffer: this.database.offer(), - autopilotThroughput: this.isAutoPilotSelected() ? this.autoPilotThroughput() : undefined, - manualThroughput: this.isAutoPilotSelected() ? undefined : this.throughput(), - }; - - if (this._hasProvisioningTypeChanged()) { - if (this.isAutoPilotSelected()) { - updateOfferParams.migrateToAutoPilot = true; - } else { - updateOfferParams.migrateToManual = true; - } - } - - const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams); - this.database.offer(updatedOffer); - this.database.offer.valueHasMutated(); - this._setBaseline(); - this._wasAutopilotOriginallySet(this.isAutoPilotSelected()); - } catch (error) { - this.isExecutionError(true); - console.error(error); - const errorMessage = getErrorMessage(error); - this.displayedError(errorMessage); - TelemetryProcessor.traceFailure( - Action.UpdateSettings, - { - databaseName: this.database && this.database.id(), - - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle(), - error: errorMessage, - errorStack: getErrorStack(error), - }, - startKey - ); - } finally { - this.isExecuting(false); - } - }; - - public onRevertClick = (): Q.Promise => { - this.throughput.setBaseline(this.throughput.getEditableOriginalValue()); - this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue()); - this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue()); - - return Q(); - }; - - public async onActivate(): Promise { - super.onActivate(); - this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings); - await this.database.loadOffer(); - } - - private _setBaseline() { - const offer = this.database && this.database.offer && this.database.offer(); - this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(offer.autoscaleMaxThroughput)); - this.autoPilotThroughput.setBaseline(offer.autoscaleMaxThroughput); - this.throughput.setBaseline(offer.manualThroughput); - } - - protected getTabsButtons(): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = []; - const label = "Save"; - if (this.saveSettingsButton.visible()) { - buttons.push({ - iconSrc: SaveIcon, - iconAlt: label, - onCommandClick: this.onSaveClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.saveSettingsButton.enabled(), - }); - } - - if (this.discardSettingsChangesButton.visible()) { - const label = "Discard"; - buttons.push({ - iconSrc: DiscardIcon, - iconAlt: label, - onCommandClick: this.onRevertClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.discardSettingsChangesButton.enabled(), - }); - } - return buttons; - } - - private _buildCommandBarOptions(): void { - ko.computed(() => - ko.toJSON([ - this.saveSettingsButton.visible, - this.saveSettingsButton.enabled, - this.discardSettingsChangesButton.visible, - this.discardSettingsChangesButton.enabled, - ]) - ).subscribe(() => this.updateNavbarWithTabsButtons()); - this.updateNavbarWithTabsButtons(); - } -} diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index b07b10a40..a7c2242c2 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -1,39 +1,36 @@ -import * as _ from "underscore"; -import * as Q from "q"; +import { stringifyNotebook, toJS } from "@nteract/commutable"; import * as ko from "knockout"; - -import * as ViewModels from "../../Contracts/ViewModels"; -import * as DataModels from "../../Contracts/DataModels"; -import TabsBase from "./TabsBase"; - -import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg"; -import CutIcon from "../../../images/notebook/Notebook-cut.svg"; -import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; -import PasteIcon from "../../../images/notebook/Notebook-paste.svg"; -import RunIcon from "../../../images/notebook/Notebook-run.svg"; -import RunAllIcon from "../../../images/notebook/Notebook-run-all.svg"; -import RestartIcon from "../../../images/notebook/Notebook-restart.svg"; -import SaveIcon from "../../../images/save-cosmos.svg"; +import * as Q from "q"; +import * as _ from "underscore"; import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg"; -import InterruptKernelIcon from "../../../images/notebook/Notebook-stop.svg"; -import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; +import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; +import CutIcon from "../../../images/notebook/Notebook-cut.svg"; +import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg"; +import PasteIcon from "../../../images/notebook/Notebook-paste.svg"; +import RestartIcon from "../../../images/notebook/Notebook-restart.svg"; +import RunAllIcon from "../../../images/notebook/Notebook-run-all.svg"; +import RunIcon from "../../../images/notebook/Notebook-run.svg"; +import { default as InterruptKernelIcon, default as KillKernelIcon } from "../../../images/notebook/Notebook-stop.svg"; +import SaveIcon from "../../../images/save-cosmos.svg"; import { Areas, ArmApiVersions } from "../../Common/Constants"; +import { configContext } from "../../ConfigContext"; +import * as DataModels from "../../Contracts/DataModels"; +import * as ViewModels from "../../Contracts/ViewModels"; +import { trackEvent } from "../../Shared/appInsights"; +import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; +import { userContext } from "../../UserContext"; +import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils"; +import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; +import Explorer from "../Explorer"; import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; -import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils"; import { KernelSpecsDisplay, NotebookClientV2 } from "../Notebook/NotebookClientV2"; -import { configContext } from "../../ConfigContext"; -import Explorer from "../Explorer"; +import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; import { NotebookContentItem } from "../Notebook/NotebookContentItem"; -import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; -import { toJS, stringifyNotebook } from "@nteract/commutable"; -import { appInsights } from "../../Shared/appInsights"; -import { userContext } from "../../UserContext"; import template from "./NotebookV2Tab.html"; +import TabsBase from "./TabsBase"; export interface NotebookTabOptions extends ViewModels.TabOptions { account: DataModels.DatabaseAccount; @@ -428,7 +425,7 @@ export default class NotebookTabV2 extends TabsBase { return; } - appInsights.trackEvent( + trackEvent( { name: "SparkPoolSelected" }, { subscriptionId: userContext.subscriptionId, diff --git a/src/Explorer/Tree/Database.ts b/src/Explorer/Tree/Database.ts index 27bb2b1e0..91df402b4 100644 --- a/src/Explorer/Tree/Database.ts +++ b/src/Explorer/Tree/Database.ts @@ -1,23 +1,22 @@ -import * as _ from "underscore"; import * as ko from "knockout"; -import * as ViewModels from "../../Contracts/ViewModels"; +import * as _ from "underscore"; import * as Constants from "../../Common/Constants"; +import { readCollections } from "../../Common/dataAccess/readCollections"; +import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer"; +import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; +import * as Logger from "../../Common/Logger"; +import { fetchPortalNotifications } from "../../Common/PortalNotifications"; import * as DataModels from "../../Contracts/DataModels"; +import * as ViewModels from "../../Contracts/ViewModels"; +import { IJunoResponse, JunoClient } from "../../Juno/JunoClient"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab"; +import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; +import { userContext } from "../../UserContext"; +import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; +import Explorer from "../Explorer"; +import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2"; import Collection from "./Collection"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; -import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; -import * as Logger from "../../Common/Logger"; -import Explorer from "../Explorer"; -import { readCollections } from "../../Common/dataAccess/readCollections"; -import { JunoClient, IJunoResponse } from "../../Juno/JunoClient"; -import { userContext } from "../../UserContext"; -import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer"; -import { fetchPortalNotifications } from "../../Common/PortalNotifications"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; export default class Database implements ViewModels.Database { public nodeKind: string; @@ -58,18 +57,13 @@ export default class Database implements ViewModels.Database { }); const pendingNotificationsPromise: Promise = this.getPendingThroughputSplitNotification(); - const useDatabaseSettingsTabV1 = userContext.features.enableDatabaseSettingsTabV1; - const tabKind: ViewModels.CollectionTabKind = useDatabaseSettingsTabV1 - ? ViewModels.CollectionTabKind.DatabaseSettings - : ViewModels.CollectionTabKind.DatabaseSettingsV2; + const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2; const matchingTabs = this.container.tabsManager.getTabs(tabKind, (tab) => tab.node?.id() === this.id()); - let settingsTab: DatabaseSettingsTab | DatabaseSettingsTabV2 = useDatabaseSettingsTabV1 - ? (matchingTabs?.[0] as DatabaseSettingsTab) - : (matchingTabs?.[0] as DatabaseSettingsTabV2); + let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2; + if (!settingsTab) { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { databaseName: this.id(), - dataExplorerArea: Constants.Areas.Tab, tabTitle: "Scale", }); @@ -77,9 +71,7 @@ export default class Database implements ViewModels.Database { (data: any) => { const pendingNotification: DataModels.Notification = data?.[0]; const tabOptions: ViewModels.TabOptions = { - tabKind: useDatabaseSettingsTabV1 - ? ViewModels.CollectionTabKind.DatabaseSettings - : ViewModels.CollectionTabKind.DatabaseSettingsV2, + tabKind, title: "Scale", tabPath: "", node: this, @@ -90,9 +82,7 @@ export default class Database implements ViewModels.Database { onLoadStartKey: startKey, onUpdateTabsButtons: this.container.onUpdateTabsButtons, }; - settingsTab = useDatabaseSettingsTabV1 - ? new DatabaseSettingsTab(tabOptions) - : new DatabaseSettingsTabV2(tabOptions); + settingsTab = new DatabaseSettingsTabV2(tabOptions); settingsTab.pendingNotification(pendingNotification); this.container.tabsManager.activateNewTab(settingsTab); }, diff --git a/src/Localization/en/SqlX.json b/src/Localization/en/SqlX.json index 465cba248..3c1d81dac 100644 --- a/src/Localization/en/SqlX.json +++ b/src/Localization/en/SqlX.json @@ -1,8 +1,8 @@ { "DedicatedGatewayDescription": "Provision a dedicated gateway cluster for your Azure Cosmos DB account. A dedicated gateway is compute that is a front-end to data in your Azure Cosmos DB account. Your dedicated gateway automatically includes the integrated cache, which can improve read performance.", "DedicatedGateway": "Dedicated Gateway", - "Enable": "Enable", - "Disable": "Disable", + "Provisioned": "Provisioned", + "Deprovisioned": "Deprovisioned", "LearnAboutDedicatedGateway": "Learn more about dedicated gateway.", "DeprovisioningDetailsText": "Learn more about deprovisioning the dedicated gateway.", "DedicatedGatewayPricing": "Learn more about dedicated gateway pricing.", diff --git a/src/Main.tsx b/src/Main.tsx index d3561242f..44c376dfa 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -234,7 +234,6 @@ const App: React.FunctionComponent = () => {
-
diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index eda8458ff..5a81ec699 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -2,7 +2,6 @@ export type Features = { readonly canExceedMaximumValue: boolean; readonly cosmosdb: boolean; readonly enableChangeFeedPolicy: boolean; - readonly enableDatabaseSettingsTabV1: boolean; readonly enableFixedCollectionWithSharedThroughput: boolean; readonly enableKOPanel: boolean; readonly enableNotebooks: boolean; @@ -40,7 +39,6 @@ export function extractFeatures(given = new URLSearchParams()): Features { canExceedMaximumValue: "true" === get("canexceedmaximumvalue"), cosmosdb: "true" === get("cosmosdb"), enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"), - enableDatabaseSettingsTabV1: "true" === get("enabledbsettingsv1"), enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"), enableKOPanel: "true" === get("enablekopanel"), enableNotebooks: "true" === get("enablenotebooks"), diff --git a/src/SelfServe/SelfServe.tsx b/src/SelfServe/SelfServe.tsx index 062d07289..2550f465c 100644 --- a/src/SelfServe/SelfServe.tsx +++ b/src/SelfServe/SelfServe.tsx @@ -23,9 +23,11 @@ const loadTranslationFile = async (className: string): Promise => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let translations: any; try { - translations = await import(`../Localization/${language}/${fileName}`); + translations = await import( + /* webpackChunkName: "Localization-[request]" */ `../Localization/${language}/${fileName}` + ); } catch (e) { - translations = await import(`../Localization/en/${fileName}`); + translations = await import(/* webpackChunkName: "Localization-en-[request]" */ `../Localization/en/${fileName}`); } i18n.addResourceBundle(language, className, translations.default, true); }; @@ -39,12 +41,12 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise => { subscriptionId: inputs.subscriptionId, }); + if (i18n.isInitialized) { + await displaySelfServeComponent(selfServeType); + } else { + i18n.on("initialized", async () => { + await displaySelfServeComponent(selfServeType); + }); + } +}; + +const displaySelfServeComponent = async (selfServeType: SelfServeType): Promise => { const descriptor = await getDescriptor(selfServeType); ReactDOM.render(renderComponent(descriptor), document.getElementById("selfServeContent")); }; diff --git a/src/SelfServe/SelfServeTelemetryProcessor.ts b/src/SelfServe/SelfServeTelemetryProcessor.ts index f26b05a1e..0cdf81a15 100644 --- a/src/SelfServe/SelfServeTelemetryProcessor.ts +++ b/src/SelfServe/SelfServeTelemetryProcessor.ts @@ -1,69 +1,24 @@ -import { sendMessage } from "../Common/MessageHandler"; -import { configContext } from "../ConfigContext"; import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts"; -import { appInsights } from "../Shared/appInsights"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; -import { userContext } from "../UserContext"; +import { trace, traceCancel, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor"; import { SelfServeTelemetryMessage } from "./SelfServeTypes"; -const action = Action.SelfServe; - -export const trace = (data: SelfServeTelemetryMessage): void => { - sendSelfServeTelemetryMessage(ActionModifiers.Mark, data); - appInsights.trackEvent({ name: Action[action] }, decorateData(data, ActionModifiers.Mark)); +export const selfServeTrace = (data: SelfServeTelemetryMessage): void => { + trace(Action.SelfServe, ActionModifiers.Mark, data, SelfServeMessageTypes.TelemetryInfo); }; -export const traceStart = (data: SelfServeTelemetryMessage): number => { - const timestamp: number = Date.now(); - sendSelfServeTelemetryMessage(ActionModifiers.Start, data); - appInsights.startTrackEvent(Action[action]); - return timestamp; +export const selfServeTraceStart = (data: SelfServeTelemetryMessage): number => { + return traceStart(Action.SelfServe, data, SelfServeMessageTypes.TelemetryInfo); }; -export const traceSuccess = (data: SelfServeTelemetryMessage, timestamp?: number): void => { - sendSelfServeTelemetryMessage(ActionModifiers.Success, data, timestamp || Date.now()); - appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Success)); +export const selfServeTraceSuccess = (data: SelfServeTelemetryMessage, timestamp?: number): void => { + traceSuccess(Action.SelfServe, data, timestamp, SelfServeMessageTypes.TelemetryInfo); }; -export const traceFailure = (data: SelfServeTelemetryMessage, timestamp?: number): void => { - sendSelfServeTelemetryMessage(ActionModifiers.Failed, data, timestamp || Date.now()); - appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Failed)); +export const selfServeTraceFailure = (data: SelfServeTelemetryMessage, timestamp?: number): void => { + traceFailure(Action.SelfServe, data, timestamp, SelfServeMessageTypes.TelemetryInfo); }; -export const traceCancel = (data: SelfServeTelemetryMessage, timestamp?: number): void => { - sendSelfServeTelemetryMessage(ActionModifiers.Cancel, data, timestamp || Date.now()); - appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Cancel)); -}; - -const sendSelfServeTelemetryMessage = ( - actionModifier: string, - data: SelfServeTelemetryMessage, - timeStamp?: number -): void => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const dataToSend: any = { - type: SelfServeMessageTypes.TelemetryInfo, - data: { - action: Action[action], - actionModifier: actionModifier, - data: JSON.stringify(decorateData(data)), - }, - }; - if (timeStamp) { - dataToSend.data.timeStamp = timeStamp; - } - sendMessage(dataToSend); -}; - -const decorateData = (data: SelfServeTelemetryMessage, actionModifier?: string) => { - return { - databaseAccountName: userContext.databaseAccount?.name, - defaultExperience: userContext.defaultExperience, - authType: userContext.authType, - subscriptionId: userContext.subscriptionId, - platform: configContext.platform, - env: process.env.NODE_ENV, - actionModifier, - ...data, - } as { [key: string]: string }; +export const selfServeTraceCancel = (data: SelfServeTelemetryMessage, timestamp?: number): void => { + traceCancel(Action.SelfServe, data, timestamp, SelfServeMessageTypes.TelemetryInfo); }; diff --git a/src/SelfServe/SelfServeTypes.ts b/src/SelfServe/SelfServeTypes.ts index a86b74c04..d8393ba19 100644 --- a/src/SelfServe/SelfServeTypes.ts +++ b/src/SelfServe/SelfServeTypes.ts @@ -1,3 +1,5 @@ +import { TelemetryData } from "../Shared/Telemetry/TelemetryProcessor"; + interface BaseInput { dataFieldName: string; errorMessage?: string; @@ -158,8 +160,6 @@ export interface RefreshParams { retryIntervalInMs: number; } -export interface SelfServeTelemetryMessage { +export interface SelfServeTelemetryMessage extends TelemetryData { selfServeClassName: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: any; } diff --git a/src/SelfServe/SqlX/SqlX.tsx b/src/SelfServe/SqlX/SqlX.tsx index 41d855fee..b27f69e69 100644 --- a/src/SelfServe/SqlX/SqlX.tsx +++ b/src/SelfServe/SqlX/SqlX.tsx @@ -1,5 +1,5 @@ import { IsDisplayable, OnChange, RefreshOptions, Values } from "../Decorators"; -import { trace } from "../SelfServeTelemetryProcessor"; +import { selfServeTrace } from "../SelfServeTelemetryProcessor"; import { ChoiceItem, Description, @@ -177,7 +177,7 @@ export default class SqlX extends SelfServeBaseClass { currentValues: Map, baselineValues: Map ): Promise => { - trace({ selfServeClassName: "SqlX" }); + selfServeTrace({ selfServeClassName: "SqlX" }); const dedicatedGatewayCurrentlyEnabled = currentValues.get("enableDedicatedGateway")?.value as boolean; const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean; @@ -234,7 +234,7 @@ export default class SqlX extends SelfServeBaseClass { portalNotification: { initialize: { titleTKey: "CreateInitializeTitle", - messageTKey: "CreateInitializeTitle", + messageTKey: "CreateInitializeMessage", }, success: { titleTKey: "CreateSuccessTitle", diff --git a/src/Shared/Telemetry/TelemetryProcessor.ts b/src/Shared/Telemetry/TelemetryProcessor.ts index e261f3635..855e3a56f 100644 --- a/src/Shared/Telemetry/TelemetryProcessor.ts +++ b/src/Shared/Telemetry/TelemetryProcessor.ts @@ -1,15 +1,25 @@ import { sendMessage } from "../../Common/MessageHandler"; import { configContext } from "../../ConfigContext"; import { MessageTypes } from "../../Contracts/ExplorerContracts"; +import { SelfServeMessageTypes } from "../../Contracts/SelfServeContracts"; import { userContext } from "../../UserContext"; -import { appInsights } from "../appInsights"; +import { startTrackEvent, stopTrackEvent, trackEvent } from "../appInsights"; import { Action, ActionModifiers } from "./TelemetryConstants"; -type TelemetryData = { [key: string]: unknown }; +// Right now, the ExplorerContracts has MessageTypes as a numeric enum (TelemetryInfo = 0) while the SelfServeContracts +// has MessageTypes as a string enum (TelemetryInfo = "TelemetryInfo"). We should move to string enums for all use cases. +type TelemetryType = MessageTypes.TelemetryInfo | SelfServeMessageTypes.TelemetryInfo; -export function trace(action: Action, actionModifier: string = ActionModifiers.Mark, data: TelemetryData = {}): void { +export type TelemetryData = { [key: string]: unknown }; + +export function trace( + action: Action, + actionModifier: string = ActionModifiers.Mark, + data: TelemetryData = {}, + type: TelemetryType = MessageTypes.TelemetryInfo +): void { sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: actionModifier, @@ -17,13 +27,17 @@ export function trace(action: Action, actionModifier: string = ActionModifiers.M }, }); - appInsights.trackEvent({ name: Action[action] }, decorateData(data, actionModifier)); + trackEvent({ name: Action[action] }, decorateData(data, actionModifier)); } -export function traceStart(action: Action, data?: TelemetryData): number { +export function traceStart( + action: Action, + data?: TelemetryData, + type: TelemetryType = MessageTypes.TelemetryInfo +): number { const timestamp: number = Date.now(); sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: ActionModifiers.Start, @@ -32,13 +46,18 @@ export function traceStart(action: Action, data?: TelemetryData): number { }, }); - appInsights.startTrackEvent(Action[action]); + startTrackEvent(Action[action]); return timestamp; } -export function traceSuccess(action: Action, data?: TelemetryData, timestamp?: number): void { +export function traceSuccess( + action: Action, + data?: TelemetryData, + timestamp?: number, + type: TelemetryType = MessageTypes.TelemetryInfo +): void { sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: ActionModifiers.Success, @@ -47,12 +66,17 @@ export function traceSuccess(action: Action, data?: TelemetryData, timestamp?: n }, }); - appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Success)); + stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Success)); } -export function traceFailure(action: Action, data?: TelemetryData, timestamp?: number): void { +export function traceFailure( + action: Action, + data?: TelemetryData, + timestamp?: number, + type: TelemetryType = MessageTypes.TelemetryInfo +): void { sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: ActionModifiers.Failed, @@ -61,12 +85,17 @@ export function traceFailure(action: Action, data?: TelemetryData, timestamp?: n }, }); - appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Failed)); + stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Failed)); } -export function traceCancel(action: Action, data?: TelemetryData, timestamp?: number): void { +export function traceCancel( + action: Action, + data?: TelemetryData, + timestamp?: number, + type: TelemetryType = MessageTypes.TelemetryInfo +): void { sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: ActionModifiers.Cancel, @@ -75,13 +104,18 @@ export function traceCancel(action: Action, data?: TelemetryData, timestamp?: nu }, }); - appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Cancel)); + stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Cancel)); } -export function traceOpen(action: Action, data?: TelemetryData, timestamp?: number): number { +export function traceOpen( + action: Action, + data?: TelemetryData, + timestamp?: number, + type: TelemetryType = MessageTypes.TelemetryInfo +): number { const validTimestamp = timestamp || Date.now(); sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: ActionModifiers.Open, @@ -90,14 +124,19 @@ export function traceOpen(action: Action, data?: TelemetryData, timestamp?: numb }, }); - appInsights.startTrackEvent(Action[action]); + startTrackEvent(Action[action]); return validTimestamp; } -export function traceMark(action: Action, data?: TelemetryData, timestamp?: number): number { +export function traceMark( + action: Action, + data?: TelemetryData, + timestamp?: number, + type: TelemetryType = MessageTypes.TelemetryInfo +): number { const validTimestamp = timestamp || Date.now(); sendMessage({ - type: MessageTypes.TelemetryInfo, + type: type, data: { action: Action[action], actionModifier: ActionModifiers.Mark, @@ -106,7 +145,7 @@ export function traceMark(action: Action, data?: TelemetryData, timestamp?: numb }, }); - appInsights.startTrackEvent(Action[action]); + startTrackEvent(Action[action]); return validTimestamp; } diff --git a/src/Shared/appInsights.ts b/src/Shared/appInsights.ts index dd18682d5..bc5bdb454 100644 --- a/src/Shared/appInsights.ts +++ b/src/Shared/appInsights.ts @@ -1,5 +1,8 @@ import { ApplicationInsights } from "@microsoft/applicationinsights-web"; +// TODO: Remove this after 06/01/21. +// This points to an old app insights instance that is difficult to access +// For now we are sending data to two instances of app insights const appInsights = new ApplicationInsights({ config: { instrumentationKey: "fa645d97-6237-4656-9559-0ee0cb55ee49", @@ -7,7 +10,38 @@ const appInsights = new ApplicationInsights({ disableCorrelationHeaders: true, }, }); -appInsights.loadAppInsights(); -appInsights.trackPageView(); // Manually call trackPageView to establish the current user/session/pageview -export { appInsights }; +const appInsights2 = new ApplicationInsights({ + config: { + instrumentationKey: "023d2c39-8f86-468e-bb8f-bcaebd9025c7", + disableFetchTracking: false, + disableCorrelationHeaders: true, + }, +}); + +appInsights.loadAppInsights(); +appInsights.trackPageView(); +appInsights2.loadAppInsights(); +appInsights2.trackPageView(); + +const trackEvent: typeof appInsights.trackEvent = (...args) => { + appInsights.trackEvent(...args); + appInsights2.trackEvent(...args); +}; + +const startTrackEvent: typeof appInsights.startTrackEvent = (...args) => { + appInsights.startTrackEvent(...args); + appInsights2.startTrackEvent(...args); +}; + +const stopTrackEvent: typeof appInsights.stopTrackEvent = (...args) => { + appInsights.stopTrackEvent(...args); + appInsights2.stopTrackEvent(...args); +}; + +const trackTrace: typeof appInsights.trackTrace = (...args) => { + appInsights.trackTrace(...args); + appInsights2.trackTrace(...args); +}; + +export { trackEvent, startTrackEvent, stopTrackEvent, trackTrace }; diff --git a/src/UserContext.ts b/src/UserContext.ts index f8d2970e9..d27c130af 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -3,6 +3,7 @@ import { DatabaseAccount } from "./Contracts/DataModels"; import { SubscriptionType } from "./Contracts/SubscriptionType"; import { DefaultAccountExperienceType } from "./DefaultAccountExperienceType"; import { extractFeatures, Features } from "./Platform/Hosted/extractFeatures"; +import { CollectionCreation } from "./Shared/Constants"; interface UserContext { readonly authType?: AuthType; @@ -24,6 +25,8 @@ interface UserContext { readonly isTryCosmosDBSubscription?: boolean; readonly portalEnv?: PortalEnv; readonly features: Features; + readonly addCollectionFlight: string; + readonly hasWriteAccess: boolean; } type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra"; @@ -33,10 +36,13 @@ const features = extractFeatures(); const { enableSDKoperations: useSDKOperations } = features; const userContext: UserContext = { + hasWriteAccess: true, isTryCosmosDBSubscription: false, portalEnv: "prod", features, useSDKOperations, + addCollectionFlight: CollectionCreation.DefaultAddCollectionDefaultFlight, + subscriptionType: CollectionCreation.DefaultSubscriptionType, }; function updateUserContext(newContext: Partial): void { diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 80dd38a68..f1fce5827 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -23,6 +23,7 @@ import { getDatabaseAccountKindFromExperience, getDatabaseAccountPropertiesFromMetadata, } from "../Platform/Hosted/HostedUtils"; +import { CollectionCreation } from "../Shared/Constants"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { PortalEnv, updateUserContext } from "../UserContext"; import { listKeys } from "../Utils/arm/generatedClients/2020-04-01/databaseAccounts"; @@ -254,6 +255,9 @@ async function configurePortal(explorerParams: ExplorerParams): Promise