mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 11:51:07 +00:00
Compare commits
17 Commits
remove-unu
...
users/srna
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e8793d1b1 | ||
|
|
f41de718a0 | ||
|
|
5820afc25a | ||
|
|
6d94f18f9a | ||
|
|
a133134b8b | ||
|
|
79dec6a8a8 | ||
|
|
53a8cea95e | ||
|
|
5f1f7a8266 | ||
|
|
a009a8ba5f | ||
|
|
3e782527d0 | ||
|
|
e6ca1d25c9 | ||
|
|
473f722dcc | ||
|
|
5741802c25 | ||
|
|
e2e58f73b1 | ||
|
|
79769e9689 | ||
|
|
542abf4d3a | ||
|
|
4bee46ce55 |
@@ -1,7 +1,10 @@
|
||||
# These options are only needed when if running end to end tests locally
|
||||
PORTAL_RUNNER_USERNAME=
|
||||
PORTAL_RUNNER_PASSWORD=
|
||||
PORTAL_RUNNER_SUBSCRIPTION=
|
||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
||||
PORTAL_RUNNER_CONNECTION_STRING=
|
||||
PORTAL_RUNNER_CONNECTION_STRING=
|
||||
CASSANDRA_CONNECTION_STRING=
|
||||
MONGO_CONNECTION_STRING=
|
||||
TABLES_CONNECTION_STRING=
|
||||
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
||||
@@ -15,8 +15,6 @@ src/Common/DeleteFeedback.ts
|
||||
src/Common/DocumentClientUtilityBase.ts
|
||||
src/Common/EditableUtility.ts
|
||||
src/Common/EnvironmentUtility.ts
|
||||
src/Common/ErrorParserUtility.test.ts
|
||||
src/Common/ErrorParserUtility.ts
|
||||
src/Common/HashMap.test.ts
|
||||
src/Common/HashMap.ts
|
||||
src/Common/HeadersUtility.test.ts
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -150,6 +150,12 @@ jobs:
|
||||
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
||||
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
||||
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
||||
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: screenshots
|
||||
path: failed-*
|
||||
nuget:
|
||||
name: Publish Nuget
|
||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||
|
||||
15
README.md
15
README.md
@@ -33,7 +33,7 @@ To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin t
|
||||
|
||||
### Emulator Development
|
||||
|
||||
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows enironment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
||||
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
||||
|
||||
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
||||
|
||||
@@ -60,7 +60,7 @@ The Cosmos Portal that consumes this repo is not currently open source. If you h
|
||||
You can however load a local running instance of data explorer in the production portal.
|
||||
|
||||
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
||||
2. Whitelist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
||||
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
||||
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
||||
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||
|
||||
@@ -84,16 +84,19 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
|
||||
4. Install dependencies: `npm install`
|
||||
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
|
||||
|
||||
#### End to End Production Runners
|
||||
#### End to End Production Tests
|
||||
|
||||
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
|
||||
|
||||
1. Copy .env.example to .env and fill in all variables
|
||||
2. Run `npm run test:e2e`
|
||||
1. Copy .env.example to .env
|
||||
2. Update the values in .env including your local data explorer endpoint (ask a teammate/codeowner for help with .env values)
|
||||
3. Make sure all packages are installed `npm install`
|
||||
4. Run the server `npm run start` and wait for it to start
|
||||
5. Run `npm run test:e2e`
|
||||
|
||||
### Releasing
|
||||
|
||||
We generally adhear to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
|
||||
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
|
||||
|
||||
# Contributing
|
||||
|
||||
|
||||
48
package-lock.json
generated
48
package-lock.json
generated
@@ -2803,12 +2803,13 @@
|
||||
}
|
||||
},
|
||||
"@nteract/monaco-editor": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.0.tgz",
|
||||
"integrity": "sha512-PGEUvy/GTBMECy4RUfh4wxO7GfA9YDBSV3hGt8MyrVz/GxUDtjB7FqrYS0ZhmVQPYl8hnV2i48F3YlypC+xIXA==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.2.tgz",
|
||||
"integrity": "sha512-51Pxt6v6qaAlbDY0BgEydk/Jxuu93t+uB8Geg3vJfE6VDphTEakB0wocBIfvcTKVV55Lx53/rTSp6QHqtaHiGg==",
|
||||
"requires": {
|
||||
"@nteract/core": "^14.0.0",
|
||||
"@nteract/messaging": "^7.0.10",
|
||||
"@nteract/messaging": "^7.0.12",
|
||||
"lodash.debounce": "^4.0.6",
|
||||
"monaco-editor": "0.18.1",
|
||||
"rxjs": "^6.3.3"
|
||||
},
|
||||
@@ -2857,6 +2858,40 @@
|
||||
"rxjs": "^6.3.3"
|
||||
}
|
||||
},
|
||||
"@nteract/messaging": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.12.tgz",
|
||||
"integrity": "sha512-5z2Ffd1hj7AsGBJTAoqJshLlUZ+ISJBjiZAdNDjb70PNEv0x8UOMk/di80RI3WBLK5MKxSJkGXfs4jfzfdW6bA==",
|
||||
"requires": {
|
||||
"@nteract/types": "^7.1.2",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"rxjs": "^6.6.0",
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nteract/commutable": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.3.4.tgz",
|
||||
"integrity": "sha512-Z6aUtIZN0CKUMJwbZjUUqaaBhT6P0RiEG5nHso+oG/FOXF20Qv+hf/TyvYhw9SXQVmmacaMk4zj0iOID20pIng==",
|
||||
"requires": {
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"uuid": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"@nteract/types": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/types/-/types-7.1.2.tgz",
|
||||
"integrity": "sha512-I/1TvaUC/m9I/LFk1HemsOUqB0eNdagu0KRLA1YEtChPh9pk5F9flglA7m5+0/j31gLXBISj5+6tL8ikA8BxOQ==",
|
||||
"requires": {
|
||||
"@nteract/commutable": "^7.3.4",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"rxjs": "^6.6.0",
|
||||
"uuid": "^8.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nteract/mythic-notifications": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
||||
@@ -14422,6 +14457,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@nteract/jupyter-widgets": "2.0.0",
|
||||
"@nteract/logos": "1.0.0",
|
||||
"@nteract/markdown": "4.4.0",
|
||||
"@nteract/monaco-editor": "3.2.0",
|
||||
"@nteract/monaco-editor": "3.2.2",
|
||||
"@nteract/octicons": "2.0.0",
|
||||
"@nteract/outputs": "3.0.9",
|
||||
"@nteract/presentational-components": "3.0.7",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AutopilotTier } from "../Contracts/DataModels";
|
||||
import { HashMap } from "./HashMap";
|
||||
|
||||
export class AuthorizationEndpoints {
|
||||
@@ -124,7 +123,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";
|
||||
@@ -262,7 +260,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";
|
||||
@@ -407,54 +404,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==";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
import { getErrorMessage } from "./ErrorHandlingUtils";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
@@ -69,7 +70,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}: ${error.message}`);
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ export function executeStoredProcedure(
|
||||
(error: any) => {
|
||||
handleError(
|
||||
error,
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`,
|
||||
"ExecuteStoredProcedure"
|
||||
"ExecuteStoredProcedure",
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||
);
|
||||
deferred.reject(error);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export function queryDocumentsPage(
|
||||
deferred.resolve(result);
|
||||
},
|
||||
(error: any) => {
|
||||
handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage");
|
||||
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -109,7 +109,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
||||
deferred.resolve(document);
|
||||
},
|
||||
(error: any) => {
|
||||
handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
|
||||
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -135,7 +135,7 @@ export function updateDocument(
|
||||
deferred.resolve(updatedDocument);
|
||||
},
|
||||
(error: any) => {
|
||||
handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument");
|
||||
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -157,7 +157,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
||||
deferred.resolve(savedDocument);
|
||||
},
|
||||
(error: any) => {
|
||||
handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument");
|
||||
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -179,7 +179,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
|
||||
deferred.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument");
|
||||
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -205,7 +205,7 @@ export function deleteConflict(
|
||||
deferred.resolve(response);
|
||||
},
|
||||
(error: any) => {
|
||||
handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict");
|
||||
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,11 +1,56 @@
|
||||
import { CosmosError, sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||
import { ARMError } from "../Utils/arm/request";
|
||||
import { HttpStatusCodes } from "./Constants";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { SubscriptionType } from "../Contracts/ViewModels";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "./Logger";
|
||||
import { replaceKnownError } from "./ErrorParserUtility";
|
||||
import { sendMessage } from "./MessageHandler";
|
||||
|
||||
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);
|
||||
export const handleError = (error: string | ARMError | Error, area: string, consoleErrorPrefix?: string): void => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
const errorCode = error instanceof ARMError ? error.code : undefined;
|
||||
|
||||
// logs error to data explorer console
|
||||
const consoleErrorMessage = consoleErrorPrefix ? `${consoleErrorPrefix}:\n ${errorMessage}` : errorMessage;
|
||||
logConsoleError(consoleErrorMessage);
|
||||
|
||||
// logs error to both app insight and kusto
|
||||
logError(errorMessage, area, errorCode);
|
||||
|
||||
// checks for errors caused by firewall and sends them to portal to handle
|
||||
sendNotificationForError(errorMessage, errorCode);
|
||||
};
|
||||
|
||||
export const getErrorMessage = (error: string | Error): string => {
|
||||
const errorMessage = typeof error === "string" ? error : error.message;
|
||||
return replaceKnownError(errorMessage);
|
||||
};
|
||||
|
||||
export const getErrorStack = (error: string | Error): string => {
|
||||
return typeof error === "string" ? undefined : error.stack;
|
||||
};
|
||||
|
||||
const sendNotificationForError = (errorMessage: string, errorCode: number | string): void => {
|
||||
if (errorCode === HttpStatusCodes.Forbidden) {
|
||||
if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
||||
return;
|
||||
}
|
||||
sendMessage({
|
||||
type: MessageTypes.ForbiddenError,
|
||||
reason: errorMessage
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const replaceKnownError = (errorMessage: string): string => {
|
||||
if (
|
||||
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
||||
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||
) {
|
||||
return "Database throughput is not supported for internal subscriptions.";
|
||||
} else if (errorMessage.indexOf("Partition key paths must contain only valid") >= 0) {
|
||||
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
};
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||
|
||||
describe("Error Parser Utility", () => {
|
||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
||||
it("should parse a backend error correctly", () => {
|
||||
// A fake error matching what is thrown by the SDK on a bad collection create request
|
||||
const innerMessage =
|
||||
"The partition key component definition path '/asdwqr31 @#$#$WRadf' could not be accepted, failed near position '10'. Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||
const message = `Message: {\"Errors\":[\"${innerMessage}\"]}\r\nActivityId: 97b2e684-7505-4921-85f6-2513b9b28220, Request URI: /apps/89fdcf25-2a0b-4d2a-aab6-e161e565b26f/services/54911149-7bb1-4e7d-a1fa-22c8b36a4bb9/partitions/cc2a7a04-5f5a-4709-bcf7-8509b264963f/replicas/132304018743619218p, RequestStats: , SDK: Microsoft.Azure.Documents.Common/2.10.0`;
|
||||
const err = new Error(message) as any;
|
||||
err.code = 400;
|
||||
err.body = {
|
||||
code: "BadRequest",
|
||||
message
|
||||
};
|
||||
err.headers = {};
|
||||
err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";
|
||||
|
||||
const parsedError = ErrorParserUtility.parse(err);
|
||||
expect(parsedError.length).toBe(1);
|
||||
expect(parsedError[0].message).toBe(innerMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
|
||||
export function replaceKnownError(err: string): string {
|
||||
if (
|
||||
window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
|
||||
err.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||
) {
|
||||
return "Database throughput is not supported for internal subscriptions.";
|
||||
} else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
|
||||
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
export function parse(err: any): DataModels.ErrorDataModel[] {
|
||||
try {
|
||||
return _parse(err);
|
||||
} catch (e) {
|
||||
return [<DataModels.ErrorDataModel>{ message: JSON.stringify(err) }];
|
||||
}
|
||||
}
|
||||
|
||||
function _parse(err: any): DataModels.ErrorDataModel[] {
|
||||
var normalizedErrors: DataModels.ErrorDataModel[] = [];
|
||||
if (err.message && !err.code) {
|
||||
normalizedErrors.push(err);
|
||||
} else {
|
||||
const innerErrors: any[] = _getInnerErrors(err.message);
|
||||
normalizedErrors = innerErrors.map(innerError =>
|
||||
typeof innerError === "string" ? { message: innerError } : innerError
|
||||
);
|
||||
}
|
||||
|
||||
return normalizedErrors;
|
||||
}
|
||||
|
||||
function _getInnerErrors(message: string): any[] {
|
||||
/*
|
||||
The backend error message has an inner-message which is a stringified object.
|
||||
|
||||
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
|
||||
Example:
|
||||
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
|
||||
For non-SQL errors the "Errors" propery is an array of string.
|
||||
Example:
|
||||
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
|
||||
*/
|
||||
|
||||
let innerMessage: any = null;
|
||||
|
||||
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
|
||||
try {
|
||||
// Multi-Partition error flavor
|
||||
const regExp = /^(.*)ActivityId: (.*)/g;
|
||||
const regString = regExp.exec(singleLineMessage);
|
||||
const innerMessageString = regString[1];
|
||||
innerMessage = JSON.parse(innerMessageString);
|
||||
} catch (e) {
|
||||
// Single-partition error flavor
|
||||
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
|
||||
const regString = regExp.exec(singleLineMessage);
|
||||
const innerMessageString = regString[1];
|
||||
innerMessage = JSON.parse(innerMessageString);
|
||||
}
|
||||
|
||||
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
|
||||
}
|
||||
@@ -21,14 +21,8 @@ export function logWarning(message: string, area: string, code?: number): void {
|
||||
return _logEntry(entry);
|
||||
}
|
||||
|
||||
export function logError(message: string | Error, area: string, code?: number): void {
|
||||
let logMessage: string;
|
||||
if (typeof message === "string") {
|
||||
logMessage = message;
|
||||
} else {
|
||||
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
|
||||
}
|
||||
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, logMessage, area, code);
|
||||
export function logError(errorMessage: string, area: string, code?: number | string): void {
|
||||
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, errorMessage, area, code);
|
||||
return _logEntry(entry);
|
||||
}
|
||||
|
||||
@@ -59,7 +53,7 @@ function _generateLogEntry(
|
||||
level: Diagnostics.LogEntryLevel,
|
||||
message: string,
|
||||
area: string,
|
||||
code?: number
|
||||
code?: number | string
|
||||
): Diagnostics.LogEntry {
|
||||
return {
|
||||
timestamp: new Date().getUTCSeconds(),
|
||||
|
||||
@@ -12,8 +12,7 @@ import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||
import { createCollection } from "./dataAccess/createCollection";
|
||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||
import * as Logger from "./Logger";
|
||||
import { handleError } from "./ErrorHandlingUtils";
|
||||
|
||||
export class QueriesClient {
|
||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||
@@ -53,13 +52,8 @@ export class QueriesClient {
|
||||
return Promise.resolve(collection);
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
||||
);
|
||||
Logger.logError(stringifiedError, "setupQueriesCollection");
|
||||
return Promise.reject(stringifiedError);
|
||||
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
@@ -102,19 +96,11 @@ export class QueriesClient {
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
let errorMessage: string;
|
||||
const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0];
|
||||
if (parsedError.code === HttpStatusCodes.Conflict.toString()) {
|
||||
errorMessage = `Query ${query.queryName} already exists`;
|
||||
} else {
|
||||
errorMessage = parsedError.message;
|
||||
if (error.code === HttpStatusCodes.Conflict.toString()) {
|
||||
error = `Query ${query.queryName} already exists`;
|
||||
}
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(parsedError), "saveQuery");
|
||||
return Promise.reject(errorMessage);
|
||||
handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
@@ -163,25 +149,15 @@ export class QueriesClient {
|
||||
return Promise.resolve(queries);
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to fetch saved queries: ${stringifiedError}`
|
||||
);
|
||||
Logger.logError(stringifiedError, "getSavedQueries");
|
||||
return Promise.reject(stringifiedError);
|
||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
},
|
||||
(error: any) => {
|
||||
// should never get into this state but we handle this regardless
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to fetch saved queries: ${stringifiedError}`
|
||||
);
|
||||
Logger.logError(stringifiedError, "getSavedQueries");
|
||||
return Promise.reject(stringifiedError);
|
||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
@@ -232,13 +208,8 @@ export class QueriesClient {
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
||||
);
|
||||
Logger.logError(stringifiedError, "deleteQuery");
|
||||
return Promise.reject(stringifiedError);
|
||||
handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
|
||||
@@ -55,7 +55,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
return collection;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");
|
||||
handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
|
||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||
return database;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase");
|
||||
handleError(error, "CreateDatabase", `Error while creating database ${params.databaseId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function createStoredProcedure(
|
||||
.scripts.storedProcedures.create(storedProcedure);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure");
|
||||
handleError(error, "CreateStoredProcedure", `Error while creating stored procedure ${storedProcedure.id}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -7,9 +7,8 @@ import {
|
||||
} 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 { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createTrigger(
|
||||
@@ -66,9 +65,7 @@ export async function createTrigger(
|
||||
.scripts.triggers.create(trigger);
|
||||
return response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`);
|
||||
logError(error.message, "CreateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -72,8 +72,8 @@ export async function createUserDefinedFunction(
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
`Error while creating user defined function ${userDefinedFunction.id}`,
|
||||
"CreateUserupdateUserDefinedFunction"
|
||||
"CreateUserupdateUserDefinedFunction",
|
||||
`Error while creating user defined function ${userDefinedFunction.id}`
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||
} catch (error) {
|
||||
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
|
||||
handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||
} catch (error) {
|
||||
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
|
||||
handleError(error, "DeleteDatabase", `Error while deleting database ${databaseId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function deleteStoredProcedure(
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure");
|
||||
handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger");
|
||||
handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function deleteUserDefinedFunction(databaseId: string, collectionId
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction");
|
||||
handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "../Constants";
|
||||
import { AuthType } from "../../AuthType";
|
||||
|
||||
export async function getIndexTransformationProgress(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, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return indexTransformationPercentage;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
|
||||
.read();
|
||||
collection = response.resource as DataModels.Collection;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying container ${collectionId}`, "ReadCollection");
|
||||
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
|
||||
@@ -56,7 +56,7 @@ export const readCollectionOffer = async (
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer");
|
||||
handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -37,7 +37,7 @@ export const readCollectionQuotaInfo = async (
|
||||
|
||||
return quota;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
|
||||
handleError(error, "ReadCollectionQuotaInfo", `Error while querying quota info for container ${collection.id}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
||||
.fetchAll();
|
||||
return sdkResponse.resources as DataModels.Collection[];
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying containers for database ${databaseId}`, "ReadCollections");
|
||||
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -47,7 +47,7 @@ export const readDatabaseOffer = async (
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying offer for database ${params.databaseId}`, "ReadDatabaseOffer");
|
||||
handleError(error, "ReadDatabaseOffer", `Error while querying offer for database ${params.databaseId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||
databases = sdkResponse.resources as DataModels.Database[];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying databases`, "ReadDatabases");
|
||||
handleError(error, "ReadDatabases", `Error while querying databases`);
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
|
||||
@@ -2,8 +2,6 @@ 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";
|
||||
|
||||
@@ -24,35 +22,9 @@ export async function readMongoDBCollectionThroughRP(
|
||||
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
collection = response.properties.resource;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||
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,7 +1,7 @@
|
||||
import { Offer } from "../../Contracts/DataModels";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
|
||||
|
||||
export const readOffers = async (): Promise<Offer[]> => {
|
||||
const clearMessage = logConsoleProgress(`Querying offers`);
|
||||
@@ -13,11 +13,11 @@ export const readOffers = async (): Promise<Offer[]> => {
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
||||
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||
if (getErrorMessage(error)?.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||
return [];
|
||||
}
|
||||
|
||||
handleError(error, `Error while querying offers`, "ReadOffers");
|
||||
handleError(error, "ReadOffers", `Error while querying offers`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function readStoredProcedures(
|
||||
.fetchAll();
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
handleError(error, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures");
|
||||
handleError(error, "ReadStoredProcedures", `Failed to query stored procedures for container ${collectionId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function readTriggers(
|
||||
.fetchAll();
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
handleError(error, `Failed to query triggers for container ${collectionId}`, "ReadTriggers");
|
||||
handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -37,8 +37,8 @@ export async function readUserDefinedFunctions(
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
`Failed to query user defined functions for container ${collectionId}`,
|
||||
"ReadUserDefinedFunctions"
|
||||
"ReadUserDefinedFunctions",
|
||||
`Failed to query user defined functions for container ${collectionId}`
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as Constants from "../Constants";
|
||||
import { sendMessage } from "../MessageHandler";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
|
||||
export interface CosmosError {
|
||||
code: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export function sendNotificationForError(error: CosmosError): void {
|
||||
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
|
||||
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
||||
return;
|
||||
}
|
||||
sendMessage({
|
||||
type: MessageTypes.ForbiddenError,
|
||||
reason: error && error.message ? error.message : error
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export async function updateCollection(
|
||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||
return collection;
|
||||
} catch (error) {
|
||||
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");
|
||||
handleError(error, "UpdateCollection", `Failed to update container ${collectionId}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -72,7 +72,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
||||
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||
return updatedOffer;
|
||||
} catch (error) {
|
||||
handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection");
|
||||
handleError(error, "UpdateCollection", `Error updating offer for ${offerResourceText}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Platform, configContext } from "../../ConfigContext";
|
||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
||||
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { HttpHeaders } from "../Constants";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
interface UpdateOfferThroughputRequest {
|
||||
subscriptionId: string;
|
||||
@@ -44,8 +45,13 @@ export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThrou
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const error = await response.json();
|
||||
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
|
||||
handleError(
|
||||
error,
|
||||
"updateOfferThroughputBeyondLimit",
|
||||
`Failed to request an increase in throughput for ${request.throughput}`
|
||||
);
|
||||
clearMessage();
|
||||
throw new Error(error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function updateStoredProcedure(
|
||||
.replace(storedProcedure);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure");
|
||||
handleError(error, "UpdateStoredProcedure", `Error while updating stored procedure ${storedProcedure.id}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function updateTrigger(
|
||||
.replace(trigger);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while updating trigger ${trigger.id}`, "UpdateTrigger");
|
||||
handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -66,8 +66,8 @@ export async function updateUserDefinedFunction(
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
`Error while updating user defined function ${userDefinedFunction.id}`,
|
||||
"UpdateUserupdateUserDefinedFunction"
|
||||
"UpdateUserupdateUserDefinedFunction",
|
||||
`Error while updating user defined function ${userDefinedFunction.id}`
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
|
||||
@@ -216,18 +216,6 @@ export interface UniqueKey {
|
||||
paths: string[];
|
||||
}
|
||||
|
||||
// Returned by DocumentDb client proxy
|
||||
// Inner errors in BackendErrorDataModel when error is in SQL syntax
|
||||
export interface ErrorDataModel {
|
||||
message: string;
|
||||
severity?: string;
|
||||
location?: {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface CreateDatabaseAndCollectionRequest {
|
||||
databaseId: string;
|
||||
collectionId: string;
|
||||
@@ -239,21 +227,12 @@ export interface CreateDatabaseAndCollectionRequest {
|
||||
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 +241,7 @@ export interface Query {
|
||||
}
|
||||
|
||||
export interface AutoPilotOfferSettings {
|
||||
tier?: AutopilotTier;
|
||||
maximumTierThroughput?: number;
|
||||
targetTier?: AutopilotTier;
|
||||
maxThroughput?: number;
|
||||
targetMaxThroughput?: number;
|
||||
}
|
||||
@@ -491,7 +468,6 @@ export interface MongoParameters extends RpParameters {
|
||||
rid?: string;
|
||||
rtype?: string;
|
||||
isAutoPilot?: Boolean;
|
||||
autoPilotTier?: string;
|
||||
autoPilotThroughput?: string;
|
||||
analyticalStorageTtl?: number;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface LogEntry {
|
||||
/**
|
||||
* The message code.
|
||||
*/
|
||||
code?: number;
|
||||
code?: number | string;
|
||||
/**
|
||||
* Any additional data to be logged.
|
||||
*/
|
||||
|
||||
@@ -162,6 +162,7 @@ export interface Collection extends CollectionBase {
|
||||
loadStoredProcedures(): Promise<any>;
|
||||
loadTriggers(): Promise<any>;
|
||||
loadOffer(): Promise<void>;
|
||||
loadAutopilotOfferWithRetry(): Promise<DataModels.Offer>;
|
||||
|
||||
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
|
||||
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
||||
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
||||
import {
|
||||
IContextualMenuItem,
|
||||
IContextualMenuProps,
|
||||
ContextualMenuItemType
|
||||
} from "office-ui-fabric-react/lib/ContextualMenu";
|
||||
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||
import * as Logger from "../../../Common/Logger";
|
||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface ArcadiaMenuPickerProps {
|
||||
selectText?: string;
|
||||
@@ -47,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
|
||||
selectedSparkPool: item.text
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked");
|
||||
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as Logger from "../../../Common/Logger";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { StringUtils } from "../../../Utils/StringUtils";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface NotebookTerminalComponentProps {
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
@@ -71,9 +69,10 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
||||
params: Map<string, string>
|
||||
): string {
|
||||
if (!serverInfo.notebookServerEndpoint) {
|
||||
const error = "Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.";
|
||||
Logger.logError(error, "NotebookTerminalComponent/createNotebookAppSrc");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(
|
||||
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
||||
"NotebookTerminalComponent/createNotebookAppSrc"
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as React from "react";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
||||
import * as Logger from "../../../Common/Logger";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface CodeOfConductComponentProps {
|
||||
junoClient: JunoClient;
|
||||
@@ -45,9 +44,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
||||
|
||||
this.props.onAcceptCodeOfConduct(response.data);
|
||||
} catch (error) {
|
||||
const message = `Failed to accept code of conduct: ${error}`;
|
||||
Logger.logError(message, "CodeOfConductComponent/acceptCodeOfConduct");
|
||||
logConsoleError(message);
|
||||
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,8 @@ import {
|
||||
Text
|
||||
} from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import * as Logger from "../../../Common/Logger";
|
||||
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||
import "./GalleryViewerComponent.less";
|
||||
@@ -28,6 +25,7 @@ import { HttpStatusCodes } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface GalleryViewerComponentProps {
|
||||
container?: Explorer;
|
||||
@@ -354,9 +352,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
|
||||
this.sampleNotebooks = response.data;
|
||||
} catch (error) {
|
||||
const message = `Failed to load sample notebooks: ${error}`;
|
||||
Logger.logError(message, "GalleryViewerComponent/loadSampleNotebooks");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GalleryViewerComponent/loadSampleNotebooks", "Failed to load sample notebooks");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +378,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to load public notebooks: ${error}`;
|
||||
Logger.logError(message, "GalleryViewerComponent/loadPublicNotebooks");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GalleryViewerComponent/loadPublicNotebooks", "Failed to load public notebooks");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,9 +398,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
|
||||
this.favoriteNotebooks = response.data;
|
||||
} catch (error) {
|
||||
const message = `Failed to load favorite notebooks: ${error}`;
|
||||
Logger.logError(message, "GalleryViewerComponent/loadFavoriteNotebooks");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GalleryViewerComponent/loadFavoriteNotebooks", "Failed to load favorite notebooks");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,9 +424,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
|
||||
this.publishedNotebooks = response.data;
|
||||
} catch (error) {
|
||||
const message = `Failed to load published notebooks: ${error}`;
|
||||
Logger.logError(message, "GalleryViewerComponent/loadPublishedNotebooks");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GalleryViewerComponent/loadPublishedNotebooks", "Failed to load published notebooks");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import Explorer from "../../Explorer";
|
||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||
import { DialogHost } from "../../../Utils/GalleryUtils";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface NotebookViewerComponentProps {
|
||||
container?: Explorer;
|
||||
@@ -100,9 +101,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({ showProgressBar: false });
|
||||
const message = `Failed to load notebook content: ${error}`;
|
||||
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "NotebookViewerComponent/loadNotebookContent", "Failed to load notebook content");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcesso
|
||||
|
||||
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface QueriesGridComponentProps {
|
||||
queriesClient: QueriesClient;
|
||||
@@ -244,7 +245,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
||||
databaseAccountName: container && container.databaseAccount().name,
|
||||
defaultExperience: container && container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: container && container.browseQueriesPane.title()
|
||||
paneTitle: container && container.browseQueriesPane.title(),
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
@@ -8,8 +8,8 @@ import * as DataModels from "../../../Contracts/DataModels";
|
||||
import ko from "knockout";
|
||||
import { TtlType, isDirty } from "./SettingsUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
jest.mock("../../../Common/dataAccess/readMongoDBCollection", () => ({
|
||||
getMongoDBCollectionIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||
jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
|
||||
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||
}));
|
||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||
|
||||
@@ -46,10 +46,9 @@ import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric
|
||||
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";
|
||||
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
interface SettingsV2TabInfo {
|
||||
tab: SettingsV2TabTypes;
|
||||
@@ -125,7 +124,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private container: Explorer;
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: boolean;
|
||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
@@ -212,6 +210,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.refreshIndexTransformationProgress();
|
||||
this.loadMongoIndexes();
|
||||
this.setAutoPilotStates();
|
||||
this.setBaseline();
|
||||
@@ -230,10 +229,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
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()
|
||||
@@ -248,10 +246,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
};
|
||||
|
||||
public refreshIndexTransformationProgress = async (): Promise<void> => {
|
||||
const currentProgress = await getMongoDBCollectionIndexTransformationProgress(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
);
|
||||
const currentProgress = await getIndexTransformationProgress(this.collection.databaseId, this.collection.id());
|
||||
this.setState({ indexTransformationProgress: currentProgress });
|
||||
};
|
||||
|
||||
@@ -351,6 +346,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
break;
|
||||
}
|
||||
|
||||
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
||||
newCollection.defaultTtl = defaultTtl;
|
||||
|
||||
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
||||
@@ -386,6 +382,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||
|
||||
if (wasIndexingPolicyModified) {
|
||||
await this.refreshIndexTransformationProgress();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isSubSettingsSaveable: false,
|
||||
isSubSettingsDiscardable: false,
|
||||
@@ -395,24 +396,55 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||
const newMongoCollection: MongoDBCollectionResource = {
|
||||
...this.mongoDBCollectionResource,
|
||||
indexes: newMongoIndexes
|
||||
};
|
||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newMongoCollection
|
||||
);
|
||||
try {
|
||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||
const newMongoCollection: MongoDBCollectionResource = {
|
||||
...this.mongoDBCollectionResource,
|
||||
indexes: newMongoIndexes
|
||||
};
|
||||
|
||||
await this.refreshIndexTransformationProgress();
|
||||
this.setState({
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||
});
|
||||
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: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.isScaleSaveable) {
|
||||
@@ -500,11 +532,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||
this.collection.offer(updatedOffer);
|
||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||
|
||||
if (this.state.isAutoPilotSelected) {
|
||||
const autoPilotOffer = await this.collection.loadAutopilotOfferWithRetry();
|
||||
this.setState({
|
||||
autoPilotThroughput: updatedOffer.content.offerAutopilotSettings.maxThroughput,
|
||||
autoPilotThroughputBaseline: updatedOffer.content.offerAutopilotSettings.maxThroughput
|
||||
autoPilotThroughput: autoPilotOffer.content.offerAutopilotSettings.maxThroughput,
|
||||
autoPilotThroughputBaseline: autoPilotOffer.content.offerAutopilotSettings.maxThroughput
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -512,6 +545,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
throughputBaseline: updatedOffer.content.offerThroughput
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||
}
|
||||
}
|
||||
this.container.isRefreshingExplorer(false);
|
||||
@@ -529,10 +564,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
},
|
||||
startKey
|
||||
);
|
||||
} catch (reason) {
|
||||
} catch (error) {
|
||||
this.container.isRefreshingExplorer(false);
|
||||
this.props.settingsTab.isExecutionError(true);
|
||||
console.error(reason);
|
||||
console.error(error);
|
||||
traceFailure(
|
||||
Action.SettingsV2Updated,
|
||||
{
|
||||
@@ -542,7 +577,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: reason
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -867,7 +903,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,
|
||||
@@ -916,6 +951,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
||||
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
||||
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
||||
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
getEstimatedAutoscaleSpendElement,
|
||||
manualToAutoscaleDisclaimerElement,
|
||||
ttlWarning,
|
||||
indexingPolicyTTLWarningMessage,
|
||||
indexingPolicynUnsavedWarningMessage,
|
||||
updateThroughputBeyondLimitWarningMessage,
|
||||
updateThroughputDelayedApplyWarningMessage,
|
||||
getThroughputApplyDelayedMessage,
|
||||
@@ -37,7 +37,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
|
||||
{manualToAutoscaleDisclaimerElement}
|
||||
{ttlWarning}
|
||||
{indexingPolicyTTLWarningMessage}
|
||||
{indexingPolicynUnsavedWarningMessage}
|
||||
{updateThroughputBeyondLimitWarningMessage}
|
||||
{updateThroughputDelayedApplyWarningMessage}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
} from "office-ui-fabric-react";
|
||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||
|
||||
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||
|
||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||
label: {
|
||||
@@ -245,15 +245,9 @@ export const ttlWarning: JSX.Element = (
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const indexingPolicyTTLWarningMessage: JSX.Element = (
|
||||
export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and
|
||||
the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For
|
||||
more information see,{" "}
|
||||
<Link target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">
|
||||
Modifying Indexing Policies
|
||||
</Link>
|
||||
.
|
||||
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -410,8 +404,8 @@ export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||
|
||||
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||
<Stack horizontal {...mongoWarningStackProps}>
|
||||
<Text>Refreshing index transformation progress</Text>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
<Text styles={infoAndToolTipTextStyle}>Refreshing index transformation progress</Text>
|
||||
<Spinner size={SpinnerSize.small} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -421,14 +415,14 @@ export const renderMongoIndexTransformationRefreshMessage = (
|
||||
): JSX.Element => {
|
||||
if (progress === 0) {
|
||||
return (
|
||||
<Text>
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
{"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>
|
||||
<Text styles={infoAndToolTipTextStyle}>
|
||||
{`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>
|
||||
|
||||
@@ -25,7 +25,9 @@ describe("IndexingPolicyComponent", () => {
|
||||
},
|
||||
onIndexingPolicyDirtyChange: () => {
|
||||
return;
|
||||
}
|
||||
},
|
||||
indexTransformationProgress: undefined,
|
||||
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { isDirty } from "../SettingsUtils";
|
||||
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
||||
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
|
||||
import { indexingPolicyTTLWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
||||
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
||||
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||
|
||||
export interface IndexingPolicyComponentProps {
|
||||
shouldDiscardIndexingPolicy: boolean;
|
||||
@@ -12,6 +13,8 @@ export interface IndexingPolicyComponentProps {
|
||||
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
||||
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
|
||||
logIndexingPolicySuccessMessage: () => void;
|
||||
indexTransformationProgress: number;
|
||||
refreshIndexTransformationProgress: () => Promise<void>;
|
||||
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -51,6 +54,9 @@ export class IndexingPolicyComponent extends React.Component<
|
||||
if (!this.indexingPolicyEditor) {
|
||||
this.createIndexingPolicyEditor();
|
||||
} else {
|
||||
this.indexingPolicyEditor.updateOptions({
|
||||
readOnly: isIndexTransforming(this.props.indexTransformationProgress)
|
||||
});
|
||||
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
||||
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
||||
indexingPolicyEditorModel.setValue(value);
|
||||
@@ -84,7 +90,7 @@ export class IndexingPolicyComponent extends React.Component<
|
||||
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
||||
value: value,
|
||||
language: "json",
|
||||
readOnly: false,
|
||||
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
|
||||
ariaLabel: "Indexing Policy"
|
||||
});
|
||||
if (this.indexingPolicyEditor) {
|
||||
@@ -108,8 +114,12 @@ export class IndexingPolicyComponent extends React.Component<
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack {...titleAndInputStackProps}>
|
||||
<IndexingPolicyRefreshComponent
|
||||
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||
/>
|
||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicyTTLWarningMessage}</MessageBar>
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
||||
)}
|
||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { IndexingPolicyRefreshComponentProps, IndexingPolicyRefreshComponent } from "./IndexingPolicyRefreshComponent";
|
||||
|
||||
describe("IndexingPolicyRefreshComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: IndexingPolicyRefreshComponentProps = {
|
||||
indexTransformationProgress: 90,
|
||||
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||
};
|
||||
|
||||
const wrapper = shallow(<IndexingPolicyRefreshComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import * as React from "react";
|
||||
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||
import {
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
} from "../../SettingsRenderUtils";
|
||||
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||
import { isIndexTransforming } from "../../SettingsUtils";
|
||||
|
||||
export interface IndexingPolicyRefreshComponentProps {
|
||||
indexTransformationProgress: number;
|
||||
refreshIndexTransformationProgress: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface IndexingPolicyRefreshComponentState {
|
||||
isRefreshing: boolean;
|
||||
}
|
||||
|
||||
export class IndexingPolicyRefreshComponent extends React.Component<
|
||||
IndexingPolicyRefreshComponentProps,
|
||||
IndexingPolicyRefreshComponentState
|
||||
> {
|
||||
constructor(props: IndexingPolicyRefreshComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isRefreshing: false
|
||||
};
|
||||
}
|
||||
|
||||
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
|
||||
|
||||
private renderIndexTransformationWarning = (): JSX.Element => {
|
||||
if (this.state.isRefreshing) {
|
||||
return mongoIndexTransformationRefreshingMessage;
|
||||
} else if (isIndexTransforming(this.props.indexTransformationProgress)) {
|
||||
return renderMongoIndexTransformationRefreshMessage(
|
||||
this.props.indexTransformationProgress,
|
||||
this.onClickRefreshIndexingTransformationLink
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private refreshIndexTransformationProgress = async () => {
|
||||
this.setState({ isRefreshing: true });
|
||||
try {
|
||||
await this.props.refreshIndexTransformationProgress();
|
||||
} catch (error) {
|
||||
handleError(error, "RefreshIndexTransformationProgress", "Refreshing index transformation progress failed");
|
||||
} finally {
|
||||
this.setState({ isRefreshing: false });
|
||||
}
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return this.renderIndexTransformationWarning() ? (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||
<StyledMessageBarBase
|
||||
messageBarType={5}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
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>
|
||||
</StyledMessageBarBase>
|
||||
`;
|
||||
@@ -2,6 +2,7 @@ import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
|
||||
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
|
||||
import { renderToString } from "react-dom/server";
|
||||
|
||||
describe("MongoIndexingPolicyComponent", () => {
|
||||
const baseProps: MongoIndexingPolicyComponentProps = {
|
||||
@@ -21,10 +22,7 @@ describe("MongoIndexingPolicyComponent", () => {
|
||||
return;
|
||||
},
|
||||
indexTransformationProgress: undefined,
|
||||
refreshIndexTransformationProgress: () =>
|
||||
new Promise(() => {
|
||||
return;
|
||||
}),
|
||||
refreshIndexTransformationProgress: () => new Promise(jest.fn()),
|
||||
onMongoIndexingPolicySaveableChange: () => {
|
||||
return;
|
||||
},
|
||||
@@ -38,16 +36,6 @@ describe("MongoIndexingPolicyComponent", () => {
|
||||
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;
|
||||
@@ -55,7 +43,7 @@ describe("MongoIndexingPolicyComponent", () => {
|
||||
it("defaults", () => {
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(undefined);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
|
||||
});
|
||||
|
||||
const sampleWarning = "sampleWarning";
|
||||
@@ -113,9 +101,12 @@ describe("MongoIndexingPolicyComponent", () => {
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
|
||||
isMongoIndexingPolicyDiscardable
|
||||
);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(
|
||||
mongoWarningNotificationMessage
|
||||
);
|
||||
if (mongoWarningNotificationMessage) {
|
||||
const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
|
||||
expect(elementAsString).toContain(mongoWarningNotificationMessage);
|
||||
} else {
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
createAndAddMongoIndexStackProps,
|
||||
separatorStyles,
|
||||
mongoIndexingPolicyAADError,
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
indexingPolicynUnsavedWarningMessage,
|
||||
infoAndToolTipTextStyle
|
||||
} from "../../SettingsRenderUtils";
|
||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
@@ -35,12 +35,13 @@ import {
|
||||
MongoIndexIdField,
|
||||
MongoNotificationType,
|
||||
getMongoIndexType,
|
||||
getMongoIndexTypeText
|
||||
getMongoIndexTypeText,
|
||||
isIndexTransforming
|
||||
} from "../../SettingsUtils";
|
||||
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||
import { AuthType } from "../../../../../AuthType";
|
||||
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||
|
||||
export interface MongoIndexingPolicyComponentProps {
|
||||
mongoIndexes: MongoIndex[];
|
||||
@@ -56,20 +57,13 @@ export interface MongoIndexingPolicyComponentProps {
|
||||
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
|
||||
> {
|
||||
export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingPolicyComponentProps> {
|
||||
private shouldCheckComponentIsDirty = true;
|
||||
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
||||
private initialIndexesColumns: IColumn[] = [
|
||||
@@ -98,13 +92,6 @@ export class MongoIndexingPolicyComponent extends React.Component<
|
||||
}
|
||||
];
|
||||
|
||||
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();
|
||||
@@ -144,10 +131,15 @@ export class MongoIndexingPolicyComponent extends React.Component<
|
||||
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
|
||||
};
|
||||
|
||||
public getMongoWarningNotificationMessage = (): string => {
|
||||
return this.props.indexesToAdd.find(
|
||||
public getMongoWarningNotificationMessage = (): JSX.Element => {
|
||||
const warningMessage = this.props.indexesToAdd.find(
|
||||
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
|
||||
)?.notification.message;
|
||||
|
||||
if (warningMessage) {
|
||||
return <Text styles={infoAndToolTipTextStyle}>{warningMessage}</Text>;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||
@@ -159,7 +151,7 @@ export class MongoIndexingPolicyComponent extends React.Component<
|
||||
<IconButton
|
||||
ariaLabel="Delete index Button"
|
||||
iconProps={{ iconName: "Delete" }}
|
||||
disabled={this.isIndexingTransforming()}
|
||||
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||
onClick={() => {
|
||||
this.props.onIndexDrop(arrayPosition);
|
||||
}}
|
||||
@@ -230,7 +222,7 @@ export class MongoIndexingPolicyComponent extends React.Component<
|
||||
|
||||
<AddMongoIndexComponent
|
||||
ref={this.addMongoIndexComponentRefs[indexesToAddLength]}
|
||||
disabled={this.isIndexingTransforming()}
|
||||
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||
position={indexesToAddLength}
|
||||
key={indexesToAddLength}
|
||||
description={undefined}
|
||||
@@ -298,55 +290,21 @@ export class MongoIndexingPolicyComponent extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
let warningMessage: JSX.Element;
|
||||
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.";
|
||||
warningMessage = indexingPolicynUnsavedWarningMessage;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderIndexTransformationWarning() && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||
)}
|
||||
|
||||
{warningMessage && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>
|
||||
<Text>{warningMessage}</Text>
|
||||
</MessageBar>
|
||||
)}
|
||||
<IndexingPolicyRefreshComponent
|
||||
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||
/>
|
||||
{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,9 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<IndexingPolicyRefreshComponent
|
||||
refreshIndexTransformationProgress={[Function]}
|
||||
/>
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<StyledLinkBase
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -8,6 +8,9 @@ exports[`IndexingPolicyComponent renders 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<IndexingPolicyRefreshComponent
|
||||
refreshIndexTransformationProgress={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
tabIndex={0}
|
||||
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
MongoWildcardPlaceHolder,
|
||||
getMongoIndexTypeText,
|
||||
SingleFieldText,
|
||||
WildcardText
|
||||
WildcardText,
|
||||
isIndexTransforming
|
||||
} from "./SettingsUtils";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
@@ -139,3 +140,10 @@ describe("SettingsUtils", () => {
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
});
|
||||
});
|
||||
|
||||
it("isIndexingTransforming", () => {
|
||||
expect(isIndexTransforming(undefined)).toBeFalsy();
|
||||
expect(isIndexTransforming(0)).toBeTruthy();
|
||||
expect(isIndexTransforming(90)).toBeTruthy();
|
||||
expect(isIndexTransforming(100)).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -250,3 +250,7 @@ export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
|
||||
}
|
||||
return WildcardText;
|
||||
};
|
||||
|
||||
export const isIndexTransforming = (indexTransformationProgress: number): boolean =>
|
||||
// index transformation progress can be 0
|
||||
indexTransformationProgress !== undefined && indexTransformationProgress !== 100;
|
||||
|
||||
@@ -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],
|
||||
@@ -140,10 +135,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -354,7 +346,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -366,7 +357,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -387,10 +377,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 +572,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -607,7 +593,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -639,10 +624,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -672,7 +654,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -688,7 +669,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -701,7 +681,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -778,7 +757,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -790,7 +768,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -811,10 +788,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 +943,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -1347,7 +1320,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1363,7 +1335,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1376,7 +1347,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -1393,7 +1363,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1415,7 +1384,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -1447,10 +1415,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -1661,7 +1626,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1673,7 +1637,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1694,10 +1657,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],
|
||||
@@ -1892,7 +1852,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1914,7 +1873,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -1946,10 +1904,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -1979,7 +1934,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1995,7 +1949,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2008,7 +1961,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -2085,7 +2037,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2097,7 +2048,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2118,10 +2068,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],
|
||||
@@ -2276,7 +2223,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -2667,7 +2613,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2683,7 +2628,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2696,7 +2640,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -2713,7 +2656,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2735,7 +2677,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -2767,10 +2708,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -2981,7 +2919,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2993,7 +2930,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3014,10 +2950,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],
|
||||
@@ -3212,7 +3145,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3234,7 +3166,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -3266,10 +3197,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -3299,7 +3227,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3315,7 +3242,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3328,7 +3254,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -3405,7 +3330,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3417,7 +3341,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3438,10 +3361,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],
|
||||
@@ -3596,7 +3516,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -3974,7 +3893,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3990,7 +3908,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4003,7 +3920,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -4020,7 +3936,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4042,7 +3957,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -4074,10 +3988,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -4288,7 +4199,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4300,7 +4210,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4321,10 +4230,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],
|
||||
@@ -4519,7 +4425,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4541,7 +4446,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -4573,10 +4477,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -4606,7 +4507,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4622,7 +4522,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4635,7 +4534,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -4712,7 +4610,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4724,7 +4621,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4745,10 +4641,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],
|
||||
@@ -4903,7 +4796,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -5297,6 +5189,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
logIndexingPolicySuccessMessage={[Function]}
|
||||
onIndexingPolicyContentChange={[Function]}
|
||||
onIndexingPolicyDirtyChange={[Function]}
|
||||
refreshIndexTransformationProgress={[Function]}
|
||||
resetShouldDiscardIndexingPolicy={[Function]}
|
||||
shouldDiscardIndexingPolicy={false}
|
||||
/>
|
||||
|
||||
@@ -166,15 +166,7 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For more information see,
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://aka.ms/cosmosdb/modify-index-policy"
|
||||
target="_blank"
|
||||
>
|
||||
Modifying Indexing Policies
|
||||
</StyledLinkBase>
|
||||
.
|
||||
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
||||
</Text>
|
||||
<Text
|
||||
id="updateThroughputBeyondLimitWarningMessage"
|
||||
@@ -341,14 +333,30 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Refreshing index transformation progress
|
||||
</Text>
|
||||
<StyledSpinnerBase
|
||||
size={2}
|
||||
size={1}
|
||||
/>
|
||||
</Stack>
|
||||
<Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
You can make more indexing changes once the current index transformation is complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
@@ -356,7 +364,15 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
Refresh to check if it has completed.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Text>
|
||||
<Text
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -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="Manual 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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -86,6 +86,7 @@ import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandBut
|
||||
import { updateUserContext, userContext } from "../UserContext";
|
||||
import { stringToBlob } from "../Utils/BlobUtils";
|
||||
import { IChoiceGroupProps } from "office-ui-fabric-react";
|
||||
import { getErrorMessage, handleError, getErrorStack } from "../Common/ErrorHandlingUtils";
|
||||
|
||||
BindingHandlersRegisterer.registerBindingHandlers();
|
||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||
@@ -212,7 +213,6 @@ export default class Explorer {
|
||||
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>;
|
||||
@@ -422,13 +422,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>();
|
||||
@@ -1047,11 +1040,11 @@ export default class Explorer {
|
||||
);
|
||||
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, startTime);
|
||||
this.databaseAccount(databaseAccount);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(logId);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Enabling Azure Synapse Link for this account failed. ${e.message || JSON.stringify(e)}`
|
||||
`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`
|
||||
);
|
||||
TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, startTime);
|
||||
} finally {
|
||||
@@ -1120,7 +1113,7 @@ export default class Explorer {
|
||||
);
|
||||
this.renewExplorerShareAccess(this, this.tokenForRenewal())
|
||||
.fail((error: any) => {
|
||||
const stringifiedError: string = error.message;
|
||||
const stringifiedError: string = getErrorMessage(error);
|
||||
this.renewTokenError("Invalid connection string specified");
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
@@ -1149,7 +1142,7 @@ export default class Explorer {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to generate share url: ${error.message}`
|
||||
`Failed to generate share url: ${getErrorMessage(error)}`
|
||||
);
|
||||
console.error(error);
|
||||
}
|
||||
@@ -1174,7 +1167,10 @@ export default class Explorer {
|
||||
deferred.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to connect: ${getErrorMessage(error)}`
|
||||
);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
@@ -1448,19 +1444,21 @@ export default class Explorer {
|
||||
this._setLoadingStatusText("Failed to fetch databases.");
|
||||
this.isRefreshingExplorer(false);
|
||||
deferred.reject(error);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: error.message
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while refreshing databases: ${error.message}`
|
||||
`Error while refreshing databases: ${errorMessage}`
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1479,7 +1477,7 @@ export default class Explorer {
|
||||
);
|
||||
}
|
||||
},
|
||||
reason => {
|
||||
error => {
|
||||
if (resourceTreeStartKey != null) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadResourceTree,
|
||||
@@ -1487,7 +1485,8 @@ export default class Explorer {
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: reason
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
resourceTreeStartKey
|
||||
);
|
||||
@@ -1536,7 +1535,7 @@ export default class Explorer {
|
||||
resolve(token);
|
||||
},
|
||||
(error: any) => {
|
||||
Logger.logError(error, "Explorer/getArcadiaToken");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/getArcadiaToken");
|
||||
resolve(undefined);
|
||||
}
|
||||
);
|
||||
@@ -1554,7 +1553,7 @@ export default class Explorer {
|
||||
workspaceItems[i] = { ...workspace, sparkPools: sparkpools };
|
||||
},
|
||||
error => {
|
||||
Logger.logError(error, "Explorer/this._arcadiaManager.listSparkPoolsAsync");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/this._arcadiaManager.listSparkPoolsAsync");
|
||||
}
|
||||
);
|
||||
sparkPromises.push(promise);
|
||||
@@ -1562,8 +1561,7 @@ export default class Explorer {
|
||||
|
||||
return Promise.all(sparkPromises).then(() => workspaceItems);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error.message);
|
||||
handleError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync", "Get Arcadia workspaces failed");
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
@@ -1598,10 +1596,10 @@ export default class Explorer {
|
||||
);
|
||||
} catch (error) {
|
||||
this._isInitializingNotebooks = false;
|
||||
Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to get notebook workspace connection info: ${error.message}`
|
||||
handleError(
|
||||
error,
|
||||
"initNotebooks/getNotebookConnectionInfoAsync",
|
||||
`Failed to get notebook workspace connection info: ${getErrorMessage(error)}`
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -1624,9 +1622,10 @@ export default class Explorer {
|
||||
|
||||
public resetNotebookWorkspace() {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookClient) {
|
||||
const error = "Attempt to reset notebook workspace, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/resetNotebookWorkspace");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(
|
||||
"Attempt to reset notebook workspace, but notebook is not enabled",
|
||||
"Explorer/resetNotebookWorkspace"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const resetConfirmationDialogProps: DialogProps = {
|
||||
@@ -1651,7 +1650,7 @@ export default class Explorer {
|
||||
const workspaces = await this.notebookWorkspaceManager.getNotebookWorkspacesAsync(databaseAccount?.id);
|
||||
return workspaces && workspaces.length > 0 && workspaces.some(workspace => workspace.name === "default");
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/_containsDefaultNotebookWorkspace");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1677,8 +1676,7 @@ export default class Explorer {
|
||||
await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(this.databaseAccount().id, "default");
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
|
||||
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${error.message}`);
|
||||
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
||||
} finally {
|
||||
clearMessage && clearMessage();
|
||||
}
|
||||
@@ -1693,7 +1691,10 @@ export default class Explorer {
|
||||
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||
} catch (error) {
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to reset notebook workspace: ${error}`);
|
||||
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, error);
|
||||
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
@@ -2060,7 +2061,8 @@ export default class Explorer {
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
trace: error.message
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -2226,8 +2228,7 @@ export default class Explorer {
|
||||
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to upload notebook, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/uploadFile");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/uploadFile");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2408,8 +2409,7 @@ export default class Explorer {
|
||||
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to rename notebook, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/renameNotebook");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/renameNotebook");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2457,8 +2457,7 @@ export default class Explorer {
|
||||
public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to create notebook directory, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/onCreateDirectory");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/onCreateDirectory");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2479,8 +2478,7 @@ export default class Explorer {
|
||||
public readFile(notebookFile: NotebookContentItem): Promise<string> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to read file, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/downloadFile");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/downloadFile");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2490,8 +2488,7 @@ export default class Explorer {
|
||||
public downloadFile(notebookFile: NotebookContentItem): Promise<void> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to download file, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/downloadFile");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/downloadFile");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2522,7 +2519,7 @@ export default class Explorer {
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Could not download notebook ${error.message}`
|
||||
`Could not download notebook ${getErrorMessage(error)}`
|
||||
);
|
||||
|
||||
clearMessage();
|
||||
@@ -2572,7 +2569,7 @@ export default class Explorer {
|
||||
);
|
||||
this.isNotebooksEnabledForAccount(isAccountInAllowedLocation);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/isNotebooksEnabledForAccount");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
||||
this.isNotebooksEnabledForAccount(false);
|
||||
}
|
||||
}
|
||||
@@ -2601,7 +2598,7 @@ export default class Explorer {
|
||||
false;
|
||||
this.isSparkEnabledForAccount(isEnabled);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/isSparkEnabledForAccount");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/isSparkEnabledForAccount");
|
||||
this.isSparkEnabledForAccount(false);
|
||||
}
|
||||
};
|
||||
@@ -2626,7 +2623,7 @@ export default class Explorer {
|
||||
(featureStatus && featureStatus.properties && featureStatus.properties.state === "Registered") || false;
|
||||
return isEnabled;
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/isSparkEnabledForAccount");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/isSparkEnabledForAccount");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -2645,8 +2642,7 @@ export default class Explorer {
|
||||
public deleteNotebookFile(item: NotebookContentItem): Promise<void> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to delete notebook file, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/deleteNotebookFile");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/deleteNotebookFile");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2695,8 +2691,7 @@ export default class Explorer {
|
||||
public onNewNotebookClicked(parent?: NotebookContentItem): void {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to create new notebook, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/onNewNotebookClicked");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/onNewNotebookClicked");
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -2729,16 +2724,17 @@ export default class Explorer {
|
||||
return this.openNotebook(newFile);
|
||||
})
|
||||
.then(() => this.resourceTree.triggerRender())
|
||||
.catch((reason: any) => {
|
||||
const error = `Failed to create a new notebook: ${reason}`;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
.catch((error: any) => {
|
||||
const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.CreateNewNotebook,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
error
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -2781,8 +2777,7 @@ export default class Explorer {
|
||||
public refreshContentItem(item: NotebookContentItem): Promise<void> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to refresh notebook list, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/refreshContentItem");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
||||
handleError(error, "Explorer/refreshContentItem");
|
||||
return Promise.reject(new Error(error));
|
||||
}
|
||||
|
||||
@@ -2968,7 +2963,7 @@ export default class Explorer {
|
||||
}
|
||||
return tokenRefreshInterval;
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/getTokenRefreshInterval");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/getTokenRefreshInterval");
|
||||
return tokenRefreshInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { InputProperty } from "../../../Contracts/ViewModels";
|
||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface GraphAccessor {
|
||||
applyFilter: () => void;
|
||||
@@ -892,7 +893,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}: ${error.message}`;
|
||||
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
filterQueryError: errorMsg
|
||||
@@ -1826,7 +1827,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: ${error.message}`;
|
||||
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
filterQueryError: errorMsg
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("Gremlin Client", () => {
|
||||
|
||||
it("should log and display error out on unknown requestId", () => {
|
||||
const gremlinClient = new GremlinClient();
|
||||
const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleMessage");
|
||||
const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
|
||||
const logErrorSpy = sinon.spy(Logger, "logError");
|
||||
|
||||
gremlinClient.initialize(emptyParams);
|
||||
@@ -122,7 +122,7 @@ describe("Gremlin Client", () => {
|
||||
});
|
||||
|
||||
it("should not aggregate RU if not a number and reset totalRequestCharge to undefined", done => {
|
||||
const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleMessage");
|
||||
const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
|
||||
const logErrorSpy = sinon.spy(Logger, "logError");
|
||||
|
||||
const gremlinClient = new GremlinClient();
|
||||
@@ -165,7 +165,7 @@ describe("Gremlin Client", () => {
|
||||
});
|
||||
|
||||
it("should not aggregate RU if undefined and reset totalRequestCharge to undefined", done => {
|
||||
const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleMessage");
|
||||
const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
|
||||
const logErrorSpy = sinon.spy(Logger, "logError");
|
||||
|
||||
const gremlinClient = new GremlinClient();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { GremlinSimpleClient, Result } from "./GremlinSimpleClient";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { HashMap } from "../../../Common/HashMap";
|
||||
import * as Logger from "../../../Common/Logger";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface GremlinClientParameters {
|
||||
endpoint: string;
|
||||
@@ -58,26 +58,23 @@ export class GremlinClient {
|
||||
}
|
||||
},
|
||||
failureCallback: (result: Result, error: any) => {
|
||||
if (typeof error !== "string") {
|
||||
error = error.message;
|
||||
}
|
||||
|
||||
const errorMessage = getErrorMessage(error);
|
||||
const requestId = result.requestId;
|
||||
|
||||
if (!requestId || !this.pendingResults.has(requestId)) {
|
||||
const msg = `Error: ${error}, unknown requestId:${requestId} ${GremlinClient.getRequestChargeString(
|
||||
const errorMsg = `Error: ${errorMessage}, unknown requestId:${requestId} ${GremlinClient.getRequestChargeString(
|
||||
result.requestCharge
|
||||
)}`;
|
||||
GremlinClient.reportError(msg);
|
||||
handleError(errorMsg, GremlinClient.LOG_AREA);
|
||||
|
||||
// Fail all pending requests if no request id (fatal)
|
||||
if (!requestId) {
|
||||
this.pendingResults.keys().forEach((reqId: string) => {
|
||||
this.abortPendingRequest(reqId, error, null);
|
||||
this.abortPendingRequest(reqId, errorMessage, null);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.abortPendingRequest(requestId, error, result.requestCharge);
|
||||
this.abortPendingRequest(requestId, errorMessage, result.requestCharge);
|
||||
}
|
||||
},
|
||||
infoCallback: (msg: string) => {
|
||||
@@ -132,15 +129,16 @@ export class GremlinClient {
|
||||
deferred.reject(error);
|
||||
this.pendingResults.delete(requestId);
|
||||
|
||||
GremlinClient.reportError(
|
||||
`Aborting pending request ${requestId}. Error:${error} ${GremlinClient.getRequestChargeString(requestCharge)}`
|
||||
);
|
||||
const errorMsg = `Aborting pending request ${requestId}. Error:${error} ${GremlinClient.getRequestChargeString(
|
||||
requestCharge
|
||||
)}`;
|
||||
handleError(errorMsg, GremlinClient.LOG_AREA);
|
||||
}
|
||||
|
||||
private flushResult(requestId: string) {
|
||||
if (!this.pendingResults.has(requestId)) {
|
||||
const msg = `Unknown requestId:${requestId}`;
|
||||
GremlinClient.reportError(msg);
|
||||
const errorMsg = `Unknown requestId:${requestId}`;
|
||||
handleError(errorMsg, GremlinClient.LOG_AREA);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,8 +156,8 @@ export class GremlinClient {
|
||||
*/
|
||||
private storePendingResult(result: Result): boolean {
|
||||
if (!this.pendingResults.has(result.requestId)) {
|
||||
const msg = `Dropping result for unknown requestId:${result.requestId}`;
|
||||
GremlinClient.reportError(msg);
|
||||
const errorMsg = `Dropping result for unknown requestId:${result.requestId}`;
|
||||
handleError(errorMsg, GremlinClient.LOG_AREA);
|
||||
return false;
|
||||
}
|
||||
const pendingResults = this.pendingResults.get(result.requestId).result;
|
||||
@@ -179,9 +177,8 @@ export class GremlinClient {
|
||||
if (result.requestCharge === undefined || typeof result.requestCharge !== "number") {
|
||||
// Clear totalRequestCharge, even if it was a valid number as the total might be incomplete therefore incorrect
|
||||
pendingResults.totalRequestCharge = undefined;
|
||||
GremlinClient.reportError(
|
||||
`Unable to perform RU aggregation calculation with non numbers. Result request charge: ${result.requestCharge}. RequestId: ${result.requestId}`
|
||||
);
|
||||
const errorMsg = `Unable to perform RU aggregation calculation with non numbers. Result request charge: ${result.requestCharge}. RequestId: ${result.requestId}`;
|
||||
handleError(errorMsg, GremlinClient.LOG_AREA);
|
||||
} else {
|
||||
if (pendingResults.totalRequestCharge === undefined) {
|
||||
pendingResults.totalRequestCharge = 0;
|
||||
@@ -190,9 +187,4 @@ export class GremlinClient {
|
||||
}
|
||||
return pendingResults.isIncomplete;
|
||||
}
|
||||
|
||||
private static reportError(msg: string): void {
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
Logger.logError(msg, GremlinClient.LOG_AREA);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as Constants from "../../Common/Constants";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export class NotebookContainerClient {
|
||||
private reconnectingNotificationId: string;
|
||||
@@ -74,7 +75,7 @@ export class NotebookContainerClient {
|
||||
}
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
||||
if (!this.reconnectingNotificationId) {
|
||||
this.reconnectingNotificationId = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
@@ -110,7 +111,7 @@ export class NotebookContainerClient {
|
||||
headers: { Authorization: authToken }
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
||||
await this.recreateNotebookWorkspaceAsync();
|
||||
}
|
||||
}
|
||||
@@ -140,7 +141,7 @@ export class NotebookContainerClient {
|
||||
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(explorer.databaseAccount().id, "default");
|
||||
await notebookWorkspaceManager.createNotebookWorkspaceAsync(explorer.databaseAccount().id, "default");
|
||||
} catch (error) {
|
||||
Logger.logError(error, "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { ImmutableNotebook } from "@nteract/commutable";
|
||||
import Explorer from "../Explorer";
|
||||
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
||||
import { CopyNotebookPaneAdapter } from "../Panes/CopyNotebookPane";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface NotebookManagerOptions {
|
||||
container: Explorer;
|
||||
@@ -147,7 +148,7 @@ export default class NotebookManager {
|
||||
// Octokit's error handler uses any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private onGitHubClientError = (error: any): void => {
|
||||
Logger.logError(error, "NotebookManager/onGitHubClientError");
|
||||
Logger.logError(getErrorMessage(error), "NotebookManager/onGitHubClientError");
|
||||
|
||||
if (error.status === HttpStatusCodes.Unauthorized) {
|
||||
this.gitHubOAuthService.resetToken();
|
||||
|
||||
@@ -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()">
|
||||
@@ -287,62 +241,6 @@
|
||||
</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">
|
||||
@@ -390,7 +288,7 @@
|
||||
range of values and is likely to have evenly distributed access patterns.</span>
|
||||
</span>
|
||||
</p>
|
||||
<input type="text" data-test="addCollection-partitionKeyValue" aria-required="true" size="40"
|
||||
<input type="text" id="partitionKeyValue" data-test="addCollection-partitionKeyValue" aria-required="true" size="40"
|
||||
class="textfontclr collid" data-bind="textInput: partitionKey,
|
||||
attr: {
|
||||
placeholder: partitionKeyPlaceholder,
|
||||
@@ -442,7 +340,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 +369,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", () => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
|
||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import * as ko from "knockout";
|
||||
import * as PricingUtils from "../../Utils/PricingUtils";
|
||||
import * as SharedConstants from "../../Shared/Constants";
|
||||
@@ -15,6 +14,7 @@ import { configContext, Platform } from "../../ConfigContext";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||
isPreferredApiTable: ko.Computed<boolean>;
|
||||
@@ -75,10 +75,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>;
|
||||
@@ -92,7 +88,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>;
|
||||
@@ -102,8 +97,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>();
|
||||
@@ -171,13 +165,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)`;
|
||||
});
|
||||
@@ -447,7 +441,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();
|
||||
@@ -490,14 +484,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>(() => {
|
||||
@@ -506,9 +492,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();
|
||||
@@ -897,10 +881,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
this.resetData();
|
||||
this.container.refreshAllDatabases();
|
||||
},
|
||||
(reason: any) => {
|
||||
(error: any) => {
|
||||
this.isExecuting(false);
|
||||
const message = ErrorParserUtility.parse(reason);
|
||||
const errorMessage = ErrorParserUtility.replaceKnownError(message[0].message);
|
||||
const errorMessage: string = getErrorMessage(error);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
const addCollectionPaneFailedMessage = {
|
||||
@@ -928,7 +911,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
flight: this.container.flight()
|
||||
},
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
error: reason
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
};
|
||||
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
|
||||
}
|
||||
@@ -942,13 +926,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);
|
||||
|
||||
@@ -1027,17 +1007,12 @@ 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;
|
||||
}
|
||||
@@ -1067,7 +1042,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
const autoscaleThroughput = this.autoPilotThroughput() * 1;
|
||||
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -1114,31 +1088,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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import * as ko from "knockout";
|
||||
import * as PricingUtils from "../../Utils/PricingUtils";
|
||||
import * as SharedConstants from "../../Shared/Constants";
|
||||
@@ -12,6 +11,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public defaultExperience: ko.Computed<string>;
|
||||
@@ -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)`;
|
||||
});
|
||||
@@ -208,7 +198,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;
|
||||
}
|
||||
|
||||
@@ -306,18 +296,22 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
this.isExecuting(true);
|
||||
|
||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||
autoPilotMaxThroughput: this.maxAutoPilotThroughputSet(),
|
||||
databaseId: addDatabasePaneStartMessage.database.id,
|
||||
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||
offerThroughput: addDatabasePaneStartMessage.offerThroughput
|
||||
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared
|
||||
};
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
createDatabaseParams.autoPilotMaxThroughput = this.maxAutoPilotThroughputSet();
|
||||
} else {
|
||||
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput;
|
||||
}
|
||||
|
||||
createDatabase(createDatabaseParams).then(
|
||||
(database: DataModels.Database) => {
|
||||
this._onCreateDatabaseSuccess(offerThroughput, startKey);
|
||||
},
|
||||
(reason: any) => {
|
||||
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
||||
(error: any) => {
|
||||
this._onCreateDatabaseFailure(error, offerThroughput, startKey);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -325,7 +319,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();
|
||||
@@ -367,11 +360,11 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
this.resetData();
|
||||
}
|
||||
|
||||
private _onCreateDatabaseFailure(reason: any, offerThroughput: number, startKey: number): void {
|
||||
private _onCreateDatabaseFailure(error: any, offerThroughput: number, startKey: number): void {
|
||||
this.isExecuting(false);
|
||||
const message = ErrorParserUtility.parse(reason);
|
||||
this.formErrors(message[0].message);
|
||||
this.formErrorsDetails(message[0].message);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
const addDatabasePaneFailedMessage = {
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
@@ -386,7 +379,8 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
flight: this.container.flight()
|
||||
},
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
error: reason
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
};
|
||||
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
|
||||
}
|
||||
@@ -413,17 +407,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 +427,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 +439,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;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as Logger from "../../Common/Logger";
|
||||
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import QueryTab from "../Tabs/QueryTab";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export class BrowseQueriesPane extends ContextualPaneBase {
|
||||
public queriesGridComponentAdapter: QueriesGridComponentAdapter;
|
||||
@@ -60,17 +61,20 @@ export class BrowseQueriesPane extends ContextualPaneBase {
|
||||
startKey
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.SetupSavedQueries,
|
||||
{
|
||||
databaseAccountName: this.container && this.container.databaseAccount().name,
|
||||
defaultExperience: this.container && this.container.defaultExperience(),
|
||||
dataExplorerArea: Areas.ContextualPane,
|
||||
paneTitle: this.title()
|
||||
paneTitle: this.title(),
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
this.formErrors(`Failed to setup a collection for saved queries: ${error.message}`);
|
||||
this.formErrors(`Failed to setup a collection for saved queries: ${errorMessage}`);
|
||||
} 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">
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { HashMap } from "../../Common/HashMap";
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
public createTableQuery: ko.Observable<string>;
|
||||
@@ -38,10 +39,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 +46,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 +57,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 +85,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 +95,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)`;
|
||||
});
|
||||
@@ -246,7 +239,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 +248,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 +273,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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,15 +336,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()}`;
|
||||
@@ -450,8 +430,9 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
};
|
||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey);
|
||||
},
|
||||
reason => {
|
||||
this.formErrors(reason.exceptionMessage);
|
||||
error => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formErrors(errorMessage);
|
||||
this.isExecuting(false);
|
||||
const addCollectionPaneFailedMessage = {
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
@@ -477,7 +458,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
toCreateKeyspace: toCreateKeyspace,
|
||||
createKeyspaceQuery: createKeyspaceQuery,
|
||||
createTableQuery: createTableQuery,
|
||||
error: reason
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
};
|
||||
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
|
||||
}
|
||||
@@ -489,8 +471,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 +492,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 +502,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 +516,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 +548,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;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ko from "knockout";
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { JunoClient, IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -13,6 +12,7 @@ import { HttpStatusCodes } from "../../Common/Constants";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { NotebookContentItemType, NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||
import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
interface Location {
|
||||
type: "MyNotebooks" | "GitHub";
|
||||
@@ -90,9 +90,7 @@ export class CopyNotebookPaneAdapter implements ReactAdapter {
|
||||
if (this.gitHubOAuthService.isLoggedIn()) {
|
||||
const response = await this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
|
||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||
const message = `Received HTTP ${response.status} when fetching pinned repos`;
|
||||
Logger.logError(message, "CopyNotebookPaneAdapter/submit");
|
||||
NotificationConsoleUtils.logConsoleError(message);
|
||||
handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit");
|
||||
}
|
||||
|
||||
if (response.data?.length > 0) {
|
||||
@@ -134,12 +132,10 @@ export class CopyNotebookPaneAdapter implements ReactAdapter {
|
||||
|
||||
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${this.name} to ${destination}`);
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formError = `Failed to copy ${this.name} to ${destination}`;
|
||||
this.formErrorDetail = `${error}`;
|
||||
|
||||
const message = `${this.formError}: ${this.formErrorDetail}`;
|
||||
Logger.logError(message, "CopyNotebookPaneAdapter/submit");
|
||||
NotificationConsoleUtils.logConsoleError(message);
|
||||
this.formErrorDetail = `${errorMessage}`;
|
||||
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", this.formError);
|
||||
return;
|
||||
} finally {
|
||||
clearMessage && clearMessage();
|
||||
|
||||
@@ -3,7 +3,6 @@ import Q from "q";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
@@ -12,6 +11,7 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
||||
public collectionIdConfirmationText: ko.Observable<string>;
|
||||
@@ -99,11 +99,11 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
|
||||
this.containerDeleteFeedback("");
|
||||
}
|
||||
},
|
||||
(reason: any) => {
|
||||
(error: any) => {
|
||||
this.isExecuting(false);
|
||||
const message = ErrorParserUtility.parse(reason);
|
||||
this.formErrors(message[0].message);
|
||||
this.formErrorsDetails(message[0].message);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.DeleteCollection,
|
||||
{
|
||||
@@ -111,7 +111,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
collectionId: selectedCollection.id(),
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: this.title()
|
||||
paneTitle: this.title(),
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
@@ -14,6 +13,7 @@ import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||
import { ARMError } from "../../Utils/arm/request";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
public databaseIdConfirmationText: ko.Observable<string>;
|
||||
@@ -108,12 +108,11 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
this.databaseDeleteFeedback("");
|
||||
}
|
||||
},
|
||||
(reason: unknown) => {
|
||||
(error: any) => {
|
||||
this.isExecuting(false);
|
||||
|
||||
const message = reason instanceof ARMError ? reason.message : ErrorParserUtility.parse(reason)[0].message;
|
||||
this.formErrors(message);
|
||||
this.formErrorsDetails(message);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.DeleteDatabase,
|
||||
{
|
||||
@@ -121,7 +120,9 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
databaseId: selectedDatabase.id(),
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: this.title()
|
||||
paneTitle: this.title(),
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _ from "underscore";
|
||||
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
|
||||
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
||||
@@ -8,13 +7,12 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { JunoUtils } from "../../Utils/JunoUtils";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import { AuthorizeAccessComponent } from "../Controls/GitHub/AuthorizeAccessComponent";
|
||||
import { GitHubReposComponent, GitHubReposComponentProps, RepoListItem } from "../Controls/GitHub/GitHubReposComponent";
|
||||
import { GitHubReposComponentAdapter } from "../Controls/GitHub/GitHubReposComponentAdapter";
|
||||
import { BranchesProps, PinnedReposProps, UnpinnedReposProps } from "../Controls/GitHub/ReposListComponent";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
interface GitHubReposPaneOptions extends ViewModels.PaneOptions {
|
||||
gitHubClient: GitHubClient;
|
||||
@@ -105,9 +103,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to save pinned repos: ${error}`;
|
||||
Logger.logError(message, "GitHubReposPane/submit");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,9 +202,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
branchesProps.lastPageInfo = response.pageInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to fetch branches: ${error}`;
|
||||
Logger.logError(message, "GitHubReposPane/loadMoreBranches");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
||||
}
|
||||
|
||||
branchesProps.isLoading = false;
|
||||
@@ -236,9 +230,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to fetch unpinned repos: ${error}`;
|
||||
Logger.logError(message, "GitHubReposPane/loadMoreUnpinnedRepos");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GitHubReposPane/loadMoreUnpinnedRepos", "Failed to fetch unpinned repos");
|
||||
}
|
||||
|
||||
this.unpinnedReposProps.isLoading = false;
|
||||
@@ -255,9 +247,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
const message = `Failed to fetch repo: ${error}`;
|
||||
Logger.logError(message, "GitHubReposPane/getRepo");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GitHubReposPane/getRepo", "Failed to fetch repo");
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
@@ -320,9 +310,7 @@ export class GitHubReposPane extends ContextualPaneBase {
|
||||
this.triggerRender();
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to fetch pinned repos: ${error}`;
|
||||
Logger.logError(message, "GitHubReposPane/refreshPinnedReposListItems");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(error, "GitHubReposPane/refreshPinnedReposListItems", "Failed to fetch pinned repos");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import ko from "knockout";
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import Explorer from "../Explorer";
|
||||
import { JunoClient } from "../../Juno/JunoClient";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
|
||||
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
|
||||
import { ImmutableNotebook } from "@nteract/commutable/src";
|
||||
import { toJS } from "@nteract/commutable";
|
||||
import { CodeOfConductComponent } from "../Controls/NotebookGallery/CodeOfConductComponent";
|
||||
import { HttpStatusCodes } from "../../Common/Constants";
|
||||
import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
parameters: ko.Observable<number>;
|
||||
@@ -111,9 +110,11 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
|
||||
this.isCodeOfConductAccepted = response.data;
|
||||
} catch (error) {
|
||||
const message = `Failed to check if code of conduct was accepted: ${error}`;
|
||||
Logger.logError(message, "PublishNotebookPaneAdapter/isCodeOfConductAccepted");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(
|
||||
error,
|
||||
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
||||
"Failed to check if code of conduct was accepted"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.isCodeOfConductAccepted = true;
|
||||
@@ -170,12 +171,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formError = `Failed to publish ${this.name} to gallery`;
|
||||
this.formErrorDetail = `${error}`;
|
||||
|
||||
const message = `${this.formError}: ${this.formErrorDetail}`;
|
||||
Logger.logError(message, "PublishNotebookPaneAdapter/submit");
|
||||
NotificationConsoleUtils.logConsoleError(message);
|
||||
this.formErrorDetail = `${errorMessage}`;
|
||||
handleError(errorMessage, "PublishNotebookPaneAdapter/submit", this.formError);
|
||||
return;
|
||||
} finally {
|
||||
clearPublishingMessage();
|
||||
@@ -189,10 +188,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
private createFormErrorForLargeImageSelection = (formError: string, formErrorDetail: string, area: string): void => {
|
||||
this.formError = formError;
|
||||
this.formErrorDetail = formErrorDetail;
|
||||
|
||||
const message = `${this.formError}: ${this.formErrorDetail}`;
|
||||
Logger.logError(message, area);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
||||
handleError(formErrorDetail, area, formError);
|
||||
this.triggerRender();
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export class RenewAdHocAccessPane extends ContextualPaneBase {
|
||||
public accessKey: ko.Observable<string>;
|
||||
@@ -82,7 +83,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
|
||||
this.container
|
||||
.renewShareAccess(this.accessKey())
|
||||
.fail((error: any) => {
|
||||
const errorMessage: string = error.message;
|
||||
const errorMessage: string = getErrorMessage(error);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import QueryTab from "../Tabs/QueryTab";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export class SaveQueryPane extends ContextualPaneBase {
|
||||
public queryName: ko.Observable<string>;
|
||||
@@ -87,18 +88,18 @@ export class SaveQueryPane extends ContextualPaneBase {
|
||||
},
|
||||
(error: any) => {
|
||||
this.isExecuting(false);
|
||||
if (typeof error != "string") {
|
||||
error = error.message;
|
||||
}
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formErrors("Failed to save query");
|
||||
this.formErrorsDetails(`Failed to save query: ${error}`);
|
||||
this.formErrorsDetails(`Failed to save query: ${errorMessage}`);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.SaveQuery,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: this.title()
|
||||
paneTitle: this.title(),
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -132,18 +133,21 @@ export class SaveQueryPane extends ContextualPaneBase {
|
||||
startKey
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.SetupSavedQueries,
|
||||
{
|
||||
databaseAccountName: this.container && this.container.databaseAccount().name,
|
||||
defaultExperience: this.container && this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: this.title()
|
||||
paneTitle: this.title(),
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
this.formErrors("Failed to setup a container for saved queries");
|
||||
this.formErrors(`Failed to setup a container for saved queries: ${error.message}`);
|
||||
this.formErrorsDetails(`Failed to setup a container for saved queries: ${errorMessage}`);
|
||||
} finally {
|
||||
this.isExecuting(false);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as ko from "knockout";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export class SetupNotebooksPane extends ContextualPaneBase {
|
||||
private description: ko.Observable<string>;
|
||||
@@ -85,7 +86,7 @@ export class SetupNotebooksPane extends ContextualPaneBase {
|
||||
"Successfully created a default notebook workspace for the account"
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = typeof error == "string" ? error : error.message;
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.CreateNotebookWorkspace,
|
||||
{
|
||||
@@ -93,7 +94,8 @@ export class SetupNotebooksPane extends ContextualPaneBase {
|
||||
defaultExperience: this.container && this.container.defaultExperience(),
|
||||
dataExplorerArea: Areas.ContextualPane,
|
||||
paneTitle: this.title(),
|
||||
error: errorMessage
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user