mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-06 19:19:56 +00:00
Compare commits
9 Commits
jbunster/r
...
jbunster/n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6adfea03ab | ||
|
|
94ff6b3e81 | ||
|
|
b4219e2994 | ||
|
|
294270b6aa | ||
|
|
e4bab1de4b | ||
|
|
703ceacd3f | ||
|
|
734df3dd18 | ||
|
|
1e19f02fd7 | ||
|
|
24b5b754ca |
@@ -42,6 +42,13 @@ module.exports = {
|
||||
"no-null/no-null": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||
eqeqeq: "error"
|
||||
eqeqeq: "error",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
|
||||
message: "Do not use JSON.stringify(error). It will print '{}'"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3023,3 +3023,7 @@ settings-pane {
|
||||
.infoBoxContent a {
|
||||
color: @AccentMediumHigh
|
||||
}
|
||||
|
||||
.collapsibleSection :hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
110
package-lock.json
generated
110
package-lock.json
generated
@@ -76,7 +76,6 @@
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
||||
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@babel/generator": "^7.9.0",
|
||||
@@ -99,8 +98,7 @@
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"dev": true
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7203,11 +7201,10 @@
|
||||
}
|
||||
},
|
||||
"create-react-class": {
|
||||
"version": "15.6.3",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
|
||||
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
|
||||
"version": "15.7.0",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||
"requires": {
|
||||
"fbjs": "^0.8.9",
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
@@ -8504,24 +8501,6 @@
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"requires": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
@@ -9667,27 +9646,6 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"fbjs": {
|
||||
"version": "0.8.17",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
|
||||
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
|
||||
"requires": {
|
||||
"core-js": "^1.0.0",
|
||||
"isomorphic-fetch": "^2.1.1",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
@@ -11888,26 +11846,6 @@
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
||||
"requires": {
|
||||
"node-fetch": "^1.0.1",
|
||||
"whatwg-fetch": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||
"requires": {
|
||||
"encoding": "^0.1.11",
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
@@ -13406,36 +13344,6 @@
|
||||
"micromatch": "^3.1.10",
|
||||
"pretty-format": "^24.9.0",
|
||||
"realpath-native": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": {
|
||||
"version": "7.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
|
||||
"integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/generator": "^7.11.6",
|
||||
"@babel/helper-module-transforms": "^7.11.0",
|
||||
"@babel/helpers": "^7.10.4",
|
||||
"@babel/parser": "^7.11.5",
|
||||
"@babel/template": "^7.10.4",
|
||||
"@babel/traverse": "^7.11.5",
|
||||
"@babel/types": "^7.11.5",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.1",
|
||||
"json5": "^2.1.2",
|
||||
"lodash": "^4.17.19",
|
||||
"resolve": "^1.3.2",
|
||||
"semver": "^5.4.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-dev-server": {
|
||||
@@ -16619,6 +16527,8 @@
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
@@ -18246,7 +18156,8 @@
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||
"dev": true
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
@@ -20006,11 +19917,6 @@
|
||||
"free-style": "3.1.0"
|
||||
}
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.22",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||
|
||||
@@ -133,6 +133,7 @@ export class Features {
|
||||
// flight names returned from the portal are always lowercase
|
||||
export class Flights {
|
||||
public static readonly SettingsV2 = "settingsv2";
|
||||
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
||||
const result = JSON.parse(await response.json());
|
||||
return result;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`);
|
||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${error.message}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Resource
|
||||
} from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Q from "q";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
@@ -39,18 +38,17 @@ export function getCommonQueryOptions(options: FeedOptions): any {
|
||||
return options;
|
||||
}
|
||||
|
||||
export function queryDocuments(
|
||||
export async function queryDocuments(
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
query: string,
|
||||
options: any
|
||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||
): Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||
options = getCommonQueryOptions(options);
|
||||
const documentsIterator = client()
|
||||
return client()
|
||||
.database(databaseId)
|
||||
.container(containerId)
|
||||
.items.query(query, options);
|
||||
return Q(documentsIterator);
|
||||
}
|
||||
|
||||
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
||||
@@ -76,17 +74,15 @@ export function updateDocument(
|
||||
collection: ViewModels.CollectionBase,
|
||||
documentId: DocumentId,
|
||||
newDocument: any
|
||||
): Q.Promise<any> {
|
||||
): Promise<any> {
|
||||
const partitionKey = documentId.partitionKeyValue;
|
||||
|
||||
return Q(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.replace(newDocument)
|
||||
.then(response => response.resource)
|
||||
);
|
||||
return client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.replace(newDocument)
|
||||
.then(response => response.resource);
|
||||
}
|
||||
|
||||
export function executeStoredProcedure(
|
||||
@@ -94,105 +90,89 @@ export function executeStoredProcedure(
|
||||
storedProcedure: StoredProcedure,
|
||||
partitionKeyValue: any,
|
||||
params: any[]
|
||||
): Q.Promise<any> {
|
||||
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
||||
const deferred = Q.defer<any>();
|
||||
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.scripts.storedProcedure(storedProcedure.id())
|
||||
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
||||
.then(response =>
|
||||
deferred.resolve({
|
||||
): Promise<any> {
|
||||
return Promise.race([
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.scripts.storedProcedure(storedProcedure.id())
|
||||
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
||||
.then(response => ({
|
||||
result: response.resource,
|
||||
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
||||
})
|
||||
})),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(`Request timed out while executing stored procedure ${storedProcedure.id()}`),
|
||||
Constants.ClientDefaults.requestTimeoutMs
|
||||
)
|
||||
)
|
||||
.catch(error => deferred.reject(error));
|
||||
|
||||
return deferred.promise.timeout(
|
||||
Constants.ClientDefaults.requestTimeoutMs,
|
||||
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||
return Q(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.items.create(newDocument)
|
||||
.then(response => response.resource)
|
||||
);
|
||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> {
|
||||
return client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.items.create(newDocument)
|
||||
.then(response => response.resource);
|
||||
}
|
||||
|
||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||
const partitionKey = documentId.partitionKeyValue;
|
||||
|
||||
return Q(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.read()
|
||||
.then(response => response.resource)
|
||||
);
|
||||
return client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.read()
|
||||
.then(response => response.resource);
|
||||
}
|
||||
|
||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||
const partitionKey = documentId.partitionKeyValue;
|
||||
|
||||
return Q(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.delete()
|
||||
);
|
||||
return client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), partitionKey)
|
||||
.delete();
|
||||
}
|
||||
|
||||
export function deleteConflict(
|
||||
collection: ViewModels.CollectionBase,
|
||||
conflictId: ConflictId,
|
||||
options: any = {}
|
||||
): Q.Promise<any> {
|
||||
): Promise<any> {
|
||||
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
||||
|
||||
return Q(
|
||||
client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.conflict(conflictId.id())
|
||||
.delete(options)
|
||||
);
|
||||
return client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.conflict(conflictId.id())
|
||||
.delete(options);
|
||||
}
|
||||
|
||||
export function refreshCachedOffers(): Q.Promise<void> {
|
||||
export async function refreshCachedOffers(): Promise<void> {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||
} else {
|
||||
return Q();
|
||||
sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
||||
export async function refreshCachedResources(options?: any): Promise<void> {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
||||
} else {
|
||||
return Q();
|
||||
sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
||||
}
|
||||
}
|
||||
|
||||
export function queryConflicts(
|
||||
export async function queryConflicts(
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
query: string,
|
||||
options: any
|
||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||
const documentsIterator = client()
|
||||
): Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||
return client()
|
||||
.database(databaseId)
|
||||
.container(containerId)
|
||||
.conflicts.query(query, options);
|
||||
return Q(documentsIterator);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Q from "q";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "./Constants";
|
||||
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||
import * as Logger from "./Logger";
|
||||
import { handleError } from "./ErrorHandlingUtils";
|
||||
|
||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||
export function queryDocuments(
|
||||
@@ -19,7 +15,7 @@ export function queryDocuments(
|
||||
containerId: string,
|
||||
query: string,
|
||||
options: any
|
||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||
): Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
|
||||
}
|
||||
|
||||
@@ -28,7 +24,7 @@ export function queryConflicts(
|
||||
containerId: string,
|
||||
query: string,
|
||||
options: any
|
||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||
): Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
|
||||
}
|
||||
|
||||
@@ -46,34 +42,26 @@ export function executeStoredProcedure(
|
||||
storedProcedure: StoredProcedure,
|
||||
partitionKeyValue: any,
|
||||
params: any[]
|
||||
): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
|
||||
): Promise<any> {
|
||||
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
||||
return DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
||||
.then(
|
||||
(response: any) => {
|
||||
deferred.resolve(response);
|
||||
logConsoleInfo(
|
||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||
);
|
||||
return response;
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
||||
error
|
||||
)}`
|
||||
handleError(
|
||||
error,
|
||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`,
|
||||
"ExecuteStoredProcedure"
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function queryDocumentsPage(
|
||||
@@ -81,164 +69,114 @@ export function queryDocumentsPage(
|
||||
documentsIterator: MinimalQueryIterator,
|
||||
firstItemIndex: number,
|
||||
options: any
|
||||
): Q.Promise<ViewModels.QueryResults> {
|
||||
var deferred = Q.defer<ViewModels.QueryResults>();
|
||||
): Promise<ViewModels.QueryResults> {
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||
Q(nextPage(documentsIterator, firstItemIndex))
|
||||
return nextPage(documentsIterator, firstItemIndex)
|
||||
.then(
|
||||
(result: ViewModels.QueryResults) => {
|
||||
const itemCount = (result.documents && result.documents.length) || 0;
|
||||
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||
deferred.resolve(result);
|
||||
return result;
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage");
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||
DataAccessUtilityBase.readDocument(collection, documentId)
|
||||
.then(
|
||||
(document: any) => {
|
||||
deferred.resolve(document);
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
return DataAccessUtilityBase.readDocument(collection, documentId)
|
||||
.catch((error: any) => {
|
||||
handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
|
||||
throw error;
|
||||
})
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function updateDocument(
|
||||
collection: ViewModels.CollectionBase,
|
||||
documentId: DocumentId,
|
||||
newDocument: any
|
||||
): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
): Promise<any> {
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
||||
return DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
||||
.then(
|
||||
(updatedDocument: any) => {
|
||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||
deferred.resolve(updatedDocument);
|
||||
return updatedDocument;
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument");
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> {
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||
DataAccessUtilityBase.createDocument(collection, newDocument)
|
||||
return DataAccessUtilityBase.createDocument(collection, newDocument)
|
||||
.then(
|
||||
(savedDocument: any) => {
|
||||
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||
deferred.resolve(savedDocument);
|
||||
return savedDocument;
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(
|
||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument");
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
|
||||
const entityName = getEntityName();
|
||||
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
||||
return DataAccessUtilityBase.deleteDocument(collection, documentId)
|
||||
.then(
|
||||
(response: any) => {
|
||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||
deferred.resolve(response);
|
||||
return response;
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument");
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function deleteConflict(
|
||||
collection: ViewModels.CollectionBase,
|
||||
conflictId: ConflictId,
|
||||
options?: any
|
||||
): Q.Promise<any> {
|
||||
var deferred = Q.defer<any>();
|
||||
|
||||
): Promise<any> {
|
||||
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
||||
return DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
||||
.then(
|
||||
(response: any) => {
|
||||
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||
deferred.resolve(response);
|
||||
return response;
|
||||
},
|
||||
(error: any) => {
|
||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict");
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
clearMessage();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(clearMessage);
|
||||
}
|
||||
|
||||
export function refreshCachedResources(options: any = {}): Q.Promise<void> {
|
||||
export function refreshCachedResources(options: any = {}): Promise<void> {
|
||||
return DataAccessUtilityBase.refreshCachedResources(options);
|
||||
}
|
||||
|
||||
export function refreshCachedOffers(): Q.Promise<void> {
|
||||
export function refreshCachedOffers(): Promise<void> {
|
||||
return DataAccessUtilityBase.refreshCachedOffers();
|
||||
}
|
||||
|
||||
11
src/Common/ErrorHandlingUtils.ts
Normal file
11
src/Common/ErrorHandlingUtils.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { CosmosError, sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "./Logger";
|
||||
import { replaceKnownError } from "./ErrorParserUtility";
|
||||
|
||||
export const handleError = (error: CosmosError, consoleErrorPrefix: string, area: string): void => {
|
||||
const sanitizedErrorMsg = replaceKnownError(error.message);
|
||||
logConsoleError(`${consoleErrorPrefix}:\n ${sanitizedErrorMsg}`);
|
||||
logError(sanitizedErrorMsg, area, error.code);
|
||||
sendNotificationForError(error);
|
||||
};
|
||||
@@ -1,41 +1,41 @@
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
|
||||
const notificationsPath = () => {
|
||||
switch (configContext.platform) {
|
||||
case Platform.Hosted:
|
||||
return "/api/guest/notifications";
|
||||
case Platform.Portal:
|
||||
return "/api/notifications";
|
||||
default:
|
||||
throw new Error(`Unknown platform: ${configContext.platform}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const databaseAccount = userContext.databaseAccount;
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
||||
databaseAccount.name
|
||||
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
return (await response.json()) as DataModels.Notification[];
|
||||
};
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
|
||||
const notificationsPath = () => {
|
||||
switch (configContext.platform) {
|
||||
case Platform.Hosted:
|
||||
return "/api/guest/notifications";
|
||||
case Platform.Portal:
|
||||
return "/api/notifications";
|
||||
default:
|
||||
throw new Error(`Unknown platform: ${configContext.platform}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const databaseAccount = userContext.databaseAccount;
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
||||
databaseAccount.name
|
||||
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
return (await response.json()) as DataModels.Notification[];
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ export class QueriesClient {
|
||||
return Promise.resolve(collection);
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
||||
@@ -136,7 +136,7 @@ export class QueriesClient {
|
||||
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
|
||||
.then(
|
||||
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
|
||||
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
||||
const fetchQueries = (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
||||
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
|
||||
return QueryUtils.queryAllPages(fetchQueries).then(
|
||||
(results: ViewModels.QueryResults) => {
|
||||
@@ -163,7 +163,7 @@ export class QueriesClient {
|
||||
return Promise.resolve(queries);
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to fetch saved queries: ${stringifiedError}`
|
||||
@@ -175,7 +175,7 @@ export class QueriesClient {
|
||||
},
|
||||
(error: any) => {
|
||||
// should never get into this state but we handle this regardless
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to fetch saved queries: ${stringifiedError}`
|
||||
@@ -232,7 +232,7 @@ export class QueriesClient {
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
const stringifiedError: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||
@@ -23,21 +22,20 @@ import {
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createDatabase } from "./createDatabase";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
let collection: DataModels.Collection;
|
||||
const clearMessage = logConsoleProgress(
|
||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||
);
|
||||
try {
|
||||
let collection: DataModels.Collection;
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (params.createNewDatabase) {
|
||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||
@@ -54,18 +52,16 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
||||
} else {
|
||||
collection = await createCollectionWithSDK(params);
|
||||
}
|
||||
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
await refreshCachedResources();
|
||||
return collection;
|
||||
} catch (error) {
|
||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
clearMessage();
|
||||
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
await refreshCachedResources();
|
||||
clearMessage();
|
||||
return collection;
|
||||
};
|
||||
|
||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
|
||||
@@ -24,36 +24,31 @@ import {
|
||||
createUpdateGremlinDatabase,
|
||||
getGremlinDatabase
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
let database: DataModels.Database;
|
||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
||||
try {
|
||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||
throw new Error("Creating database resources is not allowed for tables accounts");
|
||||
}
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
database = await createDatabaseWithARM(params);
|
||||
} else {
|
||||
database = await createDatabaseWithSDK(params);
|
||||
}
|
||||
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
|
||||
? createDatabaseWithARM(params)
|
||||
: createDatabaseWithSDK(params));
|
||||
|
||||
await refreshCachedResources();
|
||||
await refreshCachedOffers();
|
||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||
return database;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
||||
sendNotificationForError(error);
|
||||
clearMessage();
|
||||
handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||
await refreshCachedResources();
|
||||
await refreshCachedOffers();
|
||||
clearMessage();
|
||||
return database;
|
||||
}
|
||||
|
||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import {
|
||||
SqlStoredProcedureCreateUpdateParameters,
|
||||
@@ -9,9 +10,8 @@ import {
|
||||
createUpdateSqlStoredProcedure,
|
||||
getSqlStoredProcedure
|
||||
} 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 createStoredProcedure(
|
||||
@@ -21,7 +21,11 @@ export async function createStoredProcedure(
|
||||
): Promise<StoredProcedureDefinition & Resource> {
|
||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
try {
|
||||
const getResponse = await getSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
@@ -66,9 +70,7 @@ export async function createStoredProcedure(
|
||||
.scripts.storedProcedures.create(storedProcedure);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||
import {
|
||||
SqlTriggerCreateUpdateParameters,
|
||||
@@ -18,7 +19,11 @@ export async function createTrigger(
|
||||
): Promise<TriggerDefinition & Resource> {
|
||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
try {
|
||||
const getResponse = await getSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
@@ -29,7 +34,7 @@ export async function createTrigger(
|
||||
trigger.id
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create trigger failed: trigger with id ${trigger.id} already exists`);
|
||||
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
@@ -61,8 +66,8 @@ export async function createTrigger(
|
||||
.scripts.triggers.create(trigger);
|
||||
return response.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`);
|
||||
logError(error.message, "CreateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
throw error;
|
||||
} finally {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import {
|
||||
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||
@@ -9,9 +10,8 @@ import {
|
||||
createUpdateSqlUserDefinedFunction,
|
||||
getSqlUserDefinedFunction
|
||||
} 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 createUserDefinedFunction(
|
||||
@@ -21,7 +21,11 @@ export async function createUserDefinedFunction(
|
||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
try {
|
||||
const getResponse = await getSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
@@ -66,9 +70,11 @@ export async function createUserDefinedFunction(
|
||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(
|
||||
error,
|
||||
`Error while creating user defined function ${userDefinedFunction.id}`,
|
||||
"CreateUserupdateUserDefinedFunction"
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -5,9 +5,8 @@ import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-0
|
||||
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { client } from "../CosmosClient";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
@@ -23,15 +22,14 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
||||
.container(collectionId)
|
||||
.delete();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||
await refreshCachedResources();
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||
clearMessage();
|
||||
await refreshCachedResources();
|
||||
}
|
||||
|
||||
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
||||
|
||||
@@ -4,12 +4,11 @@ import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/s
|
||||
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { client } from "../CosmosClient";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
|
||||
export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||
@@ -25,15 +24,14 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||
.database(databaseId)
|
||||
.delete();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||
await refreshCachedResources();
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteDatabase", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||
clearMessage();
|
||||
await refreshCachedResources();
|
||||
}
|
||||
|
||||
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { deleteSqlStoredProcedure } 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 deleteStoredProcedure(
|
||||
@@ -13,7 +13,11 @@ export async function deleteStoredProcedure(
|
||||
): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
await deleteSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -30,9 +34,7 @@ export async function deleteStoredProcedure(
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { deleteSqlTrigger } 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 deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
await deleteSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -26,9 +30,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { deleteSqlUserDefinedFunction } 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 deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
await deleteSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -26,9 +30,7 @@ export async function deleteUserDefinedFunction(databaseId: string, collectionId
|
||||
.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
||||
let collection: DataModels.Collection;
|
||||
@@ -14,9 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
|
||||
.read();
|
||||
collection = response.resource as DataModels.Collection;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying container ${collectionId}`, "ReadCollection");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
|
||||
@@ -4,15 +4,14 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
|
||||
import { HttpHeaders } from "../Constants";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { readOffers } from "./readOffers";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export const readCollectionOffer = async (
|
||||
@@ -57,9 +56,7 @@ export const readCollectionOffer = async (
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying offer for collection ${params.collectionId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollectionOffer", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -5,9 +5,8 @@ import { ContainerDefinition, Resource } from "@azure/cosmos";
|
||||
import { HttpHeaders } from "../Constants";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
interface ResourceWithStatistics {
|
||||
statistics: DataModels.Statistic[];
|
||||
@@ -38,9 +37,7 @@ export const readCollectionQuotaInfo = async (
|
||||
|
||||
return quota;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -2,18 +2,16 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||
let collections: DataModels.Collection[];
|
||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||
try {
|
||||
if (
|
||||
@@ -22,22 +20,20 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
collections = await readCollectionsWithARM(databaseId);
|
||||
} else {
|
||||
const sdkResponse = await client()
|
||||
.database(databaseId)
|
||||
.containers.readAll()
|
||||
.fetchAll();
|
||||
collections = sdkResponse.resources as DataModels.Collection[];
|
||||
return await readCollectionsWithARM(databaseId);
|
||||
}
|
||||
|
||||
const sdkResponse = await client()
|
||||
.database(databaseId)
|
||||
.containers.readAll()
|
||||
.fetchAll();
|
||||
return sdkResponse.resources as DataModels.Collection[];
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadCollections", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying containers for database ${databaseId}`, "ReadCollections");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
}
|
||||
clearMessage();
|
||||
return collections;
|
||||
}
|
||||
|
||||
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
||||
|
||||
@@ -8,10 +8,9 @@ import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-
|
||||
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { readOffers } from "./readOffers";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export const readDatabaseOffer = async (
|
||||
@@ -48,9 +47,7 @@ export const readDatabaseOffer = async (
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying offer for database ${params.databaseId}:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying offer for database ${params.databaseId}`, "ReadDatabaseOffer");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -2,13 +2,12 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||
@@ -28,9 +27,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||
databases = sdkResponse.resources as DataModels.Database[];
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadDatabases", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying databases`, "ReadDatabases");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
|
||||
58
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
58
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "../Constants";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { AuthType } from "../../AuthType";
|
||||
|
||||
export async function readMongoDBCollectionThroughRP(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<MongoDBCollectionResource> {
|
||||
if (window.authType !== AuthType.AAD) {
|
||||
return undefined;
|
||||
}
|
||||
let collection: MongoDBCollectionResource;
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const accountName = userContext.databaseAccount.name;
|
||||
|
||||
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||
try {
|
||||
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
collection = response.properties.resource;
|
||||
} catch (error) {
|
||||
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return collection;
|
||||
}
|
||||
|
||||
export async function getMongoDBCollectionIndexTransformationProgress(
|
||||
databaseId: string,
|
||||
collectionId: string
|
||||
): Promise<number> {
|
||||
if (window.authType !== AuthType.AAD) {
|
||||
return undefined;
|
||||
}
|
||||
let indexTransformationPercentage: number;
|
||||
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||
try {
|
||||
const response = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.read({ populateQuotaInfo: true });
|
||||
|
||||
indexTransformationPercentage = parseInt(
|
||||
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
|
||||
throw error;
|
||||
}
|
||||
clearMessage();
|
||||
return indexTransformationPercentage;
|
||||
}
|
||||
@@ -3,10 +3,9 @@ import { ClientDefaults } from "../Constants";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
import { Platform, configContext } from "../../ConfigContext";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { sendCachedDataMessage } from "../MessageHandler";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export const readOffers = async (): Promise<Offer[]> => {
|
||||
@@ -36,9 +35,7 @@ export const readOffers = async (): Promise<Offer[]> => {
|
||||
return [];
|
||||
}
|
||||
|
||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadOffers", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while querying offers`, "ReadOffers");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readStoredProcedures(
|
||||
@@ -13,7 +13,11 @@ export async function readStoredProcedures(
|
||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const rpResponse = await listSqlStoredProcedures(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -31,9 +35,7 @@ export async function readStoredProcedures(
|
||||
.fetchAll();
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||
import { client } from "../CosmosClient";
|
||||
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
|
||||
export async function readTriggers(
|
||||
databaseId: string,
|
||||
@@ -13,7 +13,11 @@ export async function readTriggers(
|
||||
): Promise<(TriggerDefinition & Resource)[]> {
|
||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const rpResponse = await listSqlTriggers(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -31,9 +35,7 @@ export async function readTriggers(
|
||||
.fetchAll();
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to query triggers for container ${collectionId}`, "ReadTriggers");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function readUserDefinedFunctions(
|
||||
@@ -13,7 +13,11 @@ export async function readUserDefinedFunctions(
|
||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const rpResponse = await listSqlUserDefinedFunctions(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -31,9 +35,11 @@ export async function readUserDefinedFunctions(
|
||||
.fetchAll();
|
||||
return response?.resources;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(
|
||||
error,
|
||||
`Failed to query user defined functions for container ${collectionId}`,
|
||||
"ReadUserDefinedFunctions"
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Constants from "../Constants";
|
||||
import { sendMessage } from "../MessageHandler";
|
||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
||||
|
||||
interface CosmosError {
|
||||
export interface CosmosError {
|
||||
code: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { Collection } from "../../Contracts/DataModels";
|
||||
import { ContainerDefinition } from "@azure/cosmos";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import {
|
||||
CreateUpdateOptions,
|
||||
ExtendedResourceProperties,
|
||||
MongoDBCollectionCreateUpdateParameters,
|
||||
MongoDBCollectionResource,
|
||||
SqlContainerCreateUpdateParameters,
|
||||
SqlContainerResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
@@ -23,10 +26,9 @@ import {
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function updateCollection(
|
||||
@@ -51,6 +53,7 @@ export async function updateCollection(
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.replace(newCollection as ContainerDefinition, options);
|
||||
|
||||
collection = sdkResponse.resource as Collection;
|
||||
}
|
||||
|
||||
@@ -58,9 +61,7 @@ export async function updateCollection(
|
||||
await refreshCachedResources();
|
||||
return collection;
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
@@ -80,15 +81,6 @@ async function updateCollectionWithARM(
|
||||
switch (defaultExperience) {
|
||||
case DefaultAccountExperienceType.DocumentDB:
|
||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.MongoDB:
|
||||
return updateMongoDBCollection(
|
||||
databaseId,
|
||||
collectionId,
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
newCollection
|
||||
);
|
||||
case DefaultAccountExperienceType.Cassandra:
|
||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.Graph:
|
||||
@@ -125,26 +117,35 @@ async function updateSqlContainer(
|
||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||
}
|
||||
|
||||
async function updateMongoDBCollection(
|
||||
export async function updateMongoDBCollectionThroughRP(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
newCollection: MongoDBCollectionResource,
|
||||
updateOptions?: CreateUpdateOptions
|
||||
): Promise<MongoDBCollectionResource> {
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const accountName = userContext.databaseAccount.name;
|
||||
|
||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateParams: MongoDBCollectionCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: newCollection,
|
||||
options: updateOptions
|
||||
}
|
||||
};
|
||||
|
||||
const updateResponse = await createUpdateMongoDBCollection(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
databaseId,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
updateParams
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
|
||||
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
|
||||
@@ -6,12 +6,11 @@ import { OfferDefinition } from "@azure/cosmos";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { readCollectionOffer } from "./readCollectionOffer";
|
||||
import { readDatabaseOffer } from "./readDatabaseOffer";
|
||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import {
|
||||
updateSqlDatabaseThroughput,
|
||||
migrateSqlDatabaseToAutoscale,
|
||||
@@ -76,9 +75,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
||||
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||
return updatedOffer;
|
||||
} catch (error) {
|
||||
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import {
|
||||
SqlStoredProcedureCreateUpdateParameters,
|
||||
@@ -9,9 +10,8 @@ import {
|
||||
createUpdateSqlStoredProcedure,
|
||||
getSqlStoredProcedure
|
||||
} 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 updateStoredProcedure(
|
||||
@@ -21,7 +21,11 @@ export async function updateStoredProcedure(
|
||||
): Promise<StoredProcedureDefinition & Resource> {
|
||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const getResponse = await getSqlStoredProcedure(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -60,10 +64,7 @@ export async function updateStoredProcedure(
|
||||
.replace(storedProcedure);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
const errorMessage = error.code === "NotFound" ? `${storedProcedure.id} does not exist.` : JSON.stringify(error);
|
||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${errorMessage}`);
|
||||
logError(errorMessage, "UpdateStoredProcedure", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import {
|
||||
SqlTriggerCreateUpdateParameters,
|
||||
SqlTriggerResource
|
||||
@@ -6,9 +7,8 @@ import {
|
||||
import { TriggerDefinition } from "@azure/cosmos";
|
||||
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 updateTrigger(
|
||||
@@ -18,7 +18,11 @@ export async function updateTrigger(
|
||||
): Promise<TriggerDefinition> {
|
||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const getResponse = await getSqlTrigger(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -57,10 +61,7 @@ export async function updateTrigger(
|
||||
.replace(trigger);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
const errorMessage = error.code === "NotFound" ? `${trigger.id} does not exist.` : JSON.stringify(error);
|
||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${errorMessage}`);
|
||||
logError(errorMessage, "UpdateTrigger", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(error, `Error while updating trigger ${trigger.id}`, "UpdateTrigger");
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import {
|
||||
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||
@@ -9,9 +10,8 @@ import {
|
||||
createUpdateSqlUserDefinedFunction,
|
||||
getSqlUserDefinedFunction
|
||||
} 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 updateUserDefinedFunction(
|
||||
@@ -21,7 +21,11 @@ export async function updateUserDefinedFunction(
|
||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||
) {
|
||||
const getResponse = await getSqlUserDefinedFunction(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
@@ -60,11 +64,11 @@ export async function updateUserDefinedFunction(
|
||||
.replace(userDefinedFunction);
|
||||
return response?.resource;
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error.code === "NotFound" ? `${userDefinedFunction.id} does not exist.` : JSON.stringify(error);
|
||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${errorMessage}`);
|
||||
logError(errorMessage, "UpdateUserupdateUserDefinedFunction", error.code);
|
||||
sendNotificationForError(error);
|
||||
handleError(
|
||||
error,
|
||||
`Error while updating user defined function ${userDefinedFunction.id}`,
|
||||
"UpdateUserupdateUserDefinedFunction"
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
clearMessage();
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TriggerDefinition,
|
||||
UserDefinedFunctionDefinition
|
||||
} from "@azure/cosmos";
|
||||
import Q from "q";
|
||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
@@ -107,7 +106,7 @@ export interface CollectionBase extends TreeNode {
|
||||
|
||||
onDocumentDBDocumentsClick(): void;
|
||||
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
|
||||
expandCollection(): Q.Promise<any>;
|
||||
expandCollection(): Promise<any>;
|
||||
collapseCollection(): void;
|
||||
getDatabase(): Database;
|
||||
}
|
||||
@@ -172,7 +171,7 @@ export interface Collection extends CollectionBase {
|
||||
|
||||
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||
uploadFiles(fileList: FileList): Q.Promise<UploadDetails>;
|
||||
uploadFiles(fileList: FileList): Promise<UploadDetails>;
|
||||
|
||||
getLabel(): string;
|
||||
}
|
||||
@@ -289,6 +288,10 @@ export interface DocumentsTabOptions extends TabOptions {
|
||||
resourceTokenPartitionKey?: string;
|
||||
}
|
||||
|
||||
export interface SettingsTabV2Options extends TabOptions {
|
||||
getPendingNotification: Promise<DataModels.Notification>;
|
||||
}
|
||||
|
||||
export interface ConflictsTabOptions extends TabOptions {
|
||||
partitionKey: DataModels.PartitionKey;
|
||||
conflictIds: ko.ObservableArray<ConflictId>;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./CollapsibleSectionComponent";
|
||||
|
||||
describe("CollapsibleSectionComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: CollapsibleSectionProps = {
|
||||
title: "Sample title"
|
||||
};
|
||||
|
||||
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||
|
||||
export interface CollapsibleSectionProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CollapsibleSectionState {
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
||||
export class CollapsibleSectionComponent extends React.Component<CollapsibleSectionProps, CollapsibleSectionState> {
|
||||
constructor(props: CollapsibleSectionProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isExpanded: true
|
||||
};
|
||||
}
|
||||
|
||||
private toggleCollapsed = (): void => {
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
|
||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
|
||||
<Label>{this.props.title}</Label>
|
||||
</Stack>
|
||||
{this.state.isExpanded && this.props.children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
className="collapsibleSection"
|
||||
horizontal={true}
|
||||
onClick={[Function]}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledIconBase
|
||||
iconName="ChevronDown"
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"paddingTop": 7,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledLabelBase>
|
||||
Sample title
|
||||
</StyledLabelBase>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -57,7 +57,7 @@ class EditorViewModel extends JsonEditorViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
|
||||
protected async getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
|
||||
return ErrorMarkProvider.getErrorMark(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Q from "q";
|
||||
import * as monaco from "monaco-editor";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||
@@ -107,8 +106,8 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
|
||||
protected registerCompletionItemProvider() {}
|
||||
|
||||
// Interface. Will be implemented in children editor view model such as EditorViewModel.
|
||||
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
|
||||
return Q.Promise(() => {});
|
||||
protected getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
protected getEditorLanguage(): string {
|
||||
|
||||
@@ -8,7 +8,10 @@ import * as DataModels from "../../../Contracts/DataModels";
|
||||
import ko from "knockout";
|
||||
import { TtlType, isDirty } from "./SettingsUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||
jest.mock("../../../Common/dataAccess/readMongoDBCollection", () => ({
|
||||
getMongoDBCollectionIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||
}));
|
||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||
updateCollection: jest.fn().mockReturnValue({
|
||||
id: undefined,
|
||||
@@ -18,9 +21,16 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||
changeFeedPolicy: undefined,
|
||||
analyticalStorageTtl: undefined,
|
||||
geospatialConfig: undefined
|
||||
} as DataModels.Collection)
|
||||
} as DataModels.Collection),
|
||||
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
|
||||
id: undefined,
|
||||
shardKey: undefined,
|
||||
indexes: [],
|
||||
analyticalStorageTtl: undefined
|
||||
} as MongoDBCollectionResource)
|
||||
}));
|
||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||
}));
|
||||
@@ -35,7 +45,8 @@ describe("SettingsComponent", () => {
|
||||
node: undefined,
|
||||
hashLocation: "settings",
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: undefined
|
||||
onUpdateTabsButtons: undefined,
|
||||
getPendingNotification: Promise.resolve(undefined)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -188,13 +199,17 @@ describe("SettingsComponent", () => {
|
||||
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
||||
});
|
||||
|
||||
it("save calls updateCollection and updateOffer", async () => {
|
||||
it("save calls updateCollection, updateMongoDBCollectionThroughRP and updateOffer", async () => {
|
||||
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true });
|
||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true, isMongoIndexingPolicySaveable: true });
|
||||
wrapper.update();
|
||||
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
||||
settingsComponentInstance.mongoDBCollectionResource = {
|
||||
id: "id"
|
||||
};
|
||||
await settingsComponentInstance.onSaveClick();
|
||||
expect(updateCollection).toBeCalled();
|
||||
expect(updateMongoDBCollectionThroughRP).toBeCalled();
|
||||
expect(updateOffer).toBeCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -11,13 +11,17 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Explorer from "../../Explorer";
|
||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||
import { throughputUnit } from "./SettingsRenderUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||
import {
|
||||
MongoIndexingPolicyComponent,
|
||||
MongoIndexingPolicyComponentProps
|
||||
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||
import {
|
||||
getMaxRUs,
|
||||
hasDatabaseSharedThroughput,
|
||||
@@ -27,8 +31,11 @@ import {
|
||||
SettingsV2TabTypes,
|
||||
getTabTitle,
|
||||
isDirty,
|
||||
AddMongoIndexProps,
|
||||
MongoIndexTypes,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure
|
||||
parseConflictResolutionProcedure,
|
||||
getMongoNotification
|
||||
} from "./SettingsUtils";
|
||||
import {
|
||||
ConflictResolutionComponent,
|
||||
@@ -38,6 +45,11 @@ import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubCo
|
||||
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
||||
import "./SettingsComponent.less";
|
||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
getMongoDBCollectionIndexTransformationProgress,
|
||||
readMongoDBCollectionThroughRP
|
||||
} from "../../../Common/dataAccess/readMongoDBCollection";
|
||||
|
||||
interface SettingsV2TabInfo {
|
||||
tab: SettingsV2TabTypes;
|
||||
@@ -84,6 +96,13 @@ export interface SettingsComponentState {
|
||||
shouldDiscardIndexingPolicy: boolean;
|
||||
isIndexingPolicyDirty: boolean;
|
||||
|
||||
isMongoIndexingPolicySaveable: boolean;
|
||||
isMongoIndexingPolicyDiscardable: boolean;
|
||||
currentMongoIndexes: MongoIndex[];
|
||||
indexesToDrop: number[];
|
||||
indexesToAdd: AddMongoIndexProps[];
|
||||
indexTransformationProgress: number;
|
||||
|
||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||
conflictResolutionPolicyPath: string;
|
||||
@@ -98,17 +117,17 @@ export interface SettingsComponentState {
|
||||
|
||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||
private static readonly sixMonthsInSeconds = 15768000;
|
||||
private saveSettingsButton: ButtonV2;
|
||||
private discardSettingsChangesButton: ButtonV2;
|
||||
|
||||
public saveSettingsButton: ButtonV2;
|
||||
public discardSettingsChangesButton: ButtonV2;
|
||||
|
||||
public isAnalyticalStorageEnabled: boolean;
|
||||
private isAnalyticalStorageEnabled: boolean;
|
||||
private collection: ViewModels.Collection;
|
||||
private container: Explorer;
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: boolean;
|
||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
constructor(props: SettingsComponentProps) {
|
||||
super(props);
|
||||
@@ -158,6 +177,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
shouldDiscardIndexingPolicy: false,
|
||||
isIndexingPolicyDirty: false,
|
||||
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
currentMongoIndexes: undefined,
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
isMongoIndexingPolicyDiscardable: false,
|
||||
indexTransformationProgress: undefined,
|
||||
|
||||
conflictResolutionPolicyMode: undefined,
|
||||
conflictResolutionPolicyModeBaseline: undefined,
|
||||
conflictResolutionPolicyPath: undefined,
|
||||
@@ -186,6 +212,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadMongoIndexes();
|
||||
this.setAutoPilotStates();
|
||||
this.setBaseline();
|
||||
if (this.props.settingsTab.isActive()) {
|
||||
@@ -199,6 +226,35 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
}
|
||||
|
||||
public loadMongoIndexes = async (): Promise<void> => {
|
||||
if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.databaseAccount()
|
||||
) {
|
||||
await this.refreshIndexTransformationProgress();
|
||||
|
||||
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
);
|
||||
|
||||
if (this.mongoDBCollectionResource) {
|
||||
this.setState({
|
||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public refreshIndexTransformationProgress = async (): Promise<void> => {
|
||||
const currentProgress = await getMongoDBCollectionIndexTransformationProgress(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
);
|
||||
this.setState({ indexTransformationProgress: currentProgress });
|
||||
};
|
||||
|
||||
public isSaveSettingsButtonEnabled = (): boolean => {
|
||||
if (this.isOfferReplacePending()) {
|
||||
return false;
|
||||
@@ -208,7 +264,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.state.isScaleSaveable ||
|
||||
this.state.isSubSettingsSaveable ||
|
||||
this.state.isIndexingPolicyDirty ||
|
||||
this.state.isConflictResolutionDirty
|
||||
this.state.isConflictResolutionDirty ||
|
||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -217,7 +274,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
this.state.isScaleDiscardable ||
|
||||
this.state.isSubSettingsDiscardable ||
|
||||
this.state.isIndexingPolicyDirty ||
|
||||
this.state.isConflictResolutionDirty
|
||||
this.state.isConflictResolutionDirty ||
|
||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -336,6 +394,27 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||
const newMongoCollection: MongoDBCollectionResource = {
|
||||
...this.mongoDBCollectionResource,
|
||||
indexes: newMongoIndexes
|
||||
};
|
||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newMongoCollection
|
||||
);
|
||||
|
||||
await this.refreshIndexTransformationProgress();
|
||||
this.setState({
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.isScaleSaveable) {
|
||||
const newThroughput = this.state.throughput;
|
||||
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
||||
@@ -392,33 +471,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
};
|
||||
try {
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
this.setState({
|
||||
throughput: originalThroughputValue,
|
||||
throughputBaseline: originalThroughputValue,
|
||||
initialNotification: {
|
||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||
} as DataModels.Notification
|
||||
});
|
||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||
} catch (error) {
|
||||
traceFailure(
|
||||
Action.SettingsV2Updated,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.collection?.databaseId,
|
||||
collectionName: this.collection?.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
this.setState({
|
||||
isScaleSaveable: false,
|
||||
isScaleDiscardable: false,
|
||||
throughput: originalThroughputValue,
|
||||
throughputBaseline: originalThroughputValue,
|
||||
initialNotification: {
|
||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||
} as DataModels.Notification
|
||||
});
|
||||
} else {
|
||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||
databaseId: this.collection.databaseId,
|
||||
@@ -497,6 +561,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
||||
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
||||
indexesToAdd: [],
|
||||
indexesToDrop: [],
|
||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
||||
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
||||
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
||||
@@ -511,10 +577,23 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
isSubSettingsSaveable: false,
|
||||
isSubSettingsDiscardable: false,
|
||||
isIndexingPolicyDirty: false,
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
isMongoIndexingPolicyDiscardable: false,
|
||||
isConflictResolutionDirty: false
|
||||
});
|
||||
};
|
||||
|
||||
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||
let finalIndexes: MongoIndex[] = [];
|
||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||
if (!this.state.indexesToDrop.includes(index)) {
|
||||
finalIndexes.push(mongoIndex);
|
||||
}
|
||||
});
|
||||
finalIndexes = finalIndexes.concat(this.state.indexesToAdd.map((m: AddMongoIndexProps) => m.mongoIndex));
|
||||
return finalIndexes;
|
||||
};
|
||||
|
||||
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
||||
this.setState({ isScaleSaveable: isScaleSaveable });
|
||||
|
||||
@@ -544,6 +623,36 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
};
|
||||
|
||||
private onIndexDrop = (index: number): void => this.setState({ indexesToDrop: [...this.state.indexesToDrop, index] });
|
||||
|
||||
private onRevertIndexDrop = (index: number): void => {
|
||||
const indexesToDrop = [...this.state.indexesToDrop];
|
||||
indexesToDrop.splice(index, 1);
|
||||
this.setState({ indexesToDrop });
|
||||
};
|
||||
|
||||
private onRevertIndexAdd = (index: number): void => {
|
||||
const indexesToAdd = [...this.state.indexesToAdd];
|
||||
indexesToAdd.splice(index, 1);
|
||||
this.setState({ indexesToAdd });
|
||||
};
|
||||
|
||||
private onIndexAddOrChange = (index: number, description: string, type: MongoIndexTypes): void => {
|
||||
const newIndexesToAdd = [...this.state.indexesToAdd];
|
||||
const notification = getMongoNotification(description, type);
|
||||
const newMongoIndexWithType: AddMongoIndexProps = {
|
||||
mongoIndex: { key: { keys: [description] } } as MongoIndex,
|
||||
type: type,
|
||||
notification: notification
|
||||
};
|
||||
if (index === newIndexesToAdd.length) {
|
||||
newIndexesToAdd.push(newMongoIndexWithType);
|
||||
} else {
|
||||
newIndexesToAdd[index] = newMongoIndexWithType;
|
||||
}
|
||||
this.setState({ indexesToAdd: newIndexesToAdd });
|
||||
};
|
||||
|
||||
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
||||
this.setState({ conflictResolutionPolicyMode: newMode });
|
||||
|
||||
@@ -582,6 +691,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
||||
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
||||
|
||||
private onMongoIndexingPolicySaveableChange = (isMongoIndexingPolicySaveable: boolean): void =>
|
||||
this.setState({ isMongoIndexingPolicySaveable });
|
||||
|
||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||
|
||||
public getAnalyticalStorageTtl = (): number => {
|
||||
if (this.isAnalyticalStorageEnabled) {
|
||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||
@@ -804,6 +919,20 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
||||
};
|
||||
|
||||
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||
mongoIndexes: this.state.currentMongoIndexes,
|
||||
onIndexDrop: this.onIndexDrop,
|
||||
indexesToDrop: this.state.indexesToDrop,
|
||||
onRevertIndexDrop: this.onRevertIndexDrop,
|
||||
indexesToAdd: this.state.indexesToAdd,
|
||||
onRevertIndexAdd: this.onRevertIndexAdd,
|
||||
onIndexAddOrChange: this.onIndexAddOrChange,
|
||||
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||
onMongoIndexingPolicySaveableChange: this.onMongoIndexingPolicySaveableChange,
|
||||
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange
|
||||
};
|
||||
|
||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
@@ -820,7 +949,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
};
|
||||
|
||||
const tabs: SettingsV2TabInfo[] = [];
|
||||
if (!hasDatabaseSharedThroughput(this.collection)) {
|
||||
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.ScaleTab,
|
||||
content: <ScaleComponent {...scaleComponentProps} />
|
||||
@@ -837,6 +966,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
||||
});
|
||||
} else if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.isEnableMongoCapabilityPresent()
|
||||
) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
|
||||
});
|
||||
}
|
||||
|
||||
if (this.hasConflictResolution()) {
|
||||
|
||||
@@ -4,17 +4,11 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
||||
|
||||
export class SettingsComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
public parameters: ko.Computed<boolean>;
|
||||
|
||||
constructor(private props: SettingsComponentProps) {
|
||||
this.parameters = ko.observable<number>(Date.now());
|
||||
}
|
||||
constructor(private props: SettingsComponentProps) {}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <SettingsComponent {...this.props} />;
|
||||
}
|
||||
|
||||
public triggerRender(): void {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
getToolTipContainer,
|
||||
conflictResolutionCustomToolTip,
|
||||
changeFeedPolicyToolTip,
|
||||
conflictResolutionLwwTooltip
|
||||
conflictResolutionLwwTooltip,
|
||||
mongoIndexingPolicyDisclaimer,
|
||||
mongoIndexingPolicyAADError,
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
} from "./SettingsRenderUtils";
|
||||
|
||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
@@ -45,6 +49,16 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
{conflictResolutionLwwTooltip}
|
||||
{conflictResolutionCustomToolTip}
|
||||
{changeFeedPolicyToolTip}
|
||||
|
||||
{mongoIndexingPolicyDisclaimer}
|
||||
{mongoIndexingPolicyAADError}
|
||||
{mongoIndexTransformationRefreshingMessage}
|
||||
{renderMongoIndexTransformationRefreshMessage(0, () => {
|
||||
return;
|
||||
})}
|
||||
{renderMongoIndexTransformationRefreshMessage(90, () => {
|
||||
return;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,24 @@ import {
|
||||
Link,
|
||||
Text,
|
||||
IMessageBarStyles,
|
||||
ITextStyles
|
||||
ITextStyles,
|
||||
IDetailsRowStyles,
|
||||
IStackStyles,
|
||||
IIconStyles,
|
||||
IDetailsListStyles,
|
||||
IDropdownStyles,
|
||||
ISeparatorStyles,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Stack,
|
||||
Spinner,
|
||||
SpinnerSize
|
||||
} from "office-ui-fabric-react";
|
||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||
|
||||
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||
|
||||
export const spendAckCheckBoxStyle: ICheckboxStyles = {
|
||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||
label: {
|
||||
margin: 0,
|
||||
padding: "2 0 2 0"
|
||||
@@ -45,6 +56,20 @@ export const titleAndInputStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 5 }
|
||||
};
|
||||
|
||||
export const mongoWarningStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 5 }
|
||||
};
|
||||
|
||||
export const mongoErrorMessageStyles: Partial<IMessageBarStyles> = { root: { marginLeft: 10 } };
|
||||
|
||||
export const createAndAddMongoIndexStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 5 }
|
||||
};
|
||||
|
||||
export const addMongoIndexStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 10 }
|
||||
};
|
||||
|
||||
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 10 }
|
||||
};
|
||||
@@ -53,6 +78,54 @@ export const toolTipLabelStackTokens: IStackTokens = {
|
||||
childrenGap: 6
|
||||
};
|
||||
|
||||
export const accordionStackTokens: IStackTokens = {
|
||||
childrenGap: 10
|
||||
};
|
||||
|
||||
export const addMongoIndexSubElementsTokens: IStackTokens = {
|
||||
childrenGap: 20
|
||||
};
|
||||
|
||||
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
|
||||
|
||||
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||
|
||||
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
||||
|
||||
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = { dropdown: { paddingleft: 10, width: 202 } };
|
||||
|
||||
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
":hover": {
|
||||
background: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
".ms-FocusZone": {
|
||||
paddingTop: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const separatorStyles: Partial<ISeparatorStyles> = {
|
||||
root: [
|
||||
{
|
||||
selectors: {
|
||||
"::before": {
|
||||
background: StyleConstants.BaseMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
||||
|
||||
export const throughputUnit = "RU/s";
|
||||
@@ -313,6 +386,56 @@ export const changeFeedPolicyToolTip: JSX.Element = (
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<Link href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" target="_blank">
|
||||
{` Compound indexes `}
|
||||
</Link>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||
shell.
|
||||
</Text>
|
||||
);
|
||||
|
||||
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
<Text>
|
||||
To use the indexing policy editor, please login to the
|
||||
<Link target="_blank" href="https://portal.azure.com">
|
||||
{"azure portal."}
|
||||
</Link>
|
||||
</Text>
|
||||
</MessageBar>
|
||||
);
|
||||
|
||||
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||
<Stack horizontal {...mongoWarningStackProps}>
|
||||
<Text>Refreshing index transformation progress</Text>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export const renderMongoIndexTransformationRefreshMessage = (
|
||||
progress: number,
|
||||
performRefresh: () => void
|
||||
): JSX.Element => {
|
||||
if (progress === 0) {
|
||||
return (
|
||||
<Text>
|
||||
{"You can make more indexing changes once the current index transformation is complete. "}
|
||||
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text>
|
||||
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
|
||||
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
||||
fieldGroup: {
|
||||
height: 25,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { MongoIndexTypes, MongoNotificationType } from "../../SettingsUtils";
|
||||
import { AddMongoIndexComponent, AddMongoIndexComponentProps } from "./AddMongoIndexComponent";
|
||||
|
||||
describe("AddMongoIndexComponent", () => {
|
||||
it("renders", () => {
|
||||
const props: AddMongoIndexComponentProps = {
|
||||
position: 1,
|
||||
description: "sample_key",
|
||||
type: MongoIndexTypes.Single,
|
||||
notification: { type: MongoNotificationType.Error, message: "sample error" },
|
||||
onIndexAddOrChange: () => {
|
||||
return;
|
||||
},
|
||||
onDiscard: () => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const wrapper = shallow(<AddMongoIndexComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Stack,
|
||||
IconButton,
|
||||
TextField,
|
||||
Dropdown,
|
||||
IDropdownOption,
|
||||
ITextField
|
||||
} from "office-ui-fabric-react";
|
||||
import {
|
||||
addMongoIndexSubElementsTokens,
|
||||
mongoErrorMessageStyles,
|
||||
mongoWarningStackProps,
|
||||
shortWidthDropDownStyles,
|
||||
shortWidthTextFieldStyles
|
||||
} from "../../SettingsRenderUtils";
|
||||
import {
|
||||
getMongoIndexTypeText,
|
||||
MongoIndexTypes,
|
||||
MongoNotificationMessage,
|
||||
MongoNotificationType,
|
||||
MongoWildcardPlaceHolder
|
||||
} from "../../SettingsUtils";
|
||||
|
||||
export interface AddMongoIndexComponentProps {
|
||||
position: number;
|
||||
description: string;
|
||||
type: MongoIndexTypes;
|
||||
notification: MongoNotificationMessage;
|
||||
onIndexAddOrChange: (description: string, type: MongoIndexTypes) => void;
|
||||
onDiscard: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class AddMongoIndexComponent extends React.Component<AddMongoIndexComponentProps> {
|
||||
private descriptionTextField: ITextField;
|
||||
private indexTypes: IDropdownOption[] = [MongoIndexTypes.Single, MongoIndexTypes.Wildcard].map(
|
||||
(value: MongoIndexTypes) => ({
|
||||
text: getMongoIndexTypeText(value),
|
||||
key: value
|
||||
})
|
||||
);
|
||||
|
||||
private onDescriptionChange = (
|
||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
newValue?: string
|
||||
): void => {
|
||||
this.props.onIndexAddOrChange(newValue, this.props.type);
|
||||
};
|
||||
|
||||
private onTypeChange = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||
const newType = MongoIndexTypes[option.key as keyof typeof MongoIndexTypes];
|
||||
this.props.onIndexAddOrChange(this.props.description, newType);
|
||||
};
|
||||
|
||||
private setRef = (textField: ITextField) => (this.descriptionTextField = textField);
|
||||
|
||||
public focus = (): void => {
|
||||
this.descriptionTextField.focus();
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack {...mongoWarningStackProps}>
|
||||
<Stack horizontal tokens={addMongoIndexSubElementsTokens}>
|
||||
<TextField
|
||||
ariaLabel={"Index Field Name " + this.props.position}
|
||||
disabled={this.props.disabled}
|
||||
styles={shortWidthTextFieldStyles}
|
||||
componentRef={this.setRef}
|
||||
value={this.props.description}
|
||||
placeholder={this.props.type === MongoIndexTypes.Wildcard ? MongoWildcardPlaceHolder : undefined}
|
||||
onChange={this.onDescriptionChange}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
ariaLabel={"Index Type " + this.props.position}
|
||||
disabled={this.props.disabled}
|
||||
styles={shortWidthDropDownStyles}
|
||||
placeholder="Select an index type"
|
||||
selectedKey={this.props.type}
|
||||
options={this.indexTypes}
|
||||
onChange={this.onTypeChange}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
ariaLabel={"Undo Button " + this.props.position}
|
||||
iconProps={{ iconName: "Undo" }}
|
||||
disabled={!this.props.description && !this.props.type}
|
||||
onClick={() => this.props.onDiscard()}
|
||||
/>
|
||||
</Stack>
|
||||
{this.props.notification?.type === MongoNotificationType.Error && (
|
||||
<MessageBar styles={mongoErrorMessageStyles} messageBarType={MessageBarType.error}>
|
||||
{this.props.notification.message}
|
||||
</MessageBar>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
|
||||
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
|
||||
|
||||
describe("MongoIndexingPolicyComponent", () => {
|
||||
const baseProps: MongoIndexingPolicyComponentProps = {
|
||||
mongoIndexes: [],
|
||||
onIndexDrop: () => {
|
||||
return;
|
||||
},
|
||||
indexesToDrop: [],
|
||||
onRevertIndexDrop: () => {
|
||||
return;
|
||||
},
|
||||
indexesToAdd: [],
|
||||
onRevertIndexAdd: () => {
|
||||
return;
|
||||
},
|
||||
onIndexAddOrChange: () => {
|
||||
return;
|
||||
},
|
||||
indexTransformationProgress: undefined,
|
||||
refreshIndexTransformationProgress: () =>
|
||||
new Promise(() => {
|
||||
return;
|
||||
}),
|
||||
onMongoIndexingPolicySaveableChange: () => {
|
||||
return;
|
||||
},
|
||||
onMongoIndexingPolicyDiscardableChange: () => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("isIndexingTransforming", () => {
|
||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
|
||||
wrapper.setProps({ indexTransformationProgress: 50 });
|
||||
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(true);
|
||||
wrapper.setProps({ indexTransformationProgress: 100 });
|
||||
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
|
||||
});
|
||||
|
||||
describe("AddMongoIndexProps test", () => {
|
||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||
|
||||
it("defaults", () => {
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(undefined);
|
||||
});
|
||||
|
||||
const sampleWarning = "sampleWarning";
|
||||
const sampleError = "sampleError";
|
||||
|
||||
const cases = [
|
||||
[
|
||||
{ type: MongoNotificationType.Warning, message: sampleWarning } as MongoNotificationMessage,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
sampleWarning
|
||||
],
|
||||
[
|
||||
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined
|
||||
],
|
||||
[
|
||||
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
undefined
|
||||
],
|
||||
[undefined, false, true, true, undefined],
|
||||
[undefined, true, true, true, undefined]
|
||||
];
|
||||
|
||||
test.each(cases)(
|
||||
"",
|
||||
(
|
||||
notification: MongoNotificationMessage,
|
||||
indexToDropIsPresent: boolean,
|
||||
isMongoIndexingPolicySaveable: boolean,
|
||||
isMongoIndexingPolicyDiscardable: boolean,
|
||||
mongoWarningNotificationMessage: string
|
||||
) => {
|
||||
const addMongoIndexProps = {
|
||||
mongoIndex: { key: { keys: ["sampleKey"] } },
|
||||
type: MongoIndexTypes.Single,
|
||||
notification: notification
|
||||
};
|
||||
|
||||
let indexesToDrop: number[] = [];
|
||||
if (indexToDropIsPresent) {
|
||||
indexesToDrop = [0];
|
||||
}
|
||||
wrapper.setProps({ indexesToAdd: [addMongoIndexProps], indexesToDrop: indexesToDrop });
|
||||
wrapper.update();
|
||||
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(isMongoIndexingPolicySaveable);
|
||||
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
|
||||
isMongoIndexingPolicyDiscardable
|
||||
);
|
||||
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(
|
||||
mongoWarningNotificationMessage
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,369 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
DetailsList,
|
||||
DetailsListLayoutMode,
|
||||
Stack,
|
||||
IconButton,
|
||||
Text,
|
||||
SelectionMode,
|
||||
IDetailsRowProps,
|
||||
DetailsRow,
|
||||
IColumn,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Separator
|
||||
} from "office-ui-fabric-react";
|
||||
import {
|
||||
addMongoIndexStackProps,
|
||||
customDetailsListStyles,
|
||||
mongoIndexingPolicyDisclaimer,
|
||||
mediumWidthStackStyles,
|
||||
subComponentStackProps,
|
||||
transparentDetailsRowStyles,
|
||||
createAndAddMongoIndexStackProps,
|
||||
separatorStyles,
|
||||
mongoIndexingPolicyAADError,
|
||||
mongoIndexTransformationRefreshingMessage,
|
||||
renderMongoIndexTransformationRefreshMessage
|
||||
} from "../../SettingsRenderUtils";
|
||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import {
|
||||
MongoIndexTypes,
|
||||
AddMongoIndexProps,
|
||||
MongoIndexIdField,
|
||||
MongoNotificationType,
|
||||
getMongoIndexType,
|
||||
getMongoIndexTypeText
|
||||
} from "../../SettingsUtils";
|
||||
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||
import { AuthType } from "../../../../../AuthType";
|
||||
|
||||
export interface MongoIndexingPolicyComponentProps {
|
||||
mongoIndexes: MongoIndex[];
|
||||
onIndexDrop: (index: number) => void;
|
||||
indexesToDrop: number[];
|
||||
onRevertIndexDrop: (index: number) => void;
|
||||
indexesToAdd: AddMongoIndexProps[];
|
||||
onRevertIndexAdd: (index: number) => void;
|
||||
onIndexAddOrChange: (index: number, description: string, type: MongoIndexTypes) => void;
|
||||
indexTransformationProgress: number;
|
||||
refreshIndexTransformationProgress: () => Promise<void>;
|
||||
onMongoIndexingPolicySaveableChange: (isMongoIndexingPolicySaveable: boolean) => void;
|
||||
onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void;
|
||||
}
|
||||
|
||||
interface MongoIndexingPolicyComponentState {
|
||||
isRefreshingIndexTransformationProgress: boolean;
|
||||
}
|
||||
|
||||
interface MongoIndexDisplayProps {
|
||||
definition: JSX.Element;
|
||||
type: JSX.Element;
|
||||
actionButton: JSX.Element;
|
||||
}
|
||||
|
||||
export class MongoIndexingPolicyComponent extends React.Component<
|
||||
MongoIndexingPolicyComponentProps,
|
||||
MongoIndexingPolicyComponentState
|
||||
> {
|
||||
private shouldCheckComponentIsDirty = true;
|
||||
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
||||
private initialIndexesColumns: IColumn[] = [
|
||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{
|
||||
key: "actionButton",
|
||||
name: "Drop Index",
|
||||
fieldName: "actionButton",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true
|
||||
}
|
||||
];
|
||||
|
||||
private indexesToBeDroppedColumns: IColumn[] = [
|
||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||
{
|
||||
key: "actionButton",
|
||||
name: "Add index back",
|
||||
fieldName: "actionButton",
|
||||
minWidth: 100,
|
||||
maxWidth: 200,
|
||||
isResizable: true
|
||||
}
|
||||
];
|
||||
|
||||
constructor(props: MongoIndexingPolicyComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isRefreshingIndexTransformationProgress: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
|
||||
if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) {
|
||||
this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus();
|
||||
}
|
||||
this.onComponentUpdate();
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.onComponentUpdate();
|
||||
}
|
||||
|
||||
private onComponentUpdate = (): void => {
|
||||
if (!this.shouldCheckComponentIsDirty) {
|
||||
this.shouldCheckComponentIsDirty = true;
|
||||
return;
|
||||
}
|
||||
this.props.onMongoIndexingPolicySaveableChange(this.isMongoIndexingPolicySaveable());
|
||||
this.props.onMongoIndexingPolicyDiscardableChange(this.isMongoIndexingPolicyDiscardable());
|
||||
this.shouldCheckComponentIsDirty = false;
|
||||
};
|
||||
|
||||
public isMongoIndexingPolicySaveable = (): boolean => {
|
||||
if (this.props.indexesToAdd.length === 0 && this.props.indexesToDrop.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const addErrorsExist = !!this.props.indexesToAdd.find(addMongoIndexProps => addMongoIndexProps.notification);
|
||||
|
||||
if (addErrorsExist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public isMongoIndexingPolicyDiscardable = (): boolean => {
|
||||
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
|
||||
};
|
||||
|
||||
public getMongoWarningNotificationMessage = (): string => {
|
||||
return this.props.indexesToAdd.find(
|
||||
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
|
||||
)?.notification.message;
|
||||
};
|
||||
|
||||
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||
};
|
||||
|
||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||
return isCurrentIndex ? (
|
||||
<IconButton
|
||||
ariaLabel="Delete index Button"
|
||||
iconProps={{ iconName: "Delete" }}
|
||||
disabled={this.isIndexingTransforming()}
|
||||
onClick={() => {
|
||||
this.props.onIndexDrop(arrayPosition);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
ariaLabel="Add back Index Button"
|
||||
iconProps={{ iconName: "Add" }}
|
||||
onClick={() => {
|
||||
this.props.onRevertIndexDrop(arrayPosition);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
private getMongoIndexDisplayProps = (
|
||||
mongoIndex: MongoIndex,
|
||||
arrayPosition: number,
|
||||
isCurrentIndex: boolean
|
||||
): MongoIndexDisplayProps => {
|
||||
const keys = mongoIndex?.key?.keys;
|
||||
const type = getMongoIndexType(keys);
|
||||
const definition = keys?.join();
|
||||
let mongoIndexDisplayProps: MongoIndexDisplayProps;
|
||||
if (type) {
|
||||
mongoIndexDisplayProps = {
|
||||
definition: <Text>{definition}</Text>,
|
||||
type: <Text>{getMongoIndexTypeText(type)}</Text>,
|
||||
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex)
|
||||
};
|
||||
}
|
||||
return mongoIndexDisplayProps;
|
||||
};
|
||||
|
||||
private renderIndexesToBeAdded = (): JSX.Element => {
|
||||
const indexesToAddLength = this.props.indexesToAdd.length;
|
||||
for (let i = 0; i < indexesToAddLength; i++) {
|
||||
const existingIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||
this.addMongoIndexComponentRefs[i] = existingIndexToAddRef;
|
||||
}
|
||||
const newIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||
this.addMongoIndexComponentRefs[indexesToAddLength] = newIndexToAddRef;
|
||||
|
||||
return (
|
||||
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||
{this.props.indexesToAdd.map((mongoIndexWithType, arrayPosition) => {
|
||||
const keys = mongoIndexWithType.mongoIndex.key.keys;
|
||||
const type = mongoIndexWithType.type;
|
||||
const notification = mongoIndexWithType.notification;
|
||||
return (
|
||||
<AddMongoIndexComponent
|
||||
ref={this.addMongoIndexComponentRefs[arrayPosition]}
|
||||
position={arrayPosition}
|
||||
key={arrayPosition}
|
||||
description={keys.join()}
|
||||
type={type}
|
||||
notification={notification}
|
||||
onIndexAddOrChange={(description, type) =>
|
||||
this.props.onIndexAddOrChange(arrayPosition, description, type)
|
||||
}
|
||||
onDiscard={() => {
|
||||
this.addMongoIndexComponentRefs.splice(arrayPosition, 1);
|
||||
this.props.onRevertIndexAdd(arrayPosition);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<AddMongoIndexComponent
|
||||
ref={this.addMongoIndexComponentRefs[indexesToAddLength]}
|
||||
disabled={this.isIndexingTransforming()}
|
||||
position={indexesToAddLength}
|
||||
key={indexesToAddLength}
|
||||
description={undefined}
|
||||
type={undefined}
|
||||
notification={undefined}
|
||||
onIndexAddOrChange={(description, type) =>
|
||||
this.props.onIndexAddOrChange(indexesToAddLength, description, type)
|
||||
}
|
||||
onDiscard={() => {
|
||||
this.props.onRevertIndexAdd(indexesToAddLength);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
private renderInitialIndexes = (): JSX.Element => {
|
||||
const initialIndexes = this.props.mongoIndexes
|
||||
.map((mongoIndex, arrayPosition) => this.getMongoIndexDisplayProps(mongoIndex, arrayPosition, true))
|
||||
.filter((value, arrayPosition) => !!value && !this.props.indexesToDrop.includes(arrayPosition));
|
||||
|
||||
return (
|
||||
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||
<CollapsibleSectionComponent title="Current index(es)">
|
||||
{
|
||||
<>
|
||||
<DetailsList
|
||||
styles={customDetailsListStyles}
|
||||
disableSelectionZone
|
||||
items={initialIndexes}
|
||||
columns={this.initialIndexesColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
onRenderRow={this.onRenderRow}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
/>
|
||||
{this.renderIndexesToBeAdded()}
|
||||
</>
|
||||
}
|
||||
</CollapsibleSectionComponent>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
private renderIndexesToBeDropped = (): JSX.Element => {
|
||||
const indexesToBeDropped = this.props.indexesToDrop.map((dropIndex, arrayPosition) =>
|
||||
this.getMongoIndexDisplayProps(this.props.mongoIndexes[dropIndex], arrayPosition, false)
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack styles={mediumWidthStackStyles}>
|
||||
<CollapsibleSectionComponent title="Index(es) to be dropped">
|
||||
{indexesToBeDropped.length > 0 && (
|
||||
<DetailsList
|
||||
styles={customDetailsListStyles}
|
||||
disableSelectionZone
|
||||
items={indexesToBeDropped}
|
||||
columns={this.indexesToBeDroppedColumns}
|
||||
selectionMode={SelectionMode.none}
|
||||
onRenderRow={this.onRenderRow}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
/>
|
||||
)}
|
||||
</CollapsibleSectionComponent>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
private refreshIndexTransformationProgress = async () => {
|
||||
this.setState({ isRefreshingIndexTransformationProgress: true });
|
||||
try {
|
||||
await this.props.refreshIndexTransformationProgress();
|
||||
} catch (error) {
|
||||
handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
|
||||
} finally {
|
||||
this.setState({ isRefreshingIndexTransformationProgress: false });
|
||||
}
|
||||
};
|
||||
|
||||
public isIndexingTransforming = (): boolean =>
|
||||
// index transformation progress can be 0
|
||||
this.props.indexTransformationProgress !== undefined && this.props.indexTransformationProgress !== 100;
|
||||
|
||||
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
|
||||
|
||||
private renderIndexTransformationWarning = (): JSX.Element => {
|
||||
if (this.state.isRefreshingIndexTransformationProgress) {
|
||||
return mongoIndexTransformationRefreshingMessage;
|
||||
} else if (this.isIndexingTransforming()) {
|
||||
return renderMongoIndexTransformationRefreshMessage(
|
||||
this.props.indexTransformationProgress,
|
||||
this.onClickRefreshIndexingTransformationLink
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private renderWarningMessage = (): JSX.Element => {
|
||||
let warningMessage: string;
|
||||
if (this.getMongoWarningNotificationMessage()) {
|
||||
warningMessage = this.getMongoWarningNotificationMessage();
|
||||
} else if (this.isMongoIndexingPolicySaveable()) {
|
||||
warningMessage =
|
||||
"You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.";
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderIndexTransformationWarning() && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||
)}
|
||||
|
||||
{warningMessage && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>
|
||||
<Text>{warningMessage}</Text>
|
||||
</MessageBar>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (this.props.mongoIndexes) {
|
||||
return (
|
||||
<Stack {...subComponentStackProps}>
|
||||
{this.renderWarningMessage()}
|
||||
{mongoIndexingPolicyDisclaimer}
|
||||
{this.renderInitialIndexes()}
|
||||
<Separator styles={separatorStyles} />
|
||||
{this.renderIndexesToBeDropped()}
|
||||
</Stack>
|
||||
);
|
||||
} else {
|
||||
return window.authType !== AuthType.AAD ? mongoIndexingPolicyAADError : <Spinner size={SpinnerSize.large} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AddMongoIndexComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Index Field Name 1"
|
||||
componentRef={[Function]}
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"paddingLeft": 10,
|
||||
"width": 210,
|
||||
},
|
||||
}
|
||||
}
|
||||
value="sample_key"
|
||||
/>
|
||||
<StyledWithResponsiveMode
|
||||
ariaLabel="Index Type 1"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "Single",
|
||||
"text": "Single Field",
|
||||
},
|
||||
Object {
|
||||
"key": "Wildcard",
|
||||
"text": "Wildcard",
|
||||
},
|
||||
]
|
||||
}
|
||||
placeholder="Select an index type"
|
||||
selectedKey="Single"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"paddingleft": 10,
|
||||
"width": 202,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Undo Button 1"
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Undo",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledMessageBarBase
|
||||
messageBarType={1}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"marginLeft": 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
sample error
|
||||
</StyledMessageBarBase>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -0,0 +1,137 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<StyledLinkBase
|
||||
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||
target="_blank"
|
||||
>
|
||||
Compound indexes
|
||||
</StyledLinkBase>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||
</Text>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CollapsibleSectionComponent
|
||||
title="Current index(es)"
|
||||
>
|
||||
<StyledWithViewportComponent
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"fieldName": "definition",
|
||||
"isResizable": true,
|
||||
"key": "definition",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Definition",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "type",
|
||||
"isResizable": true,
|
||||
"key": "type",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Type",
|
||||
},
|
||||
Object {
|
||||
"fieldName": "actionButton",
|
||||
"isResizable": true,
|
||||
"key": "actionButton",
|
||||
"maxWidth": 200,
|
||||
"minWidth": 100,
|
||||
"name": "Drop Index",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableSelectionZone={true}
|
||||
items={Array []}
|
||||
layoutMode={1}
|
||||
onRenderRow={[Function]}
|
||||
selectionMode={0}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"selectors": Object {
|
||||
".ms-FocusZone": Object {
|
||||
"paddingTop": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<AddMongoIndexComponent
|
||||
disabled={false}
|
||||
key="0"
|
||||
onDiscard={[Function]}
|
||||
onIndexAddOrChange={[Function]}
|
||||
position={0}
|
||||
/>
|
||||
</Stack>
|
||||
</CollapsibleSectionComponent>
|
||||
</Stack>
|
||||
<Styled
|
||||
styles={
|
||||
Object {
|
||||
"root": Array [
|
||||
Object {
|
||||
"selectors": Object {
|
||||
"::before": Object {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<CollapsibleSectionComponent
|
||||
title="Index(es) to be dropped"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -98,11 +98,11 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
};
|
||||
|
||||
public canThroughputExceedMaximumValue = (): boolean => {
|
||||
const isPublicAzurePortal: boolean =
|
||||
configContext.platform === Platform.Portal && !this.props.container.isRunningOnNationalCloud();
|
||||
const hasPartitionKey = !!this.props.collection.partitionKey;
|
||||
|
||||
return isPublicAzurePortal && hasPartitionKey;
|
||||
return (
|
||||
!this.props.isFixedContainer &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
!this.props.container.isRunningOnNationalCloud()
|
||||
);
|
||||
};
|
||||
|
||||
public getInitialNotificationElement = (): JSX.Element => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||
import {
|
||||
getTextFieldStyles,
|
||||
getToolTipContainer,
|
||||
spendAckCheckBoxStyle,
|
||||
noLeftPaddingCheckBoxStyle,
|
||||
titleAndInputStackProps,
|
||||
checkBoxAndInputStackProps,
|
||||
getChoiceGroupStyles,
|
||||
@@ -142,7 +142,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
|
||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
|
||||
this.autoPilotInputMaxValue = Int32.Max;
|
||||
this.autoPilotInputMaxValue = this.props.isFixed ? this.props.maximum : Int32.Max;
|
||||
}
|
||||
|
||||
public hasProvisioningTypeChanged = (): boolean =>
|
||||
@@ -278,12 +278,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
{this.props.spendAckVisible && (
|
||||
<Checkbox
|
||||
id="spendAckCheckBox"
|
||||
styles={spendAckCheckBoxStyle}
|
||||
styles={noLeftPaddingCheckBoxStyle}
|
||||
label={this.props.spendAckText}
|
||||
checked={this.state.spendAckChecked}
|
||||
onChange={this.onSpendAckChecked}
|
||||
/>
|
||||
)}
|
||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -316,21 +317,21 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
{this.props.spendAckVisible && (
|
||||
<Checkbox
|
||||
id="spendAckCheckBox"
|
||||
styles={spendAckCheckBoxStyle}
|
||||
styles={noLeftPaddingCheckBoxStyle}
|
||||
label={this.props.spendAckText}
|
||||
checked={this.state.spendAckChecked}
|
||||
onChange={this.onSpendAckChecked}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.props.isFixed && <p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>}
|
||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack {...checkBoxAndInputStackProps}>
|
||||
{!this.props.isFixed && this.renderThroughputModeChoices()}
|
||||
{this.renderThroughputModeChoices()}
|
||||
|
||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||
</Stack>
|
||||
|
||||
@@ -2,12 +2,20 @@ import { collection, container } from "./TestUtils";
|
||||
import {
|
||||
getMaxRUs,
|
||||
getMinRUs,
|
||||
getMongoIndexType,
|
||||
getMongoNotification,
|
||||
getSanitizedInputValue,
|
||||
hasDatabaseSharedThroughput,
|
||||
isDirty,
|
||||
isDirtyTypes,
|
||||
MongoIndexTypes,
|
||||
MongoNotificationType,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure
|
||||
parseConflictResolutionProcedure,
|
||||
MongoWildcardPlaceHolder,
|
||||
getMongoIndexTypeText,
|
||||
SingleFieldText,
|
||||
WildcardText
|
||||
} from "./SettingsUtils";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
@@ -91,7 +99,43 @@ describe("SettingsUtils", () => {
|
||||
it("getSanitizedInputValue", () => {
|
||||
const max = 100;
|
||||
expect(getSanitizedInputValue("", max)).toEqual(0);
|
||||
expect(getSanitizedInputValue("999", max)).toEqual(99);
|
||||
expect(getSanitizedInputValue("999", max)).toEqual(100);
|
||||
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||
});
|
||||
|
||||
it("getMongoIndexType", () => {
|
||||
expect(getMongoIndexType(["Single"])).toEqual(MongoIndexTypes.Single);
|
||||
expect(getMongoIndexType(["Wildcard.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||
expect(getMongoIndexType(["Key1", "Key2"])).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("getMongoIndexTypeText", () => {
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Single)).toEqual(SingleFieldText);
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Wildcard)).toEqual(WildcardText);
|
||||
});
|
||||
|
||||
it("getMongoNotification", () => {
|
||||
const singleIndexDescription = "sampleKey";
|
||||
const wildcardIndexDescription = "sampleKey.$**";
|
||||
|
||||
let notification = getMongoNotification(singleIndexDescription, undefined);
|
||||
expect(notification.message).toEqual("Please select a type for each index.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Warning);
|
||||
|
||||
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Single);
|
||||
expect(notification).toEqual(undefined);
|
||||
|
||||
notification = getMongoNotification(wildcardIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification).toEqual(undefined);
|
||||
|
||||
notification = getMongoNotification("", MongoIndexTypes.Single);
|
||||
expect(notification.message).toEqual("Please enter a field name.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
|
||||
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification.message).toEqual(
|
||||
"Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||
);
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,17 @@ import * as SharedConstants from "../../../Shared/Constants";
|
||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
||||
|
||||
import Explorer from "../../Explorer";
|
||||
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
|
||||
const zeroValue = 0;
|
||||
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
|
||||
export const TtlOff = "off";
|
||||
export const TtlOn = "on";
|
||||
export const TtlOnNoDefault = "on-nodefault";
|
||||
export const MongoIndexIdField = "_id";
|
||||
export const MongoWildcardPlaceHolder = "properties.$**";
|
||||
export const SingleFieldText = "Single Field";
|
||||
export const WildcardText = "Wildcard";
|
||||
|
||||
export enum ChangeFeedPolicyState {
|
||||
Off = "Off",
|
||||
@@ -28,6 +33,17 @@ export enum GeospatialConfigType {
|
||||
Geometry = "Geometry"
|
||||
}
|
||||
|
||||
export enum MongoIndexTypes {
|
||||
Single = "Single",
|
||||
Wildcard = "Wildcard"
|
||||
}
|
||||
|
||||
export interface AddMongoIndexProps {
|
||||
mongoIndex: MongoIndex;
|
||||
type: MongoIndexTypes;
|
||||
notification: MongoNotificationMessage;
|
||||
}
|
||||
|
||||
export enum SettingsV2TabTypes {
|
||||
ScaleTab,
|
||||
ConflictResolutionTab,
|
||||
@@ -40,6 +56,16 @@ export interface IsComponentDirtyResult {
|
||||
isDiscardable: boolean;
|
||||
}
|
||||
|
||||
export enum MongoNotificationType {
|
||||
Warning = "Warning",
|
||||
Error = "Error"
|
||||
}
|
||||
|
||||
export interface MongoNotificationMessage {
|
||||
type: MongoNotificationType;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
||||
const database: ViewModels.Database = collection.getDatabase();
|
||||
return database?.isDatabaseShared() && !collection.offer();
|
||||
@@ -54,7 +80,7 @@ export const getMaxRUs = (collection: ViewModels.Collection, container: Explorer
|
||||
const numPartitionsFromOffer: number =
|
||||
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
|
||||
|
||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo().numPartitions;
|
||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo()?.numPartitions;
|
||||
|
||||
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
|
||||
|
||||
@@ -79,7 +105,7 @@ export const getMinRUs = (collection: ViewModels.Collection, container: Explorer
|
||||
return collectionThroughputInfo.minimumRUForCollection;
|
||||
}
|
||||
|
||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo().numPartitions;
|
||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo()?.numPartitions;
|
||||
|
||||
if (!numPartitions || numPartitions === 1) {
|
||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||
@@ -131,13 +157,12 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
|
||||
};
|
||||
|
||||
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
||||
let newValue = parseInt(newValueString);
|
||||
const newValue = parseInt(newValueString);
|
||||
if (isNaN(newValue)) {
|
||||
newValue = zeroValue;
|
||||
} else if (newValue > max) {
|
||||
newValue = Math.floor(newValue / 10);
|
||||
return zeroValue;
|
||||
}
|
||||
return newValue;
|
||||
// make sure new value does not exceed the maximum throughput
|
||||
return Math.min(newValue, max);
|
||||
};
|
||||
|
||||
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
||||
@@ -180,3 +205,48 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
||||
throw new Error(`Unknown tab ${tab}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const getMongoNotification = (description: string, type: MongoIndexTypes): MongoNotificationMessage => {
|
||||
if (description && !type) {
|
||||
return {
|
||||
type: MongoNotificationType.Warning,
|
||||
message: "Please select a type for each index."
|
||||
};
|
||||
}
|
||||
|
||||
if (type && (!description || description.trim().length === 0)) {
|
||||
return {
|
||||
type: MongoNotificationType.Error,
|
||||
message: "Please enter a field name."
|
||||
};
|
||||
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
|
||||
return {
|
||||
type: MongoNotificationType.Error,
|
||||
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getMongoIndexType = (keys: string[]): MongoIndexTypes => {
|
||||
const length = keys?.length;
|
||||
let type: MongoIndexTypes;
|
||||
|
||||
if (length === 1) {
|
||||
if (keys[0].indexOf("$**") !== -1) {
|
||||
type = MongoIndexTypes.Wildcard;
|
||||
} else {
|
||||
type = MongoIndexTypes.Single;
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
};
|
||||
|
||||
export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
|
||||
if (index === MongoIndexTypes.Single) {
|
||||
return SingleFieldText;
|
||||
}
|
||||
return WildcardText;
|
||||
};
|
||||
|
||||
@@ -983,6 +983,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
@@ -2289,6 +2290,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
@@ -3608,6 +3610,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
@@ -4914,6 +4917,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
|
||||
@@ -310,5 +310,59 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
>
|
||||
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected.
|
||||
</Text>
|
||||
<Text>
|
||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||
<StyledLinkBase
|
||||
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||
target="_blank"
|
||||
>
|
||||
Compound indexes
|
||||
</StyledLinkBase>
|
||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||
</Text>
|
||||
<StyledMessageBarBase
|
||||
messageBarType={1}
|
||||
>
|
||||
<Text>
|
||||
To use the indexing policy editor, please login to the
|
||||
<StyledLinkBase
|
||||
href="https://portal.azure.com"
|
||||
target="_blank"
|
||||
>
|
||||
azure portal.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</StyledMessageBarBase>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
Refreshing index transformation progress
|
||||
</Text>
|
||||
<StyledSpinnerBase
|
||||
size={2}
|
||||
/>
|
||||
</Stack>
|
||||
<Text>
|
||||
You can make more indexing changes once the current index transformation is complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
Refresh to check if it has completed.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Text>
|
||||
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
Refresh to check the progress.
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -119,6 +119,10 @@
|
||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
||||
</p>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: isFixed -->
|
||||
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: !isAutoPilotSelected()">
|
||||
|
||||
@@ -3,7 +3,6 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
||||
jest.mock("../../Common/dataAccess/createCollection");
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Q from "q";
|
||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -21,7 +20,7 @@ describe("ContainerSampleGenerator", () => {
|
||||
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
||||
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
explorerStub.findDatabaseWithId = () => database;
|
||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
||||
explorerStub.refreshAllDatabases = () => Promise.resolve();
|
||||
return explorerStub;
|
||||
};
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ export class ContainerSampleGenerator {
|
||||
if (!collection) {
|
||||
throw new Error("No container to populate");
|
||||
}
|
||||
const promises: Q.Promise<any>[] = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
if (this.container.isPreferredApiGraph()) {
|
||||
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
||||
|
||||
@@ -206,6 +206,7 @@ export default class Explorer {
|
||||
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||
public isSettingsV2Enabled: ko.Observable<boolean>;
|
||||
public isMongoIndexEditorEnabled: ko.Observable<boolean>;
|
||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
@@ -216,7 +217,7 @@ export default class Explorer {
|
||||
|
||||
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
||||
public shareAccessData: ko.Observable<AdHocAccessData>;
|
||||
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
|
||||
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Promise<void>;
|
||||
public renewTokenError: ko.Observable<string>;
|
||||
public tokenForRenewal: ko.Observable<string>;
|
||||
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
|
||||
@@ -412,8 +413,8 @@ export default class Explorer {
|
||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||
);
|
||||
//this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
|
||||
this.isSettingsV2Enabled = ko.observable(false);
|
||||
this.isMongoIndexEditorEnabled = ko.observable(false);
|
||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
@@ -1119,8 +1120,8 @@ export default class Explorer {
|
||||
"Initiating connection to account"
|
||||
);
|
||||
this.renewExplorerShareAccess(this, this.tokenForRenewal())
|
||||
.fail((error: any) => {
|
||||
const stringifiedError: string = JSON.stringify(error);
|
||||
.catch((error: any) => {
|
||||
const stringifiedError: string = error.message;
|
||||
this.renewTokenError("Invalid connection string specified");
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
@@ -1149,43 +1150,34 @@ export default class Explorer {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to generate share url: ${JSON.stringify(error)}`
|
||||
`Failed to generate share url: ${error.message}`
|
||||
);
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public renewShareAccess(token: string): Q.Promise<void> {
|
||||
public async renewShareAccess(token: string): Promise<void> {
|
||||
if (!this.renewExplorerShareAccess) {
|
||||
return Q.reject("Not implemented");
|
||||
throw "Not implemented";
|
||||
}
|
||||
|
||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||
const id: string = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
"Initiating connection to account"
|
||||
);
|
||||
this.renewExplorerShareAccess(this, token)
|
||||
return this.renewExplorerShareAccess(this, token)
|
||||
.then(
|
||||
() => {
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful");
|
||||
this.renewAdHocAccessPane && this.renewAdHocAccessPane.close();
|
||||
deferred.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to connect: ${JSON.stringify(error)}`
|
||||
);
|
||||
deferred.reject(error);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
}
|
||||
|
||||
public displayGuestAccessTokenRenewalPrompt(): void {
|
||||
@@ -1380,24 +1372,19 @@ export default class Explorer {
|
||||
}
|
||||
}
|
||||
|
||||
public refreshDatabaseForResourceToken(): Q.Promise<any> {
|
||||
public async refreshDatabaseForResourceToken(): Promise<any> {
|
||||
const databaseId = this.resourceTokenDatabaseId();
|
||||
const collectionId = this.resourceTokenCollectionId();
|
||||
if (!databaseId || !collectionId) {
|
||||
return Q.reject();
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const deferred: Q.Deferred<void> = Q.defer();
|
||||
readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
|
||||
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
|
||||
this.selectedNode(this.resourceTokenCollection());
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
const collection = await readCollection(databaseId, collectionId);
|
||||
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
|
||||
this.selectedNode(this.resourceTokenCollection());
|
||||
}
|
||||
|
||||
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
|
||||
public refreshAllDatabases(isInitialLoad?: boolean): Promise<any> {
|
||||
this.isRefreshingExplorer(true);
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
@@ -1414,89 +1401,85 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
// TODO: Refactor
|
||||
const deferred: Q.Deferred<any> = Q.defer();
|
||||
this._setLoadingStatusText("Fetching databases...");
|
||||
readDatabases().then(
|
||||
(databases: DataModels.Database[]) => {
|
||||
this._setLoadingStatusText("Successfully fetched databases.");
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
},
|
||||
startKey
|
||||
);
|
||||
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
||||
const deltaDatabases = this.getDeltaDatabases(databases);
|
||||
this.addDatabasesToList(deltaDatabases.toAdd);
|
||||
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
||||
this.selectedNode(currentlySelectedNode);
|
||||
this._setLoadingStatusText("Fetching containers...");
|
||||
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
|
||||
.then(
|
||||
() => {
|
||||
this._setLoadingStatusText("Successfully fetched containers.");
|
||||
deferred.resolve();
|
||||
},
|
||||
reason => {
|
||||
this._setLoadingStatusText("Failed to fetch containers.");
|
||||
deferred.reject(reason);
|
||||
}
|
||||
)
|
||||
.finally(() => this.isRefreshingExplorer(false));
|
||||
},
|
||||
error => {
|
||||
this._setLoadingStatusText("Failed to fetch databases.");
|
||||
this.isRefreshingExplorer(false);
|
||||
deferred.reject(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: JSON.stringify(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while refreshing databases: ${JSON.stringify(error)}`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise.then(
|
||||
() => {
|
||||
if (resourceTreeStartKey != null) {
|
||||
return readDatabases()
|
||||
.then(
|
||||
(databases: DataModels.Database[]) => {
|
||||
this._setLoadingStatusText("Successfully fetched databases.");
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.LoadResourceTree,
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
},
|
||||
resourceTreeStartKey
|
||||
startKey
|
||||
);
|
||||
}
|
||||
},
|
||||
reason => {
|
||||
if (resourceTreeStartKey != null) {
|
||||
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
|
||||
const deltaDatabases = this.getDeltaDatabases(databases);
|
||||
this.addDatabasesToList(deltaDatabases.toAdd);
|
||||
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
||||
this.selectedNode(currentlySelectedNode);
|
||||
this._setLoadingStatusText("Fetching containers...");
|
||||
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
|
||||
.then(
|
||||
() => this._setLoadingStatusText("Successfully fetched containers."),
|
||||
reason => {
|
||||
this._setLoadingStatusText("Failed to fetch containers.");
|
||||
throw reason;
|
||||
}
|
||||
)
|
||||
.finally(() => this.isRefreshingExplorer(false));
|
||||
},
|
||||
error => {
|
||||
this._setLoadingStatusText("Failed to fetch databases.");
|
||||
this.isRefreshingExplorer(false);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadResourceTree,
|
||||
Action.LoadDatabases,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
databaseAccountName: this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: reason
|
||||
error: error.message
|
||||
},
|
||||
resourceTreeStartKey
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while refreshing databases: ${error.message}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
if (resourceTreeStartKey != null) {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.LoadResourceTree,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
},
|
||||
resourceTreeStartKey
|
||||
);
|
||||
}
|
||||
},
|
||||
reason => {
|
||||
if (resourceTreeStartKey != null) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadResourceTree,
|
||||
{
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: reason
|
||||
},
|
||||
resourceTreeStartKey
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
@@ -1533,7 +1516,7 @@ export default class Explorer {
|
||||
this.isRefreshingExplorer(false);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while refreshing data: ${JSON.stringify(error)}`
|
||||
`Error while refreshing data: ${error.message}`
|
||||
);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.LoadDatabases,
|
||||
@@ -1600,7 +1583,7 @@ export default class Explorer {
|
||||
return Promise.all(sparkPromises).then(() => workspaceItems);
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, JSON.stringify(error));
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error.message);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
@@ -1638,7 +1621,7 @@ export default class Explorer {
|
||||
Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to get notebook workspace connection info: ${JSON.stringify(error)}`
|
||||
`Failed to get notebook workspace connection info: ${error.message}`
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -1715,7 +1698,7 @@ export default class Explorer {
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
|
||||
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${JSON.stringify(error)}`);
|
||||
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${error.message}`);
|
||||
} finally {
|
||||
clearMessage && clearMessage();
|
||||
}
|
||||
@@ -1796,7 +1779,7 @@ export default class Explorer {
|
||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||
}
|
||||
|
||||
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
|
||||
const initPromise: Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Promise.resolve();
|
||||
|
||||
initPromise.then(() => {
|
||||
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
||||
@@ -1890,7 +1873,7 @@ export default class Explorer {
|
||||
return false;
|
||||
}
|
||||
|
||||
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
|
||||
public async initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Promise<void> {
|
||||
if (inputs != null) {
|
||||
const authorizationToken = inputs.authorizationToken || "";
|
||||
const masterKey = inputs.masterKey || "";
|
||||
@@ -1940,7 +1923,6 @@ export default class Explorer {
|
||||
|
||||
this.isAccountReady(true);
|
||||
}
|
||||
return Q();
|
||||
}
|
||||
|
||||
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
||||
@@ -1951,6 +1933,10 @@ export default class Explorer {
|
||||
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
|
||||
this.isSettingsV2Enabled(true);
|
||||
}
|
||||
|
||||
if (flights.indexOf(Constants.Flights.MongoIndexEditor) !== -1) {
|
||||
this.isMongoIndexEditorEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public findSelectedCollection(): ViewModels.Collection {
|
||||
@@ -2054,7 +2040,7 @@ export default class Explorer {
|
||||
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
||||
// i.e addition of stored procedures, etc.
|
||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||
let loadCollectionPromises: Q.Promise<void>[] = [];
|
||||
let loadCollectionPromises: Promise<void>[] = [];
|
||||
|
||||
// If the user has a lot of databases, only load expanded databases.
|
||||
const databasesToLoad =
|
||||
@@ -2093,7 +2079,7 @@ export default class Explorer {
|
||||
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
|
||||
defaultExperience: this.defaultExperience && this.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
trace: JSON.stringify(error)
|
||||
trace: error.message
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -2438,7 +2424,7 @@ export default class Explorer {
|
||||
return true;
|
||||
}
|
||||
|
||||
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> {
|
||||
public async renameNotebook(notebookFile: NotebookContentItem): Promise<NotebookContentItem> {
|
||||
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
|
||||
const error = "Attempt to rename notebook, but notebook is not enabled";
|
||||
Logger.logError(error, "Explorer/renameNotebook");
|
||||
@@ -2455,39 +2441,35 @@ export default class Explorer {
|
||||
);
|
||||
if (openedNotebookTabs.length > 0) {
|
||||
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
||||
return Q.reject();
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const originalPath = notebookFile.path;
|
||||
const result = this.stringInputPane
|
||||
.openWithOptions<NotebookContentItem>({
|
||||
errorMessage: "Could not rename notebook",
|
||||
inProgressMessage: "Renaming notebook to",
|
||||
successMessage: "Renamed notebook to",
|
||||
inputLabel: "Enter new notebook name",
|
||||
paneTitle: "Rename Notebook",
|
||||
submitButtonLabel: "Rename",
|
||||
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
|
||||
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
|
||||
})
|
||||
.then(newNotebookFile => {
|
||||
const notebookTabs = this.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.NotebookV2,
|
||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||
);
|
||||
notebookTabs.forEach(tab => {
|
||||
tab.tabTitle(newNotebookFile.name);
|
||||
tab.tabPath(newNotebookFile.path);
|
||||
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
|
||||
});
|
||||
const newNotebookFile = await this.stringInputPane.openWithOptions<NotebookContentItem>({
|
||||
errorMessage: "Could not rename notebook",
|
||||
inProgressMessage: "Renaming notebook to",
|
||||
successMessage: "Renamed notebook to",
|
||||
inputLabel: "Enter new notebook name",
|
||||
paneTitle: "Rename Notebook",
|
||||
submitButtonLabel: "Rename",
|
||||
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
|
||||
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
|
||||
});
|
||||
const notebookTabs = this.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.NotebookV2,
|
||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||
);
|
||||
notebookTabs.forEach(tab => {
|
||||
tab.tabTitle(newNotebookFile.name);
|
||||
tab.tabPath(newNotebookFile.path);
|
||||
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
|
||||
});
|
||||
|
||||
return newNotebookFile;
|
||||
});
|
||||
result.then(() => this.resourceTree.triggerRender());
|
||||
return result;
|
||||
this.resourceTree.triggerRender();
|
||||
return newNotebookFile;
|
||||
}
|
||||
|
||||
public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> {
|
||||
public async onCreateDirectory(parent: NotebookContentItem): 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");
|
||||
@@ -2495,7 +2477,7 @@ export default class Explorer {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const result = this.stringInputPane.openWithOptions<NotebookContentItem>({
|
||||
const result = await this.stringInputPane.openWithOptions<NotebookContentItem>({
|
||||
errorMessage: "Could not create directory ",
|
||||
inProgressMessage: "Creating directory ",
|
||||
successMessage: "Created directory ",
|
||||
@@ -2505,7 +2487,7 @@ export default class Explorer {
|
||||
defaultInput: "",
|
||||
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input)
|
||||
});
|
||||
result.then(() => this.resourceTree.triggerRender());
|
||||
this.resourceTree.triggerRender();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2555,7 +2537,7 @@ export default class Explorer {
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Could not download notebook ${JSON.stringify(error)}`
|
||||
`Could not download notebook ${error.message}`
|
||||
);
|
||||
|
||||
clearMessage();
|
||||
|
||||
@@ -378,8 +378,7 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
* @param targetPosition
|
||||
* @return promise with shift offset
|
||||
*/
|
||||
private shiftGraph(targetPosition: Point2D): Q.Promise<Point2D> {
|
||||
const deferred: Q.Deferred<Point2D> = Q.defer<Point2D>();
|
||||
private async shiftGraph(targetPosition: Point2D): Promise<Point2D> {
|
||||
const offset = { x: this.width / 2 - targetPosition.x, y: this.height / 2 - targetPosition.y };
|
||||
this.viewCenter = targetPosition;
|
||||
|
||||
@@ -391,18 +390,15 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
.translate(-targetPosition.x, -targetPosition.y);
|
||||
};
|
||||
|
||||
this.zoomBackground
|
||||
const transition = this.zoomBackground
|
||||
.transition()
|
||||
.duration(D3ForceGraph.TRANSITION_STEP1_MS)
|
||||
.call(this.zoom.transform, transform)
|
||||
.on("end", () => {
|
||||
deferred.resolve(offset);
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(null);
|
||||
}
|
||||
.call(this.zoom.transform, transform);
|
||||
|
||||
return deferred.promise;
|
||||
await new Promise(resolve => transition.on("end", resolve));
|
||||
return offset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private onGraphDataUpdate(graph: GraphData<D3Node, D3Link>) {
|
||||
@@ -435,7 +431,7 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private animateRemoveExitSelections(): Q.Promise<void> {
|
||||
private animateRemoveExitSelections(): Promise<void> {
|
||||
const deferred1 = Q.defer<void>();
|
||||
const deferred2 = Q.defer<void>();
|
||||
const linkExitSelection = this.linkSelection.exit();
|
||||
@@ -508,7 +504,7 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
deferred2.resolve();
|
||||
}
|
||||
|
||||
return Q.allSettled([deferred1.promise, deferred2.promise]).then(undefined);
|
||||
return Promise.all([deferred1.promise, deferred2.promise]).then(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -892,7 +892,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
backendPromise.then(
|
||||
(result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge),
|
||||
(error: any) => {
|
||||
const errorMsg = `Failure in submitting query: ${query}: ${JSON.stringify(error)}`;
|
||||
const errorMsg = `Failure in submitting query: ${query}: ${error.message}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
filterQueryError: errorMsg
|
||||
@@ -1826,7 +1826,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
promise
|
||||
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
||||
.catch((error: any) => {
|
||||
const errorMsg = `Failed to process query result: ${JSON.stringify(error)}`;
|
||||
const errorMsg = `Failed to process query result: ${error.message}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
filterQueryError: errorMsg
|
||||
|
||||
@@ -57,9 +57,9 @@ export class GremlinClient {
|
||||
this.flushResult(result.requestId);
|
||||
}
|
||||
},
|
||||
failureCallback: (result: Result, error: string) => {
|
||||
failureCallback: (result: Result, error: any) => {
|
||||
if (typeof error !== "string") {
|
||||
error = JSON.stringify(error);
|
||||
error = error.message;
|
||||
}
|
||||
|
||||
const requestId = result.requestId;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import * as Q from "q";
|
||||
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
||||
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
||||
|
||||
@@ -41,11 +40,11 @@ describe("Property pane", () => {
|
||||
node: highlightedNode,
|
||||
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
||||
collectionPartitionKeyProperty: null,
|
||||
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
||||
updateVertexProperties: (editedProperties: EditedProperties): Promise<void> => Promise.resolve(),
|
||||
selectNode: (id: string): void => {},
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
||||
updatePossibleVertices: async (): Promise<PossibleVertex[]> => null,
|
||||
possibleEdgeLabels: null,
|
||||
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
||||
editGraphEdges: async (editedEdges: EditedEdges): Promise<any> => undefined,
|
||||
deleteHighlightedNode: (): void => {},
|
||||
onModeChanged: (newMode: Mode): void => {},
|
||||
viewMode: Mode.READONLY_PROP
|
||||
|
||||
@@ -36,11 +36,11 @@ export interface NodePropertiesComponentProps {
|
||||
node: GraphHighlightedNodeData;
|
||||
getPkIdFromNodeData: (v: GraphHighlightedNodeData) => string;
|
||||
collectionPartitionKeyProperty: string;
|
||||
updateVertexProperties: (editedProperties: EditedProperties) => Q.Promise<void>;
|
||||
updateVertexProperties: (editedProperties: EditedProperties) => Promise<void>;
|
||||
selectNode: (id: string) => void;
|
||||
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
|
||||
updatePossibleVertices: () => Promise<PossibleVertex[]>;
|
||||
possibleEdgeLabels: Item[];
|
||||
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<any>;
|
||||
editGraphEdges: (editedEdges: EditedEdges) => Promise<any>;
|
||||
deleteHighlightedNode: () => void;
|
||||
onModeChanged: (newMode: Mode) => void;
|
||||
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function configureStore(
|
||||
onTraceFailure(title, `${error.message} ${JSON.stringify(error.stack)}`);
|
||||
console.error(error);
|
||||
} else {
|
||||
onTraceFailure(title, JSON.stringify(error));
|
||||
onTraceFailure(title, error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -106,3 +106,7 @@
|
||||
.expanded::before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-list .main {
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -70,8 +70,7 @@ export class BrowseQueriesPane extends ContextualPaneBase {
|
||||
},
|
||||
startKey
|
||||
);
|
||||
this.formErrors("Failed to setup a collection for saved queries");
|
||||
this.formErrors(`Failed to setup a collection for saved queries: ${JSON.stringify(error)}`);
|
||||
this.formErrors(`Failed to setup a collection for saved queries: ${error.message}`);
|
||||
} finally {
|
||||
this.isExecuting(false);
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
this.isExecuting(true);
|
||||
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
|
||||
let createTableAndKeyspacePromise: Q.Promise<any>;
|
||||
let createTableAndKeyspacePromise: Promise<any>;
|
||||
const toCreateKeyspace: boolean = this.keyspaceCreateNew();
|
||||
const useAutoPilotForKeyspace: boolean =
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput()) ||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
jest.mock("../../Common/dataAccess/deleteCollection");
|
||||
import * as ko from "knockout";
|
||||
import * as sinon from "sinon";
|
||||
import Q from "q";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -58,7 +57,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||
let fakeExplorer = new Explorer();
|
||||
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
||||
fakeExplorer.refreshAllDatabases = () => Promise.resolve();
|
||||
|
||||
let pane = new DeleteCollectionConfirmationPane({
|
||||
id: "deletecollectionconfirmationpane",
|
||||
@@ -119,7 +118,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
fakeExplorer.selectedNode = ko.observable<TreeNode>();
|
||||
fakeExplorer.isLastCollection = () => true;
|
||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
||||
fakeExplorer.refreshAllDatabases = () => Promise.resolve();
|
||||
|
||||
let pane = new DeleteCollectionConfirmationPane({
|
||||
id: "deletecollectionconfirmationpane",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
jest.mock("../../Common/dataAccess/deleteDatabase");
|
||||
jest.mock("../../Shared/Telemetry/TelemetryProcessor");
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -91,7 +90,7 @@ describe("Delete Database Confirmation Pane", () => {
|
||||
collections: ko.observableArray<ViewModels.Collection>()
|
||||
} as ViewModels.Database;
|
||||
};
|
||||
fakeExplorer.refreshAllDatabases = () => Q.resolve();
|
||||
fakeExplorer.refreshAllDatabases = () => Promise.resolve();
|
||||
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||
fakeExplorer.selectedDatabaseId = ko.computed<string>(() => selectedDatabaseId);
|
||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -31,7 +30,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
this.resetData();
|
||||
}
|
||||
|
||||
public submit(): Q.Promise<any> {
|
||||
public async submit(): Promise<any> {
|
||||
if (!this._isValid()) {
|
||||
const selectedDatabase: ViewModels.Database = this.container.findSelectedDatabase();
|
||||
this.formErrors("Input database name does not match the selected database");
|
||||
@@ -39,7 +38,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
ConsoleDataType.Error,
|
||||
`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}: ${this.formErrors()}`
|
||||
);
|
||||
return Q.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.formErrors("");
|
||||
@@ -53,7 +52,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
paneTitle: this.title()
|
||||
});
|
||||
// TODO: Should not be a Q promise anymore, but the Cassandra code requires it
|
||||
let promise: Q.Promise<any>;
|
||||
let promise: Promise<any>;
|
||||
if (this.container.isPreferredApiCassandra()) {
|
||||
promise = (<CassandraAPIDataClient>this.container.tableDataClient).deleteTableOrKeyspace(
|
||||
this.container.databaseAccount().properties.cassandraEndpoint,
|
||||
@@ -62,7 +61,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
|
||||
this.container
|
||||
);
|
||||
} else {
|
||||
promise = Q(deleteDatabase(selectedDatabase.id()));
|
||||
promise = deleteDatabase(selectedDatabase.id());
|
||||
}
|
||||
return promise.then(
|
||||
() => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import * as Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
@@ -97,33 +96,30 @@ export class LoadQueryPane extends ContextualPaneBase {
|
||||
return true;
|
||||
};
|
||||
|
||||
public loadQueryFromFile(file: File): Q.Promise<void> {
|
||||
public async loadQueryFromFile(file: File): Promise<void> {
|
||||
const selectedCollection: ViewModels.Collection = this.container && this.container.findSelectedCollection();
|
||||
if (!selectedCollection) {
|
||||
// should never get into this state
|
||||
Logger.logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
|
||||
return Q.reject("No collection was selected");
|
||||
throw new Error("No collection was selected");
|
||||
} else if (this.container.isPreferredApiMongoDB()) {
|
||||
selectedCollection.onNewMongoQueryClick(selectedCollection, null);
|
||||
} else {
|
||||
selectedCollection.onNewQueryClick(selectedCollection, null);
|
||||
}
|
||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt: any): void => {
|
||||
const fileData: string = evt.target.result;
|
||||
const queryTab = this.container.tabsManager.activeTab() as QueryTab;
|
||||
queryTab.initialEditorContent(fileData);
|
||||
queryTab.sqlQueryEditorContent(fileData);
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
reader.onerror = (evt: ProgressEvent): void => {
|
||||
deferred.reject((evt as any).error.message);
|
||||
throw new Error((evt as any).error.message);
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
private updateSelectedFilesTitle(fileList: FileList) {
|
||||
|
||||
@@ -82,7 +82,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
|
||||
this.container
|
||||
.renewShareAccess(this.accessKey())
|
||||
.fail((error: any) => {
|
||||
const errorMessage: string = JSON.stringify(error);
|
||||
const errorMessage: string = error.message;
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`);
|
||||
this.formErrors(errorMessage);
|
||||
this.formErrorsDetails(errorMessage);
|
||||
|
||||
@@ -88,7 +88,7 @@ export class SaveQueryPane extends ContextualPaneBase {
|
||||
(error: any) => {
|
||||
this.isExecuting(false);
|
||||
if (typeof error != "string") {
|
||||
error = JSON.stringify(error);
|
||||
error = error.message;
|
||||
}
|
||||
this.formErrors("Failed to save query");
|
||||
this.formErrorsDetails(`Failed to save query: ${error}`);
|
||||
@@ -143,7 +143,7 @@ export class SaveQueryPane extends ContextualPaneBase {
|
||||
startKey
|
||||
);
|
||||
this.formErrors("Failed to setup a container for saved queries");
|
||||
this.formErrors(`Failed to setup a container for saved queries: ${JSON.stringify(error)}`);
|
||||
this.formErrors(`Failed to setup a container for saved queries: ${error.message}`);
|
||||
} finally {
|
||||
this.isExecuting(false);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class SetupNotebooksPane extends ContextualPaneBase {
|
||||
"Successfully created a default notebook workspace for the account"
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = typeof error == "string" ? error : JSON.stringify(error);
|
||||
const errorMessage = typeof error == "string" ? error : error.message;
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.CreateNotebookWorkspace,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import _ from "underscore";
|
||||
import Q from "q";
|
||||
|
||||
import * as Entities from "../Entities";
|
||||
import * as QueryBuilderConstants from "../Constants";
|
||||
@@ -84,11 +83,11 @@ export function filterColumns(table: DataTables.DataTable, settings: boolean[]):
|
||||
* Reorder columns based on current order.
|
||||
* If no current order is specified, reorder the columns based on intial order.
|
||||
*/
|
||||
export function reorderColumns(
|
||||
export async function reorderColumns(
|
||||
table: DataTables.DataTable,
|
||||
targetOrder: number[],
|
||||
currentOrder?: number[]
|
||||
): Q.Promise<any> {
|
||||
): Promise<any> {
|
||||
var columnsCount: number = targetOrder.length;
|
||||
var isCurrentOrderPassedIn: boolean = !!currentOrder;
|
||||
if (!isCurrentOrderPassedIn) {
|
||||
@@ -107,13 +106,9 @@ export function reorderColumns(
|
||||
var transformationOrder: number[] = isCurrentOrderPassedIn
|
||||
? calculateTransformationOrder(currentOrder, targetOrder)
|
||||
: targetOrder;
|
||||
try {
|
||||
$.fn.dataTable.ColReorder(table).fnOrder(transformationOrder);
|
||||
} catch (err) {
|
||||
return Q.reject(err);
|
||||
}
|
||||
$.fn.dataTable.ColReorder(table).fnOrder(transformationOrder);
|
||||
}
|
||||
return Q.resolve(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resetColumns(table: DataTables.DataTable): void {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import _ from "underscore";
|
||||
import Q from "q";
|
||||
import * as DataTableUtilities from "./DataTableUtilities";
|
||||
import * as DataTableOperations from "./DataTableOperations";
|
||||
import TableEntityListViewModel from "./TableEntityListViewModel";
|
||||
@@ -49,7 +48,7 @@ export default class TableCommands {
|
||||
/**
|
||||
* Edit entity
|
||||
*/
|
||||
public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
|
||||
public editEntityCommand(viewModel: TableEntityListViewModel): Promise<any> {
|
||||
if (!viewModel) {
|
||||
return null; // Error
|
||||
}
|
||||
@@ -68,7 +67,7 @@ export default class TableCommands {
|
||||
return null;
|
||||
}
|
||||
|
||||
public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
|
||||
public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Promise<any> {
|
||||
if (!viewModel) {
|
||||
return null; // Error
|
||||
}
|
||||
@@ -92,7 +91,7 @@ export default class TableCommands {
|
||||
return null;
|
||||
}
|
||||
|
||||
public customizeColumnsCommand(viewModel: TableEntityListViewModel): Q.Promise<any> {
|
||||
public customizeColumnsCommand(viewModel: TableEntityListViewModel): Promise<any> {
|
||||
var table: DataTables.DataTable = viewModel.table;
|
||||
var displayedColumnNames: string[] = DataTableOperations.getDataTableHeaders(table);
|
||||
var columnsCount: number = displayedColumnNames.length;
|
||||
@@ -120,7 +119,7 @@ export default class TableCommands {
|
||||
return null;
|
||||
}
|
||||
|
||||
public reorderColumnsBasedOnSelectedEntities(viewModel: TableEntityListViewModel): Q.Promise<boolean> {
|
||||
public reorderColumnsBasedOnSelectedEntities(viewModel: TableEntityListViewModel): Promise<boolean> {
|
||||
var selected = viewModel.selected();
|
||||
if (!selected || !selected.length) {
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as _ from "underscore";
|
||||
import Q from "q";
|
||||
import * as Entities from "./Entities";
|
||||
import { CassandraTableKey } from "./TableDataClient";
|
||||
import * as Constants from "./Constants";
|
||||
@@ -19,8 +18,8 @@ export function guid() {
|
||||
/**
|
||||
* Returns a promise that resolves in the specified number of milliseconds.
|
||||
*/
|
||||
export function delay(milliseconds: number): Q.Promise<any> {
|
||||
return Q.delay(milliseconds);
|
||||
export function delay(milliseconds: number): Promise<any> {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -225,7 +224,7 @@ export default class ConflictsTab extends TabsBase {
|
||||
});
|
||||
}
|
||||
|
||||
public refreshDocumentsGrid(): Q.Promise<any> {
|
||||
public refreshDocumentsGrid(): Promise<any> {
|
||||
// clear documents grid
|
||||
this.conflictIds([]);
|
||||
return this.createIterator()
|
||||
@@ -256,19 +255,17 @@ export default class ConflictsTab extends TabsBase {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onConflictIdClick(clickedDocumentId: ConflictId): Q.Promise<any> {
|
||||
public async onConflictIdClick(clickedDocumentId: ConflictId): Promise<any> {
|
||||
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onAcceptChangesClick = (): Q.Promise<any> => {
|
||||
public onAcceptChangesClick = async (): Promise<any> => {
|
||||
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isExecutionError(false);
|
||||
@@ -286,7 +283,7 @@ export default class ConflictsTab extends TabsBase {
|
||||
conflictResourceId: selectedConflict.resourceId
|
||||
});
|
||||
|
||||
let operationPromise: Q.Promise<any> = Q();
|
||||
let operationPromise: Promise<any>;
|
||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) {
|
||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||
|
||||
@@ -358,7 +355,7 @@ export default class ConflictsTab extends TabsBase {
|
||||
.finally(() => this.isExecuting(false));
|
||||
};
|
||||
|
||||
public onDeleteClick = (): Q.Promise<any> => {
|
||||
public onDeleteClick = (): Promise<any> => {
|
||||
this.isExecutionError(false);
|
||||
this.isExecuting(true);
|
||||
|
||||
@@ -418,40 +415,34 @@ export default class ConflictsTab extends TabsBase {
|
||||
.finally(() => this.isExecuting(false));
|
||||
};
|
||||
|
||||
public onDiscardClick = (): Q.Promise<any> => {
|
||||
public onDiscardClick = async (): Promise<any> => {
|
||||
this.selectedConflictContent(this.selectedConflictContent.getEditableOriginalValue());
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onValidDocumentEdit(): Q.Promise<any> {
|
||||
public async onValidDocumentEdit(): Promise<any> {
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid);
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onInvalidDocumentEdit(): Q.Promise<any> {
|
||||
public async onInvalidDocumentEdit(): Promise<any> {
|
||||
if (
|
||||
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentNoEdits ||
|
||||
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid
|
||||
) {
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid);
|
||||
return Q();
|
||||
}
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public onTabClick(): Promise<any> {
|
||||
return super.onTabClick().then(() => {
|
||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
||||
});
|
||||
}
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
public onActivate(): Promise<any> {
|
||||
return super.onActivate().then(() => {
|
||||
if (this._documentsIterator) {
|
||||
return Q.resolve(this._documentsIterator);
|
||||
return this._documentsIterator;
|
||||
}
|
||||
|
||||
return this.createIterator().then(
|
||||
@@ -481,7 +472,7 @@ export default class ConflictsTab extends TabsBase {
|
||||
});
|
||||
}
|
||||
|
||||
public onRefreshClick(): Q.Promise<any> {
|
||||
public onRefreshClick(): Promise<any> {
|
||||
return this.refreshDocumentsGrid().then(() => {
|
||||
this.selectedConflictContent("");
|
||||
this.selectedConflictId(null);
|
||||
@@ -489,7 +480,7 @@ export default class ConflictsTab extends TabsBase {
|
||||
});
|
||||
}
|
||||
|
||||
public createIterator(): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||
public createIterator(): Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||
// TODO: Conflict Feed does not allow filtering atm
|
||||
const query: string = undefined;
|
||||
let options: any = {};
|
||||
@@ -497,7 +488,7 @@ export default class ConflictsTab extends TabsBase {
|
||||
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options);
|
||||
}
|
||||
|
||||
public loadNextPage(): Q.Promise<any> {
|
||||
public loadNextPage(): Promise<any> {
|
||||
this.isExecuting(true);
|
||||
this.isExecutionError(false);
|
||||
return this._loadNextPageInternal()
|
||||
@@ -564,8 +555,8 @@ export default class ConflictsTab extends TabsBase {
|
||||
}
|
||||
};
|
||||
|
||||
protected _loadNextPageInternal(): Q.Promise<DataModels.ConflictId[]> {
|
||||
return Q(this._documentsIterator.fetchNext().then(response => response.resources));
|
||||
protected _loadNextPageInternal(): Promise<DataModels.ConflictId[]> {
|
||||
return this._documentsIterator.fetchNext().then(response => response.resources);
|
||||
}
|
||||
|
||||
protected _onEditorContentChange(newContent: string) {
|
||||
@@ -577,22 +568,20 @@ export default class ConflictsTab extends TabsBase {
|
||||
}
|
||||
}
|
||||
|
||||
public initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Q.Promise<any> {
|
||||
public async initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Promise<any> {
|
||||
if (documentId) {
|
||||
let parsedConflictContent: any = JSON.parse(documentToInsert);
|
||||
const renderedConflictContent: string = this.renderObjectForEditor(parsedConflictContent, null, 4);
|
||||
this.selectedConflictContent.setBaseline(renderedConflictContent);
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
}
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public initDocumentEditorForReplace(
|
||||
public async initDocumentEditorForReplace(
|
||||
documentId: ConflictId,
|
||||
conflictContent: any,
|
||||
currentContent: any
|
||||
): Q.Promise<any> {
|
||||
): Promise<any> {
|
||||
if (documentId) {
|
||||
currentContent = ConflictsTab.removeSystemProperties(currentContent);
|
||||
const renderedCurrentContent: string = this.renderObjectForEditor(currentContent, null, 4);
|
||||
@@ -605,11 +594,9 @@ export default class ConflictsTab extends TabsBase {
|
||||
this.selectedConflictContent.setBaseline(renderedConflictContent);
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
}
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Q.Promise<any> {
|
||||
public async initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Promise<any> {
|
||||
if (documentId) {
|
||||
let parsedDocumentToDelete: any = JSON.parse(documentToDelete);
|
||||
parsedDocumentToDelete = ConflictsTab.removeSystemProperties(parsedDocumentToDelete);
|
||||
@@ -617,15 +604,12 @@ export default class ConflictsTab extends TabsBase {
|
||||
this.selectedConflictContent.setBaseline(renderedDocumentToDelete);
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
}
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public initDocumentEditorForNoOp(documentId: ConflictId): Q.Promise<any> {
|
||||
public async initDocumentEditorForNoOp(documentId: ConflictId): Promise<any> {
|
||||
this.selectedConflictContent(null);
|
||||
this.selectedConflictCurrent(null);
|
||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||
return Q();
|
||||
}
|
||||
|
||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||
|
||||
@@ -85,13 +85,6 @@
|
||||
<span>Learn more about minimum throughput </span>
|
||||
<a href="https://docs.microsoft.com/azure/cosmos-db/set-throughput" target="_blank">here.</a>
|
||||
</p>
|
||||
<p data-bind="visible: canRequestBelowMinRU">
|
||||
Need to scale below <span data-bind="text: minRUsText"></span> RU/s? Reach out by filling
|
||||
<a
|
||||
href="https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbRzBPrdEMjvxPuDm8fCLUtXpUQ0JLV1Y5VVlDS1RNUE1aRzVHQlVQTVA1SS4u"
|
||||
>this questionnaire</a
|
||||
>.
|
||||
</p>
|
||||
<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"
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as SharedConstants from "../../Shared/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import DiscardIcon from "../../../images/discard.svg";
|
||||
import editable from "../../Common/EditableUtility";
|
||||
import Q from "q";
|
||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||
import TabsBase from "./TabsBase";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -22,12 +21,12 @@ import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/update
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
|
||||
const updateThroughputBeyondLimitWarningMessage: string = `
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
The service will scale out and increase throughput for the selected database.
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
The service will scale out and increase throughput for the selected database.
|
||||
This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`;
|
||||
|
||||
const updateThroughputDelayedApplyWarningMessage: string = `
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
This operation will take some time to complete.`;
|
||||
|
||||
const currentThroughput: (isAutoscale: boolean, throughput: number) => string = (isAutoscale, throughput) =>
|
||||
@@ -36,17 +35,17 @@ const currentThroughput: (isAutoscale: boolean, throughput: number) => string =
|
||||
: `Current manual throughput: ${throughput} RU/s`;
|
||||
|
||||
const throughputApplyDelayedMessage = (isAutoscale: boolean, throughput: number, databaseName: string) =>
|
||||
`The request to increase the throughput has successfully been submitted.
|
||||
`The request to increase the throughput has successfully been submitted.
|
||||
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
|
||||
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
|
||||
|
||||
const throughputApplyShortDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) =>
|
||||
`A request to increase the throughput is currently in progress.
|
||||
`A request to increase the throughput is currently in progress.
|
||||
This operation will take some time to complete.<br />
|
||||
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
|
||||
|
||||
const throughputApplyLongDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) =>
|
||||
`A request to increase the throughput is currently in progress.
|
||||
`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: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
|
||||
|
||||
@@ -61,7 +60,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
public saveSettingsButton: ViewModels.Button;
|
||||
public discardSettingsChangesButton: ViewModels.Button;
|
||||
|
||||
public canRequestBelowMinRU: ko.PureComputed<boolean>;
|
||||
public canRequestSupport: ko.PureComputed<boolean>;
|
||||
public canThroughputExceedMaximumValue: ko.Computed<boolean>;
|
||||
public costsVisible: ko.Computed<boolean>;
|
||||
@@ -69,7 +67,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
public isTemplateReady: ko.Observable<boolean>;
|
||||
public minRUAnotationVisible: ko.Computed<boolean>;
|
||||
public minRUs: ko.Computed<number>;
|
||||
public minRUsText: ko.PureComputed<string>;
|
||||
public maxRUs: ko.Computed<number>;
|
||||
public maxRUsText: ko.PureComputed<string>;
|
||||
public maxRUThroughputInputLimit: ko.Computed<number>;
|
||||
@@ -205,11 +202,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
this.canThroughputExceedMaximumValue = ko.pureComputed<boolean>(
|
||||
() => configContext.platform === Platform.Portal && !this.container.isRunningOnNationalCloud()
|
||||
);
|
||||
|
||||
this.canRequestBelowMinRU = ko.pureComputed(() => {
|
||||
return true; // TODO: Implement and test
|
||||
});
|
||||
|
||||
this.canRequestSupport = ko.pureComputed(() => {
|
||||
if (
|
||||
configContext.platform === Platform.Emulator ||
|
||||
@@ -284,8 +276,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
return this.maxRUs();
|
||||
});
|
||||
|
||||
this.minRUsText = ko.pureComputed(() => this.minRUs().toLocaleString());
|
||||
|
||||
this.maxRUsText = ko.pureComputed(() => {
|
||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million.toLocaleString();
|
||||
});
|
||||
@@ -553,7 +543,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
}
|
||||
};
|
||||
|
||||
public onRevertClick = (): Q.Promise<any> => {
|
||||
public onRevertClick = async (): Promise<any> => {
|
||||
this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
|
||||
this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue());
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
@@ -561,11 +551,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
} else {
|
||||
this.selectedAutoPilotTier.setBaseline(this.selectedAutoPilotTier.getEditableOriginalValue());
|
||||
}
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
public onActivate(): Promise<any> {
|
||||
return super.onActivate().then(async () => {
|
||||
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||
await this.database.loadOffer();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -340,24 +339,21 @@ export default class DocumentsTab extends TabsBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
public onShowFilterClick(): Q.Promise<any> {
|
||||
public async onShowFilterClick(): Promise<any> {
|
||||
this.isFilterCreated(true);
|
||||
this.isFilterExpanded(true);
|
||||
|
||||
$(".filterDocExpanded").addClass("active");
|
||||
$("#content").addClass("active");
|
||||
$(".querydropdown").focus();
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onHideFilterClick(): Q.Promise<any> {
|
||||
public async onHideFilterClick(): Promise<any> {
|
||||
this.isFilterExpanded(false);
|
||||
|
||||
$(".filterDocExpanded").removeClass("active");
|
||||
$("#content").removeClass("active");
|
||||
$(".queryButton").focus();
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onCloseButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
@@ -369,7 +365,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onApplyFilterClick(): Q.Promise<any> {
|
||||
public onApplyFilterClick(): Promise<any> {
|
||||
// clear documents grid
|
||||
this.documentIds([]);
|
||||
return this.createIterator()
|
||||
@@ -398,7 +394,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
});
|
||||
}
|
||||
|
||||
public refreshDocumentsGrid(): Q.Promise<any> {
|
||||
public refreshDocumentsGrid(): Promise<any> {
|
||||
return this.onApplyFilterClick();
|
||||
}
|
||||
|
||||
@@ -411,19 +407,17 @@ export default class DocumentsTab extends TabsBase {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onDocumentIdClick(clickedDocumentId: DocumentId): Q.Promise<any> {
|
||||
public async onDocumentIdClick(clickedDocumentId: DocumentId): Promise<any> {
|
||||
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onNewDocumentClick = (): Q.Promise<any> => {
|
||||
public onNewDocumentClick = async (): Promise<any> => {
|
||||
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
this.selectedDocumentId(null);
|
||||
|
||||
@@ -431,11 +425,9 @@ export default class DocumentsTab extends TabsBase {
|
||||
this.initialDocumentContent(defaultDocument);
|
||||
this.selectedDocumentContent.setBaseline(defaultDocument);
|
||||
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onSaveNewDocumentClick = (): Q.Promise<any> => {
|
||||
public onSaveNewDocumentClick = (): Promise<any> => {
|
||||
this.isExecutionError(false);
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||
@@ -493,15 +485,13 @@ export default class DocumentsTab extends TabsBase {
|
||||
.finally(() => this.isExecuting(false));
|
||||
};
|
||||
|
||||
public onRevertNewDocumentClick = (): Q.Promise<any> => {
|
||||
public onRevertNewDocumentClick = async (): Promise<any> => {
|
||||
this.initialDocumentContent("");
|
||||
this.selectedDocumentContent("");
|
||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onSaveExisitingDocumentClick = (): Q.Promise<any> => {
|
||||
public onSaveExisitingDocumentClick = (): Promise<any> => {
|
||||
const selectedDocumentId = this.selectedDocumentId();
|
||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
||||
|
||||
@@ -560,15 +550,13 @@ export default class DocumentsTab extends TabsBase {
|
||||
.finally(() => this.isExecuting(false));
|
||||
};
|
||||
|
||||
public onRevertExisitingDocumentClick = (): Q.Promise<any> => {
|
||||
public onRevertExisitingDocumentClick = async (): Promise<any> => {
|
||||
this.selectedDocumentContent.setBaseline(this.initialDocumentContent());
|
||||
this.initialDocumentContent.valueHasMutated();
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onDeleteExisitingDocumentClick = (): Q.Promise<any> => {
|
||||
public onDeleteExisitingDocumentClick = async (): Promise<any> => {
|
||||
const selectedDocumentId = this.selectedDocumentId();
|
||||
const msg = !this.isPreferredApiMongoDB
|
||||
? "Are you sure you want to delete the selected item ?"
|
||||
@@ -577,30 +565,27 @@ export default class DocumentsTab extends TabsBase {
|
||||
if (window.confirm(msg)) {
|
||||
return this._deleteDocument(selectedDocumentId);
|
||||
}
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onValidDocumentEdit(): Q.Promise<any> {
|
||||
public async onValidDocumentEdit(): Promise<any> {
|
||||
if (
|
||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid ||
|
||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid
|
||||
) {
|
||||
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid);
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onInvalidDocumentEdit(): Q.Promise<any> {
|
||||
public async onInvalidDocumentEdit(): Promise<any> {
|
||||
if (
|
||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid ||
|
||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid
|
||||
) {
|
||||
this.editorState(ViewModels.DocumentExplorerState.newDocumentInvalid);
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -608,22 +593,20 @@ export default class DocumentsTab extends TabsBase {
|
||||
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid
|
||||
) {
|
||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid);
|
||||
return Q();
|
||||
return;
|
||||
}
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public onTabClick(): Promise<any> {
|
||||
return super.onTabClick().then(() => {
|
||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||
});
|
||||
}
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
public onActivate(): Promise<any> {
|
||||
return super.onActivate().then(() => {
|
||||
if (this._documentsIterator) {
|
||||
return Q.resolve(this._documentsIterator);
|
||||
return this._documentsIterator;
|
||||
}
|
||||
|
||||
return this.createIterator().then(
|
||||
@@ -653,7 +636,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
});
|
||||
}
|
||||
|
||||
public onRefreshClick(): Q.Promise<any> {
|
||||
public onRefreshClick(): Promise<any> {
|
||||
return this.refreshDocumentsGrid().then(() => {
|
||||
this.selectedDocumentContent("");
|
||||
this.selectedDocumentId(null);
|
||||
@@ -665,11 +648,11 @@ export default class DocumentsTab extends TabsBase {
|
||||
return window.confirm(msg);
|
||||
};
|
||||
|
||||
protected __deleteDocument(documentId: DocumentId): Q.Promise<any> {
|
||||
protected __deleteDocument(documentId: DocumentId): Promise<any> {
|
||||
return deleteDocument(this.collection, documentId);
|
||||
}
|
||||
|
||||
private _deleteDocument(selectedDocumentId: DocumentId): Q.Promise<any> {
|
||||
private _deleteDocument(selectedDocumentId: DocumentId): Promise<any> {
|
||||
this.isExecutionError(false);
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
|
||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||
@@ -714,7 +697,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
.finally(() => this.isExecuting(false));
|
||||
}
|
||||
|
||||
public createIterator(): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||
public createIterator(): Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||
let filters = this.lastFilterContents();
|
||||
const filter: string = this.filterContent().trim();
|
||||
const query: string = this.buildQuery(filter);
|
||||
@@ -728,14 +711,14 @@ export default class DocumentsTab extends TabsBase {
|
||||
return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
|
||||
}
|
||||
|
||||
public selectDocument(documentId: DocumentId): Q.Promise<any> {
|
||||
public selectDocument(documentId: DocumentId): Promise<any> {
|
||||
this.selectedDocumentId(documentId);
|
||||
return readDocument(this.collection, documentId).then((content: any) => {
|
||||
this.initDocumentEditor(documentId, content);
|
||||
});
|
||||
}
|
||||
|
||||
public loadNextPage(): Q.Promise<any> {
|
||||
public loadNextPage(): Promise<any> {
|
||||
this.isExecuting(true);
|
||||
this.isExecutionError(false);
|
||||
return this._loadNextPageInternal()
|
||||
@@ -776,7 +759,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
this.isExecutionError(true);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
typeof error === "string" ? error : JSON.stringify(error)
|
||||
typeof error === "string" ? error : error.message
|
||||
);
|
||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceFailure(
|
||||
@@ -809,8 +792,8 @@ export default class DocumentsTab extends TabsBase {
|
||||
}
|
||||
};
|
||||
|
||||
protected _loadNextPageInternal(): Q.Promise<DataModels.DocumentId[]> {
|
||||
return Q(this._documentsIterator.fetchNext().then(response => response.resources));
|
||||
protected _loadNextPageInternal(): Promise<DataModels.DocumentId[]> {
|
||||
return this._documentsIterator.fetchNext().then(response => response.resources);
|
||||
}
|
||||
|
||||
protected _onEditorContentChange(newContent: string) {
|
||||
@@ -822,7 +805,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
}
|
||||
}
|
||||
|
||||
public initDocumentEditor(documentId: DocumentId, documentContent: any): Q.Promise<any> {
|
||||
public async initDocumentEditor(documentId: DocumentId, documentContent: any): Promise<any> {
|
||||
if (documentId) {
|
||||
const content: string = this.renderObjectForEditor(documentContent, null, 4);
|
||||
this.selectedDocumentContent.setBaseline(content);
|
||||
@@ -832,8 +815,6 @@ export default class DocumentsTab extends TabsBase {
|
||||
: ViewModels.DocumentExplorerState.newDocumentValid;
|
||||
this.editorState(newState);
|
||||
}
|
||||
|
||||
return Q();
|
||||
}
|
||||
|
||||
public buildQuery(filter: string): string {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import * as Q from "q";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import TabsBase from "./TabsBase";
|
||||
import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter";
|
||||
@@ -114,7 +113,7 @@ export default class GraphTab extends TabsBase {
|
||||
: `${account.name}.graphs.azure.com:443/`;
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public onTabClick(): Promise<any> {
|
||||
return super.onTabClick().then(() => {
|
||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Q from "q";
|
||||
import MongoUtility from "../../Common/MongoUtility";
|
||||
import QueryTab from "./QueryTab";
|
||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||
@@ -20,10 +19,10 @@ export default class MongoQueryTab extends QueryTab {
|
||||
return MongoUtility.tojson(value, null, false);
|
||||
}
|
||||
|
||||
protected _initIterator(): Q.Promise<MinimalQueryIterator> {
|
||||
protected async _initIterator(): Promise<MinimalQueryIterator> {
|
||||
let options: any = {};
|
||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
||||
this._iterator = queryIterator(this.collection.databaseId, this.collection, this.sqlStatementToExecute());
|
||||
return Q(this._iterator);
|
||||
return this._iterator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
|
||||
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
||||
import Q from "q";
|
||||
import TabsBase from "./TabsBase";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -53,7 +52,7 @@ export default class MongoShellTab extends TabsBase {
|
||||
// }
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public onTabClick(): Promise<any> {
|
||||
return super.onTabClick().then(() => {
|
||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as _ from "underscore";
|
||||
import * as Q from "q";
|
||||
import * as ko from "knockout";
|
||||
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -84,7 +83,7 @@ export default class NotebookTabV2 extends TabsBase {
|
||||
});
|
||||
}
|
||||
|
||||
public onCloseTabButtonClick(): Q.Promise<any> {
|
||||
public async onCloseTabButtonClick(): Promise<any> {
|
||||
const cleanup = () => {
|
||||
this.notebookComponentAdapter.notebookShutdown();
|
||||
this.isActive(false);
|
||||
@@ -100,11 +99,11 @@ export default class NotebookTabV2 extends TabsBase {
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
return Q.resolve(null);
|
||||
} else {
|
||||
cleanup();
|
||||
return Q.resolve(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async reconfigureServiceEndpoints() {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -163,13 +162,13 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
||||
this._buildCommandBarOptions();
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public onTabClick(): Promise<any> {
|
||||
return super.onTabClick().then(() => {
|
||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
|
||||
});
|
||||
}
|
||||
|
||||
public onExecuteQueryClick = (): Q.Promise<any> => {
|
||||
public onExecuteQueryClick = (): Promise<any> => {
|
||||
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
|
||||
this.sqlStatementToExecute(sqlStatement);
|
||||
this.allResultsMetadata([]);
|
||||
@@ -191,7 +190,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
||||
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
|
||||
};
|
||||
|
||||
public onFetchNextPageClick(): Q.Promise<any> {
|
||||
public onFetchNextPageClick(): Promise<any> {
|
||||
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
||||
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
||||
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
|
||||
@@ -265,7 +264,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
||||
return true;
|
||||
};
|
||||
|
||||
private _executeQueryDocumentsPage(firstItemIndex: number): Q.Promise<any> {
|
||||
private _executeQueryDocumentsPage(firstItemIndex: number): Promise<any> {
|
||||
this.errors([]);
|
||||
this.roundTrips(undefined);
|
||||
if (this._iterator == null) {
|
||||
@@ -277,7 +276,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
||||
}
|
||||
|
||||
// TODO: Position and enable spinner when request is in progress
|
||||
private _queryDocumentsPage(firstItemIndex: number): Q.Promise<any> {
|
||||
private _queryDocumentsPage(firstItemIndex: number): Promise<any> {
|
||||
this.isExecutionError(false);
|
||||
this._resetAggregateQueryMetrics();
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
|
||||
@@ -485,16 +484,14 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
||||
}
|
||||
}
|
||||
|
||||
protected _initIterator(): Q.Promise<MinimalQueryIterator> {
|
||||
protected _initIterator(): Promise<MinimalQueryIterator> {
|
||||
const options: any = QueryTab.getIteratorOptions(this.collection);
|
||||
if (this._resourceTokenPartitionKey) {
|
||||
options.partitionKey = this._resourceTokenPartitionKey;
|
||||
}
|
||||
|
||||
return Q(
|
||||
queryDocuments(this.collection.databaseId, this.collection.id(), this.sqlStatementToExecute(), options).then(
|
||||
iterator => (this._iterator = iterator)
|
||||
)
|
||||
return queryDocuments(this.collection.databaseId, this.collection.id(), this.sqlStatementToExecute(), options).then(
|
||||
iterator => (this._iterator = iterator)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import TabsBase from "./TabsBase";
|
||||
import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel";
|
||||
@@ -130,38 +129,38 @@ export default class QueryTablesTab extends TabsBase {
|
||||
this.buildCommandBarOptions();
|
||||
}
|
||||
|
||||
public onExecuteQueryClick = (): Q.Promise<any> => {
|
||||
public onExecuteQueryClick = (): Promise<any> => {
|
||||
this.queryViewModel().runQuery();
|
||||
return null;
|
||||
};
|
||||
|
||||
public onQueryBuilderClick = (): Q.Promise<any> => {
|
||||
public onQueryBuilderClick = (): Promise<any> => {
|
||||
this.queryViewModel().selectHelper();
|
||||
return null;
|
||||
};
|
||||
|
||||
public onQueryTextClick = (): Q.Promise<any> => {
|
||||
public onQueryTextClick = (): Promise<any> => {
|
||||
this.queryViewModel().selectEditor();
|
||||
return null;
|
||||
};
|
||||
|
||||
public onAddEntityClick = (): Q.Promise<any> => {
|
||||
public onAddEntityClick = (): Promise<any> => {
|
||||
this.container.addTableEntityPane.tableViewModel = this.tableEntityListViewModel();
|
||||
this.container.addTableEntityPane.open();
|
||||
return null;
|
||||
};
|
||||
|
||||
public onEditEntityClick = (): Q.Promise<any> => {
|
||||
public onEditEntityClick = (): Promise<any> => {
|
||||
this.tableCommands.editEntityCommand(this.tableEntityListViewModel());
|
||||
return null;
|
||||
};
|
||||
|
||||
public onDeleteEntityClick = (): Q.Promise<any> => {
|
||||
public onDeleteEntityClick = (): Promise<any> => {
|
||||
this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel());
|
||||
return null;
|
||||
};
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
public onActivate(): Promise<any> {
|
||||
return super.onActivate().then(() => {
|
||||
const columns =
|
||||
!!this.tableEntityListViewModel() &&
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as ko from "knockout";
|
||||
import * as monaco from "monaco-editor";
|
||||
import Q from "q";
|
||||
import DiscardIcon from "../../../images/discard.svg";
|
||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
@@ -186,7 +185,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
||||
this._setBaselines();
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public onTabClick(): Promise<any> {
|
||||
return super.onTabClick().then(() => {
|
||||
if (this.isNew()) {
|
||||
this.collection.selectedSubnodeKind(this.tabKind);
|
||||
@@ -197,13 +196,11 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
||||
public abstract onSaveClick: () => Promise<any>;
|
||||
public abstract onUpdateClick: () => Promise<any>;
|
||||
|
||||
public onDiscard = (): Q.Promise<any> => {
|
||||
public onDiscard = async (): Promise<any> => {
|
||||
this.setBaselines();
|
||||
const original = this.editorContent.getEditableOriginalValue();
|
||||
const editorModel = this.editor() && this.editor().getModel();
|
||||
editorModel && editorModel.setValue(original);
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onSaveOrUpdateClick(): Promise<any> {
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as SharedConstants from "../../Shared/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import DiscardIcon from "../../../images/discard.svg";
|
||||
import editable from "../../Common/EditableUtility";
|
||||
import Q from "q";
|
||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||
import TabsBase from "./TabsBase";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -24,21 +23,21 @@ import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/update
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
|
||||
const ttlWarning: string = `
|
||||
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
|
||||
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
|
||||
For more information see, <a target="_blank" href="https://aka.ms/cosmos-db-ttl">Time to Live (TTL) in Azure Cosmos DB</a>.`;
|
||||
|
||||
const indexingPolicyTTLWarningMessage: string = `
|
||||
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.
|
||||
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, <a target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">Modifying Indexing Policies</a>.`;
|
||||
|
||||
const updateThroughputBeyondLimitWarningMessage: string = `
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
The service will scale out and increase throughput for the selected container.
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
The service will scale out and increase throughput for the selected container.
|
||||
This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`;
|
||||
|
||||
const updateThroughputDelayedApplyWarningMessage: string = `
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
You are about to request an increase in throughput beyond the pre-allocated capacity.
|
||||
This operation will take some time to complete.`;
|
||||
|
||||
// TODO: move to a utility classs and add unit tests
|
||||
@@ -82,7 +81,7 @@ const throughputApplyDelayedMessage = (
|
||||
collectionName: string,
|
||||
requestedThroughput: number
|
||||
): string => `
|
||||
The request to increase the throughput has successfully been submitted.
|
||||
The request to increase the throughput has successfully been submitted.
|
||||
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
|
||||
Database: ${databaseName}, Container: ${collectionName} ${currentThroughput(
|
||||
isAutoscale,
|
||||
@@ -115,7 +114,7 @@ const throughputApplyLongDelayMessage = (
|
||||
collectionName: string,
|
||||
requestedThroughput: number
|
||||
): string => `
|
||||
A request to increase the throughput is currently in progress.
|
||||
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: ${databaseName}, Container: ${collectionName} ${currentThroughput(
|
||||
isAutoscale,
|
||||
@@ -492,11 +491,11 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
});
|
||||
|
||||
this.canThroughputExceedMaximumValue = ko.pureComputed<boolean>(() => {
|
||||
const isPublicAzurePortal: boolean =
|
||||
configContext.platform === Platform.Portal && !this.container.isRunningOnNationalCloud();
|
||||
const hasPartitionKey = !!this.collection.partitionKey;
|
||||
|
||||
return isPublicAzurePortal && hasPartitionKey;
|
||||
return (
|
||||
this._isFixedContainer() &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
!this.container.isRunningOnNationalCloud()
|
||||
);
|
||||
});
|
||||
|
||||
this.canRequestSupport = ko.pureComputed(() => {
|
||||
@@ -1231,7 +1230,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.isExecuting(false);
|
||||
};
|
||||
|
||||
public onRevertClick = (): Q.Promise<any> => {
|
||||
public onRevertClick = async (): Promise<any> => {
|
||||
TelemetryProcessor.trace(Action.DiscardSettings, ActionModifiers.Mark, {
|
||||
message: "Settings Discarded"
|
||||
});
|
||||
@@ -1274,21 +1273,17 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.selectedAutoPilotTier(originalAutoPilotTier);
|
||||
}
|
||||
}
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onValidIndexingPolicyEdit(): Q.Promise<any> {
|
||||
public async onValidIndexingPolicyEdit(): Promise<any> {
|
||||
this.indexingPolicyContent.editableIsValid(true);
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onInvalidIndexingPolicyEdit(): Q.Promise<any> {
|
||||
public async onInvalidIndexingPolicyEdit(): Promise<any> {
|
||||
this.indexingPolicyContent.editableIsValid(false);
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
public onActivate(): Promise<any> {
|
||||
return super.onActivate().then(async () => {
|
||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||
const database: ViewModels.Database = this.collection.getDatabase();
|
||||
|
||||
@@ -1,22 +1,91 @@
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import TabsBase from "./TabsBase";
|
||||
import { SettingsComponentAdapter } from "../Controls/Settings/SettingsComponentAdapter";
|
||||
import { SettingsComponentProps } from "../Controls/Settings/SettingsComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { traceFailure } from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
|
||||
export default class SettingsTabV2 extends TabsBase {
|
||||
public settingsComponentAdapter: SettingsComponentAdapter;
|
||||
private notificationRead: ko.Observable<boolean>;
|
||||
private notification: DataModels.Notification;
|
||||
private offerRead: ko.Observable<boolean>;
|
||||
private currentCollection: ViewModels.Collection;
|
||||
private options: ViewModels.SettingsTabV2Options;
|
||||
|
||||
constructor(options: ViewModels.TabOptions) {
|
||||
constructor(options: ViewModels.SettingsTabV2Options) {
|
||||
super(options);
|
||||
this.options = options;
|
||||
this.tabId = "SettingsV2-" + this.tabId;
|
||||
const props: SettingsComponentProps = {
|
||||
settingsTab: this
|
||||
};
|
||||
this.settingsComponentAdapter = new SettingsComponentAdapter(props);
|
||||
this.currentCollection = this.collection as ViewModels.Collection;
|
||||
this.notificationRead = ko.observable(false);
|
||||
this.offerRead = ko.observable(false);
|
||||
this.settingsComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||
if (this.notificationRead() && this.offerRead()) {
|
||||
this.pendingNotification(this.notification);
|
||||
this.notification = undefined;
|
||||
this.offerRead(false);
|
||||
this.notificationRead(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public onActivate(): Q.Promise<unknown> {
|
||||
public onActivate(): Promise<unknown> {
|
||||
this.isExecuting(true);
|
||||
this.currentCollection.loadOffer().then(
|
||||
() => {
|
||||
// passed in options and set by parent as "Settings" by default
|
||||
this.tabTitle("Scale & Settings");
|
||||
this.offerRead(true);
|
||||
this.options.getPendingNotification.then(
|
||||
(data: DataModels.Notification) => {
|
||||
this.notification = data;
|
||||
this.notificationRead(true);
|
||||
this.isExecuting(false);
|
||||
},
|
||||
error => {
|
||||
this.notification = undefined;
|
||||
this.notificationRead(true);
|
||||
this.isExecuting(false);
|
||||
traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseAccountName: this.options.collection.container.databaseAccount().name,
|
||||
databaseName: this.options.collection.databaseId,
|
||||
collectionName: this.options.collection.id(),
|
||||
defaultExperience: this.options.collection.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle,
|
||||
error: error
|
||||
},
|
||||
this.options.onLoadStartKey
|
||||
);
|
||||
logConsoleError(
|
||||
`Error while fetching container settings for container ${this.options.collection.id()}: ${JSON.stringify(
|
||||
error
|
||||
)}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this.offerRead(true);
|
||||
this.isExecuting(false);
|
||||
}
|
||||
);
|
||||
|
||||
return super.onActivate().then(() => {
|
||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.SettingsV2);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as _ from "underscore";
|
||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
@@ -61,13 +60,11 @@ export default class StoredProcedureTab extends ScriptTabBase {
|
||||
});
|
||||
};
|
||||
|
||||
public onDiscard = (): Q.Promise<any> => {
|
||||
public onDiscard = async (): Promise<any> => {
|
||||
this.setBaselines();
|
||||
const original = this.editorContent.getEditableOriginalValue();
|
||||
this.originalSprocBody(original);
|
||||
this.originalSprocBody.valueHasMutated(); // trigger a re-render of the editor
|
||||
|
||||
return Q();
|
||||
};
|
||||
|
||||
public onUpdateClick = (): Promise<any> => {
|
||||
@@ -284,8 +281,7 @@ export default class StoredProcedureTab extends ScriptTabBase {
|
||||
.finally(() => this.isExecuting(false));
|
||||
}
|
||||
|
||||
public onDelete(): Q.Promise<any> {
|
||||
public async onDelete(): Promise<any> {
|
||||
// TODO
|
||||
return Q();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
@@ -93,9 +92,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
public async onTabClick(): Promise<any> {
|
||||
this.getContainer().tabsManager.activateTab(this);
|
||||
return Q();
|
||||
}
|
||||
|
||||
protected updateSelectedNode(): void {
|
||||
@@ -127,7 +125,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
||||
};
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
public async onActivate(): Promise<any> {
|
||||
this.updateSelectedNode();
|
||||
if (!!this.collection) {
|
||||
this.collection.selectedSubnodeKind(this.tabKind);
|
||||
@@ -149,7 +147,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
tabTitle: this.tabTitle(),
|
||||
tabId: this.tabId
|
||||
});
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
||||
@@ -172,9 +169,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
return true;
|
||||
};
|
||||
|
||||
public refresh(): Q.Promise<any> {
|
||||
public async refresh(): Promise<any> {
|
||||
location.reload();
|
||||
return Q();
|
||||
}
|
||||
|
||||
protected getContainer(): Explorer {
|
||||
|
||||
@@ -551,7 +551,11 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
|
||||
await this.loadOffer();
|
||||
const isSettingsV2Enabled = this.container.isSettingsV2Enabled();
|
||||
if (!isSettingsV2Enabled) {
|
||||
await this.loadOffer();
|
||||
}
|
||||
|
||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
||||
@@ -578,28 +582,30 @@ export default class Collection implements ViewModels.Collection {
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
};
|
||||
|
||||
const isSettingsV2Enabled = this.container.isSettingsV2Enabled();
|
||||
var settingsTab: TabsBase;
|
||||
if (isSettingsV2Enabled) {
|
||||
settingsTab = matchingTabs && (matchingTabs[0] as SettingsTabV2);
|
||||
let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2);
|
||||
this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
||||
} else {
|
||||
settingsTab = matchingTabs && (matchingTabs[0] as SettingsTab);
|
||||
let settingsTab = matchingTabs && (matchingTabs[0] as SettingsTab);
|
||||
this.launchSettingsTabV1(settingsTab, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
||||
}
|
||||
};
|
||||
|
||||
private launchSettingsTabV1 = (
|
||||
settingsTab: SettingsTab,
|
||||
traceStartData: any,
|
||||
settingsTabOptions: ViewModels.TabOptions,
|
||||
getPendingNotification: Q.Promise<DataModels.Notification>
|
||||
): void => {
|
||||
if (!settingsTab) {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
||||
settingsTabOptions.onLoadStartKey = startKey;
|
||||
|
||||
pendingNotificationsPromise.then(
|
||||
getPendingNotification.then(
|
||||
(data: any) => {
|
||||
const pendingNotification: DataModels.Notification = data && data[0];
|
||||
if (isSettingsV2Enabled) {
|
||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.SettingsV2;
|
||||
settingsTab = new SettingsTabV2(settingsTabOptions);
|
||||
} else {
|
||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.Settings;
|
||||
settingsTab = new SettingsTab(settingsTabOptions);
|
||||
}
|
||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.Settings;
|
||||
settingsTab = new SettingsTab(settingsTabOptions);
|
||||
this.container.tabsManager.activateNewTab(settingsTab);
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
},
|
||||
@@ -612,20 +618,20 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: tabTitle,
|
||||
tabTitle: settingsTabOptions.title,
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while fetching container settings for container ${this.id()}: ${JSON.stringify(error)}`
|
||||
`Error while fetching container settings for container ${this.id()}: ${error.message}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
pendingNotificationsPromise.then(
|
||||
getPendingNotification.then(
|
||||
(pendingNotification: DataModels.Notification) => {
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
@@ -638,6 +644,28 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
};
|
||||
|
||||
private launchSettingsTabV2 = (
|
||||
settingsTabV2: SettingsTabV2,
|
||||
traceStartData: any,
|
||||
settingsTabOptions: ViewModels.TabOptions,
|
||||
getPendingNotification: Q.Promise<DataModels.Notification>
|
||||
): void => {
|
||||
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
|
||||
...settingsTabOptions,
|
||||
getPendingNotification: getPendingNotification
|
||||
};
|
||||
|
||||
if (!settingsTabV2) {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
||||
settingsTabV2Options.onLoadStartKey = startKey;
|
||||
settingsTabV2Options.tabKind = ViewModels.CollectionTabKind.SettingsV2;
|
||||
settingsTabV2 = new SettingsTabV2(settingsTabV2Options);
|
||||
this.container.tabsManager.activateNewTab(settingsTabV2);
|
||||
} else {
|
||||
this.container.tabsManager.activateTab(settingsTabV2);
|
||||
}
|
||||
};
|
||||
|
||||
private async loadCollectionQuotaInfo(): Promise<void> {
|
||||
// TODO: Use the collection entity cache to get quota info
|
||||
const quotaInfoWithUniqueKeyPolicy = await readCollectionQuotaInfo(this);
|
||||
@@ -841,7 +869,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: typeof error === "string" ? error : JSON.stringify(error)
|
||||
error: typeof error === "string" ? error : error.message
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -900,7 +928,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: typeof error === "string" ? error : JSON.stringify(error)
|
||||
error: typeof error === "string" ? error : error.message
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -960,7 +988,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: typeof error === "string" ? error : JSON.stringify(error)
|
||||
error: typeof error === "string" ? error : error.message
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1157,7 +1185,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
},
|
||||
error => {
|
||||
record.numFailed++;
|
||||
record.errors = [...record.errors, JSON.stringify(error)];
|
||||
record.errors = [...record.errors, error.message];
|
||||
return Q.resolve();
|
||||
}
|
||||
);
|
||||
@@ -1210,7 +1238,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
(error: any) => {
|
||||
Logger.logError(
|
||||
JSON.stringify({
|
||||
error: JSON.stringify(error),
|
||||
error: error.message,
|
||||
accountName: this.container && this.container.databaseAccount(),
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user