mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-29 05:41:40 +00:00
Compare commits
24 Commits
user/swvis
...
remove-rup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
123ec2e45c | ||
|
|
2f4abfa796 | ||
|
|
79769e9689 | ||
|
|
542abf4d3a | ||
|
|
4bee46ce55 | ||
|
|
fe58722002 | ||
|
|
94ff6b3e81 | ||
|
|
b4219e2994 | ||
|
|
294270b6aa | ||
|
|
e4bab1de4b | ||
|
|
703ceacd3f | ||
|
|
734df3dd18 | ||
|
|
1e19f02fd7 | ||
|
|
24b5b754ca | ||
|
|
e09730d782 | ||
|
|
09a95fded4 | ||
|
|
7ffa18a190 | ||
|
|
30353c26f3 | ||
|
|
34d8704071 | ||
|
|
23714831bd | ||
|
|
9a5d46b6e0 | ||
|
|
b9245101bc | ||
|
|
274deb13be | ||
|
|
9d50577800 |
@@ -42,6 +42,13 @@ module.exports = {
|
||||
"no-null/no-null": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||
eqeqeq: "error"
|
||||
eqeqeq: "error",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
|
||||
message: "Do not use JSON.stringify(error). It will print '{}'"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
# Add steps that build, run tests, deploy, and more:
|
||||
# https://aka.ms/yaml
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: "ubuntu-latest"
|
||||
|
||||
|
||||
@@ -3023,3 +3023,7 @@ settings-pane {
|
||||
.infoBoxContent a {
|
||||
color: @AccentMediumHigh
|
||||
}
|
||||
|
||||
.collapsibleSection :hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
110
package-lock.json
generated
110
package-lock.json
generated
@@ -76,7 +76,6 @@
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
||||
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@babel/generator": "^7.9.0",
|
||||
@@ -99,8 +98,7 @@
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"dev": true
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7203,11 +7201,10 @@
|
||||
}
|
||||
},
|
||||
"create-react-class": {
|
||||
"version": "15.6.3",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
|
||||
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
|
||||
"version": "15.7.0",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||
"requires": {
|
||||
"fbjs": "^0.8.9",
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
@@ -8504,24 +8501,6 @@
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"requires": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
@@ -9667,27 +9646,6 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"fbjs": {
|
||||
"version": "0.8.17",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
|
||||
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
|
||||
"requires": {
|
||||
"core-js": "^1.0.0",
|
||||
"isomorphic-fetch": "^2.1.1",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
@@ -11888,26 +11846,6 @@
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
||||
"requires": {
|
||||
"node-fetch": "^1.0.1",
|
||||
"whatwg-fetch": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||
"requires": {
|
||||
"encoding": "^0.1.11",
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
@@ -13406,36 +13344,6 @@
|
||||
"micromatch": "^3.1.10",
|
||||
"pretty-format": "^24.9.0",
|
||||
"realpath-native": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": {
|
||||
"version": "7.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
|
||||
"integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/generator": "^7.11.6",
|
||||
"@babel/helper-module-transforms": "^7.11.0",
|
||||
"@babel/helpers": "^7.10.4",
|
||||
"@babel/parser": "^7.11.5",
|
||||
"@babel/template": "^7.10.4",
|
||||
"@babel/traverse": "^7.11.5",
|
||||
"@babel/types": "^7.11.5",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.1",
|
||||
"json5": "^2.1.2",
|
||||
"lodash": "^4.17.19",
|
||||
"resolve": "^1.3.2",
|
||||
"semver": "^5.4.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-dev-server": {
|
||||
@@ -16619,6 +16527,8 @@
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
@@ -18246,7 +18156,8 @@
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||
"dev": true
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
@@ -20006,11 +19917,6 @@
|
||||
"free-style": "3.1.0"
|
||||
}
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.22",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"offerThroughput": 400,
|
||||
"databaseLevelThroughput": false,
|
||||
"collectionId": "Persons",
|
||||
"rupmEnabled": false,
|
||||
"partitionKey": { "kind": "Hash", "paths": ["/name"] },
|
||||
"data": [
|
||||
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AutopilotTier } from "../Contracts/DataModels";
|
||||
import { HashMap } from "./HashMap";
|
||||
|
||||
export class AuthorizationEndpoints {
|
||||
@@ -109,7 +108,6 @@ export class CapabilityNames {
|
||||
export class Features {
|
||||
public static readonly cosmosdb = "cosmosdb";
|
||||
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
|
||||
public static readonly enableRupm = "enablerupm";
|
||||
public static readonly executeSproc = "dataexplorerexecutesproc";
|
||||
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
||||
public static readonly enableTtl = "enablettl";
|
||||
@@ -124,7 +122,6 @@ export class Features {
|
||||
public static readonly notebookBasePath = "notebookbasepath";
|
||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
||||
public static readonly ttl90Days = "ttl90days";
|
||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||
@@ -133,6 +130,7 @@ export class Features {
|
||||
// flight names returned from the portal are always lowercase
|
||||
export class Flights {
|
||||
public static readonly SettingsV2 = "settingsv2";
|
||||
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
@@ -179,12 +177,6 @@ export class CassandraBackend {
|
||||
public static readonly schemaApi: string = "api/cassandra/schema";
|
||||
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
|
||||
}
|
||||
|
||||
export class RUPMStates {
|
||||
public static on: string = "on";
|
||||
public static off: string = "off";
|
||||
}
|
||||
|
||||
export class Queries {
|
||||
public static CustomPageOption: string = "custom";
|
||||
public static UnlimitedPageOption: string = "unlimited";
|
||||
@@ -261,7 +253,6 @@ export class HttpHeaders {
|
||||
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
||||
public static autoPilotThroughput = "autoscaleSettings";
|
||||
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
||||
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
|
||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
||||
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||
@@ -406,54 +397,6 @@ export enum ConflictOperationType {
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export class AutoPilot {
|
||||
public static tier1Text: string = "4,000 RU/s";
|
||||
public static tier2Text: string = "20,000 RU/s";
|
||||
public static tier3Text: string = "100,000 RU/s";
|
||||
public static tier4Text: string = "500,000 RU/s";
|
||||
|
||||
public static tierText = {
|
||||
[AutopilotTier.Tier1]: "Tier 1",
|
||||
[AutopilotTier.Tier2]: "Tier 2",
|
||||
[AutopilotTier.Tier3]: "Tier 3",
|
||||
[AutopilotTier.Tier4]: "Tier 4"
|
||||
};
|
||||
|
||||
public static tierMaxRus = {
|
||||
[AutopilotTier.Tier1]: 2000,
|
||||
[AutopilotTier.Tier2]: 20000,
|
||||
[AutopilotTier.Tier3]: 100000,
|
||||
[AutopilotTier.Tier4]: 500000
|
||||
};
|
||||
|
||||
public static tierMinRus = {
|
||||
[AutopilotTier.Tier1]: 0,
|
||||
[AutopilotTier.Tier2]: 0,
|
||||
[AutopilotTier.Tier3]: 0,
|
||||
[AutopilotTier.Tier4]: 0
|
||||
};
|
||||
|
||||
public static tierStorageInGB = {
|
||||
[AutopilotTier.Tier1]: 50,
|
||||
[AutopilotTier.Tier2]: 200,
|
||||
[AutopilotTier.Tier3]: 1000,
|
||||
[AutopilotTier.Tier4]: 5000
|
||||
};
|
||||
}
|
||||
|
||||
export class DataExplorerVersions {
|
||||
public static readonly v_1_0_0: string = "1.0.0";
|
||||
public static readonly v_1_0_1: string = "1.0.1";
|
||||
}
|
||||
|
||||
export class DataExplorerFeatures {
|
||||
public static offerCache: string = "OfferCache";
|
||||
}
|
||||
|
||||
export const DataExplorerFeaturesVersions: any = {
|
||||
OfferCache: DataExplorerVersions.v_1_0_1
|
||||
};
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
||||
const result = JSON.parse(await response.json());
|
||||
return result;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`);
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${error.message}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,22 +168,6 @@ export function deleteConflict(
|
||||
);
|
||||
}
|
||||
|
||||
export function refreshCachedOffers(): Q.Promise<void> {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||
} else {
|
||||
return Q();
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
||||
} else {
|
||||
return Q();
|
||||
}
|
||||
}
|
||||
|
||||
export function queryConflicts(
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Q from "q";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "./Constants";
|
||||
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||
import * as Logger from "./Logger";
|
||||
import { handleError } from "./ErrorHandlingUtils";
|
||||
|
||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||
export function queryDocuments(
|
||||
@@ -59,13 +56,11 @@ export function executeStoredProcedure(
|
||||
);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
||||
error
|
||||
)}`
|
||||
handleError(
|
||||
error,
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`,
|
||||
"ExecuteStoredProcedure"
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -93,9 +88,7 @@ export function queryDocumentsPage(
|
||||
deferred.resolve(result);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage");
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -116,9 +109,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
||||
deferred.resolve(document);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -144,9 +135,7 @@ export function updateDocument(
|
||||
deferred.resolve(updatedDocument);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument");
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -168,11 +157,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
||||
deferred.resolve(savedDocument);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(
|
||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument");
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -194,9 +179,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
|
||||
deferred.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument");
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -222,9 +205,7 @@ export function deleteConflict(
|
||||
deferred.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict");
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -234,11 +215,3 @@ export function deleteConflict(
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
export function refreshCachedResources(options: any = {}): Q.Promise<void> {
|
||||
return DataAccessUtilityBase.refreshCachedResources(options);
|
||||
}
|
||||
|
||||
export function refreshCachedOffers(): Q.Promise<void> {
|
||||
return DataAccessUtilityBase.refreshCachedOffers();
|
||||
}
|
||||
|
||||
11
src/Common/ErrorHandlingUtils.ts
Normal file
11
src/Common/ErrorHandlingUtils.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { CosmosError, sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "./Logger";
|
||||
import { replaceKnownError } from "./ErrorParserUtility";
|
||||
|
||||
export const handleError = (error: CosmosError, consoleErrorPrefix: string, area: string): void => {
|
||||
const sanitizedErrorMsg = replaceKnownError(error.message);
|
||||
logConsoleError(`${consoleErrorPrefix}:\n ${sanitizedErrorMsg}`);
|
||||
logError(sanitizedErrorMsg, area, error.code);
|
||||
sendNotificationForError(error);
|
||||
};
|
||||
@@ -1,41 +1,41 @@
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
|
||||
const notificationsPath = () => {
|
||||
switch (configContext.platform) {
|
||||
case Platform.Hosted:
|
||||
return "/api/guest/notifications";
|
||||
case Platform.Portal:
|
||||
return "/api/notifications";
|
||||
default:
|
||||
throw new Error(`Unknown platform: ${configContext.platform}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const databaseAccount = userContext.databaseAccount;
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
||||
databaseAccount.name
|
||||
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
return (await response.json()) as DataModels.Notification[];
|
||||
};
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
|
||||
const notificationsPath = () => {
|
||||
switch (configContext.platform) {
|
||||
case Platform.Hosted:
|
||||
return "/api/guest/notifications";
|
||||
case Platform.Portal:
|
||||
return "/api/notifications";
|
||||
default:
|
||||
throw new Error(`Unknown platform: ${configContext.platform}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const databaseAccount = userContext.databaseAccount;
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
||||
databaseAccount.name
|
||||
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
return (await response.json()) as DataModels.Notification[];
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ export class QueriesClient {
|
||||
return Promise.resolve(collection);
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
||||
@@ -163,7 +163,7 @@ export class QueriesClient {
|
||||
return Promise.resolve(queries);
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to fetch saved queries: ${stringifiedError}`
|
||||
@@ -175,7 +175,7 @@ export class QueriesClient {
|
||||
},
|
||||
(error: any) => {
|
||||
// should never get into this state but we handle this regardless
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to fetch saved queries: ${stringifiedError}`
|
||||
@@ -232,7 +232,7 @@ export class QueriesClient {
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||
@@ -23,21 +22,19 @@ import {
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createDatabase } from "./createDatabase";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
let collection: DataModels.Collection;
|
||||
const clearMessage = logConsoleProgress(
|
||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||
);
|
||||
try {
|
||||
let collection: DataModels.Collection;
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (params.createNewDatabase) {
|
||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||
@@ -54,18 +51,15 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
||||
} else {
|
||||
collection = await createCollectionWithSDK(params);
|
||||
}
|
||||
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
return collection;
|
||||
} catch (error) {
|
||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
clearMessage();
|
||||
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
await refreshCachedResources();
|
||||
clearMessage();
|
||||
return collection;
|
||||
};
|
||||
|
||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
|
||||
@@ -24,36 +24,28 @@ import {
|
||||
createUpdateGremlinDatabase,
|
||||
getGremlinDatabase
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
let database: DataModels.Database;
|
||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
||||
try {
|
||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||
throw new Error("Creating database resources is not allowed for tables accounts");
|
||||
}
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
database = await createDatabaseWithARM(params);
|
||||
} else {
|
||||
database = await createDatabaseWithSDK(params);
|
||||
}
|
||||
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
|
||||
? createDatabaseWithARM(params)
|
||||
: createDatabaseWithSDK(params));
|
||||
|
||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||
return database;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
||||
sendNotificationForError(error);
|
||||
clearMessage();
|
||||
handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||
await refreshCachedResources();
|
||||
await refreshCachedOffers();
|
||||
clearMessage();
|
||||
return database;
|
||||
}
|
||||
|
||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
|
||||
@@ -1,28 +1,78 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import {
|
||||
SqlStoredProcedureCreateUpdateParameters,
|
||||
SqlStoredProcedureResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import {
|
||||
createUpdateSqlStoredProcedure,
|
||||
getSqlStoredProcedure
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createStoredProcedure(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
storedProcedure: StoredProcedureDefinition
|
||||
): Promise<StoredProcedureDefinition & Resource> {
|
||||
let createdStoredProcedure: StoredProcedureDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
try {
|
||||
const getResponse = await getSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
storedProcedure.id
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(
|
||||
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: storedProcedure as SqlStoredProcedureResource,
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
storedProcedure.id,
|
||||
createSprocParams
|
||||
);
|
||||
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedures.create(storedProcedure);
|
||||
createdStoredProcedure = response.resource;
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return createdStoredProcedure;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,76 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import {
|
||||
SqlTriggerCreateUpdateParameters,
|
||||
SqlTriggerResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createTrigger(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
trigger: TriggerDefinition
|
||||
): Promise<TriggerDefinition & Resource> {
|
||||
let createdTrigger: TriggerDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
try {
|
||||
const getResponse = await getSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
trigger.id
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: trigger as SqlTriggerResource,
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
const rpResponse = await createUpdateSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
trigger.id,
|
||||
createTriggerParams
|
||||
);
|
||||
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.triggers.create(trigger);
|
||||
createdTrigger = response.resource;
|
||||
return response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`);
|
||||
logError(error.message, "CreateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return createdTrigger;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,82 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import {
|
||||
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||
SqlUserDefinedFunctionResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import {
|
||||
createUpdateSqlUserDefinedFunction,
|
||||
getSqlUserDefinedFunction
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createUserDefinedFunction(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
userDefinedFunction: UserDefinedFunctionDefinition
|
||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
try {
|
||||
const getResponse = await getSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
userDefinedFunction.id
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(
|
||||
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
userDefinedFunction.id,
|
||||
createUDFParams
|
||||
);
|
||||
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
||||
createdUserDefinedFunction = response.resource;
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(
|
||||
error,
|
||||
`Error while creating user defined function ${userDefinedFunction.id}`,
|
||||
"CreateUserupdateUserDefinedFunction"
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return createdUserDefinedFunction;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { sendCachedDataMessage } from "../MessageHandler";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
|
||||
describe("deleteCollection", () => {
|
||||
@@ -18,7 +17,6 @@ describe("deleteCollection", () => {
|
||||
} as DatabaseAccount,
|
||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||
});
|
||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("should call ARM if logged in with AAD", async () => {
|
||||
|
||||
@@ -5,12 +5,10 @@ import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-0
|
||||
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { client } from "../CosmosClient";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
|
||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||
@@ -23,15 +21,13 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
||||
.container(collectionId)
|
||||
.delete();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||
clearMessage();
|
||||
await refreshCachedResources();
|
||||
}
|
||||
|
||||
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { sendCachedDataMessage } from "../MessageHandler";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
|
||||
describe("deleteDatabase", () => {
|
||||
@@ -18,7 +17,6 @@ describe("deleteDatabase", () => {
|
||||
} as DatabaseAccount,
|
||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||
});
|
||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("should call ARM if logged in with AAD", async () => {
|
||||
|
||||
@@ -4,12 +4,10 @@ import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/s
|
||||
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { client } from "../CosmosClient";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||
@@ -25,15 +23,13 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||
.database(databaseId)
|
||||
.delete();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteDatabase", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||
clearMessage();
|
||||
await refreshCachedResources();
|
||||
}
|
||||
|
||||
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function deleteStoredProcedure(
|
||||
databaseId: string,
|
||||
@@ -10,17 +13,30 @@ export async function deleteStoredProcedure(
|
||||
): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
||||
try {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedure(storedProcedureId)
|
||||
.delete();
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
await deleteSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
storedProcedureId
|
||||
);
|
||||
} else {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedure(storedProcedureId)
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
||||
try {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.trigger(triggerId)
|
||||
.delete();
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
await deleteSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
triggerId
|
||||
);
|
||||
} else {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.trigger(triggerId)
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
||||
try {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunction(id)
|
||||
.delete();
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
await deleteSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
id
|
||||
);
|
||||
} else {
|
||||
await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunction(id)
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
||||
let collection: DataModels.Collection;
|
||||
@@ -14,9 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
|
||||
.read();
|
||||
collection = response.resource as DataModels.Collection;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying container ${collectionId}`, "ReadCollection");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
|
||||
@@ -4,15 +4,14 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
|
||||
import { HttpHeaders } from "../Constants";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { readOffers } from "./readOffers";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export const readCollectionOffer = async (
|
||||
@@ -57,9 +56,7 @@ export const readCollectionOffer = async (
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying offer for collection ${params.collectionId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollectionOffer", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -5,9 +5,8 @@ import { ContainerDefinition, Resource } from "@azure/cosmos";
|
||||
import { HttpHeaders } from "../Constants";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
interface ResourceWithStatistics {
|
||||
statistics: DataModels.Statistic[];
|
||||
@@ -38,9 +37,7 @@ export const readCollectionQuotaInfo = async (
|
||||
|
||||
return quota;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -2,18 +2,16 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||
let collections: DataModels.Collection[];
|
||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||
try {
|
||||
if (
|
||||
@@ -22,22 +20,20 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
collections = await readCollectionsWithARM(databaseId);
|
||||
} else {
|
||||
const sdkResponse = await client()
|
||||
.database(databaseId)
|
||||
.containers.readAll()
|
||||
.fetchAll();
|
||||
collections = sdkResponse.resources as DataModels.Collection[];
|
||||
return await readCollectionsWithARM(databaseId);
|
||||
}
|
||||
|
||||
const sdkResponse = await client()
|
||||
.database(databaseId)
|
||||
.containers.readAll()
|
||||
.fetchAll();
|
||||
return sdkResponse.resources as DataModels.Collection[];
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollections", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying containers for database ${databaseId}`, "ReadCollections");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
clearMessage();
|
||||
return collections;
|
||||
}
|
||||
|
||||
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
||||
|
||||
@@ -8,10 +8,9 @@ import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-
|
||||
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { readOffers } from "./readOffers";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export const readDatabaseOffer = async (
|
||||
@@ -48,9 +47,7 @@ export const readDatabaseOffer = async (
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying offer for database ${params.databaseId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying offer for database ${params.databaseId}`, "ReadDatabaseOffer");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -2,24 +2,23 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||
return [{ id: "TablesDB" } as DataModels.Database];
|
||||
}
|
||||
|
||||
let databases: DataModels.Database[];
|
||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
databases = await readDatabasesWithARM();
|
||||
} else {
|
||||
const sdkResponse = await client()
|
||||
@@ -28,9 +27,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||
databases = sdkResponse.resources as DataModels.Database[];
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadDatabases", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying databases`, "ReadDatabases");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
|
||||
58
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
58
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "../Constants";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { AuthType } from "../../AuthType";
|
||||
|
||||
export async function readMongoDBCollectionThroughRP(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<MongoDBCollectionResource> {
|
||||
if (window.authType !== AuthType.AAD) {
|
||||
return undefined;
|
||||
}
|
||||
let collection: MongoDBCollectionResource;
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const accountName = userContext.databaseAccount.name;
|
||||
|
||||
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||
try {
|
||||
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
collection = response.properties.resource;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return collection;
|
||||
}
|
||||
|
||||
export async function getMongoDBCollectionIndexTransformationProgress(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<number> {
|
||||
if (window.authType !== AuthType.AAD) {
|
||||
return undefined;
|
||||
}
|
||||
let indexTransformationPercentage: number;
|
||||
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.read({ populateQuotaInfo: true });
|
||||
|
||||
indexTransformationPercentage = parseInt(
|
||||
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return indexTransformationPercentage;
|
||||
}
|
||||
@@ -1,26 +1,10 @@
|
||||
import { Offer } from "../../Contracts/DataModels";
|
||||
import { ClientDefaults } from "../Constants";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
import { Platform, configContext } from "../../ConfigContext";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendCachedDataMessage } from "../MessageHandler";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
export const readOffers = async (): Promise<Offer[]> => {
|
||||
const clearMessage = logConsoleProgress(`Querying offers`);
|
||||
try {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
||||
userContext.databaseAccount.id,
|
||||
ClientDefaults.portalCacheTimeoutMs
|
||||
]);
|
||||
}
|
||||
} catch (error) {
|
||||
// If error getting cached Offers, continue on and read via SDK
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client()
|
||||
@@ -33,9 +17,7 @@ export const readOffers = async (): Promise<Offer[]> => {
|
||||
return [];
|
||||
}
|
||||
|
||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadOffers", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying offers`, "ReadOffers");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readStoredProcedures(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
||||
let sprocs: (StoredProcedureDefinition & Resource)[];
|
||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const rpResponse = await listSqlStoredProcedures(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId
|
||||
);
|
||||
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedures.readAll()
|
||||
.fetchAll();
|
||||
sprocs = response.resources;
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
clearMessage();
|
||||
return sprocs;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
export async function readTriggers(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<(TriggerDefinition & Resource)[]> {
|
||||
let triggers: (TriggerDefinition & Resource)[];
|
||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const rpResponse = await listSqlTriggers(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId
|
||||
);
|
||||
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.triggers.readAll()
|
||||
.fetchAll();
|
||||
triggers = response.resources;
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to query triggers for container ${collectionId}`, "ReadTriggers");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
clearMessage();
|
||||
return triggers;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,47 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readUserDefinedFunctions(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
||||
let udfs: (UserDefinedFunctionDefinition & Resource)[];
|
||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const rpResponse = await listSqlUserDefinedFunctions(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId
|
||||
);
|
||||
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunctions.readAll()
|
||||
.fetchAll();
|
||||
udfs = response.resources;
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(
|
||||
error,
|
||||
`Failed to query user defined functions for container ${collectionId}`,
|
||||
"ReadUserDefinedFunctions"
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
clearMessage();
|
||||
return udfs;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Constants from "../Constants";
|
||||
import { sendMessage } from "../MessageHandler";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
|
||||
interface CosmosError {
|
||||
export interface CosmosError {
|
||||
code: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { Collection } from "../../Contracts/DataModels";
|
||||
import { ContainerDefinition } from "@azure/cosmos";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import {
|
||||
CreateUpdateOptions,
|
||||
ExtendedResourceProperties,
|
||||
MongoDBCollectionCreateUpdateParameters,
|
||||
MongoDBCollectionResource,
|
||||
SqlContainerCreateUpdateParameters,
|
||||
SqlContainerResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
@@ -23,10 +26,8 @@ import {
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function updateCollection(
|
||||
@@ -51,16 +52,14 @@ export async function updateCollection(
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.replace(newCollection as ContainerDefinition, options);
|
||||
|
||||
collection = sdkResponse.resource as Collection;
|
||||
}
|
||||
|
||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||
await refreshCachedResources();
|
||||
return collection;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
@@ -80,15 +79,6 @@ async function updateCollectionWithARM(
|
||||
switch (defaultExperience) {
|
||||
case DefaultAccountExperienceType.DocumentDB:
|
||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.MongoDB:
|
||||
return updateMongoDBCollection(
|
||||
databaseId,
|
||||
collectionId,
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
newCollection
|
||||
);
|
||||
case DefaultAccountExperienceType.Cassandra:
|
||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.Graph:
|
||||
@@ -125,26 +115,35 @@ async function updateSqlContainer(
|
||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||
}
|
||||
|
||||
async function updateMongoDBCollection(
|
||||
export async function updateMongoDBCollectionThroughRP(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
newCollection: MongoDBCollectionResource,
|
||||
updateOptions?: CreateUpdateOptions
|
||||
): Promise<MongoDBCollectionResource> {
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const accountName = userContext.databaseAccount.name;
|
||||
|
||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateParams: MongoDBCollectionCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: newCollection,
|
||||
options: updateOptions
|
||||
}
|
||||
};
|
||||
|
||||
const updateResponse = await createUpdateMongoDBCollection(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
databaseId,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
updateParams
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
|
||||
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
|
||||
@@ -6,12 +6,10 @@ import { OfferDefinition } from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { readCollectionOffer } from "./readCollectionOffer";
|
||||
import { readDatabaseOffer } from "./readDatabaseOffer";
|
||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import {
|
||||
updateSqlDatabaseThroughput,
|
||||
migrateSqlDatabaseToAutoscale,
|
||||
@@ -71,14 +69,10 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
||||
} else {
|
||||
updatedOffer = await updateOfferWithSDK(params);
|
||||
}
|
||||
await refreshCachedOffers();
|
||||
await refreshCachedResources();
|
||||
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||
return updatedOffer;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
@@ -382,8 +376,7 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
|
||||
const currentOffer = params.currentOffer;
|
||||
const newOffer: Offer = {
|
||||
content: {
|
||||
offerThroughput: undefined,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
offerThroughput: undefined
|
||||
},
|
||||
_etag: undefined,
|
||||
_ts: undefined,
|
||||
@@ -406,10 +399,14 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
|
||||
|
||||
const options: RequestOptions = {};
|
||||
if (params.migrateToAutoPilot) {
|
||||
options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||
options.initialHeaders = {
|
||||
[HttpHeaders.migrateOfferToAutopilot]: "true"
|
||||
};
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
} else if (params.migrateToManual) {
|
||||
options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||
options.initialHeaders = {
|
||||
[HttpHeaders.migrateOfferToManualThroughput]: "true"
|
||||
};
|
||||
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ describe("updateOfferThroughputBeyondLimit", () => {
|
||||
resourceGroup: "foo",
|
||||
databaseAccountName: "foo",
|
||||
databaseName: "foo",
|
||||
throughput: 1000000000,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
throughput: 1000000000
|
||||
});
|
||||
expect(window.fetch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ interface UpdateOfferThroughputRequest {
|
||||
databaseName: string;
|
||||
collectionName?: string;
|
||||
throughput: number;
|
||||
offerIsRUPerMinuteThroughputEnabled: boolean;
|
||||
offerAutopilotSettings?: AutoPilotOfferSettings;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,72 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import {
|
||||
SqlStoredProcedureCreateUpdateParameters,
|
||||
SqlStoredProcedureResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import {
|
||||
createUpdateSqlStoredProcedure,
|
||||
getSqlStoredProcedure
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function updateStoredProcedure(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
storedProcedure: StoredProcedureDefinition
|
||||
): Promise<StoredProcedureDefinition & Resource> {
|
||||
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const getResponse = await getSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
storedProcedure.id
|
||||
);
|
||||
|
||||
if (getResponse?.properties?.resource) {
|
||||
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: storedProcedure as SqlStoredProcedureResource,
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
storedProcedure.id,
|
||||
createSprocParams
|
||||
);
|
||||
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||
}
|
||||
|
||||
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.storedProcedure(storedProcedure.id)
|
||||
.replace(storedProcedure);
|
||||
updatedStoredProcedure = response.resource;
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return updatedStoredProcedure;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,69 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import {
|
||||
SqlTriggerCreateUpdateParameters,
|
||||
SqlTriggerResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { TriggerDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function updateTrigger(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
trigger: TriggerDefinition
|
||||
): Promise<TriggerDefinition> {
|
||||
let updatedTrigger: TriggerDefinition;
|
||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const getResponse = await getSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
trigger.id
|
||||
);
|
||||
|
||||
if (getResponse?.properties?.resource) {
|
||||
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: trigger as SqlTriggerResource,
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
const rpResponse = await createUpdateSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
trigger.id,
|
||||
createTriggerParams
|
||||
);
|
||||
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
|
||||
}
|
||||
|
||||
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.trigger(trigger.id)
|
||||
.replace(trigger);
|
||||
updatedTrigger = response.resource;
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while updating trigger ${trigger.id}`, "UpdateTrigger");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return updatedTrigger;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,76 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import {
|
||||
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||
SqlUserDefinedFunctionResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import {
|
||||
createUpdateSqlUserDefinedFunction,
|
||||
getSqlUserDefinedFunction
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function updateUserDefinedFunction(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
userDefinedFunction: UserDefinedFunctionDefinition
|
||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const getResponse = await getSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
userDefinedFunction.id
|
||||
);
|
||||
|
||||
if (getResponse?.properties?.resource) {
|
||||
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||
options: {}
|
||||
}
|
||||
};
|
||||
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
databaseId,
|
||||
collectionId,
|
||||
userDefinedFunction.id,
|
||||
createUDFParams
|
||||
);
|
||||
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||
}
|
||||
|
||||
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
|
||||
}
|
||||
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||
.replace(userDefinedFunction);
|
||||
updatedUserDefinedFunction = response.resource;
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(
|
||||
error,
|
||||
`Error while updating user defined function ${userDefinedFunction.id}`,
|
||||
"UpdateUserupdateUserDefinedFunction"
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
clearMessage();
|
||||
return updatedUserDefinedFunction;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,6 @@ export interface Offer extends Resource {
|
||||
offerType?: string;
|
||||
content?: {
|
||||
offerThroughput: number;
|
||||
offerIsRUPerMinuteThroughputEnabled: boolean;
|
||||
collectionThroughputInfo?: OfferThroughputInfo;
|
||||
offerAutopilotSettings?: AutoPilotOfferSettings;
|
||||
};
|
||||
@@ -233,27 +232,17 @@ export interface CreateDatabaseAndCollectionRequest {
|
||||
collectionId: string;
|
||||
offerThroughput: number;
|
||||
databaseLevelThroughput: boolean;
|
||||
rupmEnabled?: boolean;
|
||||
partitionKey?: PartitionKey;
|
||||
indexingPolicy?: IndexingPolicy;
|
||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||
autoPilot?: AutoPilotCreationSettings;
|
||||
analyticalStorageTtl?: number;
|
||||
hasAutoPilotV2FeatureFlag?: boolean;
|
||||
}
|
||||
|
||||
export interface AutoPilotCreationSettings {
|
||||
autopilotTier?: AutopilotTier;
|
||||
maxThroughput?: number;
|
||||
}
|
||||
|
||||
export enum AutopilotTier {
|
||||
Tier1 = 1,
|
||||
Tier2 = 2,
|
||||
Tier3 = 3,
|
||||
Tier4 = 4
|
||||
}
|
||||
|
||||
export interface Query {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
@@ -262,9 +251,7 @@ export interface Query {
|
||||
}
|
||||
|
||||
export interface AutoPilotOfferSettings {
|
||||
tier?: AutopilotTier;
|
||||
maximumTierThroughput?: number;
|
||||
targetTier?: AutopilotTier;
|
||||
maxThroughput?: number;
|
||||
targetMaxThroughput?: number;
|
||||
}
|
||||
@@ -491,7 +478,6 @@ export interface MongoParameters extends RpParameters {
|
||||
rid?: string;
|
||||
rtype?: string;
|
||||
isAutoPilot?: Boolean;
|
||||
autoPilotTier?: string;
|
||||
autoPilotThroughput?: string;
|
||||
analyticalStorageTtl?: number;
|
||||
}
|
||||
|
||||
@@ -289,6 +289,10 @@ export interface DocumentsTabOptions extends TabOptions {
|
||||
resourceTokenPartitionKey?: string;
|
||||
}
|
||||
|
||||
export interface SettingsTabV2Options extends TabOptions {
|
||||
getPendingNotification: Q.Promise<DataModels.Notification>;
|
||||
}
|
||||
|
||||
export interface ConflictsTabOptions extends TabOptions {
|
||||
partitionKey: DataModels.PartitionKey;
|
||||
conflictIds: ko.ObservableArray<ConflictId>;
|
||||
|
||||
@@ -123,8 +123,4 @@ describe("Component Registerer", () => {
|
||||
it("should register dynamic-list component", () => {
|
||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register throughput-input component", () => {
|
||||
expect(ko.components.isRegistered("throughput-input")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
|
||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
||||
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
||||
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
|
||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||
|
||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||
@@ -23,7 +22,6 @@ ko.components.register("editor", new EditorComponent());
|
||||
ko.components.register("json-editor", new JsonEditorComponent());
|
||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||
ko.components.register("dynamic-list", DynamicListComponent);
|
||||
ko.components.register("throughput-input", ThroughputInputComponent);
|
||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./CollapsibleSectionComponent";
|
||||
|
||||
describe("CollapsibleSectionComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: CollapsibleSectionProps = {
|
||||
title: "Sample title"
|
||||
};
|
||||
|
||||
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||
|
||||
export interface CollapsibleSectionProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CollapsibleSectionState {
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
||||
export class CollapsibleSectionComponent extends React.Component<CollapsibleSectionProps, CollapsibleSectionState> {
|
||||
constructor(props: CollapsibleSectionProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isExpanded: true
|
||||
};
|
||||
}
|
||||
|
||||
private toggleCollapsed = (): void => {
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
|
||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
|
||||
<Label>{this.props.title}</Label>
|
||||
</Stack>
|
||||
{this.state.isExpanded && this.props.children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
className="collapsibleSection"
|
||||
horizontal={true}
|
||||
onClick={[Function]}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledIconBase
|
||||
iconName="ChevronDown"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"paddingTop": 7,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledLabelBase>
|
||||
Sample title
|
||||
</StyledLabelBase>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -44,7 +44,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
||||
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
|
||||
}[] = [
|
||||
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
|
||||
{ key: "feature.enablerupm", label: "Enable RUPM", value: "true" },
|
||||
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
|
||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||
|
||||
@@ -131,12 +131,6 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
label="Enable change feed policy"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablerupm"
|
||||
label="Enable RUPM"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.dataexplorerexecutesproc"
|
||||
|
||||
@@ -8,7 +8,10 @@ import * as DataModels from "../../../Contracts/DataModels";
|
||||
import ko from "knockout";
|
||||
import { TtlType, isDirty } from "./SettingsUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||
jest.mock("../../../Common/dataAccess/readMongoDBCollection", () => ({
|
||||
getMongoDBCollectionIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||
}));
|
||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||
updateCollection: jest.fn().mockReturnValue({
|
||||
id: undefined,
|
||||
@@ -18,9 +21,17 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||
changeFeedPolicy: undefined,
|
||||
analyticalStorageTtl: undefined,
|
||||
geospatialConfig: undefined
|
||||
} as DataModels.Collection)
|
||||
} as DataModels.Collection),
|
||||
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
|
||||
id: undefined,
|
||||
shardKey: undefined,
|
||||
indexes: [],
|
||||
analyticalStorageTtl: undefined
|
||||
} as MongoDBCollectionResource)
|
||||
}));
|
||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import Q from "q";
|
||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||
}));
|
||||
@@ -35,7 +46,10 @@ describe("SettingsComponent", () => {
|
||||
node: undefined,
|
||||
hashLocation: "settings",
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: undefined
|
||||
onUpdateTabsButtons: undefined,
|
||||
getPendingNotification: Q.Promise<DataModels.Notification>(() => {
|
||||
return;
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
@@ -188,13 +202,17 @@ describe("SettingsComponent", () => {
|
||||
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
||||
});
|
||||
|
||||
it("save calls updateCollection and updateOffer", async () => {
|
||||
it("save calls updateCollection, updateMongoDBCollectionThroughRP and updateOffer", async () => {
|
||||
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true });
|
||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true, isMongoIndexingPolicySaveable: true });
|
||||
wrapper.update();
|
||||
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
||||
settingsComponentInstance.mongoDBCollectionResource = {
|
||||
id: "id"
|
||||
};
|
||||
await settingsComponentInstance.onSaveClick();
|
||||
expect(updateCollection).toBeCalled();
|
||||
expect(updateMongoDBCollectionThroughRP).toBeCalled();
|
||||
expect(updateOffer).toBeCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -11,13 +11,17 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Explorer from "../../Explorer";
|
||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||
import { throughputUnit } from "./SettingsRenderUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||
import {
|
||||
MongoIndexingPolicyComponent,
|
||||
MongoIndexingPolicyComponentProps
|
||||
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||
import {
|
||||
getMaxRUs,
|
||||
hasDatabaseSharedThroughput,
|
||||
@@ -27,8 +31,11 @@ import {
|
||||
SettingsV2TabTypes,
|
||||
getTabTitle,
|
||||
isDirty,
|
||||
AddMongoIndexProps,
|
||||
MongoIndexTypes,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure
|
||||
parseConflictResolutionProcedure,
|
||||
getMongoNotification
|
||||
} from "./SettingsUtils";
|
||||
import {
|
||||
ConflictResolutionComponent,
|
||||
@@ -38,6 +45,11 @@ import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubCo
|
||||
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
||||
import "./SettingsComponent.less";
|
||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
getMongoDBCollectionIndexTransformationProgress,
|
||||
readMongoDBCollectionThroughRP
|
||||
} from "../../../Common/dataAccess/readMongoDBCollection";
|
||||
|
||||
interface SettingsV2TabInfo {
|
||||
tab: SettingsV2TabTypes;
|
||||
@@ -84,6 +96,13 @@ export interface SettingsComponentState {
|
||||
shouldDiscardIndexingPolicy: boolean;
|
||||
isIndexingPolicyDirty: boolean;
|
||||
|
||||
isMongoIndexingPolicySaveable: boolean;
|
||||
isMongoIndexingPolicyDiscardable: boolean;
|
||||
currentMongoIndexes: MongoIndex[];
|
||||
indexesToDrop: number[];
|
||||
indexesToAdd: AddMongoIndexProps[];
|
||||
indexTransformationProgress: number;
|
||||
|
||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||
conflictResolutionPolicyPath: string;
|
||||
@@ -98,17 +117,16 @@ export interface SettingsComponentState {
|
||||
|
||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||
private static readonly sixMonthsInSeconds = 15768000;
|
||||
private saveSettingsButton: ButtonV2;
|
||||
private discardSettingsChangesButton: ButtonV2;
|
||||
|
||||
public saveSettingsButton: ButtonV2;
|
||||
public discardSettingsChangesButton: ButtonV2;
|
||||
|
||||
public isAnalyticalStorageEnabled: boolean;
|
||||
private isAnalyticalStorageEnabled: boolean;
|
||||
private collection: ViewModels.Collection;
|
||||
private container: Explorer;
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: boolean;
|
||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
constructor(props: SettingsComponentProps) {
|
||||
super(props);
|
||||
@@ -158,6 +176,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
shouldDiscardIndexingPolicy: false,
|
||||
isIndexingPolicyDirty: false,
|
||||
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
currentMongoIndexes: undefined,
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
isMongoIndexingPolicyDiscardable: false,
|
||||
indexTransformationProgress: undefined,
|
||||
|
||||
conflictResolutionPolicyMode: undefined,
|
||||
conflictResolutionPolicyModeBaseline: undefined,
|
||||
conflictResolutionPolicyPath: undefined,
|
||||
@@ -186,6 +211,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadMongoIndexes();
|
||||
this.setAutoPilotStates();
|
||||
this.setBaseline();
|
||||
if (this.props.settingsTab.isActive()) {
|
||||
@@ -199,6 +225,36 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
}
|
||||
|
||||
public loadMongoIndexes = async (): Promise<void> => {
|
||||
if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.isEnableMongoCapabilityPresent() &&
|
||||
this.container.databaseAccount()
|
||||
) {
|
||||
await this.refreshIndexTransformationProgress();
|
||||
|
||||
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
);
|
||||
|
||||
if (this.mongoDBCollectionResource) {
|
||||
this.setState({
|
||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public refreshIndexTransformationProgress = async (): Promise<void> => {
|
||||
const currentProgress = await getMongoDBCollectionIndexTransformationProgress(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
);
|
||||
this.setState({ indexTransformationProgress: currentProgress });
|
||||
};
|
||||
|
||||
public isSaveSettingsButtonEnabled = (): boolean => {
|
||||
if (this.isOfferReplacePending()) {
|
||||
return false;
|
||||
@@ -208,7 +264,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.state.isScaleSaveable ||
|
||||
this.state.isSubSettingsSaveable ||
|
||||
this.state.isIndexingPolicyDirty ||
|
||||
this.state.isConflictResolutionDirty
|
||||
this.state.isConflictResolutionDirty ||
|
||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -217,7 +274,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.state.isScaleDiscardable ||
|
||||
this.state.isSubSettingsDiscardable ||
|
||||
this.state.isIndexingPolicyDirty ||
|
||||
this.state.isConflictResolutionDirty
|
||||
this.state.isConflictResolutionDirty ||
|
||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -336,6 +394,57 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||
try {
|
||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||
const newMongoCollection: MongoDBCollectionResource = {
|
||||
...this.mongoDBCollectionResource,
|
||||
indexes: newMongoIndexes
|
||||
};
|
||||
|
||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newMongoCollection
|
||||
);
|
||||
|
||||
await this.refreshIndexTransformationProgress();
|
||||
this.setState({
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||
});
|
||||
traceSuccess(
|
||||
Action.MongoIndexUpdated,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount()?.name,
|
||||
databaseName: this.collection?.databaseId,
|
||||
collectionName: this.collection?.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle()
|
||||
},
|
||||
startKey
|
||||
);
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.MongoIndexUpdated,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount()?.name,
|
||||
databaseName: this.collection?.databaseId,
|
||||
collectionName: this.collection?.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: error.message
|
||||
},
|
||||
startKey
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.isScaleSaveable) {
|
||||
const newThroughput = this.state.throughput;
|
||||
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
||||
@@ -345,8 +454,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
} else {
|
||||
newOffer.content = {
|
||||
offerThroughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
offerThroughput: newThroughput
|
||||
};
|
||||
}
|
||||
|
||||
@@ -389,36 +497,20 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
throughput: newThroughput
|
||||
};
|
||||
try {
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
this.setState({
|
||||
throughput: originalThroughputValue,
|
||||
throughputBaseline: originalThroughputValue,
|
||||
initialNotification: {
|
||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||
} as DataModels.Notification
|
||||
});
|
||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.SettingsV2Updated,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.collection && this.collection.databaseId,
|
||||
collectionName: this.collection && this.collection.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
this.setState({
|
||||
isScaleSaveable: false,
|
||||
isScaleDiscardable: false,
|
||||
throughput: originalThroughputValue,
|
||||
throughputBaseline: originalThroughputValue,
|
||||
initialNotification: {
|
||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||
} as DataModels.Notification
|
||||
});
|
||||
} else {
|
||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||
databaseId: this.collection.databaseId,
|
||||
@@ -457,6 +549,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
Action.SettingsV2Updated,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount()?.name,
|
||||
databaseName: this.collection?.databaseId,
|
||||
collectionName: this.collection?.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle()
|
||||
@@ -471,9 +565,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
Action.SettingsV2Updated,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount()?.name,
|
||||
databaseName: this.collection?.databaseId,
|
||||
collectionName: this.collection?.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle()
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: reason.message
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -492,6 +589,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
||||
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
||||
indexesToAdd: [],
|
||||
indexesToDrop: [],
|
||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
||||
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
||||
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
||||
@@ -506,10 +605,23 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
isSubSettingsSaveable: false,
|
||||
isSubSettingsDiscardable: false,
|
||||
isIndexingPolicyDirty: false,
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
isMongoIndexingPolicyDiscardable: false,
|
||||
isConflictResolutionDirty: false
|
||||
});
|
||||
};
|
||||
|
||||
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||
let finalIndexes: MongoIndex[] = [];
|
||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||
if (!this.state.indexesToDrop.includes(index)) {
|
||||
finalIndexes.push(mongoIndex);
|
||||
}
|
||||
});
|
||||
finalIndexes = finalIndexes.concat(this.state.indexesToAdd.map((m: AddMongoIndexProps) => m.mongoIndex));
|
||||
return finalIndexes;
|
||||
};
|
||||
|
||||
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
||||
this.setState({ isScaleSaveable: isScaleSaveable });
|
||||
|
||||
@@ -539,6 +651,36 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
};
|
||||
|
||||
private onIndexDrop = (index: number): void => this.setState({ indexesToDrop: [...this.state.indexesToDrop, index] });
|
||||
|
||||
private onRevertIndexDrop = (index: number): void => {
|
||||
const indexesToDrop = [...this.state.indexesToDrop];
|
||||
indexesToDrop.splice(index, 1);
|
||||
this.setState({ indexesToDrop });
|
||||
};
|
||||
|
||||
private onRevertIndexAdd = (index: number): void => {
|
||||
const indexesToAdd = [...this.state.indexesToAdd];
|
||||
indexesToAdd.splice(index, 1);
|
||||
this.setState({ indexesToAdd });
|
||||
};
|
||||
|
||||
private onIndexAddOrChange = (index: number, description: string, type: MongoIndexTypes): void => {
|
||||
const newIndexesToAdd = [...this.state.indexesToAdd];
|
||||
const notification = getMongoNotification(description, type);
|
||||
const newMongoIndexWithType: AddMongoIndexProps = {
|
||||
mongoIndex: { key: { keys: [description] } } as MongoIndex,
|
||||
type: type,
|
||||
notification: notification
|
||||
};
|
||||
if (index === newIndexesToAdd.length) {
|
||||
newIndexesToAdd.push(newMongoIndexWithType);
|
||||
} else {
|
||||
newIndexesToAdd[index] = newMongoIndexWithType;
|
||||
}
|
||||
this.setState({ indexesToAdd: newIndexesToAdd });
|
||||
};
|
||||
|
||||
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
||||
this.setState({ conflictResolutionPolicyMode: newMode });
|
||||
|
||||
@@ -577,6 +719,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
||||
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
||||
|
||||
private onMongoIndexingPolicySaveableChange = (isMongoIndexingPolicySaveable: boolean): void =>
|
||||
this.setState({ isMongoIndexingPolicySaveable });
|
||||
|
||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||
|
||||
public getAnalyticalStorageTtl = (): number => {
|
||||
if (this.isAnalyticalStorageEnabled) {
|
||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||
@@ -747,7 +895,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
isFixedContainer: this.isFixedContainer,
|
||||
autoPilotTiersList: this.autoPilotTiersList,
|
||||
onThroughputChange: this.onThroughputChange,
|
||||
throughput: this.state.throughput,
|
||||
throughputBaseline: this.state.throughputBaseline,
|
||||
@@ -799,6 +946,20 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
||||
};
|
||||
|
||||
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||
mongoIndexes: this.state.currentMongoIndexes,
|
||||
onIndexDrop: this.onIndexDrop,
|
||||
indexesToDrop: this.state.indexesToDrop,
|
||||
onRevertIndexDrop: this.onRevertIndexDrop,
|
||||
indexesToAdd: this.state.indexesToAdd,
|
||||
onRevertIndexAdd: this.onRevertIndexAdd,
|
||||
onIndexAddOrChange: this.onIndexAddOrChange,
|
||||
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||
onMongoIndexingPolicySaveableChange: this.onMongoIndexingPolicySaveableChange,
|
||||
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange
|
||||
};
|
||||
|
||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
@@ -815,7 +976,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
};
|
||||
|
||||
const tabs: SettingsV2TabInfo[] = [];
|
||||
if (!hasDatabaseSharedThroughput(this.collection)) {
|
||||
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.ScaleTab,
|
||||
content: <ScaleComponent {...scaleComponentProps} />
|
||||
@@ -832,6 +993,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
||||
});
|
||||
} else if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.isEnableMongoCapabilityPresent()
|
||||
) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
|
||||
});
|
||||
}
|
||||
|
||||
if (this.hasConflictResolution()) {
|
||||
|
||||
@@ -4,17 +4,11 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
||||
|
||||
export class SettingsComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
public parameters: ko.Computed<boolean>;
|
||||
|
||||
constructor(private props: SettingsComponentProps) {
|
||||
this.parameters = ko.observable<number>(Date.now());
|
||||
}
|
||||
constructor(private props: SettingsComponentProps) {}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <SettingsComponent {...this.props} />;
|
||||
}
|
||||
|
||||
public triggerRender(): void {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
getToolTipContainer,
|
||||
conflictResolutionCustomToolTip,
|
||||
changeFeedPolicyToolTip,
|
||||
conflictResolutionLwwTooltip
|
||||
conflictResolutionLwwTooltip,
|
||||
mongoIndexingPolicyDisclaimer,
|
||||
mongoIndexingPolicyAADError,
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
} from "./SettingsRenderUtils";
|
||||
|
||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
@@ -27,7 +31,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
{getAutoPilotV3SpendElement(1000, true)}
|
||||
{getAutoPilotV3SpendElement(undefined, true)}
|
||||
|
||||
{getEstimatedSpendElement(1000, "mooncake", 2, false, true)}
|
||||
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
||||
|
||||
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
||||
|
||||
@@ -45,6 +49,16 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
{conflictResolutionLwwTooltip}
|
||||
{conflictResolutionCustomToolTip}
|
||||
{changeFeedPolicyToolTip}
|
||||
|
||||
{mongoIndexingPolicyDisclaimer}
|
||||
{mongoIndexingPolicyAADError}
|
||||
{mongoIndexTransformationRefreshingMessage}
|
||||
{renderMongoIndexTransformationRefreshMessage(0, () => {
|
||||
return;
|
||||
})}
|
||||
{renderMongoIndexTransformationRefreshMessage(90, () => {
|
||||
return;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,24 @@ import {
|
||||
Link,
|
||||
Text,
|
||||
IMessageBarStyles,
|
||||
ITextStyles
|
||||
ITextStyles,
|
||||
IDetailsRowStyles,
|
||||
IStackStyles,
|
||||
IIconStyles,
|
||||
IDetailsListStyles,
|
||||
IDropdownStyles,
|
||||
ISeparatorStyles,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Stack,
|
||||
Spinner,
|
||||
SpinnerSize
|
||||
} from "office-ui-fabric-react";
|
||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||
|
||||
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||
|
||||
export const spendAckCheckBoxStyle: ICheckboxStyles = {
|
||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||
label: {
|
||||
margin: 0,
|
||||
padding: "2 0 2 0"
|
||||
@@ -45,6 +56,20 @@ export const titleAndInputStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 5 }
|
||||
};
|
||||
|
||||
export const mongoWarningStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 5 }
|
||||
};
|
||||
|
||||
export const mongoErrorMessageStyles: Partial<IMessageBarStyles> = { root: { marginLeft: 10 } };
|
||||
|
||||
export const createAndAddMongoIndexStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 5 }
|
||||
};
|
||||
|
||||
export const addMongoIndexStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 10 }
|
||||
};
|
||||
|
||||
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 10 }
|
||||
};
|
||||
@@ -53,6 +78,54 @@ export const toolTipLabelStackTokens: IStackTokens = {
|
||||
childrenGap: 6
|
||||
};
|
||||
|
||||
export const accordionStackTokens: IStackTokens = {
|
||||
childrenGap: 10
|
||||
};
|
||||
|
||||
export const addMongoIndexSubElementsTokens: IStackTokens = {
|
||||
childrenGap: 20
|
||||
};
|
||||
|
||||
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
|
||||
|
||||
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||
|
||||
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
||||
|
||||
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = { dropdown: { paddingleft: 10, width: 202 } };
|
||||
|
||||
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
":hover": {
|
||||
background: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
".ms-FocusZone": {
|
||||
paddingTop: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const separatorStyles: Partial<ISeparatorStyles> = {
|
||||
root: [
|
||||
{
|
||||
selectors: {
|
||||
"::before": {
|
||||
background: StyleConstants.BaseMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
||||
|
||||
export const throughputUnit = "RU/s";
|
||||
@@ -126,10 +199,9 @@ export const getEstimatedSpendElement = (
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
regions: number,
|
||||
multimaster: boolean,
|
||||
rupmEnabled: boolean
|
||||
multimaster: boolean
|
||||
): JSX.Element => {
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const dailyPrice: number = hourlyPrice * 24;
|
||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
@@ -313,6 +385,56 @@ export const changeFeedPolicyToolTip: JSX.Element = (
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<Link href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" target="_blank">
|
||||
{` Compound indexes `}
|
||||
</Link>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||
shell.
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
<Text>
|
||||
To use the indexing policy editor, please login to the
|
||||
<Link target="_blank" href="https://portal.azure.com">
|
||||
{"azure portal."}
|
||||
</Link>
|
||||
</Text>
|
||||
</MessageBar>
|
||||
);
|
||||
|
||||
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||
<Stack horizontal {...mongoWarningStackProps}>
|
||||
<Text>Refreshing index transformation progress</Text>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export const renderMongoIndexTransformationRefreshMessage = (
|
||||
progress: number,
|
||||
performRefresh: () => void
|
||||
): JSX.Element => {
|
||||
if (progress === 0) {
|
||||
return (
|
||||
<Text>
|
||||
{"You can make more indexing changes once the current index transformation is complete. "}
|
||||
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text>
|
||||
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
|
||||
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
||||
fieldGroup: {
|
||||
height: 25,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { MongoIndexTypes, MongoNotificationType } from "../../SettingsUtils";
|
||||
import { AddMongoIndexComponent, AddMongoIndexComponentProps } from "./AddMongoIndexComponent";
|
||||
|
||||
describe("AddMongoIndexComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: AddMongoIndexComponentProps = {
|
||||
position: 1,
|
||||
description: "sample_key",
|
||||
type: MongoIndexTypes.Single,
|
||||
notification: { type: MongoNotificationType.Error, message: "sample error" },
|
||||
onIndexAddOrChange: () => {
|
||||
return;
|
||||
},
|
||||
onDiscard: () => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const wrapper = shallow(<AddMongoIndexComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Stack,
|
||||
IconButton,
|
||||
TextField,
|
||||
Dropdown,
|
||||
IDropdownOption,
|
||||
ITextField
|
||||
} from "office-ui-fabric-react";
|
||||
import {
|
||||
addMongoIndexSubElementsTokens,
|
||||
mongoErrorMessageStyles,
|
||||
mongoWarningStackProps,
|
||||
shortWidthDropDownStyles,
|
||||
shortWidthTextFieldStyles
|
||||
} from "../../SettingsRenderUtils";
|
||||
import {
|
||||
getMongoIndexTypeText,
|
||||
MongoIndexTypes,
|
||||
MongoNotificationMessage,
|
||||
MongoNotificationType,
|
||||
MongoWildcardPlaceHolder
|
||||
} from "../../SettingsUtils";
|
||||
|
||||
export interface AddMongoIndexComponentProps {
|
||||
position: number;
|
||||
description: string;
|
||||
type: MongoIndexTypes;
|
||||
notification: MongoNotificationMessage;
|
||||
onIndexAddOrChange: (description: string, type: MongoIndexTypes) => void;
|
||||
onDiscard: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class AddMongoIndexComponent extends React.Component<AddMongoIndexComponentProps> {
|
||||
private descriptionTextField: ITextField;
|
||||
private indexTypes: IDropdownOption[] = [MongoIndexTypes.Single, MongoIndexTypes.Wildcard].map(
|
||||
(value: MongoIndexTypes) => ({
|
||||
text: getMongoIndexTypeText(value),
|
||||
key: value
|
||||
})
|
||||
);
|
||||
|
||||
private onDescriptionChange = (
|
||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
newValue?: string
|
||||
): void => {
|
||||
this.props.onIndexAddOrChange(newValue, this.props.type);
|
||||
};
|
||||
|
||||
private onTypeChange = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||
const newType = MongoIndexTypes[option.key as keyof typeof MongoIndexTypes];
|
||||
this.props.onIndexAddOrChange(this.props.description, newType);
|
||||
};
|
||||
|
||||
private setRef = (textField: ITextField) => (this.descriptionTextField = textField);
|
||||
|
||||
public focus = (): void => {
|
||||
this.descriptionTextField.focus();
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack {...mongoWarningStackProps}>
|
||||
<Stack horizontal tokens={addMongoIndexSubElementsTokens}>
|
||||
<TextField
|
||||
ariaLabel={"Index Field Name " + this.props.position}
|
||||
disabled={this.props.disabled}
|
||||
styles={shortWidthTextFieldStyles}
|
||||
componentRef={this.setRef}
|
||||
value={this.props.description}
|
||||
placeholder={this.props.type === MongoIndexTypes.Wildcard ? MongoWildcardPlaceHolder : undefined}
|
||||
onChange={this.onDescriptionChange}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
ariaLabel={"Index Type " + this.props.position}
|
||||
disabled={this.props.disabled}
|
||||
styles={shortWidthDropDownStyles}
|
||||
placeholder="Select an index type"
|
||||
selectedKey={this.props.type}
|
||||
options={this.indexTypes}
|
||||
onChange={this.onTypeChange}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
ariaLabel={"Undo Button " + this.props.position}
|
||||
iconProps={{ iconName: "Undo" }}
|
||||
disabled={!this.props.description && !this.props.type}
|
||||
onClick={() => this.props.onDiscard()}
|
||||
/>
|
||||
</Stack>
|
||||
{this.props.notification?.type === MongoNotificationType.Error && (
|
||||
<MessageBar styles={mongoErrorMessageStyles} messageBarType={MessageBarType.error}>
|
||||
{this.props.notification.message}
|
||||
</MessageBar>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
|
||||
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
|
||||
|
||||
describe("MongoIndexingPolicyComponent", () => {
|
||||
const baseProps: MongoIndexingPolicyComponentProps = {
|
||||
mongoIndexes: [],
|
||||
onIndexDrop: () => {
|
||||
return;
|
||||
},
|
||||
indexesToDrop: [],
|
||||
onRevertIndexDrop: () => {
|
||||
return;
|
||||
},
|
||||
indexesToAdd: [],
|
||||
onRevertIndexAdd: () => {
|
||||
return;
|
||||
},
|
||||
onIndexAddOrChange: () => {
|
||||
return;
|
||||
},
|
||||
indexTransformationProgress: undefined,
|
||||
refreshIndexTransformationProgress: () =>
|
||||
new Promise(() => {
|
||||
return;
|
||||
}),
|
||||
onMongoIndexingPolicySaveableChange: () => {
|
||||
return;
|
||||
},
|
||||
onMongoIndexingPolicyDiscardableChange: () => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("isIndexingTransforming", () => {
|
||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
|
||||
wrapper.setProps({ indexTransformationProgress: 50 });
|
||||
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(true);
|
||||
wrapper.setProps({ indexTransformationProgress: 100 });
|
||||
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
|
||||
});
|
||||
|
||||
describe("AddMongoIndexProps test", () => {
|
||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||
|
||||
it("defaults", () => {
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(undefined);
|
||||
});
|
||||
|
||||
const sampleWarning = "sampleWarning";
|
||||
const sampleError = "sampleError";
|
||||
|
||||
const cases = [
|
||||
[
|
||||
{ type: MongoNotificationType.Warning, message: sampleWarning } as MongoNotificationMessage,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
sampleWarning
|
||||
],
|
||||
[
|
||||
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined
|
||||
],
|
||||
[
|
||||
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
undefined
|
||||
],
|
||||
[undefined, false, true, true, undefined],
|
||||
[undefined, true, true, true, undefined]
|
||||
];
|
||||
|
||||
test.each(cases)(
|
||||
"",
|
||||
(
|
||||
notification: MongoNotificationMessage,
|
||||
indexToDropIsPresent: boolean,
|
||||
isMongoIndexingPolicySaveable: boolean,
|
||||
isMongoIndexingPolicyDiscardable: boolean,
|
||||
mongoWarningNotificationMessage: string
|
||||
) => {
|
||||
const addMongoIndexProps = {
|
||||
mongoIndex: { key: { keys: ["sampleKey"] } },
|
||||
type: MongoIndexTypes.Single,
|
||||
notification: notification
|
||||
};
|
||||
|
||||
let indexesToDrop: number[] = [];
|
||||
if (indexToDropIsPresent) {
|
||||
indexesToDrop = [0];
|
||||
}
|
||||
wrapper.setProps({ indexesToAdd: [addMongoIndexProps], indexesToDrop: indexesToDrop });
|
||||
wrapper.update();
|
||||
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(isMongoIndexingPolicySaveable);
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
|
||||
isMongoIndexingPolicyDiscardable
|
||||
);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(
|
||||
mongoWarningNotificationMessage
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,369 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
DetailsList,
|
||||
DetailsListLayoutMode,
|
||||
Stack,
|
||||
IconButton,
|
||||
Text,
|
||||
SelectionMode,
|
||||
IDetailsRowProps,
|
||||
DetailsRow,
|
||||
IColumn,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Separator
|
||||
} from "office-ui-fabric-react";
|
||||
import {
|
||||
addMongoIndexStackProps,
|
||||
customDetailsListStyles,
|
||||
mongoIndexingPolicyDisclaimer,
|
||||
mediumWidthStackStyles,
|
||||
subComponentStackProps,
|
||||
transparentDetailsRowStyles,
|
||||
createAndAddMongoIndexStackProps,
|
||||
separatorStyles,
|
||||
mongoIndexingPolicyAADError,
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
} from "../../SettingsRenderUtils";
|
||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
MongoIndexTypes,
|
||||
AddMongoIndexProps,
|
||||
MongoIndexIdField,
|
||||
MongoNotificationType,
|
||||
getMongoIndexType,
|
||||
getMongoIndexTypeText
|
||||
} from "../../SettingsUtils";
|
||||
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||
import { AuthType } from "../../../../../AuthType";
|
||||
|
||||
export interface MongoIndexingPolicyComponentProps {
|
||||
mongoIndexes: MongoIndex[];
|
||||
onIndexDrop: (index: number) => void;
|
||||
indexesToDrop: number[];
|
||||
onRevertIndexDrop: (index: number) => void;
|
||||
indexesToAdd: AddMongoIndexProps[];
|
||||
onRevertIndexAdd: (index: number) => void;
|
||||
onIndexAddOrChange: (index: number, description: string, type: MongoIndexTypes) => void;
|
||||
indexTransformationProgress: number;
|
||||
refreshIndexTransformationProgress: () => Promise<void>;
|
||||
onMongoIndexingPolicySaveableChange: (isMongoIndexingPolicySaveable: boolean) => void;
|
||||
onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void;
|
||||
}
|
||||
|
||||
interface MongoIndexingPolicyComponentState {
|
||||
isRefreshingIndexTransformationProgress: boolean;
|
||||
}
|
||||
|
||||
interface MongoIndexDisplayProps {
|
||||
definition: JSX.Element;
|
||||
type: JSX.Element;
|
||||
actionButton: JSX.Element;
|
||||
}
|
||||
|
||||
export class MongoIndexingPolicyComponent extends React.Component<
|
||||
MongoIndexingPolicyComponentProps,
|
||||
MongoIndexingPolicyComponentState
|
||||
> {
|
||||
private shouldCheckComponentIsDirty = true;
|
||||
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
||||
private initialIndexesColumns: IColumn[] = [
|
||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{
|
||||
key: "actionButton",
|
||||
name: "Drop Index",
|
||||
fieldName: "actionButton",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true
|
||||
}
|
||||
];
|
||||
|
||||
private indexesToBeDroppedColumns: IColumn[] = [
|
||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{
|
||||
key: "actionButton",
|
||||
name: "Add index back",
|
||||
fieldName: "actionButton",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true
|
||||
}
|
||||
];
|
||||
|
||||
constructor(props: MongoIndexingPolicyComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isRefreshingIndexTransformationProgress: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
|
||||
if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) {
|
||||
this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus();
|
||||
}
|
||||
this.onComponentUpdate();
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.onComponentUpdate();
|
||||
}
|
||||
|
||||
private onComponentUpdate = (): void => {
|
||||
if (!this.shouldCheckComponentIsDirty) {
|
||||
this.shouldCheckComponentIsDirty = true;
|
||||
return;
|
||||
}
|
||||
this.props.onMongoIndexingPolicySaveableChange(this.isMongoIndexingPolicySaveable());
|
||||
this.props.onMongoIndexingPolicyDiscardableChange(this.isMongoIndexingPolicyDiscardable());
|
||||
this.shouldCheckComponentIsDirty = false;
|
||||
};
|
||||
|
||||
public isMongoIndexingPolicySaveable = (): boolean => {
|
||||
if (this.props.indexesToAdd.length === 0 && this.props.indexesToDrop.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const addErrorsExist = !!this.props.indexesToAdd.find(addMongoIndexProps => addMongoIndexProps.notification);
|
||||
|
||||
if (addErrorsExist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public isMongoIndexingPolicyDiscardable = (): boolean => {
|
||||
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
|
||||
};
|
||||
|
||||
public getMongoWarningNotificationMessage = (): string => {
|
||||
return this.props.indexesToAdd.find(
|
||||
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
|
||||
)?.notification.message;
|
||||
};
|
||||
|
||||
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||
};
|
||||
|
||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||
return isCurrentIndex ? (
|
||||
<IconButton
|
||||
ariaLabel="Delete index Button"
|
||||
iconProps={{ iconName: "Delete" }}
|
||||
disabled={this.isIndexingTransforming()}
|
||||
onClick={() => {
|
||||
this.props.onIndexDrop(arrayPosition);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
ariaLabel="Add back Index Button"
|
||||
iconProps={{ iconName: "Add" }}
|
||||
onClick={() => {
|
||||
this.props.onRevertIndexDrop(arrayPosition);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
private getMongoIndexDisplayProps = (
|
||||
mongoIndex: MongoIndex,
|
||||
arrayPosition: number,
|
||||
isCurrentIndex: boolean
|
||||
): MongoIndexDisplayProps => {
|
||||
const keys = mongoIndex?.key?.keys;
|
||||
const type = getMongoIndexType(keys);
|
||||
const definition = keys?.join();
|
||||
let mongoIndexDisplayProps: MongoIndexDisplayProps;
|
||||
if (type) {
|
||||
mongoIndexDisplayProps = {
|
||||
definition: <Text>{definition}</Text>,
|
||||
type: <Text>{getMongoIndexTypeText(type)}</Text>,
|
||||
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex)
|
||||
};
|
||||
}
|
||||
return mongoIndexDisplayProps;
|
||||
};
|
||||
|
||||
private renderIndexesToBeAdded = (): JSX.Element => {
|
||||
const indexesToAddLength = this.props.indexesToAdd.length;
|
||||
for (let i = 0; i < indexesToAddLength; i++) {
|
||||
const existingIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||
this.addMongoIndexComponentRefs[i] = existingIndexToAddRef;
|
||||
}
|
||||
const newIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||
this.addMongoIndexComponentRefs[indexesToAddLength] = newIndexToAddRef;
|
||||
|
||||
return (
|
||||
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||
{this.props.indexesToAdd.map((mongoIndexWithType, arrayPosition) => {
|
||||
const keys = mongoIndexWithType.mongoIndex.key.keys;
|
||||
const type = mongoIndexWithType.type;
|
||||
const notification = mongoIndexWithType.notification;
|
||||
return (
|
||||
<AddMongoIndexComponent
|
||||
ref={this.addMongoIndexComponentRefs[arrayPosition]}
|
||||
position={arrayPosition}
|
||||
key={arrayPosition}
|
||||
description={keys.join()}
|
||||
type={type}
|
||||
notification={notification}
|
||||
onIndexAddOrChange={(description, type) =>
|
||||
this.props.onIndexAddOrChange(arrayPosition, description, type)
|
||||
}
|
||||
onDiscard={() => {
|
||||
this.addMongoIndexComponentRefs.splice(arrayPosition, 1);
|
||||
this.props.onRevertIndexAdd(arrayPosition);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<AddMongoIndexComponent
|
||||
ref={this.addMongoIndexComponentRefs[indexesToAddLength]}
|
||||
disabled={this.isIndexingTransforming()}
|
||||
position={indexesToAddLength}
|
||||
key={indexesToAddLength}
|
||||
description={undefined}
|
||||
type={undefined}
|
||||
notification={undefined}
|
||||
onIndexAddOrChange={(description, type) =>
|
||||
this.props.onIndexAddOrChange(indexesToAddLength, description, type)
|
||||
}
|
||||
onDiscard={() => {
|
||||
this.props.onRevertIndexAdd(indexesToAddLength);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
private renderInitialIndexes = (): JSX.Element => {
|
||||
const initialIndexes = this.props.mongoIndexes
|
||||
.map((mongoIndex, arrayPosition) => this.getMongoIndexDisplayProps(mongoIndex, arrayPosition, true))
|
||||
.filter((value, arrayPosition) => !!value && !this.props.indexesToDrop.includes(arrayPosition));
|
||||
|
||||
return (
|
||||
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||
<CollapsibleSectionComponent title="Current index(es)">
|
||||
{
|
||||
<>
|
||||
<DetailsList
|
||||
styles={customDetailsListStyles}
|
||||
disableSelectionZone
|
||||
items={initialIndexes}
|
||||
columns={this.initialIndexesColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
onRenderRow={this.onRenderRow}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
/>
|
||||
{this.renderIndexesToBeAdded()}
|
||||
</>
|
||||
}
|
||||
</CollapsibleSectionComponent>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
private renderIndexesToBeDropped = (): JSX.Element => {
|
||||
const indexesToBeDropped = this.props.indexesToDrop.map((dropIndex, arrayPosition) =>
|
||||
this.getMongoIndexDisplayProps(this.props.mongoIndexes[dropIndex], arrayPosition, false)
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack styles={mediumWidthStackStyles}>
|
||||
<CollapsibleSectionComponent title="Index(es) to be dropped">
|
||||
{indexesToBeDropped.length > 0 && (
|
||||
<DetailsList
|
||||
styles={customDetailsListStyles}
|
||||
disableSelectionZone
|
||||
items={indexesToBeDropped}
|
||||
columns={this.indexesToBeDroppedColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
onRenderRow={this.onRenderRow}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
/>
|
||||
)}
|
||||
</CollapsibleSectionComponent>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
private refreshIndexTransformationProgress = async () => {
|
||||
this.setState({ isRefreshingIndexTransformationProgress: true });
|
||||
try {
|
||||
await this.props.refreshIndexTransformationProgress();
|
||||
} catch (error) {
|
||||
handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
|
||||
} finally {
|
||||
this.setState({ isRefreshingIndexTransformationProgress: false });
|
||||
}
|
||||
};
|
||||
|
||||
public isIndexingTransforming = (): boolean =>
|
||||
// index transformation progress can be 0
|
||||
this.props.indexTransformationProgress !== undefined && this.props.indexTransformationProgress !== 100;
|
||||
|
||||
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
|
||||
|
||||
private renderIndexTransformationWarning = (): JSX.Element => {
|
||||
if (this.state.isRefreshingIndexTransformationProgress) {
|
||||
return mongoIndexTransformationRefreshingMessage;
|
||||
} else if (this.isIndexingTransforming()) {
|
||||
return renderMongoIndexTransformationRefreshMessage(
|
||||
this.props.indexTransformationProgress,
|
||||
this.onClickRefreshIndexingTransformationLink
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private renderWarningMessage = (): JSX.Element => {
|
||||
let warningMessage: string;
|
||||
if (this.getMongoWarningNotificationMessage()) {
|
||||
warningMessage = this.getMongoWarningNotificationMessage();
|
||||
} else if (this.isMongoIndexingPolicySaveable()) {
|
||||
warningMessage =
|
||||
"You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.";
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderIndexTransformationWarning() && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||
)}
|
||||
|
||||
{warningMessage && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>
|
||||
<Text>{warningMessage}</Text>
|
||||
</MessageBar>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (this.props.mongoIndexes) {
|
||||
return (
|
||||
<Stack {...subComponentStackProps}>
|
||||
{this.renderWarningMessage()}
|
||||
{mongoIndexingPolicyDisclaimer}
|
||||
{this.renderInitialIndexes()}
|
||||
<Separator styles={separatorStyles} />
|
||||
{this.renderIndexesToBeDropped()}
|
||||
</Stack>
|
||||
);
|
||||
} else {
|
||||
return window.authType !== AuthType.AAD ? mongoIndexingPolicyAADError : <Spinner size={SpinnerSize.large} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AddMongoIndexComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Index Field Name 1"
|
||||
componentRef={[Function]}
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"paddingLeft": 10,
|
||||
"width": 210,
|
||||
},
|
||||
}
|
||||
}
|
||||
value="sample_key"
|
||||
/>
|
||||
<StyledWithResponsiveMode
|
||||
ariaLabel="Index Type 1"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "Single",
|
||||
"text": "Single Field",
|
||||
},
|
||||
Object {
|
||||
"key": "Wildcard",
|
||||
"text": "Wildcard",
|
||||
},
|
||||
]
|
||||
}
|
||||
placeholder="Select an index type"
|
||||
selectedKey="Single"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"paddingleft": 10,
|
||||
"width": 202,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Undo Button 1"
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Undo",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledMessageBarBase
|
||||
messageBarType={1}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"marginLeft": 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
sample error
|
||||
</StyledMessageBarBase>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -0,0 +1,137 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<StyledLinkBase
|
||||
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||
target="_blank"
|
||||
>
|
||||
Compound indexes
|
||||
</StyledLinkBase>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||
</Text>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CollapsibleSectionComponent
|
||||
title="Current index(es)"
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"fieldName": "definition",
|
||||
"isResizable": true,
|
||||
"key": "definition",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Definition",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "type",
|
||||
"isResizable": true,
|
||||
"key": "type",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Type",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "actionButton",
|
||||
"isResizable": true,
|
||||
"key": "actionButton",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Drop Index",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={Array []}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
".ms-FocusZone": Object {
|
||||
"paddingTop": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<AddMongoIndexComponent
|
||||
disabled={false}
|
||||
key="0"
|
||||
onDiscard={[Function]}
|
||||
onIndexAddOrChange={[Function]}
|
||||
position={0}
|
||||
/>
|
||||
</Stack>
|
||||
</CollapsibleSectionComponent>
|
||||
</Stack>
|
||||
<Styled
|
||||
styles={
|
||||
Object {
|
||||
"root": Array [
|
||||
Object {
|
||||
"selectors": Object {
|
||||
"::before": Object {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<CollapsibleSectionComponent
|
||||
title="Index(es) to be dropped"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -20,7 +20,6 @@ describe("ScaleComponent", () => {
|
||||
collection: collection,
|
||||
container: container,
|
||||
isFixedContainer: false,
|
||||
autoPilotTiersList: [],
|
||||
onThroughputChange: () => {
|
||||
return;
|
||||
},
|
||||
|
||||
@@ -24,7 +24,6 @@ export interface ScaleComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
container: Explorer;
|
||||
isFixedContainer: boolean;
|
||||
autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
||||
onThroughputChange: (newThroughput: number) => void;
|
||||
throughput: number;
|
||||
throughputBaseline: number;
|
||||
@@ -86,7 +85,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
|
||||
public getThroughputTitle = (): string => {
|
||||
if (this.props.isAutoPilotSelected) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(false);
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
|
||||
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
||||
@@ -98,11 +97,11 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
};
|
||||
|
||||
public canThroughputExceedMaximumValue = (): boolean => {
|
||||
const isPublicAzurePortal: boolean =
|
||||
configContext.platform === Platform.Portal && !this.props.container.isRunningOnNationalCloud();
|
||||
const hasPartitionKey = !!this.props.collection.partitionKey;
|
||||
|
||||
return isPublicAzurePortal && hasPartitionKey;
|
||||
return (
|
||||
!this.props.isFixedContainer &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
!this.props.container.isRunningOnNationalCloud()
|
||||
);
|
||||
};
|
||||
|
||||
public getInitialNotificationElement = (): JSX.Element => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
getToolTipContainer,
|
||||
spendAckCheckBoxStyle,
|
||||
noLeftPaddingCheckBoxStyle,
|
||||
titleAndInputStackProps,
|
||||
checkBoxAndInputStackProps,
|
||||
getChoiceGroupStyles,
|
||||
@@ -142,7 +142,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
|
||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
|
||||
this.autoPilotInputMaxValue = Int32.Max;
|
||||
this.autoPilotInputMaxValue = this.props.isFixed ? this.props.maximum : Int32.Max;
|
||||
}
|
||||
|
||||
public hasProvisioningTypeChanged = (): boolean =>
|
||||
@@ -174,8 +174,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false
|
||||
multimaster
|
||||
);
|
||||
} else {
|
||||
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
||||
@@ -278,12 +277,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
{this.props.spendAckVisible && (
|
||||
<Checkbox
|
||||
id="spendAckCheckBox"
|
||||
styles={spendAckCheckBoxStyle}
|
||||
styles={noLeftPaddingCheckBoxStyle}
|
||||
label={this.props.spendAckText}
|
||||
checked={this.state.spendAckChecked}
|
||||
onChange={this.onSpendAckChecked}
|
||||
/>
|
||||
)}
|
||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -316,21 +316,21 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
{this.props.spendAckVisible && (
|
||||
<Checkbox
|
||||
id="spendAckCheckBox"
|
||||
styles={spendAckCheckBoxStyle}
|
||||
styles={noLeftPaddingCheckBoxStyle}
|
||||
label={this.props.spendAckText}
|
||||
checked={this.state.spendAckChecked}
|
||||
onChange={this.onSpendAckChecked}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.props.isFixed && <p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>}
|
||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack {...checkBoxAndInputStackProps}>
|
||||
{!this.props.isFixed && this.renderThroughputModeChoices()}
|
||||
{this.renderThroughputModeChoices()}
|
||||
|
||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||
</Stack>
|
||||
|
||||
@@ -2,12 +2,20 @@ import { collection, container } from "./TestUtils";
|
||||
import {
|
||||
getMaxRUs,
|
||||
getMinRUs,
|
||||
getMongoIndexType,
|
||||
getMongoNotification,
|
||||
getSanitizedInputValue,
|
||||
hasDatabaseSharedThroughput,
|
||||
isDirty,
|
||||
isDirtyTypes,
|
||||
MongoIndexTypes,
|
||||
MongoNotificationType,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure
|
||||
parseConflictResolutionProcedure,
|
||||
MongoWildcardPlaceHolder,
|
||||
getMongoIndexTypeText,
|
||||
SingleFieldText,
|
||||
WildcardText
|
||||
} from "./SettingsUtils";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
@@ -91,7 +99,43 @@ describe("SettingsUtils", () => {
|
||||
it("getSanitizedInputValue", () => {
|
||||
const max = 100;
|
||||
expect(getSanitizedInputValue("", max)).toEqual(0);
|
||||
expect(getSanitizedInputValue("999", max)).toEqual(99);
|
||||
expect(getSanitizedInputValue("999", max)).toEqual(100);
|
||||
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||
});
|
||||
|
||||
it("getMongoIndexType", () => {
|
||||
expect(getMongoIndexType(["Single"])).toEqual(MongoIndexTypes.Single);
|
||||
expect(getMongoIndexType(["Wildcard.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||
expect(getMongoIndexType(["Key1", "Key2"])).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("getMongoIndexTypeText", () => {
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Single)).toEqual(SingleFieldText);
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Wildcard)).toEqual(WildcardText);
|
||||
});
|
||||
|
||||
it("getMongoNotification", () => {
|
||||
const singleIndexDescription = "sampleKey";
|
||||
const wildcardIndexDescription = "sampleKey.$**";
|
||||
|
||||
let notification = getMongoNotification(singleIndexDescription, undefined);
|
||||
expect(notification.message).toEqual("Please select a type for each index.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Warning);
|
||||
|
||||
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Single);
|
||||
expect(notification).toEqual(undefined);
|
||||
|
||||
notification = getMongoNotification(wildcardIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification).toEqual(undefined);
|
||||
|
||||
notification = getMongoNotification("", MongoIndexTypes.Single);
|
||||
expect(notification.message).toEqual("Please enter a field name.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
|
||||
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification.message).toEqual(
|
||||
"Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||
);
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,17 @@ import * as SharedConstants from "../../../Shared/Constants";
|
||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||
|
||||
import Explorer from "../../Explorer";
|
||||
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
|
||||
const zeroValue = 0;
|
||||
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
|
||||
export const TtlOff = "off";
|
||||
export const TtlOn = "on";
|
||||
export const TtlOnNoDefault = "on-nodefault";
|
||||
export const MongoIndexIdField = "_id";
|
||||
export const MongoWildcardPlaceHolder = "properties.$**";
|
||||
export const SingleFieldText = "Single Field";
|
||||
export const WildcardText = "Wildcard";
|
||||
|
||||
export enum ChangeFeedPolicyState {
|
||||
Off = "Off",
|
||||
@@ -28,6 +33,17 @@ export enum GeospatialConfigType {
|
||||
Geometry = "Geometry"
|
||||
}
|
||||
|
||||
export enum MongoIndexTypes {
|
||||
Single = "Single",
|
||||
Wildcard = "Wildcard"
|
||||
}
|
||||
|
||||
export interface AddMongoIndexProps {
|
||||
mongoIndex: MongoIndex;
|
||||
type: MongoIndexTypes;
|
||||
notification: MongoNotificationMessage;
|
||||
}
|
||||
|
||||
export enum SettingsV2TabTypes {
|
||||
ScaleTab,
|
||||
ConflictResolutionTab,
|
||||
@@ -40,6 +56,16 @@ export interface IsComponentDirtyResult {
|
||||
isDiscardable: boolean;
|
||||
}
|
||||
|
||||
export enum MongoNotificationType {
|
||||
Warning = "Warning",
|
||||
Error = "Error"
|
||||
}
|
||||
|
||||
export interface MongoNotificationMessage {
|
||||
type: MongoNotificationType;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
||||
const database: ViewModels.Database = collection.getDatabase();
|
||||
return database?.isDatabaseShared() && !collection.offer();
|
||||
@@ -54,7 +80,7 @@ export const getMaxRUs = (collection: ViewModels.Collection, container: Explorer
|
||||
const numPartitionsFromOffer: number =
|
||||
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
|
||||
|
||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo().numPartitions;
|
||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo()?.numPartitions;
|
||||
|
||||
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
|
||||
|
||||
@@ -79,7 +105,7 @@ export const getMinRUs = (collection: ViewModels.Collection, container: Explorer
|
||||
return collectionThroughputInfo.minimumRUForCollection;
|
||||
}
|
||||
|
||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo().numPartitions;
|
||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo()?.numPartitions;
|
||||
|
||||
if (!numPartitions || numPartitions === 1) {
|
||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||
@@ -131,13 +157,12 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
|
||||
};
|
||||
|
||||
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
||||
let newValue = parseInt(newValueString);
|
||||
const newValue = parseInt(newValueString);
|
||||
if (isNaN(newValue)) {
|
||||
newValue = zeroValue;
|
||||
} else if (newValue > max) {
|
||||
newValue = Math.floor(newValue / 10);
|
||||
return zeroValue;
|
||||
}
|
||||
return newValue;
|
||||
// make sure new value does not exceed the maximum throughput
|
||||
return Math.min(newValue, max);
|
||||
};
|
||||
|
||||
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
||||
@@ -180,3 +205,48 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
||||
throw new Error(`Unknown tab ${tab}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getMongoNotification = (description: string, type: MongoIndexTypes): MongoNotificationMessage => {
|
||||
if (description && !type) {
|
||||
return {
|
||||
type: MongoNotificationType.Warning,
|
||||
message: "Please select a type for each index."
|
||||
};
|
||||
}
|
||||
|
||||
if (type && (!description || description.trim().length === 0)) {
|
||||
return {
|
||||
type: MongoNotificationType.Error,
|
||||
message: "Please enter a field name."
|
||||
};
|
||||
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
|
||||
return {
|
||||
type: MongoNotificationType.Error,
|
||||
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getMongoIndexType = (keys: string[]): MongoIndexTypes => {
|
||||
const length = keys?.length;
|
||||
let type: MongoIndexTypes;
|
||||
|
||||
if (length === 1) {
|
||||
if (keys[0].indexOf("$**") !== -1) {
|
||||
type = MongoIndexTypes.Wildcard;
|
||||
} else {
|
||||
type = MongoIndexTypes.Single;
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
};
|
||||
|
||||
export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
|
||||
if (index === MongoIndexTypes.Single) {
|
||||
return SingleFieldText;
|
||||
}
|
||||
return WildcardText;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,6 @@ export const collection = ({
|
||||
offer: ko.observable<DataModels.Offer>({
|
||||
content: {
|
||||
offerThroughput: 10000,
|
||||
offerIsRUPerMinuteThroughputEnabled: false,
|
||||
collectionThroughputInfo: {
|
||||
minimumRUForCollection: 6000,
|
||||
numPhysicalPartitions: 4
|
||||
|
||||
@@ -40,7 +40,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -56,7 +55,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -69,7 +67,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -86,7 +83,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -108,7 +104,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -138,12 +133,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -354,7 +344,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -366,7 +355,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -387,10 +375,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -585,7 +570,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -607,7 +591,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -637,12 +620,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -672,7 +650,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -688,7 +665,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -701,7 +677,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -778,7 +753,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -790,7 +764,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -811,10 +784,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -969,7 +939,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -983,6 +952,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
@@ -1346,7 +1316,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1362,7 +1331,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1375,7 +1343,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -1392,7 +1359,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1414,7 +1380,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -1444,12 +1409,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -1660,7 +1620,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1672,7 +1631,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1693,10 +1651,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -1891,7 +1846,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1913,7 +1867,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -1943,12 +1896,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -1978,7 +1926,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1994,7 +1941,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2007,7 +1953,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -2084,7 +2029,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2096,7 +2040,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2117,10 +2060,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -2275,7 +2215,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -2289,6 +2228,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
@@ -2665,7 +2605,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2681,7 +2620,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2694,7 +2632,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -2711,7 +2648,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2733,7 +2669,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -2763,12 +2698,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -2979,7 +2909,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2991,7 +2920,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3012,10 +2940,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -3210,7 +3135,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3232,7 +3156,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -3262,12 +3185,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -3297,7 +3215,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3313,7 +3230,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3326,7 +3242,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -3403,7 +3318,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3415,7 +3329,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3436,10 +3349,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -3594,7 +3504,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -3608,6 +3517,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
@@ -3971,7 +3881,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3987,7 +3896,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4000,7 +3908,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -4017,7 +3924,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4039,7 +3945,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -4069,12 +3974,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -4285,7 +4185,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4297,7 +4196,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4318,10 +4216,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -4516,7 +4411,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4538,7 +4432,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -4568,12 +4461,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -4603,7 +4491,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4619,7 +4506,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4632,7 +4518,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -4709,7 +4594,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4721,7 +4605,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4742,10 +4625,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -4900,7 +4780,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -4914,6 +4793,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
|
||||
@@ -69,15 +69,15 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
|
||||
<b>
|
||||
¥
|
||||
1.29
|
||||
1.02
|
||||
hourly
|
||||
/
|
||||
¥
|
||||
31.06
|
||||
24.48
|
||||
daily
|
||||
/
|
||||
¥
|
||||
944.60
|
||||
744.60
|
||||
monthly
|
||||
|
||||
</b>
|
||||
@@ -310,5 +310,59 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
>
|
||||
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected.
|
||||
</Text>
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<StyledLinkBase
|
||||
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||
target="_blank"
|
||||
>
|
||||
Compound indexes
|
||||
</StyledLinkBase>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||
</Text>
|
||||
<StyledMessageBarBase
|
||||
messageBarType={1}
|
||||
>
|
||||
<Text>
|
||||
To use the indexing policy editor, please login to the
|
||||
<StyledLinkBase
|
||||
href="https://portal.azure.com"
|
||||
target="_blank"
|
||||
>
|
||||
azure portal.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</StyledMessageBarBase>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
Refreshing index transformation progress
|
||||
</Text>
|
||||
<StyledSpinnerBase
|
||||
size={2}
|
||||
/>
|
||||
</Stack>
|
||||
<Text>
|
||||
You can make more indexing changes once the current index transformation is complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
Refresh to check if it has completed.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Text>
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
Refresh to check the progress.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import editable from "../../../Common/EditableUtility";
|
||||
import { ThroughputInputComponent, ThroughputInputParams, ThroughputInputViewModel } from "./ThroughputInputComponent";
|
||||
|
||||
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
|
||||
|
||||
describe.skip("Throughput Input Component", () => {
|
||||
let component: any;
|
||||
let vm: ThroughputInputViewModel;
|
||||
const testId: string = "ThroughputValue";
|
||||
const value: ViewModels.Editable<number> = editable.observable(500);
|
||||
const minimum: ko.Observable<number> = ko.observable(400);
|
||||
const maximum: ko.Observable<number> = ko.observable(2000);
|
||||
|
||||
function buildListOptions(
|
||||
value: ViewModels.Editable<number>,
|
||||
minimum: ko.Observable<number>,
|
||||
maxium: ko.Observable<number>,
|
||||
canExceedMaximumValue?: boolean
|
||||
): ThroughputInputParams {
|
||||
return {
|
||||
testId,
|
||||
value,
|
||||
minimum,
|
||||
maximum,
|
||||
canExceedMaximumValue: ko.computed<boolean>(() => Boolean(canExceedMaximumValue)),
|
||||
costsVisible: ko.observable(false),
|
||||
isFixed: false,
|
||||
label: ko.observable("Label"),
|
||||
requestUnitsUsageCost: ko.observable("requestUnitsUsageCost"),
|
||||
showAsMandatory: false,
|
||||
autoPilotTiersList: null,
|
||||
autoPilotUsageCost: null,
|
||||
isAutoPilotSelected: null,
|
||||
selectedAutoPilotTier: null,
|
||||
throughputAutoPilotRadioId: null,
|
||||
throughputProvisionedRadioId: null,
|
||||
throughputModeRadioName: null
|
||||
};
|
||||
}
|
||||
|
||||
function simulateKeyPressSpace(target: HTMLElement): Promise<boolean> {
|
||||
const event = new KeyboardEvent("keydown", {
|
||||
key: "space"
|
||||
});
|
||||
|
||||
const result = target.dispatchEvent(event);
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(result);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
component = ThroughputInputComponent;
|
||||
document.body.innerHTML = component.template as any;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ko.cleanNode(document);
|
||||
});
|
||||
|
||||
describe("Rendering", () => {
|
||||
it("should display value text", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
expect(($("input") as HTMLInputElement).value).toContain(value().toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe("Behavior", () => {
|
||||
it("should decrease value", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
value(450);
|
||||
$(".testhook-decreaseThroughput").click();
|
||||
expect(value()).toBe(400);
|
||||
$(".testhook-decreaseThroughput").click();
|
||||
expect(value()).toBe(400);
|
||||
});
|
||||
|
||||
it("should increase value", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
value(1950);
|
||||
$(".test-increaseThroughput").click();
|
||||
expect(value()).toBe(2000);
|
||||
$(".test-increaseThroughput").click();
|
||||
expect(value()).toBe(2000);
|
||||
});
|
||||
|
||||
it("should respect lower bound limits", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
value(minimum());
|
||||
$(".testhook-decreaseThroughput").click();
|
||||
expect(value()).toBe(minimum());
|
||||
});
|
||||
|
||||
it("should respect upper bound limits", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
value(maximum());
|
||||
$(".test-increaseThroughput").click();
|
||||
expect(value()).toBe(maximum());
|
||||
});
|
||||
|
||||
it("should allow throughput to exceed upper bound limit when canExceedMaximumValue is set", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
value(maximum());
|
||||
$(".test-increaseThroughput").click();
|
||||
expect(value()).toBe(maximum() + 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Accessibility", () => {
|
||||
it.skip("should decrease value with keypress", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
const target = $(".testhook-decreaseThroughput");
|
||||
|
||||
value(500);
|
||||
expect(value()).toBe(500);
|
||||
|
||||
const result = await simulateKeyPressSpace(target);
|
||||
expect(value()).toBe(400);
|
||||
});
|
||||
|
||||
it.skip("should increase value with keypress", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
||||
await ko.applyBindings(vm);
|
||||
const target = $(".test-increaseThroughput");
|
||||
|
||||
value(400);
|
||||
expect(value()).toBe(400);
|
||||
|
||||
const result = await simulateKeyPressSpace(target);
|
||||
// expect(value()).toBe(500);
|
||||
});
|
||||
|
||||
it("should set the decreaseButtonAriaLabel using the default step value", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 100");
|
||||
});
|
||||
|
||||
it("should set the increaseButtonAriaLabel using the default step value", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 100");
|
||||
});
|
||||
|
||||
it("should set the increaseButtonAriaLabel using the params step value", async () => {
|
||||
const options = buildListOptions(value, minimum, maximum, true);
|
||||
options.step = 10;
|
||||
vm = new component.viewModel(options);
|
||||
await ko.applyBindings(vm);
|
||||
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 10");
|
||||
});
|
||||
|
||||
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
|
||||
const options = buildListOptions(value, minimum, maximum, true);
|
||||
options.step = 10;
|
||||
vm = new component.viewModel(options);
|
||||
await ko.applyBindings(vm);
|
||||
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 10");
|
||||
});
|
||||
|
||||
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
|
||||
const options = buildListOptions(value, minimum, maximum, true);
|
||||
options.step = 10;
|
||||
vm = new component.viewModel(options);
|
||||
await ko.applyBindings(vm);
|
||||
});
|
||||
|
||||
it("should have aria-label attribute on increase button", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
const ariaLabel = $(".test-increaseThroughput").attributes.getNamedItem("aria-label").value;
|
||||
expect(ariaLabel).toBe("Increase throughput by 100");
|
||||
});
|
||||
|
||||
it("should have aria-label attribute on increase button", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
const ariaLabel = $(".testhook-decreaseThroughput").attributes.getNamedItem("aria-label").value;
|
||||
expect(ariaLabel).toBe("Decrease throughput by 100");
|
||||
});
|
||||
|
||||
it("should have role on increase button", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
const role = $(".test-increaseThroughput").attributes.getNamedItem("role").value;
|
||||
expect(role).toBe("button");
|
||||
});
|
||||
|
||||
it("should have role on decrease button", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("role").value;
|
||||
expect(role).toBe("button");
|
||||
});
|
||||
|
||||
it("should have tabindex 0 on increase button", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
|
||||
expect(role).toBe("0");
|
||||
});
|
||||
|
||||
it("should have tabindex 0 on decrease button", async () => {
|
||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
||||
await ko.applyBindings(vm);
|
||||
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
|
||||
expect(role).toBe("0");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,145 +0,0 @@
|
||||
<div>
|
||||
<div>
|
||||
<p class="pkPadding">
|
||||
<!-- ko if: showAsMandatory -->
|
||||
<span class="mandatoryStar">*</span>
|
||||
<!-- /ko -->
|
||||
|
||||
<span class="addCollectionLabel" data-bind="text: label"></span>
|
||||
|
||||
<!-- ko if: infoBubbleText -->
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="../../../../images/info-bubble.svg" alt="More information" />
|
||||
<span data-bind="text: infoBubbleText" class="tooltiptext throughputRuInfo"></span>
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ko if: !isFixed -->
|
||||
<div data-bind="visible: showAutoPilot" class="throughputModeContainer">
|
||||
<input
|
||||
class="throughputModeRadio"
|
||||
aria-label="Autopilot mode"
|
||||
data-test="throughput-autoPilot"
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
checked: isAutoPilotSelected,
|
||||
checkedValue: true,
|
||||
attr: {
|
||||
id: throughputAutoPilotRadioId,
|
||||
name: throughputModeRadioName,
|
||||
'aria-checked': isAutoPilotSelected() ? 'true' : 'false'
|
||||
}"
|
||||
/>
|
||||
<span
|
||||
class="throughputModeSpace"
|
||||
data-bind="
|
||||
attr: {
|
||||
for: throughputAutoPilotRadioId
|
||||
}"
|
||||
>Autopilot (preview)
|
||||
</span>
|
||||
|
||||
<input
|
||||
class="throughputModeRadio nonFirstRadio"
|
||||
aria-label="Provisioned Throughput mode"
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
checked: isAutoPilotSelected,
|
||||
checkedValue: false,
|
||||
attr: {
|
||||
id: throughputProvisionedRadioId,
|
||||
name: throughputModeRadioName,
|
||||
'aria-checked': !isAutoPilotSelected() ? 'true' : 'false'
|
||||
}"
|
||||
/>
|
||||
<span
|
||||
class="throughputModeSpace"
|
||||
data-bind="
|
||||
attr: {
|
||||
for: throughputProvisionedRadioId
|
||||
}"
|
||||
>Manual
|
||||
</span>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div data-bind="visible: isAutoPilotSelected">
|
||||
<select
|
||||
name="autoPilotTiers"
|
||||
class="collid select-font-size"
|
||||
aria-label="Autopilot Max RU/s"
|
||||
data-bind="
|
||||
options: autoPilotTiersList,
|
||||
optionsText: 'text',
|
||||
optionsValue: 'value',
|
||||
value: selectedAutoPilotTier,
|
||||
optionsCaption: 'Choose Max RU/s'"
|
||||
>
|
||||
</select>
|
||||
<p>
|
||||
<span
|
||||
data-bind="
|
||||
html: autoPilotUsageCost,
|
||||
visible: selectedAutoPilotTier"
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: !isAutoPilotSelected()">
|
||||
<div data-bind="setTemplateReady: true">
|
||||
<p class="addContainerThroughputInput">
|
||||
<input
|
||||
type="number"
|
||||
required
|
||||
data-bind="
|
||||
textInput: value,
|
||||
css: {
|
||||
dirty: value.editableIsDirty
|
||||
},
|
||||
enable: isEnabled,
|
||||
attr:{
|
||||
'data-test': testId,
|
||||
'class': cssClass,
|
||||
step: step,
|
||||
min: minimum,
|
||||
max: canExceedMaximumValue() ? null : maximum,
|
||||
'aria-label': ariaLabel
|
||||
}"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p data-bind="visible: costsVisible">
|
||||
<span data-bind="html: requestUnitsUsageCost"></span>
|
||||
</p>
|
||||
|
||||
<!-- ko if: spendAckVisible -->
|
||||
<p class="pkPadding">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label="acknowledge spend throughput"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: spendAckText,
|
||||
id: spendAckId
|
||||
},
|
||||
checked: spendAckChecked"
|
||||
/>
|
||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
||||
</p>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: isFixed -->
|
||||
<p>
|
||||
Choose unlimited storage capacity for more than 10,000 RU/s.
|
||||
</p>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,261 +0,0 @@
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||
import ThroughputInputComponentTemplate from "./ThroughputInputComponent.html";
|
||||
|
||||
/**
|
||||
* Throughput Input:
|
||||
*
|
||||
* Creates a set of controls to input, sanitize and increase/decrease throughput
|
||||
*
|
||||
* How to use in your markup:
|
||||
* <throughput-input params="{ value: anObservableToHoldTheValue, minimum: anObservableWithMinimum, maximum: anObservableWithMaximum }">
|
||||
* </throughput-input>
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parameters for this component
|
||||
*/
|
||||
export interface ThroughputInputParams {
|
||||
/**
|
||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
||||
*/
|
||||
onTemplateReady?: () => void;
|
||||
|
||||
/**
|
||||
* Observable to bind the Throughput value to
|
||||
*/
|
||||
value: ViewModels.Editable<number>;
|
||||
|
||||
/**
|
||||
* Text to use as id for testing
|
||||
*/
|
||||
testId: string;
|
||||
|
||||
/**
|
||||
* Text to use as aria-label
|
||||
*/
|
||||
ariaLabel?: ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* Minimum value in the range
|
||||
*/
|
||||
minimum: ko.Observable<number>;
|
||||
|
||||
/**
|
||||
* Maximum value in the range
|
||||
*/
|
||||
maximum: ko.Observable<number>;
|
||||
|
||||
/**
|
||||
* Step value for increase/decrease
|
||||
*/
|
||||
step?: number;
|
||||
|
||||
/**
|
||||
* Observable to bind the Throughput enabled status
|
||||
*/
|
||||
isEnabled?: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Should show pricing controls
|
||||
*/
|
||||
costsVisible: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* RU price
|
||||
*/
|
||||
requestUnitsUsageCost: ko.Subscribable<string>; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable
|
||||
|
||||
/**
|
||||
* State of the spending acknowledge checkbox
|
||||
*/
|
||||
spendAckChecked?: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* id of the spending acknowledge checkbox
|
||||
*/
|
||||
spendAckId?: ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* spending acknowledge text
|
||||
*/
|
||||
spendAckText?: ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* Show spending acknowledge controls
|
||||
*/
|
||||
spendAckVisible?: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Display * to the left of the label
|
||||
*/
|
||||
showAsMandatory: boolean;
|
||||
|
||||
/**
|
||||
* If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed
|
||||
*/
|
||||
isFixed: boolean;
|
||||
|
||||
/**
|
||||
* Label of the provisioned throughut control
|
||||
*/
|
||||
label: ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* Text of the info bubble for provisioned throughut control
|
||||
*/
|
||||
infoBubbleText?: ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* Computed value that decides if value can exceed maximum allowable value
|
||||
*/
|
||||
canExceedMaximumValue?: ko.Computed<boolean>;
|
||||
|
||||
/**
|
||||
* CSS classes to apply on input element
|
||||
*/
|
||||
cssClass?: string;
|
||||
|
||||
isAutoPilotSelected: ko.Observable<boolean>;
|
||||
throughputAutoPilotRadioId: string;
|
||||
throughputProvisionedRadioId: string;
|
||||
throughputModeRadioName: string;
|
||||
autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
autoPilotUsageCost: ko.Computed<string>;
|
||||
showAutoPilot?: ko.Observable<boolean>;
|
||||
}
|
||||
|
||||
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
||||
public ariaLabel: ko.Observable<string>;
|
||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||
public step: number;
|
||||
public testId: string;
|
||||
public value: ViewModels.Editable<number>;
|
||||
public minimum: ko.Observable<number>;
|
||||
public maximum: ko.Observable<number>;
|
||||
public isEnabled: ko.Observable<boolean>;
|
||||
public cssClass: string;
|
||||
public decreaseButtonAriaLabel: string;
|
||||
public increaseButtonAriaLabel: string;
|
||||
public costsVisible: ko.Observable<boolean>;
|
||||
public requestUnitsUsageCost: ko.Subscribable<string>;
|
||||
public spendAckChecked: ko.Observable<boolean>;
|
||||
public spendAckId: ko.Observable<string>;
|
||||
public spendAckText: ko.Observable<string>;
|
||||
public spendAckVisible: ko.Observable<boolean>;
|
||||
public showAsMandatory: boolean;
|
||||
public infoBubbleText: string | ko.Observable<string>;
|
||||
public label: ko.Observable<string>;
|
||||
public isFixed: boolean;
|
||||
public showAutoPilot: ko.Observable<boolean>;
|
||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
||||
public throughputAutoPilotRadioId: string;
|
||||
public throughputProvisionedRadioId: string;
|
||||
public throughputModeRadioName: string;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public autoPilotUsageCost: ko.Computed<string>;
|
||||
|
||||
public constructor(options: ThroughputInputParams) {
|
||||
super();
|
||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
||||
if (isTemplateReady && options.onTemplateReady) {
|
||||
options.onTemplateReady();
|
||||
}
|
||||
});
|
||||
|
||||
const params: ThroughputInputParams = options;
|
||||
this.testId = params.testId || "ThroughputValue";
|
||||
this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || "");
|
||||
this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false);
|
||||
this.step = params.step || ThroughputInputViewModel._defaultStep;
|
||||
this.isEnabled = params.isEnabled || ko.observable(true);
|
||||
this.cssClass = params.cssClass || "textfontclr collid";
|
||||
this.minimum = params.minimum;
|
||||
this.maximum = params.maximum;
|
||||
this.value = params.value;
|
||||
this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step.toString();
|
||||
this.increaseButtonAriaLabel = "Increase throughput by " + this.step.toString();
|
||||
this.costsVisible = options.costsVisible;
|
||||
this.requestUnitsUsageCost = options.requestUnitsUsageCost;
|
||||
this.spendAckChecked = options.spendAckChecked || ko.observable<boolean>(false);
|
||||
this.spendAckId = options.spendAckId || ko.observable<string>();
|
||||
this.spendAckText = options.spendAckText || ko.observable<string>();
|
||||
this.spendAckVisible = options.spendAckVisible || ko.observable<boolean>(false);
|
||||
this.showAsMandatory = !!options.showAsMandatory;
|
||||
this.isFixed = !!options.isFixed;
|
||||
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
|
||||
this.label = options.label || ko.observable<string>();
|
||||
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
|
||||
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
|
||||
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
|
||||
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
|
||||
this.throughputModeRadioName = options.throughputModeRadioName;
|
||||
this.autoPilotTiersList = options.autoPilotTiersList;
|
||||
this.selectedAutoPilotTier = options.selectedAutoPilotTier;
|
||||
this.autoPilotUsageCost = options.autoPilotUsageCost;
|
||||
}
|
||||
|
||||
public decreaseThroughput() {
|
||||
let offerThroughput: number = this._getSanitizedValue();
|
||||
|
||||
if (offerThroughput > this.minimum()) {
|
||||
offerThroughput -= this.step;
|
||||
if (offerThroughput < this.minimum()) {
|
||||
offerThroughput = this.minimum();
|
||||
}
|
||||
|
||||
this.value(offerThroughput);
|
||||
}
|
||||
}
|
||||
|
||||
public increaseThroughput() {
|
||||
let offerThroughput: number = this._getSanitizedValue();
|
||||
|
||||
if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) {
|
||||
offerThroughput += this.step;
|
||||
if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) {
|
||||
offerThroughput = this.maximum();
|
||||
}
|
||||
|
||||
this.value(offerThroughput);
|
||||
}
|
||||
}
|
||||
|
||||
public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.increaseThroughput();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
this.decreaseThroughput();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
private _getSanitizedValue(): number {
|
||||
const throughput = this.value();
|
||||
return isNaN(throughput) ? 0 : Number(throughput);
|
||||
}
|
||||
|
||||
private static _defaultStep: number = 100;
|
||||
}
|
||||
|
||||
export const ThroughputInputComponent = {
|
||||
viewModel: ThroughputInputViewModel,
|
||||
template: ThroughputInputComponentTemplate
|
||||
};
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<input
|
||||
class="throughputModeRadio nonFirstRadio"
|
||||
aria-label="Provisioned Throughput mode"
|
||||
aria-label="Manual mode"
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabindex="0"
|
||||
@@ -119,6 +119,10 @@
|
||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
||||
</p>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: isFixed -->
|
||||
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: !isAutoPilotSelected()">
|
||||
|
||||
@@ -19,7 +19,6 @@ describe("ContainerSampleGenerator", () => {
|
||||
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
|
||||
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
||||
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
||||
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
explorerStub.findDatabaseWithId = () => database;
|
||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
||||
return explorerStub;
|
||||
|
||||
@@ -15,7 +15,6 @@ import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
||||
import Database from "./Tree/Database";
|
||||
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
|
||||
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
|
||||
import { refreshCachedResources } from "../Common/DocumentClientUtilityBase";
|
||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||
@@ -206,13 +205,13 @@ export default class Explorer {
|
||||
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||
public isSettingsV2Enabled: ko.Observable<boolean>;
|
||||
public isMongoIndexEditorEnabled: ko.Observable<boolean>;
|
||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
|
||||
|
||||
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
||||
public shareAccessData: ko.Observable<AdHocAccessData>;
|
||||
@@ -412,8 +411,8 @@ export default class Explorer {
|
||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||
);
|
||||
//this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
|
||||
this.isSettingsV2Enabled = ko.observable(false);
|
||||
this.isMongoIndexEditorEnabled = ko.observable(false);
|
||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
@@ -422,13 +421,6 @@ export default class Explorer {
|
||||
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
|
||||
);
|
||||
|
||||
this.hasAutoPilotV2FeatureFlag = ko.computed(() => {
|
||||
if (this.isFeatureEnabled(Constants.Features.enableAutoPilotV2)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
this.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||
|
||||
this.databases = ko.observableArray<ViewModels.Database>();
|
||||
@@ -1120,7 +1112,7 @@ export default class Explorer {
|
||||
);
|
||||
this.renewExplorerShareAccess(this, this.tokenForRenewal())
|
||||
.fail((error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
this.renewTokenError("Invalid connection string specified");
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
@@ -1149,7 +1141,7 @@ export default class Explorer {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to generate share url: ${JSON.stringify(error)}`
|
||||
`Failed to generate share url: ${error.message}`
|
||||
);
|
||||
console.error(error);
|
||||
}
|
||||
@@ -1174,10 +1166,7 @@ export default class Explorer {
|
||||
deferred.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to connect: ${JSON.stringify(error)}`
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -1457,13 +1446,13 @@ export default class Explorer {
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: JSON.stringify(error)
|
||||
error: error.message
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while refreshing databases: ${JSON.stringify(error)}`
|
||||
`Error while refreshing databases: ${error.message}`
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1515,41 +1504,7 @@ export default class Explorer {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
this.isRefreshingExplorer(true);
|
||||
refreshCachedResources().then(
|
||||
() => {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
description: "Refresh successful",
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
},
|
||||
startKey
|
||||
);
|
||||
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases();
|
||||
},
|
||||
(error: any) => {
|
||||
this.isRefreshingExplorer(false);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while refreshing data: ${JSON.stringify(error)}`
|
||||
);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
description: "Unable to refresh cached resources",
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases();
|
||||
this.refreshNotebookList();
|
||||
};
|
||||
|
||||
@@ -1600,7 +1555,7 @@ export default class Explorer {
|
||||
return Promise.all(sparkPromises).then(() => workspaceItems);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, JSON.stringify(error));
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error.message);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
@@ -1638,7 +1593,7 @@ export default class Explorer {
|
||||
Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to get notebook workspace connection info: ${JSON.stringify(error)}`
|
||||
`Failed to get notebook workspace connection info: ${error.message}`
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -1715,7 +1670,7 @@ export default class Explorer {
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
|
||||
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${error.message}`);
|
||||
} finally {
|
||||
clearMessage && clearMessage();
|
||||
}
|
||||
@@ -1951,6 +1906,10 @@ export default class Explorer {
|
||||
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
|
||||
this.isSettingsV2Enabled(true);
|
||||
}
|
||||
|
||||
if (flights.indexOf(Constants.Flights.MongoIndexEditor) !== -1) {
|
||||
this.isMongoIndexEditorEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public findSelectedCollection(): ViewModels.Collection {
|
||||
@@ -2093,7 +2052,7 @@ export default class Explorer {
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
trace: JSON.stringify(error)
|
||||
trace: error.message
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -2555,7 +2514,7 @@ export default class Explorer {
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Could not download notebook ${JSON.stringify(error)}`
|
||||
`Could not download notebook ${error.message}`
|
||||
);
|
||||
|
||||
clearMessage();
|
||||
|
||||
@@ -892,7 +892,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
backendPromise.then(
|
||||
(result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge),
|
||||
(error: any) => {
|
||||
const errorMsg = `Failure in submitting query: ${query}: ${JSON.stringify(error)}`;
|
||||
const errorMsg = `Failure in submitting query: ${query}: ${error.message}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
filterQueryError: errorMsg
|
||||
@@ -1826,7 +1826,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
promise
|
||||
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
||||
.catch((error: any) => {
|
||||
const errorMsg = `Failed to process query result: ${JSON.stringify(error)}`;
|
||||
const errorMsg = `Failed to process query result: ${error.message}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
filterQueryError: errorMsg
|
||||
|
||||
@@ -57,9 +57,9 @@ export class GremlinClient {
|
||||
this.flushResult(result.requestId);
|
||||
}
|
||||
},
|
||||
failureCallback: (result: Result, error: string) => {
|
||||
failureCallback: (result: Result, error: any) => {
|
||||
if (typeof error !== "string") {
|
||||
error = JSON.stringify(error);
|
||||
error = error.message;
|
||||
}
|
||||
|
||||
const requestId = result.requestId;
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
@@ -62,7 +62,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
@@ -126,7 +126,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
@@ -208,7 +208,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
@@ -289,7 +289,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
@@ -348,7 +348,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isAuthWithResourceToken = ko.observable(true);
|
||||
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function configureStore(
|
||||
onTraceFailure(title, `${error.message} ${JSON.stringify(error.stack)}`);
|
||||
console.error(error);
|
||||
} else {
|
||||
onTraceFailure(title, JSON.stringify(error));
|
||||
onTraceFailure(title, error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -106,3 +106,7 @@
|
||||
.expanded::before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-list .main {
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -127,7 +127,6 @@
|
||||
be shared across all containers within the database.</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
|
||||
<!-- 1 -->
|
||||
<throughput-input-autopilot-v3 params="{
|
||||
@@ -158,38 +157,6 @@
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
|
||||
<!-- 1 -->
|
||||
<throughput-input params="{
|
||||
testId: 'databaseThroughputValue',
|
||||
value: throughputDatabase,
|
||||
minimum: minThroughputRU,
|
||||
maximum: maxThroughputRU,
|
||||
isEnabled: databaseCreateNewShared() && databaseCreateNew(),
|
||||
label: sharedThroughputRangeText,
|
||||
ariaLabel: sharedThroughputRangeText,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||
spendAckChecked: throughputSpendAck,
|
||||
spendAckId: 'throughputSpendAck',
|
||||
spendAckText: throughputSpendAckText,
|
||||
spendAckVisible: throughputSpendAckVisible,
|
||||
showAsMandatory: true,
|
||||
infoBubbleText: ruToolTipText,
|
||||
throughputAutoPilotRadioId: 'newContainer-databaseThroughput-autoPilotRadio',
|
||||
throughputProvisionedRadioId: 'newContainer-databaseThroughput-manualRadio',
|
||||
throughputModeRadioName: 'sharedThroughputModeRadio',
|
||||
isAutoPilotSelected: isSharedAutoPilotSelected,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
canExceedMaximumValue: canExceedMaximumValue,
|
||||
autoPilotTiersList: sharedAutoPilotTiersList,
|
||||
selectedAutoPilotTier: selectedSharedAutoPilotTier
|
||||
}">
|
||||
</throughput-input>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
<!-- Database provisioned throughput - End -->
|
||||
</div>
|
||||
|
||||
@@ -211,19 +178,6 @@
|
||||
data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }">
|
||||
</div>
|
||||
|
||||
<!-- <p class="seconddivpadding" data-bind="visible:(container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) && !databaseHasSharedOffer()">
|
||||
Where did 'fixed' containers go?
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="/info-bubble.svg" alt="More information">
|
||||
<span class="tooltiptext noFixedCollectionsTooltipWidth">
|
||||
We lowered the minimum throughput for partitioned containers to 400 RU/s, removing the only drawback partitioned containers had. <br/><br/>
|
||||
We are planning to deprecate ability to create non-partitioned containers, as they do not allow you to scale elastically.
|
||||
If for some reason you still need a container without partition key, you can use our SDKs to create one. <br/><br/>
|
||||
Please <a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback">contact us</a> if you have questions or concerns.
|
||||
</span>
|
||||
</span>
|
||||
</p> -->
|
||||
|
||||
<!-- Indexing For Shared Throughput - start -->
|
||||
<div class="seconddivpadding"
|
||||
data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()">
|
||||
@@ -276,7 +230,7 @@
|
||||
<!-- Fixed option button - Start -->
|
||||
<div class="tab">
|
||||
<input type="radio" id="tab1" name="storage" value="10" class="radio" data-bind="checked: storage">
|
||||
<label for="tab1">Fixed (10 GB)</label>
|
||||
<label for="tab1">Fixed (20 GB)</label>
|
||||
</div>
|
||||
<!-- Fixed option button - End -->
|
||||
|
||||
@@ -287,96 +241,8 @@
|
||||
</div>
|
||||
<!-- Unlimited option button - End -->
|
||||
</div>
|
||||
|
||||
<!-- Fixed Button Content - Start -->
|
||||
<div class="tabcontent" data-bind="visible: isFixedStorageSelected() && !databaseHasSharedOffer()">
|
||||
<!-- 2 -->
|
||||
<!-- note: this is used when creating a fixed collection without shared throughput. only manual throughput is available. -->
|
||||
<throughput-input params="{
|
||||
testId: 'fixedThroughputValue',
|
||||
value: throughputSinglePartition,
|
||||
minimum: minThroughputRU,
|
||||
maximum: maxThroughputRU,
|
||||
isEnabled: isFixedStorageSelected() && !databaseHasSharedOffer(),
|
||||
label: throughputRangeText,
|
||||
ariaLabel: throughputRangeText,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCost
|
||||
showAsMandatory: true,
|
||||
isFixed: true,
|
||||
infoBubbleText: ruToolTipText,
|
||||
canExceedMaximumValue: canExceedMaximumValue
|
||||
}">
|
||||
</throughput-input>
|
||||
<div data-bind="visible: rupmVisible">
|
||||
<div class="tabs">
|
||||
<p class="pkPadding">
|
||||
<span class="mandatoryStar">*</span>
|
||||
<span class="addCollectionLabel">RU/m</span>
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="/info-bubble.svg" alt="More information">
|
||||
<span class="tooltiptext throughputRuInfo">
|
||||
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
|
||||
per
|
||||
minute
|
||||
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
|
||||
with
|
||||
RU/m
|
||||
enabled, the RU/m budget will be 50,000 RU/m.
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
|
||||
<div class="tab">
|
||||
<input type="radio" id="rupmOn" name="rupmcoll" value="on" class="radio"
|
||||
data-bind="checked: rupm">
|
||||
<label for="rupmOn">ON</label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<input type="radio" id="rupmOff" name="rupmcoll" value="off" class="radio"
|
||||
data-bind="checked: rupm">
|
||||
<label for="rupmOff">OFF</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Fixed Button Content - End -->
|
||||
|
||||
<!-- Unlimited Button Content - Start -->
|
||||
<div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()">
|
||||
<div data-bind="visible: rupmVisible">
|
||||
<div class="tabs">
|
||||
<p>
|
||||
<span class="mandatoryStar">*</span>
|
||||
<span class="addCollectionLabel">RU/m</span>
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="/info-bubble.svg" alt="More information">
|
||||
<span class="tooltiptext throughputRuInfo">
|
||||
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
|
||||
per
|
||||
minute
|
||||
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
|
||||
with
|
||||
RU/m
|
||||
enabled, the RU/m budget will be 50,000 RU/m.
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
|
||||
<div class="tab">
|
||||
<input type="radio" id="rupmOn2" name="rupmcoll2" value="on" class="radio"
|
||||
data-bind="checked: rupm">
|
||||
<label for="rupmOn2">ON</label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<input type="radio" id="rupmOff2" name="rupmcoll2" value="off" class="radio"
|
||||
data-bind="checked: rupm">
|
||||
<label for="rupmOff2">OFF</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="visible: partitionKeyVisible">
|
||||
<p>
|
||||
<span class="mandatoryStar">*</span>
|
||||
@@ -442,7 +308,6 @@
|
||||
<!-- Provision collection throughput checkbox - end -->
|
||||
|
||||
<!-- Provision collection throughput spinner - start -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
|
||||
<!-- 3 -->
|
||||
<throughput-input-autopilot-v3 params="{
|
||||
@@ -472,40 +337,6 @@
|
||||
}">
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
|
||||
<!-- 3 -->
|
||||
<throughput-input params="{
|
||||
testId: 'collectionThroughputValue',
|
||||
value: throughputMultiPartition,
|
||||
minimum: minThroughputRU,
|
||||
maximum: maxThroughputRU,
|
||||
isEnabled: displayCollectionThroughput,
|
||||
label: throughputRangeText,
|
||||
ariaLabel: throughputRangeText,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: dedicatedRequestUnitsUsageCost,
|
||||
spendAckChecked: throughputSpendAck,
|
||||
spendAckId: 'throughputSpendAckCollection',
|
||||
spendAckText: throughputSpendAckText,
|
||||
spendAckVisible: throughputSpendAckVisible,
|
||||
showAsMandatory: true,
|
||||
infoBubbleText: ruToolTipText,
|
||||
throughputAutoPilotRadioId: 'newContainer-containerThroughput-autoPilotRadio',
|
||||
throughputProvisionedRadioId: 'newContainer-containerThroughput-manualRadio',
|
||||
throughputModeRadioName: 'throughputModeRadioName',
|
||||
isAutoPilotSelected: isAutoPilotSelected,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
canExceedMaximumValue: canExceedMaximumValue,
|
||||
autoPilotTiersList: autoPilotTiersList,
|
||||
selectedAutoPilotTier: selectedAutoPilotTier,
|
||||
showAutoPilot: !isFixedStorageSelected()
|
||||
}">
|
||||
</throughput-input>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- Provision collection throughput spinner - end -->
|
||||
<!-- /ko -->
|
||||
<!-- Provision collection throughput - end -->
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import AddCollectionPane from "./AddCollectionPane";
|
||||
import Explorer from "../Explorer";
|
||||
import ko from "knockout";
|
||||
import { AutopilotTier, DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
|
||||
describe("Add Collection Pane", () => {
|
||||
describe("isValid()", () => {
|
||||
@@ -41,25 +40,6 @@ describe("Add Collection Pane", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("should be true if autopilot enabled and select valid tier", () => {
|
||||
explorer.databaseAccount(mockDatabaseAccount);
|
||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
||||
addCollectionPane.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
addCollectionPane.isAutoPilotSelected(true);
|
||||
addCollectionPane.selectedAutoPilotTier(AutopilotTier.Tier2);
|
||||
expect(addCollectionPane.isValid()).toBe(true);
|
||||
});
|
||||
|
||||
it("should be false if autopilot enabled and select invalid tier", () => {
|
||||
explorer.databaseAccount(mockDatabaseAccount);
|
||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
||||
addCollectionPane.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
addCollectionPane.isAutoPilotSelected(true);
|
||||
addCollectionPane.selectedAutoPilotTier(0);
|
||||
expect(addCollectionPane.isValid()).toBe(false);
|
||||
});
|
||||
|
||||
it("should be true if graph API and partition key is not /id nor /label", () => {
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
|
||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||
@@ -42,8 +41,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public partitionKeyVisible: ko.Computed<boolean>;
|
||||
public partitionKeyPattern: ko.Computed<string>;
|
||||
public partitionKeyTitle: ko.Computed<string>;
|
||||
public rupm: ko.Observable<string>;
|
||||
public rupmVisible: ko.Observable<boolean>;
|
||||
public storage: ko.Observable<string>;
|
||||
public throughputSinglePartition: ViewModels.Editable<number>;
|
||||
public throughputMultiPartition: ViewModels.Editable<number>;
|
||||
@@ -76,10 +73,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public debugstring: ko.Computed<string>;
|
||||
public displayCollectionThroughput: ko.Computed<boolean>;
|
||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
||||
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public selectedSharedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public sharedAutoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public isSharedAutoPilotSelected: ko.Observable<boolean>;
|
||||
public autoPilotThroughput: ko.Observable<number>;
|
||||
public sharedAutoPilotThroughput: ko.Observable<number>;
|
||||
@@ -93,7 +86,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public isAnalyticalStorageOn: ko.Observable<boolean>;
|
||||
public isSynapseLinkUpdating: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public ruToolTipText: ko.Computed<string>;
|
||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||
public showUpsellMessage: ko.PureComputed<boolean>;
|
||||
@@ -103,8 +95,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
constructor(options: AddCollectionPaneOptions) {
|
||||
super(options);
|
||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
|
||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
this.formWarnings = ko.observable<string>();
|
||||
@@ -149,12 +140,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
return "";
|
||||
});
|
||||
this.rupm = ko.observable<string>(Constants.RUPMStates.off);
|
||||
this.rupmVisible = ko.observable<boolean>(false);
|
||||
const featureSubcription = this.container.features.subscribe(() => {
|
||||
this.rupmVisible(this.container.isFeatureEnabled(Constants.Features.enableRupm));
|
||||
featureSubcription.dispose();
|
||||
});
|
||||
|
||||
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
||||
|
||||
@@ -172,13 +157,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
this.databaseHasSharedOffer = ko.observable<boolean>(true);
|
||||
this.throughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
@@ -207,7 +192,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
account.properties.readLocations.length) ||
|
||||
1;
|
||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
||||
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
let throughputSpendAckText: string;
|
||||
let estimatedSpend: string;
|
||||
@@ -217,23 +201,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
||||
} else {
|
||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
this.sharedAutoPilotThroughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -270,7 +246,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
account.properties.readLocations.length) ||
|
||||
1;
|
||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
||||
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
let throughputSpendAckText: string;
|
||||
let estimatedSpend: string;
|
||||
@@ -280,15 +255,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
this.throughputMultiPartition(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled
|
||||
multimaster
|
||||
);
|
||||
} else {
|
||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
@@ -296,7 +269,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -448,7 +420,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||
const autoscaleThroughput = this.autoPilotThroughput() * 1;
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
const selectedThroughput: number = this._getThroughput();
|
||||
@@ -491,14 +463,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.isAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
|
||||
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
|
||||
);
|
||||
this.sharedAutoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
|
||||
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
|
||||
);
|
||||
this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
@@ -507,9 +471,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
return "";
|
||||
}
|
||||
const isDatabaseThroughput: boolean = this.databaseCreateNewShared();
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, isDatabaseThroughput);
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput);
|
||||
});
|
||||
|
||||
this.resetData();
|
||||
@@ -704,8 +666,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
storage: this.storage(),
|
||||
offerThroughput: this._getThroughput(),
|
||||
partitionKey: this.partitionKey(),
|
||||
databaseId: this.databaseId(),
|
||||
rupm: this.rupm()
|
||||
databaseId: this.databaseId()
|
||||
}),
|
||||
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
|
||||
subscriptionQuotaId: this.container.quotaId(),
|
||||
@@ -806,7 +767,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
id: this.collectionId(),
|
||||
storage: this.storage(),
|
||||
partitionKey,
|
||||
rupm: this.rupm(),
|
||||
|
||||
uniqueKeyPolicy,
|
||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||
}),
|
||||
@@ -881,7 +842,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
id: this.collectionId(),
|
||||
storage: this.storage(),
|
||||
partitionKey,
|
||||
rupm: this.rupm(),
|
||||
|
||||
uniqueKeyPolicy,
|
||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||
}),
|
||||
@@ -896,9 +857,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
};
|
||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey);
|
||||
this.resetData();
|
||||
return refreshCachedResources().then(() => {
|
||||
this.container.refreshAllDatabases();
|
||||
});
|
||||
this.container.refreshAllDatabases();
|
||||
},
|
||||
(reason: any) => {
|
||||
this.isExecuting(false);
|
||||
@@ -919,7 +878,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
id: this.collectionId(),
|
||||
storage: this.storage(),
|
||||
partitionKey,
|
||||
rupm: this.rupm(),
|
||||
|
||||
uniqueKeyPolicy,
|
||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||
},
|
||||
@@ -945,13 +904,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
this.throughputSpendAck(false);
|
||||
this.isAutoPilotSelected(false);
|
||||
this.isSharedAutoPilotSelected(false);
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
} else {
|
||||
this.selectedAutoPilotTier(undefined);
|
||||
this.selectedSharedAutoPilotTier(undefined);
|
||||
}
|
||||
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
|
||||
this.uniqueKeys([]);
|
||||
this.useIndexingForSharedThroughput(true);
|
||||
|
||||
@@ -1005,20 +960,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
public onRupmOptionsKeyDown(source: any, event: KeyboardEvent): boolean {
|
||||
if (event.key === "ArrowRight") {
|
||||
this.rupm("off");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft") {
|
||||
this.rupm("on");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public onEnableSynapseLinkButtonClicked() {
|
||||
this.container.openEnableSynapseLinkDialog();
|
||||
}
|
||||
@@ -1030,32 +971,18 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) {
|
||||
const autoPilot = this._getAutoPilot();
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
|
||||
!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||
) {
|
||||
this.formErrors(
|
||||
!this.hasAutoPilotV2FeatureFlag()
|
||||
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||
: "Please select an Autopilot tier from the list."
|
||||
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const throughput = this._getThroughput();
|
||||
const maxThroughputWithRUPM =
|
||||
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this._calculateNumberOfPartitions();
|
||||
|
||||
if (this.rupm() === Constants.RUPMStates.on && throughput > maxThroughputWithRUPM) {
|
||||
this.formErrors(
|
||||
`The maximum supported provisioned throughput with RU/m enabled is ${maxThroughputWithRUPM} RU/s. Please turn off RU/m to incease thoughput above ${maxThroughputWithRUPM} RU/s.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
|
||||
this.formErrors(`Please acknowledge the estimated daily spend.`);
|
||||
@@ -1070,7 +997,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
const autoscaleThroughput = this.autoPilotThroughput() * 1;
|
||||
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -1117,31 +1043,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.databaseCreateNewShared() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.databaseCreateNewShared() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.selectedSharedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedSharedAutoPilotTier() };
|
||||
if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()) {
|
||||
return {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
};
|
||||
}
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.autoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedAutoPilotTier() };
|
||||
if (this.isAutoPilotSelected() && this.autoPilotThroughput()) {
|
||||
return {
|
||||
maxThroughput: this.autoPilotThroughput() * 1
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
|
||||
<input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]"
|
||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { placeholder: databaseIdPlaceHolder }"
|
||||
aria-label="Database id" autofocus>
|
||||
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { 'aria-label': databaseIdLabel, 'placeholder': databaseIdPlaceHolder }"
|
||||
autofocus>
|
||||
|
||||
<!-- Database provisioned throughput - Start -->
|
||||
<!-- ko if: canConfigureThroughput -->
|
||||
@@ -89,7 +89,6 @@
|
||||
data-bind="text: databaseLevelThroughputTooltipText"></span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: databaseCreateNewShared">
|
||||
<throughput-input-autopilot-v3 params="{
|
||||
step: 100,
|
||||
@@ -124,42 +123,6 @@
|
||||
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: databaseCreateNewShared">
|
||||
<throughput-input params="{
|
||||
step: 100,
|
||||
value: throughput,
|
||||
testId: 'sharedThroughputValue',
|
||||
minimum: minThroughputRU,
|
||||
maximum: maxThroughputRU,
|
||||
isEnabled: databaseCreateNewShared,
|
||||
label: throughputRangeText,
|
||||
ariaLabel: throughputRangeText,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||
spendAckChecked: throughputSpendAck,
|
||||
spendAckId: 'throughputSpendAckDatabase',
|
||||
spendAckText: throughputSpendAckText,
|
||||
spendAckVisible: throughputSpendAckVisible,
|
||||
showAsMandatory: true,
|
||||
infoBubbleText: ruToolTipText,
|
||||
throughputAutoPilotRadioId: 'newDatabase-databaseThroughput-autoPilotRadio',
|
||||
throughputProvisionedRadioId: 'newDatabase-databaseThroughput-manualRadio',
|
||||
throughputModeRadioName: 'throughputModeRadioName',
|
||||
isAutoPilotSelected: isAutoPilotSelected,
|
||||
autoPilotTiersList: autoPilotTiersList,
|
||||
selectedAutoPilotTier: selectedAutoPilotTier,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
canExceedMaximumValue: canExceedMaximumValue
|
||||
}">
|
||||
</throughput-input>
|
||||
<p data-bind="visible: canRequestSupport">
|
||||
<!-- TODO: Replace link with call to the Azure Support blade --><a
|
||||
href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request">Contact
|
||||
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
<!-- Database provisioned throughput - End -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,12 +38,9 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public upsellAnchorUrl: ko.PureComputed<string>;
|
||||
public upsellAnchorText: ko.PureComputed<string>;
|
||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
||||
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public maxAutoPilotThroughputSet: ko.Observable<number>;
|
||||
public autoPilotUsageCost: ko.Computed<string>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public ruToolTipText: ko.Computed<string>;
|
||||
public isFreeTierAccount: ko.Computed<boolean>;
|
||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||
@@ -53,8 +50,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
super(options);
|
||||
this.title((this.container && this.container.addDatabaseText()) || "New Database");
|
||||
this.databaseId = ko.observable<string>();
|
||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
|
||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
|
||||
@@ -94,10 +90,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
this.minThroughputRU = ko.observable<number>();
|
||||
this.throughputSpendAckText = ko.observable<string>();
|
||||
this.throughputSpendAck = ko.observable<boolean>(false);
|
||||
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
|
||||
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
|
||||
);
|
||||
this.isAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
@@ -105,13 +97,11 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, true /* isDatabaseThroughput */);
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */);
|
||||
});
|
||||
this.throughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
@@ -142,19 +132,12 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
let estimatedSpendAcknowledge: string;
|
||||
let estimatedSpend: string;
|
||||
if (!this.isAutoPilotSelected()) {
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
||||
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
} else {
|
||||
@@ -169,7 +152,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
}
|
||||
@@ -208,7 +190,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
|
||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
|
||||
@@ -325,7 +307,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public resetData() {
|
||||
this.databaseId("");
|
||||
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
||||
this.selectedAutoPilotTier(undefined);
|
||||
this.isAutoPilotSelected(false);
|
||||
this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this._updateThroughputLimitByDatabase();
|
||||
@@ -413,17 +394,12 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
const autoPilot = this._isAutoPilotSelectedAndWhatTier();
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
|
||||
!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||
) {
|
||||
this.formErrors(
|
||||
!this.hasAutoPilotV2FeatureFlag()
|
||||
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||
: "Please select an Autopilot tier from the list."
|
||||
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -438,7 +414,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
|
||||
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -451,15 +426,10 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.maxAutoPilotThroughputSet() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedAutoPilotTier() };
|
||||
if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) {
|
||||
return {
|
||||
maxThroughput: this.maxAutoPilotThroughputSet() * 1
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@ export class BrowseQueriesPane extends ContextualPaneBase {
|
||||
},
|
||||
startKey
|
||||
);
|
||||
this.formErrors("Failed to setup a collection for saved queries");
|
||||
this.formErrors(`Failed to setup a collection for saved queries: ${JSON.stringify(error)}`);
|
||||
this.formErrors(`Failed to setup a collection for saved queries: ${error.message}`);
|
||||
} finally {
|
||||
this.isExecuting(false);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@
|
||||
</span>
|
||||
</div>
|
||||
<!-- 1 -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
@@ -173,38 +172,6 @@
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
|
||||
<throughput-input
|
||||
params="{
|
||||
testId: 'cassandraThroughputValue-v2-shared',
|
||||
value: keyspaceThroughput,
|
||||
minimum: minThroughputRU,
|
||||
maximum: maxThroughputRU,
|
||||
isEnabled: keyspaceCreateNew() && keyspaceHasSharedOffer(),
|
||||
label: sharedThroughputRangeText,
|
||||
ariaLabel: sharedThroughputRangeText,
|
||||
requestUnitsUsageCost: requestUnitsUsageCostShared,
|
||||
spendAckChecked: sharedThroughputSpendAck,
|
||||
spendAckId: 'sharedThroughputSpendAck-v2-shared',
|
||||
spendAckText: sharedThroughputSpendAckText,
|
||||
spendAckVisible: sharedThroughputSpendAckVisible,
|
||||
showAsMandatory: true,
|
||||
infoBubbleText: ruToolTipText,
|
||||
throughputAutoPilotRadioId: 'newKeyspace-databaseThroughput-autoPilotRadio-v2-shared',
|
||||
throughputProvisionedRadioId: 'newKeyspace-databaseThroughput-manualRadio-v2-shared',
|
||||
isAutoPilotSelected: isSharedAutoPilotSelected,
|
||||
autoPilotTiersList: sharedAutoPilotTiersList,
|
||||
costsVisible: costsVisible,
|
||||
selectedAutoPilotTier: selectedSharedAutoPilotTier,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
canExceedMaximumValue: canExceedMaximumValue
|
||||
}"
|
||||
>
|
||||
</throughput-input>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
<!-- Database provisioned throughput - End -->
|
||||
</div>
|
||||
<div class="seconddivpadding">
|
||||
@@ -257,7 +224,6 @@
|
||||
</span>
|
||||
</div>
|
||||
<!-- 2 -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
@@ -289,40 +255,6 @@
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
|
||||
<throughput-input
|
||||
params="{
|
||||
testId: 'cassandraSharedThroughputValue-v2-dedicated',
|
||||
value: throughput,
|
||||
minimum: minThroughputRU,
|
||||
maximum: maxThroughputRU,
|
||||
isEnabled: !keyspaceHasSharedOffer() || dedicateTableThroughput(),
|
||||
label: throughputRangeText,
|
||||
ariaLabel: throughputRangeText,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCostDedicated,
|
||||
spendAckChecked: throughputSpendAck,
|
||||
spendAckId: 'throughputSpendAckCassandra-v2-dedicated',
|
||||
spendAckText: throughputSpendAckText,
|
||||
spendAckVisible: throughputSpendAckVisible,
|
||||
showAsMandatory: true,
|
||||
infoBubbleText: ruToolTipText,
|
||||
throughputAutoPilotRadioId: 'newKeyspace-containerThroughput-autoPilotRadio-v2-dedicated',
|
||||
throughputProvisionedRadioId: 'newKeyspace-containerThroughput-manualRadio-v2-dedicated',
|
||||
isAutoPilotSelected: isAutoPilotSelected,
|
||||
autoPilotTiersList: autoPilotTiersList,
|
||||
selectedAutoPilotTier: selectedAutoPilotTier,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
showAutoPilot: false,
|
||||
canExceedMaximumValue: canExceedMaximumValue
|
||||
}"
|
||||
>
|
||||
</throughput-input>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
<!-- Provision table throughput - end -->
|
||||
</div>
|
||||
<div class="paneFooter">
|
||||
|
||||
@@ -38,10 +38,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
public sharedThroughputSpendAck: ko.Observable<boolean>;
|
||||
public sharedThroughputSpendAckText: ko.Observable<string>;
|
||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
||||
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public selectedSharedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public sharedAutoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public isSharedAutoPilotSelected: ko.Observable<boolean>;
|
||||
public selectedAutoPilotThroughput: ko.Observable<number>;
|
||||
public sharedAutoPilotThroughput: ko.Observable<number>;
|
||||
@@ -49,7 +45,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
public sharedThroughputSpendAckVisible: ko.Computed<boolean>;
|
||||
public throughputSpendAckVisible: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public isFreeTierAccount: ko.Computed<boolean>;
|
||||
public ruToolTipText: ko.Computed<string>;
|
||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||
@@ -61,8 +56,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
this.title("Add Table");
|
||||
this.createTableQuery = ko.observable<string>("CREATE TABLE ");
|
||||
this.keyspaceCreateNew = ko.observable<boolean>(true);
|
||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
|
||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
this.keyspaceOffers = new HashMap<DataModels.Offer>();
|
||||
this.keyspaceIds = ko.observableArray<string>();
|
||||
@@ -90,8 +84,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
});
|
||||
|
||||
this.tableId = ko.observable<string>("");
|
||||
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.isAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.selectedAutoPilotThroughput = ko.observable<number>();
|
||||
@@ -102,11 +94,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
if (!enableAutoPilot) {
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
}
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
});
|
||||
this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
@@ -144,19 +136,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
let estimatedSpend: string;
|
||||
let estimatedDedicatedSpendAcknowledge: string;
|
||||
if (!this.isAutoPilotSelected()) {
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
||||
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
} else {
|
||||
@@ -171,7 +156,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
}
|
||||
@@ -196,19 +180,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
let estimatedSpend: string;
|
||||
let estimatedSharedSpendAcknowledge: string;
|
||||
if (!this.isSharedAutoPilotSelected()) {
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
this.keyspaceThroughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster);
|
||||
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
this.keyspaceThroughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
} else {
|
||||
@@ -223,7 +200,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
}
|
||||
@@ -246,7 +222,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
|
||||
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected()) {
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
|
||||
@@ -255,7 +231,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||
const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
|
||||
@@ -280,22 +256,13 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
updateKeyspaceIds(this.container.nonSystemDatabases());
|
||||
}
|
||||
|
||||
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
|
||||
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
|
||||
);
|
||||
this.sharedAutoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
|
||||
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
|
||||
);
|
||||
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
const autoPilot = this._getAutoPilot();
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
const isDatabaseThroughput: boolean = this.keyspaceCreateNew();
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, isDatabaseThroughput);
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,8 +294,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false
|
||||
databaseId: this.keyspaceId()
|
||||
}),
|
||||
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
|
||||
subscriptionQuotaId: this.container.quotaId(),
|
||||
@@ -352,15 +318,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
|
||||
let createTableAndKeyspacePromise: Q.Promise<any>;
|
||||
const toCreateKeyspace: boolean = this.keyspaceCreateNew();
|
||||
const useAutoPilotForKeyspace: boolean =
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.selectedSharedAutoPilotTier());
|
||||
const useAutoPilotForKeyspace: boolean = this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput();
|
||||
const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
|
||||
const createKeyspaceQuery: string = this.keyspaceHasSharedOffer()
|
||||
? useAutoPilotForKeyspace
|
||||
? !this.hasAutoPilotV2FeatureFlag()
|
||||
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
|
||||
: `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.selectedSharedAutoPilotTier()};`
|
||||
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
|
||||
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};`
|
||||
: `${createKeyspaceQueryPrefix};`;
|
||||
const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`;
|
||||
@@ -385,7 +347,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false,
|
||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||
}),
|
||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||
@@ -432,7 +393,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false,
|
||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||
}),
|
||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||
@@ -462,7 +422,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false,
|
||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||
},
|
||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||
@@ -489,8 +448,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
||||
this.isAutoPilotSelected(false);
|
||||
this.isSharedAutoPilotSelected(false);
|
||||
this.selectedAutoPilotTier(null);
|
||||
this.selectedSharedAutoPilotTier(null);
|
||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
||||
@@ -512,7 +469,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.sharedThroughputSpendAck()
|
||||
@@ -523,7 +479,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -538,17 +493,12 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
) {
|
||||
const autoPilot = this._getAutoPilot();
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
|
||||
!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||
) {
|
||||
this.formErrors(
|
||||
!this.hasAutoPilotV2FeatureFlag()
|
||||
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||
: "Please select an Autopilot tier from the list."
|
||||
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -575,33 +525,20 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.keyspaceCreateNew() &&
|
||||
this.keyspaceHasSharedOffer() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.keyspaceCreateNew() &&
|
||||
this.keyspaceHasSharedOffer() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.selectedSharedAutoPilotTier())
|
||||
this.keyspaceCreateNew() &&
|
||||
this.keyspaceHasSharedOffer() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedSharedAutoPilotTier() };
|
||||
return {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.selectedAutoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedAutoPilotTier() };
|
||||
if (this.selectedAutoPilotThroughput()) {
|
||||
return {
|
||||
maxThroughput: this.selectedAutoPilotThroughput() * 1
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -82,7 +82,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
|
||||
this.container
|
||||
.renewShareAccess(this.accessKey())
|
||||
.fail((error: any) => {
|
||||
const errorMessage: string = JSON.stringify(error);
|
||||
const errorMessage: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
|
||||
@@ -88,7 +88,7 @@ export class SaveQueryPane extends ContextualPaneBase {
|
||||
(error: any) => {
|
||||
this.isExecuting(false);
|
||||
if (typeof error != "string") {
|
||||
error = JSON.stringify(error);
|
||||
error = error.message;
|
||||
}
|
||||
this.formErrors("Failed to save query");
|
||||
this.formErrorsDetails(`Failed to save query: ${error}`);
|
||||
@@ -143,7 +143,7 @@ export class SaveQueryPane extends ContextualPaneBase {
|
||||
startKey
|
||||
);
|
||||
this.formErrors("Failed to setup a container for saved queries");
|
||||
this.formErrors(`Failed to setup a container for saved queries: ${JSON.stringify(error)}`);
|
||||
this.formErrors(`Failed to setup a container for saved queries: ${error.message}`);
|
||||
} finally {
|
||||
this.isExecuting(false);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class SetupNotebooksPane extends ContextualPaneBase {
|
||||
"Successfully created a default notebook workspace for the account"
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = typeof error == "string" ? error : JSON.stringify(error);
|
||||
const errorMessage = typeof error == "string" ? error : error.message;
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.CreateNotebookWorkspace,
|
||||
{
|
||||
|
||||
@@ -15,13 +15,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
import { sendMessage } from "../../Common/MessageHandler";
|
||||
import Explorer from "../Explorer";
|
||||
import {
|
||||
queryDocuments,
|
||||
refreshCachedResources,
|
||||
deleteDocument,
|
||||
updateDocument,
|
||||
createDocument
|
||||
} from "../../Common/DocumentClientUtilityBase";
|
||||
import { queryDocuments, deleteDocument, updateDocument, createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
|
||||
export interface CassandraTableKeys {
|
||||
@@ -424,7 +418,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
ConsoleDataType.Info,
|
||||
`Successfully created a keyspace with query ${createKeyspaceQuery}`
|
||||
);
|
||||
refreshCachedResources().finally(() => deferred.resolve());
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
@@ -471,15 +465,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
ConsoleDataType.Info,
|
||||
`Successfully created a table with query ${createTableQuery}`
|
||||
);
|
||||
refreshCachedResources(null).then(
|
||||
() => {
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
// Still resolve since the keyspace/table was successfully created at this point.
|
||||
deferred.resolve();
|
||||
}
|
||||
);
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
@@ -520,15 +506,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
ConsoleDataType.Info,
|
||||
`Successfully deleted resource with query ${deleteQuery}`
|
||||
);
|
||||
refreshCachedResources(null).then(
|
||||
() => {
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
// Still resolve since the keyspace/table was successfully deleted at this point.
|
||||
deferred.resolve();
|
||||
}
|
||||
);
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
<span class="scaleSettingTitle">Scale</span>
|
||||
</div>
|
||||
<div class="ssTextAllignment" id="scaleRegion">
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
testId: testId,
|
||||
@@ -51,34 +50,6 @@
|
||||
}"
|
||||
>
|
||||
</throughput-input-autopilot-v3>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<throughput-input
|
||||
params="{
|
||||
testId: testId,
|
||||
class: 'scaleForm dirty',
|
||||
value: throughput,
|
||||
minimum: minRUs,
|
||||
maximum: maxRUThroughputInputLimit,
|
||||
canExceedMaximumValue: canThroughputExceedMaximumValue,
|
||||
step: throughputIncreaseFactor,
|
||||
label: throughputTitle,
|
||||
ariaLabel: throughputAriaLabel,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
|
||||
throughputProvisionedRadioId: throughputProvisionedRadioId,
|
||||
throughputModeRadioName: throughputModeRadioName,
|
||||
showAutoPilot: userCanChangeProvisioningTypes,
|
||||
isAutoPilotSelected: isAutoPilotSelected,
|
||||
autoPilotTiersList: autoPilotTiersList,
|
||||
selectedAutoPilotTier: selectedAutoPilotTier,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
canExceedMaximumValue: canExceedMaximumValue
|
||||
}"
|
||||
>
|
||||
</throughput-input>
|
||||
<!-- /ko -->
|
||||
|
||||
<div class="estimatedCost" data-bind="visible: costsVisible">
|
||||
<p data-bind="visible: minRUAnotationVisible">
|
||||
|
||||
@@ -54,7 +54,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
// editables
|
||||
public isAutoPilotSelected: ViewModels.Editable<boolean>;
|
||||
public throughput: ViewModels.Editable<number>;
|
||||
public selectedAutoPilotTier: ViewModels.Editable<DataModels.AutopilotTier>;
|
||||
public autoPilotThroughput: ViewModels.Editable<number>;
|
||||
public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor;
|
||||
|
||||
@@ -81,11 +80,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
public throughputTitle: ko.PureComputed<string>;
|
||||
public throughputAriaLabel: ko.PureComputed<string>;
|
||||
public userCanChangeProvisioningTypes: ko.Observable<boolean>;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public autoPilotUsageCost: ko.PureComputed<string>;
|
||||
public warningMessage: ko.Computed<string>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public overrideWithAutoPilotSettings: ko.Computed<boolean>;
|
||||
public overrideWithProvisionedThroughputSettings: ko.Computed<boolean>;
|
||||
public testId: string;
|
||||
@@ -102,9 +99,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
super(options);
|
||||
|
||||
this.container = options.node && (options.node as ViewModels.Database).container;
|
||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||
this.selectedAutoPilotTier = editable.observable<DataModels.AutopilotTier>();
|
||||
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>();
|
||||
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
||||
|
||||
// html element ids
|
||||
@@ -119,23 +113,13 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
this.autoPilotThroughput = editable.observable<number>();
|
||||
const offer = this.database && this.database.offer && this.database.offer();
|
||||
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
|
||||
this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.isAutoPilotSelected(true);
|
||||
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.tier) {
|
||||
if (AutoPilotUtils.isValidAutoPilotTier(offerAutopilotSettings.tier)) {
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.isAutoPilotSelected(true);
|
||||
this.selectedAutoPilotTier(offerAutopilotSettings.tier);
|
||||
this.autoPilotTiersList(AutoPilotUtils.getAvailableAutoPilotTiersOptions(offerAutopilotSettings.tier));
|
||||
}
|
||||
this.userCanChangeProvisioningTypes = ko.observable(true);
|
||||
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.isAutoPilotSelected(true);
|
||||
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,13 +134,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
});
|
||||
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
|
||||
const autoPilot = this.autoPilotThroughput();
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, true /* isDatabaseThroughput */);
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */);
|
||||
});
|
||||
|
||||
this.requestUnitsUsageCost = ko.pureComputed(() => {
|
||||
@@ -181,8 +163,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
multimaster
|
||||
);
|
||||
} else {
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -216,16 +197,10 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
});
|
||||
|
||||
this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
@@ -283,7 +258,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
|
||||
this.throughputTitle = ko.pureComputed<string>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
|
||||
return `Throughput (${this.minRUs().toLocaleString()} - unlimited RU/s)`;
|
||||
@@ -306,7 +281,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
this.warningMessage = ko.computed<string>(() => {
|
||||
const offer = this.database && this.database.offer && this.database.offer();
|
||||
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
|
||||
if (this.overrideWithProvisionedThroughputSettings()) {
|
||||
return AutoPilotUtils.manualToAutoscaleDisclaimer;
|
||||
}
|
||||
|
||||
@@ -316,9 +291,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
|
||||
) {
|
||||
const throughput = offer.content.offerAutopilotSettings
|
||||
? !this.hasAutoPilotV2FeatureFlag()
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
: offer.content.offerAutopilotSettings.maximumTierThroughput
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
: offer.content.offerThroughput;
|
||||
|
||||
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
|
||||
@@ -375,20 +348,13 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
const isAutoPilot = this.isAutoPilotSelected();
|
||||
const isManual = !this.isAutoPilotSelected();
|
||||
if (isAutoPilot) {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && !AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()))
|
||||
) {
|
||||
if (!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) {
|
||||
return false;
|
||||
}
|
||||
if (this.isAutoPilotSelected.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput.editableIsDirty()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
|
||||
) {
|
||||
if (this.autoPilotThroughput.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -412,10 +378,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected.editableIsDirty()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
|
||||
) {
|
||||
if (this.isAutoPilotSelected.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -496,8 +459,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
databaseAccountName: userContext.databaseAccount.name,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.database.id(),
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
throughput: newThroughput
|
||||
};
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.database.offer().content.offerThroughput = originalThroughputValue;
|
||||
@@ -547,11 +509,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
public onRevertClick = (): Q.Promise<any> => {
|
||||
this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
|
||||
this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue());
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue());
|
||||
} else {
|
||||
this.selectedAutoPilotTier.setBaseline(this.selectedAutoPilotTier.getEditableOriginalValue());
|
||||
}
|
||||
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue());
|
||||
|
||||
return Q();
|
||||
};
|
||||
@@ -569,17 +527,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
|
||||
|
||||
this.throughput.setBaseline(offerThroughput);
|
||||
this.userCanChangeProvisioningTypes(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
|
||||
this.userCanChangeProvisioningTypes(true);
|
||||
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
const selectedAutoPilotTier = offerAutopilotSettings && offerAutopilotSettings.tier;
|
||||
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotTier(selectedAutoPilotTier));
|
||||
this.selectedAutoPilotTier.setBaseline(selectedAutoPilotTier);
|
||||
} else {
|
||||
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput;
|
||||
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot));
|
||||
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput);
|
||||
}
|
||||
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput;
|
||||
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot));
|
||||
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput);
|
||||
}
|
||||
|
||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||
|
||||
@@ -776,7 +776,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
this.isExecutionError(true);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
typeof error === "string" ? error : JSON.stringify(error)
|
||||
typeof error === "string" ? error : error.message
|
||||
);
|
||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
|
||||
<div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion">
|
||||
<!-- ko ifnot: isAutoScaleEnabled -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
testId: testId,
|
||||
@@ -77,94 +76,10 @@
|
||||
}"
|
||||
>
|
||||
</throughput-input-autopilot-v3>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
|
||||
<throughput-input
|
||||
params="{
|
||||
testId: testId,
|
||||
class: 'scaleForm dirty',
|
||||
value: throughput,
|
||||
minimum: minRUs,
|
||||
maximum: maxRUThroughputInputLimit,
|
||||
isEnabled: !hasDatabaseSharedThroughput(),
|
||||
canExceedMaximumValue: canThroughputExceedMaximumValue,
|
||||
label: throughputTitle,
|
||||
ariaLabel: throughputAriaLabel,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
|
||||
throughputProvisionedRadioId: throughputProvisionedRadioId,
|
||||
throughputModeRadioName: throughputModeRadioName,
|
||||
showAutoPilot: userCanChangeProvisioningTypes,
|
||||
isAutoPilotSelected: isAutoPilotSelected,
|
||||
autoPilotTiersList: autoPilotTiersList,
|
||||
selectedAutoPilotTier: selectedAutoPilotTier,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
canExceedMaximumValue: canExceedMaximumValue
|
||||
}"
|
||||
>
|
||||
</throughput-input>
|
||||
<!-- /ko -->
|
||||
|
||||
<div class="storageCapacityTitle throughputStorageValue" data-bind="html: storageCapacityTitle"></div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div data-bind="visible: rupmVisible">
|
||||
<div class="formTitle">RU/m</div>
|
||||
<div class="tabs" aria-label="RU/m">
|
||||
<div class="tab">
|
||||
<label
|
||||
data-bind="
|
||||
attr:{
|
||||
for: rupmOnId
|
||||
},
|
||||
css: {
|
||||
dirty: rupm.editableIsDirty,
|
||||
selectedRadio: rupm() === 'on',
|
||||
unselectedRadio: rupm() !== 'on'
|
||||
}"
|
||||
>On</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="rupm"
|
||||
value="on"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: rupmOnId
|
||||
},
|
||||
checked: rupm"
|
||||
/>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<label
|
||||
data-bind="
|
||||
attr:{
|
||||
for: rupmOffId
|
||||
},
|
||||
css: {
|
||||
dirty: rupm.editableIsDirty,
|
||||
selectedRadio: rupm() === 'off',
|
||||
unselectedRadio: rupm() !== 'off'
|
||||
}"
|
||||
>Off</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="rupm"
|
||||
value="off"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: rupmOffId
|
||||
},
|
||||
checked: rupm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Replace link with call to the Azure Support blade -->
|
||||
<div data-bind="visible: isAutoScaleEnabled">
|
||||
<div class="autoScaleThroughputTitle">Throughput (RU/s)</div>
|
||||
|
||||
@@ -79,7 +79,6 @@ describe("Settings tab", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("single master, should not show conflict resolution", () => {
|
||||
@@ -178,7 +177,6 @@ describe("Settings tab", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("On TTL changed", () => {
|
||||
@@ -251,7 +249,6 @@ describe("Settings tab", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("null if it didnt change", () => {
|
||||
@@ -327,7 +324,6 @@ describe("Settings tab", () => {
|
||||
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
||||
const explorer = new Explorer();
|
||||
explorer.defaultExperience(defaultApi);
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
const offer: DataModels.Offer = null;
|
||||
const defaultTtl = 200;
|
||||
@@ -450,158 +446,4 @@ describe("Settings tab", () => {
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AutoPilot", () => {
|
||||
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
|
||||
const explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
explorer.databaseAccount({
|
||||
id: "test",
|
||||
kind: "",
|
||||
location: "",
|
||||
name: "",
|
||||
tags: "",
|
||||
type: "",
|
||||
properties: {
|
||||
enableMultipleWriteLocations: true,
|
||||
documentEndpoint: "",
|
||||
cassandraEndpoint: "",
|
||||
gremlinEndpoint: "",
|
||||
tableEndpoint: ""
|
||||
}
|
||||
});
|
||||
|
||||
const offer: DataModels.Offer = {
|
||||
id: "test",
|
||||
_etag: "_etag",
|
||||
_rid: "_rid",
|
||||
_self: "_self",
|
||||
_ts: "_ts",
|
||||
content: {
|
||||
offerThroughput: 0,
|
||||
offerIsRUPerMinuteThroughputEnabled: false,
|
||||
offerAutopilotSettings: {
|
||||
tier: autoPilotTier
|
||||
}
|
||||
}
|
||||
};
|
||||
const container: DataModels.Collection = {
|
||||
_rid: "_rid",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 0,
|
||||
id: "mycoll",
|
||||
conflictResolutionPolicy: {
|
||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||
conflictResolutionPath: "/_ts"
|
||||
}
|
||||
};
|
||||
|
||||
return new Collection(explorer, "mydb", container, quotaInfo, offer);
|
||||
}
|
||||
|
||||
function getSettingsTab(autoPilotTier: DataModels.AutopilotTier = DataModels.AutopilotTier.Tier1): SettingsTab {
|
||||
return new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: getCollection(autoPilotTier),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
}
|
||||
describe("Visible", () => {
|
||||
it("no autopilot configured, should not be visible", () => {
|
||||
const settingsTab1 = getSettingsTab(0);
|
||||
expect(settingsTab1.isAutoPilotSelected()).toBe(false);
|
||||
|
||||
const settingsTab2 = getSettingsTab(2);
|
||||
expect(settingsTab2.isAutoPilotSelected()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Autopilot Save", () => {
|
||||
it("edit with valid new tier, save should be enabled", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
|
||||
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier3);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
|
||||
|
||||
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier2);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("edit with same tier, save should be disabled", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
|
||||
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier2);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("edit with invalid tier, save should be disabled", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
|
||||
settingsTab.selectedAutoPilotTier(5);
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Autopilot Discard", () => {
|
||||
it("edit tier, discard should be enabled and correctly dicard", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(false);
|
||||
|
||||
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier3);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
|
||||
|
||||
settingsTab.onRevertClick();
|
||||
expect(settingsTab.selectedAutoPilotTier()).toBe(DataModels.AutopilotTier.Tier2);
|
||||
|
||||
settingsTab.selectedAutoPilotTier(0);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
|
||||
|
||||
settingsTab.onRevertClick();
|
||||
expect(settingsTab.selectedAutoPilotTier()).toBe(DataModels.AutopilotTier.Tier2);
|
||||
});
|
||||
});
|
||||
|
||||
it("On TTL changed", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
|
||||
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
settingsTab.timeToLive("on");
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("On Index Policy changed", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
|
||||
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
settingsTab.indexingPolicyContent({ somethingDifferent: "" });
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("On Conflict Resolution Mode changed", () => {
|
||||
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
|
||||
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
settingsTab.conflictResolutionPolicyPath("/somethingElse");
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
|
||||
|
||||
settingsTab.onRevertClick();
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
|
||||
settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom);
|
||||
settingsTab.conflictResolutionPolicyProcedure("resolver");
|
||||
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
|
||||
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,11 +143,9 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
public geospatialVisible: ko.Computed<boolean>;
|
||||
public indexingPolicyContent: ViewModels.Editable<any>;
|
||||
public isIndexingPolicyEditorInitializing: ko.Observable<boolean>;
|
||||
public rupm: ViewModels.Editable<string>;
|
||||
public conflictResolutionPolicyMode: ViewModels.Editable<string>;
|
||||
public conflictResolutionPolicyPath: ViewModels.Editable<string>;
|
||||
public conflictResolutionPolicyProcedure: ViewModels.Editable<string>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
|
||||
public saveSettingsButton: ViewModels.Button;
|
||||
public discardSettingsChangesButton: ViewModels.Button;
|
||||
@@ -183,9 +181,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
public partitionKeyValue: ko.Observable<string>;
|
||||
public isLargePartitionKeyEnabled: ko.Computed<boolean>;
|
||||
public requestUnitsUsageCost: ko.Computed<string>;
|
||||
public rupmOnId: string;
|
||||
public rupmOffId: string;
|
||||
public rupmVisible: ko.Computed<boolean>;
|
||||
public scaleExpanded: ko.Observable<boolean>;
|
||||
public settingsExpanded: ko.Observable<boolean>;
|
||||
public shouldDisplayPortalUsePrompt: ko.Computed<boolean>;
|
||||
@@ -205,8 +200,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
public userCanChangeProvisioningTypes: ko.Observable<boolean>;
|
||||
public warningMessage: ko.Computed<string>;
|
||||
public shouldShowKeyspaceSharedThroughputMessage: ko.Computed<boolean>;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
||||
public autoPilotThroughput: ko.Observable<number>;
|
||||
public autoPilotUsageCost: ko.Computed<string>;
|
||||
@@ -232,7 +225,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
super(options);
|
||||
this.container = options.collection && options.collection.container;
|
||||
this.isIndexingPolicyEditorInitializing = ko.observable<boolean>(false);
|
||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
||||
|
||||
@@ -245,8 +237,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.ttlOnId = `ttlOn${this.tabId}`;
|
||||
this.changeFeedPolicyOffId = `changeFeedOff${this.tabId}`;
|
||||
this.changeFeedPolicyOnId = `changeFeedOn${this.tabId}`;
|
||||
this.rupmOnId = `rupmOn${this.tabId}`;
|
||||
this.rupmOffId = `rupmOff${this.tabId}`;
|
||||
this.conflictResolutionPolicyModeCustom = `conflictResolutionPolicyModeCustom${this.tabId}`;
|
||||
this.conflictResolutionPolicyModeLWW = `conflictResolutionPolicyModeLWW${this.tabId}`;
|
||||
this.conflictResolutionPolicyModeCRDT = `conflictResolutionPolicyModeCRDT${this.tabId}`;
|
||||
@@ -278,7 +268,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.analyticalStorageTtlSelection = editable.observable<string>();
|
||||
this.analyticalStorageTtlSeconds = editable.observable<number>();
|
||||
this.indexingPolicyContent = editable.observable<any>();
|
||||
this.rupm = editable.observable<string>();
|
||||
// Mongo container with system partition key still treat as "Fixed"
|
||||
this._isFixedContainer = ko.pureComputed(
|
||||
() =>
|
||||
@@ -288,31 +277,17 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
this.isAutoPilotSelected = ko.observable(false);
|
||||
this._wasAutopilotOriginallySet = ko.observable(false);
|
||||
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>();
|
||||
this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
const offer = this.collection && this.collection.offer && this.collection.offer();
|
||||
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
|
||||
|
||||
this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
|
||||
this.userCanChangeProvisioningTypes = ko.observable(true);
|
||||
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this.isAutoPilotSelected(true);
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.tier) {
|
||||
if (AutoPilotUtils.isValidAutoPilotTier(offerAutopilotSettings.tier)) {
|
||||
this.isAutoPilotSelected(true);
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.selectedAutoPilotTier(offerAutopilotSettings.tier);
|
||||
const availableAutoPilotTiers = AutoPilotUtils.getAvailableAutoPilotTiersOptions(offerAutopilotSettings.tier);
|
||||
this.autoPilotTiersList(availableAutoPilotTiers);
|
||||
}
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this.isAutoPilotSelected(true);
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,16 +302,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
});
|
||||
|
||||
this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
@@ -348,25 +317,18 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
if (!originalAutoPilotSettings) {
|
||||
return false;
|
||||
}
|
||||
const originalAutoPilotSetting = !this.hasAutoPilotV2FeatureFlag()
|
||||
? originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput
|
||||
: originalAutoPilotSettings && originalAutoPilotSettings.tier;
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput() != originalAutoPilotSetting) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier() !== originalAutoPilotSetting)
|
||||
) {
|
||||
const originalAutoPilotSetting = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
|
||||
if (this.autoPilotThroughput() !== originalAutoPilotSetting) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
|
||||
const autoPilot = this.autoPilotThroughput();
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, false /* isDatabaseThroughput */);
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */);
|
||||
});
|
||||
|
||||
this.requestUnitsUsageCost = ko.pureComputed(() => {
|
||||
@@ -377,7 +339,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
const serverId: string = this.container.serverId();
|
||||
const offerThroughput: number = this.throughput();
|
||||
const rupmEnabled = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
const regions =
|
||||
(account &&
|
||||
@@ -395,8 +356,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled
|
||||
multimaster
|
||||
);
|
||||
} else {
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -453,32 +413,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
);
|
||||
});
|
||||
|
||||
this.rupmVisible = ko.computed(() => {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
return false;
|
||||
}
|
||||
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
|
||||
return true;
|
||||
}
|
||||
for (let i = 0, len = this.container.databases().length; i < len; i++) {
|
||||
for (let j = 0, len2 = this.container.databases()[i].collections().length; j < len2; j++) {
|
||||
const collectionOffer = this.container
|
||||
.databases()
|
||||
[i].collections()
|
||||
[j].offer();
|
||||
if (
|
||||
collectionOffer &&
|
||||
collectionOffer.content &&
|
||||
collectionOffer.content.offerIsRUPerMinuteThroughputEnabled
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.ttlVisible = ko.computed(() => {
|
||||
return (this.container && !this.container.isPreferredApiCassandra()) || false;
|
||||
});
|
||||
@@ -492,11 +426,11 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
});
|
||||
|
||||
this.canThroughputExceedMaximumValue = ko.pureComputed<boolean>(() => {
|
||||
const isPublicAzurePortal: boolean =
|
||||
configContext.platform === Platform.Portal && !this.container.isRunningOnNationalCloud();
|
||||
const hasPartitionKey = !!this.collection.partitionKey;
|
||||
|
||||
return isPublicAzurePortal && hasPartitionKey;
|
||||
return (
|
||||
this._isFixedContainer() &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
!this.container.isRunningOnNationalCloud()
|
||||
);
|
||||
});
|
||||
|
||||
this.canRequestSupport = ko.pureComputed(() => {
|
||||
@@ -609,7 +543,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
this.throughputTitle = ko.pureComputed<string>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
|
||||
const minThroughput: string = this.minRUs().toLocaleString();
|
||||
@@ -690,12 +624,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return true;
|
||||
} else if (this.isAutoPilotSelected()) {
|
||||
const validAutopilotChange =
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this._isAutoPilotDirty() &&
|
||||
AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
this._isAutoPilotDirty() &&
|
||||
AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()));
|
||||
this._isAutoPilotDirty() && AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput());
|
||||
if (validAutopilotChange) {
|
||||
return true;
|
||||
}
|
||||
@@ -749,14 +678,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.rupm() === Constants.RUPMStates.on &&
|
||||
this.throughput() >
|
||||
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this.collection.quotaInfo()?.numPartitions
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.timeToLive.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -785,10 +706,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.rupm.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
@@ -838,10 +755,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.rupm.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.conflictResolutionPolicyMode.editableIsDirty() ||
|
||||
this.conflictResolutionPolicyPath.editableIsDirty() ||
|
||||
@@ -901,14 +814,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
offer.hasOwnProperty("headers") &&
|
||||
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
|
||||
) {
|
||||
if (AutoPilotUtils.isValidV2AutoPilotOffer(offer)) {
|
||||
return "Tier upgrade will take some time to complete.";
|
||||
}
|
||||
|
||||
const throughput = offer.content.offerAutopilotSettings
|
||||
? !this.hasAutoPilotV2FeatureFlag()
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
: offer.content.offerAutopilotSettings.maximumTierThroughput
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
: undefined;
|
||||
|
||||
const targetThroughput =
|
||||
@@ -927,7 +834,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
|
||||
if (this.overrideWithProvisionedThroughputSettings()) {
|
||||
return AutoPilotUtils.manualToAutoscaleDisclaimer;
|
||||
}
|
||||
|
||||
@@ -1091,25 +998,17 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.throughput.editableIsDirty() ||
|
||||
this.rupm.editableIsDirty() ||
|
||||
this._isAutoPilotDirty() ||
|
||||
this._hasProvisioningTypeChanged()
|
||||
) {
|
||||
if (this.throughput.editableIsDirty() || this._isAutoPilotDirty() || this._hasProvisioningTypeChanged()) {
|
||||
const newThroughput = this.throughput();
|
||||
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
|
||||
const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
|
||||
|
||||
if (newOffer.content) {
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
|
||||
} else {
|
||||
newOffer = _.extend({}, newOffer, {
|
||||
content: {
|
||||
offerThroughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
offerThroughput: newThroughput
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1117,18 +1016,12 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
maxThroughput: this.autoPilotThroughput()
|
||||
};
|
||||
} else {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
tier: this.selectedAutoPilotTier()
|
||||
};
|
||||
}
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
maxThroughput: this.autoPilotThroughput()
|
||||
};
|
||||
|
||||
// user has changed from provisioned --> autoscale
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
if (this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
} else {
|
||||
@@ -1136,10 +1029,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
}
|
||||
} else {
|
||||
this.isAutoPilotSelected(false);
|
||||
this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
|
||||
this.userCanChangeProvisioningTypes(true);
|
||||
|
||||
// user has changed from autoscale --> provisioned
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
if (this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||
} else {
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
@@ -1157,8 +1050,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
throughput: newThroughput
|
||||
};
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
@@ -1222,7 +1114,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle(),
|
||||
error: error
|
||||
error: error.message
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -1241,7 +1133,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.geospatialConfigType.setBaseline(this.geospatialConfigType.getEditableOriginalValue());
|
||||
this.analyticalStorageTtlSelection.setBaseline(this.analyticalStorageTtlSelection.getEditableOriginalValue());
|
||||
this.analyticalStorageTtlSeconds.setBaseline(this.analyticalStorageTtlSeconds.getEditableOriginalValue());
|
||||
this.rupm.setBaseline(this.rupm.getEditableOriginalValue());
|
||||
this.changeFeedPolicyToggled.setBaseline(this.changeFeedPolicyToggled.getEditableOriginalValue());
|
||||
|
||||
this.conflictResolutionPolicyMode.setBaseline(this.conflictResolutionPolicyMode.getEditableOriginalValue());
|
||||
@@ -1266,13 +1157,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings;
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
|
||||
this.autoPilotThroughput(originalAutoPilotMaxThroughput);
|
||||
} else {
|
||||
const originalAutoPilotTier = originalAutoPilotSettings && originalAutoPilotSettings.tier;
|
||||
this.selectedAutoPilotTier(originalAutoPilotTier);
|
||||
}
|
||||
const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
|
||||
this.autoPilotThroughput(originalAutoPilotMaxThroughput);
|
||||
}
|
||||
|
||||
return Q();
|
||||
@@ -1453,7 +1339,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
}
|
||||
|
||||
private _getThroughputUnit(): string {
|
||||
return this.rupm() === Constants.RUPMStates.on ? "RU/m" : "RU/s";
|
||||
return "RU/s";
|
||||
}
|
||||
|
||||
public getUpdatedConflictResolutionPolicy(): DataModels.ConflictResolutionPolicy {
|
||||
@@ -1570,13 +1456,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.collection.offer().content &&
|
||||
this.collection.offer().content.offerThroughput;
|
||||
|
||||
const offerIsRUPerMinuteThroughputEnabled =
|
||||
this.collection &&
|
||||
this.collection.offer &&
|
||||
this.collection.offer() &&
|
||||
this.collection.offer().content &&
|
||||
this.collection.offer().content.offerIsRUPerMinuteThroughputEnabled;
|
||||
|
||||
const changeFeedPolicyToggled: ChangeFeedPolicyToggledState = this.changeFeedPolicyToggled();
|
||||
this.changeFeedPolicyToggled.setBaseline(changeFeedPolicyToggled);
|
||||
this.throughput.setBaseline(offerThroughput);
|
||||
@@ -1596,7 +1475,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionProcedure
|
||||
)
|
||||
);
|
||||
this.rupm.setBaseline(offerIsRUPerMinuteThroughputEnabled ? Constants.RUPMStates.on : Constants.RUPMStates.off);
|
||||
|
||||
const indexingPolicyContent = this.collection.indexingPolicy();
|
||||
const value: string = JSON.stringify(indexingPolicyContent, null, 4);
|
||||
@@ -1617,17 +1495,15 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.GEOMETRY;
|
||||
this.geospatialConfigType.setBaseline(geospatialConfigType);
|
||||
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
const maxThroughput =
|
||||
this.collection &&
|
||||
this.collection.offer &&
|
||||
this.collection.offer() &&
|
||||
this.collection.offer().content &&
|
||||
this.collection.offer().content.offerAutopilotSettings &&
|
||||
this.collection.offer().content.offerAutopilotSettings.maxThroughput;
|
||||
const maxThroughput =
|
||||
this.collection &&
|
||||
this.collection.offer &&
|
||||
this.collection.offer() &&
|
||||
this.collection.offer().content &&
|
||||
this.collection.offer().content.offerAutopilotSettings &&
|
||||
this.collection.offer().content.offerAutopilotSettings.maxThroughput;
|
||||
|
||||
this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput);
|
||||
}
|
||||
this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput);
|
||||
}
|
||||
|
||||
private _createIndexingPolicyEditor() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user