mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-24 11:21:23 +00:00
Compare commits
12 Commits
remove-rup
...
remove-ru-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627c346559 | ||
|
|
415ebc505b | ||
|
|
a133134b8b | ||
|
|
79dec6a8a8 | ||
|
|
53a8cea95e | ||
|
|
5f1f7a8266 | ||
|
|
a009a8ba5f | ||
|
|
3e782527d0 | ||
|
|
e6ca1d25c9 | ||
|
|
473f722dcc | ||
|
|
5741802c25 | ||
|
|
e2e58f73b1 |
@@ -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,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();
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as HeadersUtility from "../HeadersUtility";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ContainerDefinition, Resource } from "@azure/cosmos";
|
||||
import { HttpHeaders } from "../Constants";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
interface ResourceWithStatistics {
|
||||
statistics: DataModels.Statistic[];
|
||||
}
|
||||
|
||||
export const readCollectionQuotaInfo = async (
|
||||
collection: ViewModels.Collection
|
||||
): Promise<DataModels.CollectionQuotaInfo> => {
|
||||
const clearMessage = logConsoleProgress(`Querying containers for database ${collection.id}`);
|
||||
const options: RequestOptions = {};
|
||||
options.populateQuotaInfo = true;
|
||||
options.initialHeaders = options.initialHeaders || {};
|
||||
options.initialHeaders[HttpHeaders.populatePartitionStatistics] = true;
|
||||
|
||||
try {
|
||||
const response = await client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.read(options);
|
||||
const quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
|
||||
const resource = response.resource as ContainerDefinition & Resource & ResourceWithStatistics;
|
||||
quota["usageSizeInKB"] = resource.statistics.reduce(
|
||||
(previousValue: number, currentValue: DataModels.Statistic) => previousValue + currentValue.sizeInKB,
|
||||
0
|
||||
);
|
||||
quota["numPartitions"] = resource.statistics.length;
|
||||
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
|
||||
|
||||
return quota;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
|
||||
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,25 +0,0 @@
|
||||
import { updateOfferThroughputBeyondLimit } from "./updateOfferThroughputBeyondLimit";
|
||||
|
||||
describe("updateOfferThroughputBeyondLimit", () => {
|
||||
it("should call fetch", async () => {
|
||||
window.fetch = jest.fn(() => {
|
||||
return {
|
||||
ok: true
|
||||
};
|
||||
});
|
||||
window.dataExplorer = {
|
||||
logConsoleData: jest.fn(),
|
||||
deleteInProgressConsoleDataWithId: jest.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any;
|
||||
await updateOfferThroughputBeyondLimit({
|
||||
subscriptionId: "foo",
|
||||
resourceGroup: "foo",
|
||||
databaseAccountName: "foo",
|
||||
databaseName: "foo",
|
||||
throughput: 1000000000,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
});
|
||||
expect(window.fetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Platform, configContext } from "../../ConfigContext";
|
||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
||||
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { HttpHeaders } from "../Constants";
|
||||
|
||||
interface UpdateOfferThroughputRequest {
|
||||
subscriptionId: string;
|
||||
resourceGroup: string;
|
||||
databaseAccountName: string;
|
||||
databaseName: string;
|
||||
collectionName?: string;
|
||||
throughput: number;
|
||||
offerIsRUPerMinuteThroughputEnabled: boolean;
|
||||
offerAutopilotSettings?: AutoPilotOfferSettings;
|
||||
}
|
||||
|
||||
export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThroughputRequest): Promise<void> {
|
||||
if (configContext.platform !== Platform.Portal) {
|
||||
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
|
||||
}
|
||||
|
||||
const resourceDescriptionInfo = request.collectionName
|
||||
? `database ${request.databaseName} and container ${request.collectionName}`
|
||||
: `database ${request.databaseName}`;
|
||||
|
||||
const clearMessage = logConsoleProgress(
|
||||
`Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
|
||||
);
|
||||
|
||||
const url = `${configContext.BACKEND_ENDPOINT}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(request),
|
||||
headers: { [authorizationHeader.header]: authorizationHeader.token, [HttpHeaders.contentType]: "application/json" }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
logConsoleInfo(
|
||||
`Successfully requested an increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
|
||||
);
|
||||
clearMessage();
|
||||
return undefined;
|
||||
}
|
||||
const error = await response.json();
|
||||
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
|
||||
clearMessage();
|
||||
throw new Error(error.message);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -191,18 +191,6 @@ export interface OfferWithHeaders extends Offer {
|
||||
headers: any;
|
||||
}
|
||||
|
||||
export interface CollectionQuotaInfo {
|
||||
storedProcedures: number;
|
||||
triggers: number;
|
||||
functions: number;
|
||||
documentsSize: number;
|
||||
collectionSize: number;
|
||||
documentsCount: number;
|
||||
usageSizeInKB: number;
|
||||
numPartitions: number;
|
||||
uniqueKeyPolicy?: UniqueKeyPolicy; // TODO: This should ideally not be a part of the collection quota. Remove after refactoring. (#119617)
|
||||
}
|
||||
|
||||
export interface OfferThroughputInfo {
|
||||
minimumRUForCollection: number;
|
||||
numPhysicalPartitions: number;
|
||||
@@ -216,18 +204,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;
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface LogEntry {
|
||||
/**
|
||||
* The message code.
|
||||
*/
|
||||
code?: number;
|
||||
code?: number | string;
|
||||
/**
|
||||
* Any additional data to be logged.
|
||||
*/
|
||||
|
||||
@@ -117,7 +117,6 @@ export interface Collection extends CollectionBase {
|
||||
analyticalStorageTtl: ko.Observable<number>;
|
||||
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||
quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
||||
offer: ko.Observable<DataModels.Offer>;
|
||||
conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
||||
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
||||
|
||||
@@ -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", () => ({
|
||||
|
||||
@@ -1,55 +1,49 @@
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as SharedConstants from "../../../Shared/Constants";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import DiscardIcon from "../../../../images/discard.svg";
|
||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||
import { traceStart, traceFailure, traceSuccess, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Explorer from "../../Explorer";
|
||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||
import Explorer from "../../Explorer";
|
||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||
import { throughputUnit } from "./SettingsRenderUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||
import {
|
||||
MongoIndexingPolicyComponent,
|
||||
MongoIndexingPolicyComponentProps
|
||||
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||
import {
|
||||
getMaxRUs,
|
||||
hasDatabaseSharedThroughput,
|
||||
GeospatialConfigType,
|
||||
TtlType,
|
||||
ChangeFeedPolicyState,
|
||||
SettingsV2TabTypes,
|
||||
getTabTitle,
|
||||
isDirty,
|
||||
AddMongoIndexProps,
|
||||
MongoIndexTypes,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure,
|
||||
getMongoNotification
|
||||
} from "./SettingsUtils";
|
||||
import "./SettingsComponent.less";
|
||||
import {
|
||||
ConflictResolutionComponent,
|
||||
ConflictResolutionComponentProps
|
||||
} from "./SettingsSubComponents/ConflictResolutionComponent";
|
||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
|
||||
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
||||
import "./SettingsComponent.less";
|
||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
getMongoDBCollectionIndexTransformationProgress,
|
||||
readMongoDBCollectionThroughRP
|
||||
} from "../../../Common/dataAccess/readMongoDBCollection";
|
||||
MongoIndexingPolicyComponent,
|
||||
MongoIndexingPolicyComponentProps
|
||||
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
|
||||
import {
|
||||
AddMongoIndexProps,
|
||||
ChangeFeedPolicyState,
|
||||
GeospatialConfigType,
|
||||
getMongoNotification,
|
||||
getTabTitle,
|
||||
hasDatabaseSharedThroughput,
|
||||
isDirty,
|
||||
MongoIndexTypes,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure,
|
||||
SettingsV2TabTypes,
|
||||
TtlType
|
||||
} from "./SettingsUtils";
|
||||
|
||||
interface SettingsV2TabInfo {
|
||||
tab: SettingsV2TabTypes;
|
||||
@@ -211,6 +205,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.refreshIndexTransformationProgress();
|
||||
this.loadMongoIndexes();
|
||||
this.setAutoPilotStates();
|
||||
this.setBaseline();
|
||||
@@ -232,8 +227,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.container.isEnableMongoCapabilityPresent() &&
|
||||
this.container.databaseAccount()
|
||||
) {
|
||||
await this.refreshIndexTransformationProgress();
|
||||
|
||||
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
@@ -248,10 +241,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 +341,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 +377,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,
|
||||
@@ -437,7 +433,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: error.message
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -448,7 +445,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
if (this.state.isScaleSaveable) {
|
||||
const newThroughput = this.state.throughput;
|
||||
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
||||
const originalThroughputValue: number = this.state.throughput;
|
||||
|
||||
if (newOffer.content) {
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
@@ -486,62 +482,33 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
getMaxRUs(this.collection, this.container) <=
|
||||
SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
this.container
|
||||
) {
|
||||
const requestPayload = {
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
databaseAccountName: userContext.databaseAccount.name,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
};
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||
databaseId: this.collection.databaseId,
|
||||
collectionId: this.collection.id(),
|
||||
currentOffer: this.collection.offer(),
|
||||
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
||||
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
|
||||
};
|
||||
if (this.hasProvisioningTypeChanged()) {
|
||||
if (this.state.isAutoPilotSelected) {
|
||||
updateOfferParams.migrateToAutoPilot = true;
|
||||
} else {
|
||||
updateOfferParams.migrateToManual = true;
|
||||
}
|
||||
}
|
||||
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||
this.collection.offer(updatedOffer);
|
||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||
if (this.state.isAutoPilotSelected) {
|
||||
this.setState({
|
||||
isScaleSaveable: false,
|
||||
isScaleDiscardable: false,
|
||||
throughput: originalThroughputValue,
|
||||
throughputBaseline: originalThroughputValue,
|
||||
initialNotification: {
|
||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||
} as DataModels.Notification
|
||||
autoPilotThroughput: updatedOffer.content.offerAutopilotSettings.maxThroughput,
|
||||
autoPilotThroughputBaseline: updatedOffer.content.offerAutopilotSettings.maxThroughput
|
||||
});
|
||||
} else {
|
||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||
databaseId: this.collection.databaseId,
|
||||
collectionId: this.collection.id(),
|
||||
currentOffer: this.collection.offer(),
|
||||
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
||||
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
|
||||
};
|
||||
if (this.hasProvisioningTypeChanged()) {
|
||||
if (this.state.isAutoPilotSelected) {
|
||||
updateOfferParams.migrateToAutoPilot = true;
|
||||
} else {
|
||||
updateOfferParams.migrateToManual = true;
|
||||
}
|
||||
}
|
||||
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||
this.collection.offer(updatedOffer);
|
||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||
if (this.state.isAutoPilotSelected) {
|
||||
this.setState({
|
||||
autoPilotThroughput: updatedOffer.content.offerAutopilotSettings.maxThroughput,
|
||||
autoPilotThroughputBaseline: updatedOffer.content.offerAutopilotSettings.maxThroughput
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
throughput: updatedOffer.content.offerThroughput,
|
||||
throughputBaseline: updatedOffer.content.offerThroughput
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
throughput: updatedOffer.content.offerThroughput,
|
||||
throughputBaseline: updatedOffer.content.offerThroughput
|
||||
});
|
||||
}
|
||||
}
|
||||
this.container.isRefreshingExplorer(false);
|
||||
@@ -559,10 +526,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,
|
||||
{
|
||||
@@ -572,7 +539,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: reason.message
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -945,6 +913,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
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { shallow } from "enzyme";
|
||||
import ko from "knockout";
|
||||
import React from "react";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||
import { container, collection } from "../TestUtils";
|
||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||
import Explorer from "../../../Explorer";
|
||||
import * as Constants from "../../../../Common/Constants";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import Explorer from "../../../Explorer";
|
||||
import { throughputUnit } from "../SettingsRenderUtils";
|
||||
import * as SharedConstants from "../../../../Shared/Constants";
|
||||
import ko from "knockout";
|
||||
import { collection, container } from "../TestUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||
|
||||
describe("ScaleComponent", () => {
|
||||
const nonNationalCloudContainer = new Explorer();
|
||||
@@ -48,9 +47,7 @@ describe("ScaleComponent", () => {
|
||||
let wrapper = shallow(<ScaleComponent {...baseProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
|
||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
|
||||
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput);
|
||||
|
||||
const newCollection = { ...collection };
|
||||
const maxThroughput = 5000;
|
||||
@@ -109,11 +106,6 @@ describe("ScaleComponent", () => {
|
||||
expect(scaleComponent.isAutoScaleEnabled()).toEqual(true);
|
||||
});
|
||||
|
||||
it("getMaxRUThroughputInputLimit", () => {
|
||||
const scaleComponent = new ScaleComponent(baseProps);
|
||||
expect(scaleComponent.getMaxRUThroughputInputLimit()).toEqual(40000);
|
||||
});
|
||||
|
||||
it("getThroughputTitle", () => {
|
||||
let scaleComponent = new ScaleComponent(baseProps);
|
||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||
@@ -126,26 +118,4 @@ describe("ScaleComponent", () => {
|
||||
scaleComponent = new ScaleComponent(newProps);
|
||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (autoscale)");
|
||||
});
|
||||
|
||||
it("canThroughputExceedMaximumValue", () => {
|
||||
let scaleComponent = new ScaleComponent(baseProps);
|
||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||
|
||||
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
||||
scaleComponent = new ScaleComponent(newProps);
|
||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
||||
});
|
||||
|
||||
it("getThroughputWarningMessage", () => {
|
||||
const throughputBeyondLimit = SharedConstants.CollectionCreation.DefaultCollectionRUs1Million + 1000;
|
||||
const throughputBeyondMaxRus = SharedConstants.CollectionCreation.DefaultCollectionRUs1Million - 1000;
|
||||
|
||||
const newProps = { ...baseProps, container: nonNationalCloudContainer, throughput: throughputBeyondLimit };
|
||||
let scaleComponent = new ScaleComponent(newProps);
|
||||
expect(scaleComponent.getThroughputWarningMessage().props.id).toEqual("updateThroughputBeyondLimitWarningMessage");
|
||||
|
||||
newProps.throughput = throughputBeyondMaxRus;
|
||||
scaleComponent = new ScaleComponent(newProps);
|
||||
expect(scaleComponent.getThroughputWarningMessage().props.id).toEqual("updateThroughputDelayedApplyWarningMessage");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import { Label, MessageBar, MessageBarType, Stack, Text, TextField } from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import * as Constants from "../../../../Common/Constants";
|
||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import { configContext, Platform } from "../../../../ConfigContext";
|
||||
import * as DataModels from "../../../../Contracts/DataModels";
|
||||
import * as SharedConstants from "../../../../Shared/Constants";
|
||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||
import Explorer from "../../../Explorer";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
subComponentStackProps,
|
||||
titleAndInputStackProps,
|
||||
throughputUnit,
|
||||
getThroughputApplyLongDelayMessage,
|
||||
getThroughputApplyShortDelayMessage,
|
||||
updateThroughputBeyondLimitWarningMessage,
|
||||
updateThroughputDelayedApplyWarningMessage
|
||||
subComponentStackProps,
|
||||
throughputUnit,
|
||||
titleAndInputStackProps
|
||||
} from "../SettingsRenderUtils";
|
||||
import { getMaxRUs, getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
|
||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||
import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||
import { configContext, Platform } from "../../../../ConfigContext";
|
||||
import { getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
|
||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||
|
||||
export interface ScaleComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
@@ -75,40 +71,17 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
);
|
||||
};
|
||||
|
||||
public getMaxRUThroughputInputLimit = (): number => {
|
||||
if (configContext.platform === Platform.Hosted && this.props.collection.partitionKey) {
|
||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
||||
}
|
||||
|
||||
return getMaxRUs(this.props.collection, this.props.container);
|
||||
};
|
||||
|
||||
public getThroughputTitle = (): string => {
|
||||
if (this.props.isAutoPilotSelected) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
}
|
||||
|
||||
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
||||
const maxThroughput: string =
|
||||
this.canThroughputExceedMaximumValue() && !this.props.isFixedContainer
|
||||
? "unlimited"
|
||||
: getMaxRUs(this.props.collection, this.props.container).toLocaleString();
|
||||
const maxThroughput: string = !this.props.isFixedContainer ? "unlimited" : "10000";
|
||||
return `Throughput (${minThroughput} - ${maxThroughput} RU/s)`;
|
||||
};
|
||||
|
||||
public canThroughputExceedMaximumValue = (): boolean => {
|
||||
return (
|
||||
!this.props.isFixedContainer &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
!this.props.container.isRunningOnNationalCloud()
|
||||
);
|
||||
};
|
||||
|
||||
public getInitialNotificationElement = (): JSX.Element => {
|
||||
if (this.props.initialNotification) {
|
||||
return this.getLongDelayMessage();
|
||||
}
|
||||
|
||||
const offer = this.props.collection?.offer && this.props.collection.offer();
|
||||
if (
|
||||
offer &&
|
||||
@@ -135,47 +108,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
public getThroughputWarningMessage = (): JSX.Element => {
|
||||
const throughputExceedsBackendLimits: boolean =
|
||||
this.canThroughputExceedMaximumValue() &&
|
||||
getMaxRUs(this.props.collection, this.props.container) <=
|
||||
SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
this.props.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
||||
|
||||
if (throughputExceedsBackendLimits && !!this.props.collection.partitionKey && !this.props.isFixedContainer) {
|
||||
return updateThroughputBeyondLimitWarningMessage;
|
||||
}
|
||||
|
||||
const throughputExceedsMaxValue: boolean =
|
||||
!this.isEmulator && this.props.throughput > getMaxRUs(this.props.collection, this.props.container);
|
||||
|
||||
if (throughputExceedsMaxValue && !!this.props.collection.partitionKey && !this.props.isFixedContainer) {
|
||||
return updateThroughputDelayedApplyWarningMessage;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
public getLongDelayMessage = (): JSX.Element => {
|
||||
const matches: string[] = this.props.initialNotification?.description.match(
|
||||
`Throughput update for (.*) ${throughputUnit}`
|
||||
);
|
||||
|
||||
const throughput = this.props.throughputBaseline;
|
||||
const targetThroughput: number = matches.length > 1 && Number(matches[1]);
|
||||
if (targetThroughput) {
|
||||
return getThroughputApplyLongDelayMessage(
|
||||
this.props.wasAutopilotOriginallySet,
|
||||
throughput,
|
||||
throughputUnit,
|
||||
this.props.collection.databaseId,
|
||||
this.props.collection.id(),
|
||||
targetThroughput
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
|
||||
private getThroughputInputComponent = (): JSX.Element => (
|
||||
<ThroughputInputAutoPilotV3Component
|
||||
databaseAccount={this.props.container.databaseAccount()}
|
||||
@@ -184,9 +116,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
throughputBaseline={this.props.throughputBaseline}
|
||||
onThroughputChange={this.props.onThroughputChange}
|
||||
minimum={getMinRUs(this.props.collection, this.props.container)}
|
||||
maximum={this.getMaxRUThroughputInputLimit()}
|
||||
isEnabled={!hasDatabaseSharedThroughput(this.props.collection)}
|
||||
canExceedMaximumValue={this.canThroughputExceedMaximumValue()}
|
||||
label={this.getThroughputTitle()}
|
||||
isEmulator={this.isEmulator}
|
||||
isFixed={this.props.isFixedContainer}
|
||||
@@ -199,7 +129,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
spendAckChecked={false}
|
||||
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
||||
throughputBaseline: 100,
|
||||
onThroughputChange: undefined,
|
||||
minimum: 10000,
|
||||
maximum: 400,
|
||||
step: 100,
|
||||
isEnabled: true,
|
||||
isEmulator: false,
|
||||
@@ -38,8 +37,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
||||
},
|
||||
onScaleDiscardableChange: () => {
|
||||
return;
|
||||
},
|
||||
getThroughputWarningMessage: () => undefined
|
||||
}
|
||||
};
|
||||
|
||||
it("throughput input visible", () => {
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
import React from "react";
|
||||
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
getToolTipContainer,
|
||||
noLeftPaddingCheckBoxStyle,
|
||||
titleAndInputStackProps,
|
||||
checkBoxAndInputStackProps,
|
||||
getChoiceGroupStyles,
|
||||
messageBarStyles,
|
||||
getEstimatedSpendElement,
|
||||
getEstimatedAutoscaleSpendElement,
|
||||
getAutoPilotV3SpendElement,
|
||||
manualToAutoscaleDisclaimerElement
|
||||
} from "../../SettingsRenderUtils";
|
||||
import {
|
||||
Text,
|
||||
TextField,
|
||||
Checkbox,
|
||||
ChoiceGroup,
|
||||
IChoiceGroupOption,
|
||||
Checkbox,
|
||||
Stack,
|
||||
Label,
|
||||
Link,
|
||||
MessageBar,
|
||||
MessageBarType
|
||||
MessageBarType,
|
||||
Stack,
|
||||
Text,
|
||||
TextField
|
||||
} from "office-ui-fabric-react";
|
||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||
import * as SharedConstants from "../../../../../Shared/Constants";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../../../Contracts/DataModels";
|
||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||
import {
|
||||
checkBoxAndInputStackProps,
|
||||
getAutoPilotV3SpendElement,
|
||||
getChoiceGroupStyles,
|
||||
getEstimatedAutoscaleSpendElement,
|
||||
getEstimatedSpendElement,
|
||||
getTextFieldStyles,
|
||||
getToolTipContainer,
|
||||
manualToAutoscaleDisclaimerElement,
|
||||
messageBarStyles,
|
||||
noLeftPaddingCheckBoxStyle,
|
||||
titleAndInputStackProps
|
||||
} from "../../SettingsRenderUtils";
|
||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||
|
||||
export interface ThroughputInputAutoPilotV3Props {
|
||||
databaseAccount: DataModels.DatabaseAccount;
|
||||
@@ -38,7 +36,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
||||
throughputBaseline: number;
|
||||
onThroughputChange: (newThroughput: number) => void;
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
step?: number;
|
||||
isEnabled?: boolean;
|
||||
spendAckChecked?: boolean;
|
||||
@@ -59,7 +56,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
||||
onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
|
||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||
getThroughputWarningMessage: () => JSX.Element;
|
||||
}
|
||||
|
||||
interface ThroughputInputAutoPilotV3State {
|
||||
@@ -119,13 +115,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
if (isDirty(this.props.throughput, this.props.throughputBaseline)) {
|
||||
isDiscardable = true;
|
||||
isSaveable = true;
|
||||
if (
|
||||
!this.props.throughput ||
|
||||
this.props.throughput < this.props.minimum ||
|
||||
(this.props.throughput > this.props.maximum && (this.props.isEmulator || this.props.isFixed)) ||
|
||||
(this.props.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
!this.props.canExceedMaximumValue)
|
||||
) {
|
||||
if (!this.props.throughput || this.props.throughput < this.props.minimum) {
|
||||
isSaveable = false;
|
||||
}
|
||||
}
|
||||
@@ -141,8 +131,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
};
|
||||
|
||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
|
||||
this.autoPilotInputMaxValue = this.props.isFixed ? this.props.maximum : Int32.Max;
|
||||
this.throughputInputMaxValue = Number.MAX_SAFE_INTEGER;
|
||||
this.autoPilotInputMaxValue = Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
public hasProvisioningTypeChanged = (): boolean =>
|
||||
@@ -306,12 +296,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
onChange={this.onThroughputChange}
|
||||
/>
|
||||
|
||||
{this.props.getThroughputWarningMessage() && (
|
||||
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
||||
{this.props.getThroughputWarningMessage()}
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
{!this.props.isEmulator && this.getRequestUnitsUsageCost()}
|
||||
|
||||
{this.props.spendAckVisible && (
|
||||
|
||||
@@ -8,6 +8,9 @@ exports[`IndexingPolicyComponent renders 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<IndexingPolicyRefreshComponent
|
||||
refreshIndexTransformationProgress={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
tabIndex={0}
|
||||
|
||||
@@ -8,29 +8,6 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledMessageBarBase
|
||||
messageBarType={5}
|
||||
>
|
||||
<Text
|
||||
id="throughputApplyLongDelayMessage"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.
|
||||
<br />
|
||||
Database:
|
||||
test
|
||||
, Container:
|
||||
test
|
||||
|
||||
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
|
||||
</Text>
|
||||
</StyledMessageBarBase>
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
@@ -39,8 +16,6 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
|
||||
}
|
||||
>
|
||||
<ThroughputInputAutoPilotV3Component
|
||||
canExceedMaximumValue={true}
|
||||
getThroughputWarningMessage={[Function]}
|
||||
isAutoPilotSelected={false}
|
||||
isEmulator={false}
|
||||
isEnabled={true}
|
||||
@@ -48,7 +23,6 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
|
||||
label="Throughput (6,000 - unlimited RU/s)"
|
||||
maxAutoPilotThroughput={4000}
|
||||
maxAutoPilotThroughputBaseline={4000}
|
||||
maximum={40000}
|
||||
minimum={6000}
|
||||
onAutoPilotSelected={[Function]}
|
||||
onMaxAutoPilotThroughputChange={[Function]}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { collection, container } from "./TestUtils";
|
||||
import {
|
||||
getMaxRUs,
|
||||
getMinRUs,
|
||||
getMongoIndexType,
|
||||
getMongoNotification,
|
||||
@@ -15,18 +14,14 @@ import {
|
||||
MongoWildcardPlaceHolder,
|
||||
getMongoIndexTypeText,
|
||||
SingleFieldText,
|
||||
WildcardText
|
||||
WildcardText,
|
||||
isIndexTransforming
|
||||
} from "./SettingsUtils";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import ko from "knockout";
|
||||
|
||||
describe("SettingsUtils", () => {
|
||||
it("getMaxRUs", () => {
|
||||
expect(collection.offer().content.collectionThroughputInfo.numPhysicalPartitions).toEqual(4);
|
||||
expect(getMaxRUs(collection, container)).toEqual(40000);
|
||||
});
|
||||
|
||||
it("getMinRUs", () => {
|
||||
expect(collection.offer().content.collectionThroughputInfo.numPhysicalPartitions).toEqual(4);
|
||||
expect(getMinRUs(collection, container)).toEqual(6000);
|
||||
@@ -139,3 +134,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();
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import * as SharedConstants from "../../../Shared/Constants";
|
||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||
|
||||
import Explorer from "../../Explorer";
|
||||
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import Explorer from "../../Explorer";
|
||||
|
||||
const zeroValue = 0;
|
||||
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
|
||||
@@ -71,22 +69,6 @@ export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection):
|
||||
return database?.isDatabaseShared() && !collection.offer();
|
||||
};
|
||||
|
||||
export const getMaxRUs = (collection: ViewModels.Collection, container: Explorer): number => {
|
||||
const isTryCosmosDBSubscription = container?.isTryCosmosDBSubscription() || false;
|
||||
if (isTryCosmosDBSubscription) {
|
||||
return Constants.TryCosmosExperience.maxRU;
|
||||
}
|
||||
|
||||
const numPartitionsFromOffer: number =
|
||||
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
|
||||
|
||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo()?.numPartitions;
|
||||
|
||||
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
|
||||
|
||||
return SharedConstants.CollectionCreation.MaxRUPerPartition * numPartitions;
|
||||
};
|
||||
|
||||
export const getMinRUs = (collection: ViewModels.Collection, container: Explorer): number => {
|
||||
const isTryCosmosDBSubscription = container?.isTryCosmosDBSubscription();
|
||||
if (isTryCosmosDBSubscription) {
|
||||
@@ -105,21 +87,7 @@ export const getMinRUs = (collection: ViewModels.Collection, container: Explorer
|
||||
return collectionThroughputInfo.minimumRUForCollection;
|
||||
}
|
||||
|
||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo()?.numPartitions;
|
||||
|
||||
if (!numPartitions || numPartitions === 1) {
|
||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||
}
|
||||
|
||||
const baseRU = SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||
|
||||
const quotaInKb = collection.quotaInfo().collectionSize;
|
||||
const quotaInGb = PricingUtils.usageInGB(quotaInKb);
|
||||
|
||||
const perPartitionGBQuota: number = Math.max(10, quotaInGb / numPartitions);
|
||||
const baseRUbyPartitions: number = ((numPartitions * perPartitionGBQuota) / 10) * 100;
|
||||
|
||||
return Math.max(baseRU, baseRUbyPartitions);
|
||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||
};
|
||||
|
||||
export const parseConflictResolutionMode = (modeFromBackend: string): DataModels.ConflictResolutionMode => {
|
||||
@@ -250,3 +218,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;
|
||||
|
||||
@@ -18,7 +18,6 @@ export const collection = ({
|
||||
excludedPaths: []
|
||||
}),
|
||||
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
|
||||
quotaInfo: ko.observable<DataModels.CollectionQuotaInfo>({} as DataModels.CollectionQuotaInfo),
|
||||
offer: ko.observable<DataModels.Offer>({
|
||||
content: {
|
||||
offerThroughput: 10000,
|
||||
|
||||
@@ -43,7 +43,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -86,7 +85,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -349,7 +347,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -575,7 +572,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -657,7 +653,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -760,7 +755,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -1301,7 +1295,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperty": "partitionKey",
|
||||
"quotaInfo": [Function],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": Object {},
|
||||
}
|
||||
@@ -1323,7 +1316,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -1366,7 +1358,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -1629,7 +1620,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -1855,7 +1845,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -1937,7 +1926,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -2040,7 +2028,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -2616,7 +2603,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -2659,7 +2645,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -2922,7 +2907,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -3148,7 +3132,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -3230,7 +3213,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -3333,7 +3315,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -3874,7 +3855,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"version": 2,
|
||||
},
|
||||
"partitionKeyProperty": "partitionKey",
|
||||
"quotaInfo": [Function],
|
||||
"readSettings": [Function],
|
||||
"uniqueKeyPolicy": Object {},
|
||||
}
|
||||
@@ -3896,7 +3876,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -3939,7 +3918,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -4202,7 +4180,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -4428,7 +4405,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"collectionId": [Function],
|
||||
"collectionIdTitle": [Function],
|
||||
"collectionWithThroughputInShared": [Function],
|
||||
@@ -4510,7 +4486,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"databaseCreateNewShared": [Function],
|
||||
@@ -4613,7 +4588,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canRequestSupport": [Function],
|
||||
"container": [Circular],
|
||||
"costsVisible": [Function],
|
||||
"createTableQuery": [Function],
|
||||
@@ -5189,6 +5163,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]}
|
||||
|
||||
@@ -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
|
||||
@@ -1039,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 {
|
||||
@@ -1112,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,
|
||||
@@ -1141,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);
|
||||
}
|
||||
@@ -1166,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);
|
||||
}
|
||||
)
|
||||
@@ -1440,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}`
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1471,7 +1477,7 @@ export default class Explorer {
|
||||
);
|
||||
}
|
||||
},
|
||||
reason => {
|
||||
error => {
|
||||
if (resourceTreeStartKey != null) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadResourceTree,
|
||||
@@ -1479,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
|
||||
);
|
||||
@@ -1528,7 +1535,7 @@ export default class Explorer {
|
||||
resolve(token);
|
||||
},
|
||||
(error: any) => {
|
||||
Logger.logError(error, "Explorer/getArcadiaToken");
|
||||
Logger.logError(getErrorMessage(error), "Explorer/getArcadiaToken");
|
||||
resolve(undefined);
|
||||
}
|
||||
);
|
||||
@@ -1546,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);
|
||||
@@ -1554,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([]);
|
||||
}
|
||||
}
|
||||
@@ -1590,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 {
|
||||
@@ -1616,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 = {
|
||||
@@ -1643,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;
|
||||
}
|
||||
}
|
||||
@@ -1669,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();
|
||||
}
|
||||
@@ -1685,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);
|
||||
@@ -2052,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
|
||||
);
|
||||
@@ -2218,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);
|
||||
}
|
||||
|
||||
@@ -2400,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);
|
||||
}
|
||||
|
||||
@@ -2449,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);
|
||||
}
|
||||
|
||||
@@ -2471,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);
|
||||
}
|
||||
|
||||
@@ -2482,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);
|
||||
}
|
||||
|
||||
@@ -2514,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();
|
||||
@@ -2564,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);
|
||||
}
|
||||
}
|
||||
@@ -2593,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);
|
||||
}
|
||||
};
|
||||
@@ -2618,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;
|
||||
}
|
||||
};
|
||||
@@ -2637,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);
|
||||
}
|
||||
|
||||
@@ -2687,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);
|
||||
}
|
||||
|
||||
@@ -2721,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
|
||||
);
|
||||
@@ -2773,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));
|
||||
}
|
||||
|
||||
@@ -2960,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -288,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,
|
||||
|
||||
@@ -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>;
|
||||
@@ -61,7 +61,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public maxCollectionsReachedMessage: ko.Observable<string>;
|
||||
public requestUnitsUsageCost: ko.Computed<string>;
|
||||
public dedicatedRequestUnitsUsageCost: ko.Computed<string>;
|
||||
public canRequestSupport: ko.PureComputed<boolean>;
|
||||
public largePartitionKey: ko.Observable<boolean> = ko.observable<boolean>(false);
|
||||
public useIndexingForSharedThroughput: ko.Observable<boolean> = ko.observable<boolean>(true);
|
||||
public costsVisible: ko.PureComputed<boolean>;
|
||||
@@ -314,19 +313,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
});
|
||||
|
||||
this.canRequestSupport = ko.pureComputed(() => {
|
||||
if (
|
||||
configContext.platform !== Platform.Emulator &&
|
||||
!this.container.isTryCosmosDBSubscription() &&
|
||||
configContext.platform !== Platform.Portal
|
||||
) {
|
||||
const offerThroughput: number = this._getThroughput();
|
||||
return offerThroughput <= 100000;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.costsVisible = ko.pureComputed(() => {
|
||||
return configContext.platform !== Platform.Emulator;
|
||||
});
|
||||
@@ -881,10 +867,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 = {
|
||||
@@ -912,7 +897,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);
|
||||
}
|
||||
|
||||
@@ -117,10 +117,6 @@
|
||||
showAutoPilot: !isFreeTierAccount()
|
||||
}">
|
||||
</throughput-input-autopilot-v3>
|
||||
<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 -->
|
||||
<!-- Database provisioned throughput - End -->
|
||||
|
||||
@@ -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>;
|
||||
@@ -31,7 +31,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public throughputSpendAck: ko.Observable<boolean>;
|
||||
public throughputSpendAckVisible: ko.Computed<boolean>;
|
||||
public requestUnitsUsageCost: ko.Computed<string>;
|
||||
public canRequestSupport: ko.PureComputed<boolean>;
|
||||
public costsVisible: ko.PureComputed<boolean>;
|
||||
public upsellMessage: ko.PureComputed<string>;
|
||||
public upsellMessageAriaLabel: ko.PureComputed<string>;
|
||||
@@ -168,19 +167,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
return estimatedSpend;
|
||||
});
|
||||
|
||||
this.canRequestSupport = ko.pureComputed(() => {
|
||||
if (
|
||||
configContext.platform !== Platform.Emulator &&
|
||||
!this.container.isTryCosmosDBSubscription() &&
|
||||
configContext.platform !== Platform.Portal
|
||||
) {
|
||||
const offerThroughput: number = this.throughput();
|
||||
return offerThroughput <= 100000;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.isFreeTierAccount = ko.computed<boolean>(() => {
|
||||
const databaseAccount = this.container && this.container.databaseAccount && this.container.databaseAccount();
|
||||
const isFreeTierAccount =
|
||||
@@ -296,18 +282,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);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -356,11 +346,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(),
|
||||
@@ -375,7 +365,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
@@ -32,7 +33,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
public keyspaceThroughput: ko.Observable<number>;
|
||||
public keyspaceCreateNew: ko.Observable<boolean>;
|
||||
public dedicateTableThroughput: ko.Observable<boolean>;
|
||||
public canRequestSupport: ko.PureComputed<boolean>;
|
||||
public throughputSpendAckText: ko.Observable<string>;
|
||||
public throughputSpendAck: ko.Observable<boolean>;
|
||||
public sharedThroughputSpendAck: ko.Observable<boolean>;
|
||||
@@ -227,15 +227,6 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
return configContext.platform !== Platform.Emulator;
|
||||
});
|
||||
|
||||
this.canRequestSupport = ko.pureComputed(() => {
|
||||
if (configContext.platform !== Platform.Emulator && !this.container.isTryCosmosDBSubscription()) {
|
||||
const offerThroughput: number = this.throughput();
|
||||
return offerThroughput <= 100000;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
|
||||
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
@@ -429,8 +420,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,
|
||||
@@ -456,7 +448,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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -3,11 +3,6 @@ export var Int32 = {
|
||||
Max: 2147483647
|
||||
};
|
||||
|
||||
export var Int64 = {
|
||||
Min: -9223372036854775808,
|
||||
Max: 9223372036854775807
|
||||
};
|
||||
|
||||
var yearMonthDay = "\\d{4}[- ][01]\\d[- ][0-3]\\d";
|
||||
var timeOfDay = "T[0-2]\\d:[0-5]\\d(:[0-5]\\d(\\.\\d+)?)?";
|
||||
var timeZone = "Z|[+-][0-2]\\d:[0-5]\\d";
|
||||
|
||||
@@ -4,8 +4,8 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
const UPLOAD_FILE_SIZE_LIMIT = 2097152;
|
||||
|
||||
@@ -61,9 +61,9 @@ export class UploadItemsPane extends ContextualPaneBase {
|
||||
this._resetFileInput();
|
||||
},
|
||||
(error: any) => {
|
||||
const message = ErrorParserUtility.parse(error);
|
||||
this.formErrors(message[0].message);
|
||||
this.formErrorsDetails(message[0].message);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import * as ko from "knockout";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as React from "react";
|
||||
@@ -9,6 +8,7 @@ import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
|
||||
import { UploadItemsPaneComponent, UploadItemsPaneProps } from "./UploadItemsPaneComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
const UPLOAD_FILE_SIZE_LIMIT = 2097152;
|
||||
|
||||
@@ -107,9 +107,9 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
|
||||
this.selectedFilesTitle = "";
|
||||
},
|
||||
error => {
|
||||
const message = ErrorParserUtility.parse(error);
|
||||
this.formError = message[0].message;
|
||||
this.formErrorDetail = message[0].message;
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.formError = errorMessage;
|
||||
this.formErrorDetail = errorMessage;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
|
||||
@@ -16,9 +16,9 @@ import * as Entities from "../Entities";
|
||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||
import * as TableEntityProcessor from "../TableEntityProcessor";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as ErrorParserUtility from "../../../Common/ErrorParserUtility";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
interface IListTableEntitiesSegmentedResult extends Entities.IListTableEntitiesResult {
|
||||
ExceedMaximumRetries?: boolean;
|
||||
@@ -387,17 +387,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const parsedErrors = ErrorParserUtility.parse(error);
|
||||
var errors = parsedErrors.map((error: DataModels.ErrorDataModel) => {
|
||||
return <ViewModels.QueryError>{
|
||||
message: error.message,
|
||||
start: error.location ? error.location.start : undefined,
|
||||
end: error.location ? error.location.end : undefined,
|
||||
code: error.code,
|
||||
severity: error.severity
|
||||
};
|
||||
});
|
||||
this.queryErrorMessage(errors[0].message);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
this.queryErrorMessage(errorMessage);
|
||||
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
@@ -408,7 +399,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
||||
defaultExperience: this.queryTablesTab.collection.container.defaultExperience(),
|
||||
dataExplorerArea: Areas.Tab,
|
||||
tabTitle: this.queryTablesTab.tabTitle(),
|
||||
error: error
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
this.queryTablesTab.onLoadStartKey
|
||||
);
|
||||
|
||||
@@ -7,16 +7,14 @@ import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/Notifi
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as Entities from "./Entities";
|
||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import * as TableConstants from "./Constants";
|
||||
import * as TableEntityProcessor from "./TableEntityProcessor";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
import { sendMessage } from "../../Common/MessageHandler";
|
||||
import Explorer from "../Explorer";
|
||||
import { queryDocuments, deleteDocument, updateDocument, createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface CassandraTableKeys {
|
||||
partitionKeys: CassandraTableKey[];
|
||||
@@ -188,14 +186,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve(entity);
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while adding new row to table ${collection.id()}:\n ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "AddRowCassandra", reason.code);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
error => {
|
||||
handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
@@ -267,14 +260,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve(newEntity);
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to update row ${newEntity.RowKey._}: ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "UpdateRowCassandra", reason.code);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
error => {
|
||||
handleError(error, "UpdateRowCassandra", `Failed to update row ${newEntity.RowKey._}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
@@ -332,16 +320,11 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
ContinuationToken: data.paginationToken
|
||||
});
|
||||
},
|
||||
reason => {
|
||||
(error: any) => {
|
||||
if (shouldNotify) {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to query rows for table ${collection.id()}: ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "QueryDocumentsCassandra", reason.status);
|
||||
this._checkForbiddenError(reason);
|
||||
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
||||
}
|
||||
deferred.reject(reason);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.done(() => {
|
||||
@@ -379,13 +362,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
`Successfully deleted row ${currEntityToDelete.RowKey._}`
|
||||
);
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while deleting row ${currEntityToDelete.RowKey._}:\n ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "DeleteRowCassandra", reason.code);
|
||||
this._checkForbiddenError(reason);
|
||||
error => {
|
||||
handleError(error, "DeleteRowCassandra", `Error while deleting row ${currEntityToDelete.RowKey._}`);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
@@ -420,14 +398,13 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while creating a keyspace with query ${createKeyspaceQuery}:\n ${JSON.stringify(reason)}`
|
||||
error => {
|
||||
handleError(
|
||||
error,
|
||||
"CreateKeyspaceCassandra",
|
||||
`Error while creating a keyspace with query ${createKeyspaceQuery}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "CreateKeyspaceCassandra", reason.code);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
@@ -467,14 +444,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while creating a table with query ${createTableQuery}:\n ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "CreateTableCassandra", reason.code);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
error => {
|
||||
handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
@@ -508,14 +480,13 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while deleting resource with query ${deleteQuery}:\n ${JSON.stringify(reason)}`
|
||||
error => {
|
||||
handleError(
|
||||
error,
|
||||
"DeleteKeyspaceOrTableCassandra",
|
||||
`Error while deleting resource with query ${deleteQuery}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "DeleteKeyspaceOrTableCassandra", reason.code);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
@@ -563,14 +534,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve(data);
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error fetching keys for table ${collection.id()}:\n ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "FetchKeysCassandra", reason.status);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
(error: any) => {
|
||||
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.done(() => {
|
||||
@@ -618,14 +584,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
);
|
||||
deferred.resolve(data.columns);
|
||||
},
|
||||
reason => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error fetching schema for table ${collection.id()}:\n ${JSON.stringify(reason)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(reason), "FetchSchemaCassandra", reason.status);
|
||||
this._checkForbiddenError(reason);
|
||||
deferred.reject(reason);
|
||||
(error: any) => {
|
||||
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.done(() => {
|
||||
@@ -712,13 +673,4 @@ export class CassandraAPIDataClient extends TableDataClient {
|
||||
|
||||
displayTokenRenewalPromptForStatus(xhrObj.status);
|
||||
};
|
||||
|
||||
private _checkForbiddenError(reason: any) {
|
||||
if (reason && reason.code === Constants.HttpStatusCodes.Forbidden) {
|
||||
sendMessage({
|
||||
type: MessageTypes.ForbiddenError,
|
||||
reason: typeof reason === "string" ? "reason" : JSON.stringify(reason)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user