Compare commits

..

1 Commits

Author SHA1 Message Date
Steve Faulkner
a266fdc89c Create codeql-analysis.yml 2020-10-14 14:58:44 -05:00
147 changed files with 1510 additions and 3114 deletions

View File

@@ -42,13 +42,6 @@ module.exports = {
"no-null/no-null": "error", "no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }], "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 '{}'"
}
]
} }
}; };

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 5 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -3,9 +3,6 @@
# Add steps that build, run tests, deploy, and more: # Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml # https://aka.ms/yaml
trigger:
- master
pool: pool:
vmImage: "ubuntu-latest" vmImage: "ubuntu-latest"

View File

@@ -3023,7 +3023,3 @@ settings-pane {
.infoBoxContent a { .infoBoxContent a {
color: @AccentMediumHigh color: @AccentMediumHigh
} }
.collapsibleSection :hover{
cursor: pointer;
}

110
package-lock.json generated
View File

@@ -76,6 +76,7 @@
"version": "7.9.0", "version": "7.9.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
"dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.8.3", "@babel/code-frame": "^7.8.3",
"@babel/generator": "^7.9.0", "@babel/generator": "^7.9.0",
@@ -98,7 +99,8 @@
"source-map": { "source-map": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
} }
} }
}, },
@@ -7201,10 +7203,11 @@
} }
}, },
"create-react-class": { "create-react-class": {
"version": "15.7.0", "version": "15.6.3",
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
"requires": { "requires": {
"fbjs": "^0.8.9",
"loose-envify": "^1.3.1", "loose-envify": "^1.3.1",
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
@@ -8501,6 +8504,24 @@
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"dev": true "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": { "end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -9646,6 +9667,27 @@
"bser": "2.1.1" "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": { "fd-slicer": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -11846,6 +11888,26 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" "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": { "isstream": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -13344,6 +13406,36 @@
"micromatch": "^3.1.10", "micromatch": "^3.1.10",
"pretty-format": "^24.9.0", "pretty-format": "^24.9.0",
"realpath-native": "^1.1.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": { "jest-dev-server": {
@@ -16527,8 +16619,6 @@
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"dev": true,
"optional": true,
"requires": { "requires": {
"asap": "~2.0.3" "asap": "~2.0.3"
} }
@@ -18156,8 +18246,7 @@
"setimmediate": { "setimmediate": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
"dev": true
}, },
"setprototypeof": { "setprototypeof": {
"version": "1.1.1", "version": "1.1.1",
@@ -19917,6 +20006,11 @@
"free-style": "3.1.0" "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": { "uglify-js": {
"version": "3.4.10", "version": "3.4.10",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",

View File

@@ -133,7 +133,6 @@ export class Features {
// flight names returned from the portal are always lowercase // flight names returned from the portal are always lowercase
export class Flights { export class Flights {
public static readonly SettingsV2 = "settingsv2"; public static readonly SettingsV2 = "settingsv2";
public static readonly MongoIndexEditor = "mongoindexeditor";
} }
export class AfecFeatures { export class AfecFeatures {

View File

@@ -112,6 +112,7 @@ describe("endpoint", () => {
describe("requestPlugin", () => { describe("requestPlugin", () => {
beforeEach(() => { beforeEach(() => {
delete window.dataExplorerPlatform;
resetConfigContext(); resetConfigContext();
}); });

View File

@@ -69,7 +69,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
const result = JSON.parse(await response.json()); const result = JSON.parse(await response.json());
return result; return result;
} catch (error) { } catch (error) {
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${error.message}`); logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`);
return Promise.reject(error); return Promise.reject(error);
} }
} }

View File

@@ -7,6 +7,7 @@ import {
Resource Resource
} from "@azure/cosmos"; } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Q from "q";
import { configContext, Platform } from "../ConfigContext"; import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
@@ -38,17 +39,18 @@ export function getCommonQueryOptions(options: FeedOptions): any {
return options; return options;
} }
export async function queryDocuments( export function queryDocuments(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Promise<QueryIterator<ItemDefinition & Resource>> { ): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
options = getCommonQueryOptions(options); options = getCommonQueryOptions(options);
return client() const documentsIterator = client()
.database(databaseId) .database(databaseId)
.container(containerId) .container(containerId)
.items.query(query, options); .items.query(query, options);
return Q(documentsIterator);
} }
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object { export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
@@ -74,15 +76,17 @@ export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,
newDocument: any newDocument: any
): Promise<any> { ): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return client() return Q(
.database(collection.databaseId) client()
.container(collection.id()) .database(collection.databaseId)
.item(documentId.id(), partitionKey) .container(collection.id())
.replace(newDocument) .item(documentId.id(), partitionKey)
.then(response => response.resource); .replace(newDocument)
.then(response => response.resource)
);
} }
export function executeStoredProcedure( export function executeStoredProcedure(
@@ -90,89 +94,105 @@ export function executeStoredProcedure(
storedProcedure: StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
params: any[] params: any[]
): Promise<any> { ): Q.Promise<any> {
return Promise.race([ // 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({
result: response.resource,
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
})
)
.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() client()
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(storedProcedure.id()) .items.create(newDocument)
.execute(partitionKeyValue, params, { enableScriptLogging: true }) .then(response => response.resource)
.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
)
)
]);
} }
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> { export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.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): Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return client() return Q(
.database(collection.databaseId) client()
.container(collection.id()) .database(collection.databaseId)
.item(documentId.id(), partitionKey) .container(collection.id())
.read() .item(documentId.id(), partitionKey)
.then(response => response.resource); .read()
.then(response => response.resource)
);
} }
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> { export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return client() return Q(
.database(collection.databaseId) client()
.container(collection.id()) .database(collection.databaseId)
.item(documentId.id(), partitionKey) .container(collection.id())
.delete(); .item(documentId.id(), partitionKey)
.delete()
);
} }
export function deleteConflict( export function deleteConflict(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
conflictId: ConflictId, conflictId: ConflictId,
options: any = {} options: any = {}
): Promise<any> { ): Q.Promise<any> {
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId); options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
return client() return Q(
.database(collection.databaseId) client()
.container(collection.id()) .database(collection.databaseId)
.conflict(conflictId.id()) .container(collection.id())
.delete(options); .conflict(conflictId.id())
.delete(options)
);
} }
export async function refreshCachedOffers(): Promise<void> { export function refreshCachedOffers(): Q.Promise<void> {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
sendCachedDataMessage(MessageTypes.RefreshOffers, []); return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
} else {
return Q();
} }
} }
export async function refreshCachedResources(options?: any): Promise<void> { export function refreshCachedResources(options?: any): Q.Promise<void> {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
sendCachedDataMessage(MessageTypes.RefreshResources, []); return sendCachedDataMessage(MessageTypes.RefreshResources, []);
} else {
return Q();
} }
} }
export async function queryConflicts( export function queryConflicts(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Promise<QueryIterator<ConflictDefinition & Resource>> { ): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
return client() const documentsIterator = client()
.database(databaseId) .database(databaseId)
.container(containerId) .container(containerId)
.conflicts.query(query, options); .conflicts.query(query, options);
return Q(documentsIterator);
} }

View File

@@ -1,13 +1,17 @@
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; 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 * as ViewModels from "../Contracts/ViewModels";
import ConflictId from "../Explorer/Tree/ConflictId"; import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import StoredProcedure from "../Explorer/Tree/StoredProcedure"; import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import * as Constants from "./Constants"; import * as Constants from "./Constants";
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
import * as DataAccessUtilityBase from "./DataAccessUtilityBase"; import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities"; import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
import { handleError } from "./ErrorHandlingUtils"; import * as Logger from "./Logger";
// TODO: Log all promise resolutions and errors with verbosity levels // TODO: Log all promise resolutions and errors with verbosity levels
export function queryDocuments( export function queryDocuments(
@@ -15,7 +19,7 @@ export function queryDocuments(
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Promise<QueryIterator<ItemDefinition & Resource>> { ): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options); return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
} }
@@ -24,7 +28,7 @@ export function queryConflicts(
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Promise<QueryIterator<ConflictDefinition & Resource>> { ): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options); return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
} }
@@ -42,26 +46,34 @@ export function executeStoredProcedure(
storedProcedure: StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
params: any[] params: any[]
): Promise<any> { ): Q.Promise<any> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`); const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
return DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params) DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
.then( .then(
(response: any) => { (response: any) => {
deferred.resolve(response);
logConsoleInfo( logConsoleInfo(
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}` `Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
); );
return response;
}, },
(error: any) => { (error: any) => {
handleError( logConsoleError(
error, `Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`, error
"ExecuteStoredProcedure" )}`
); );
throw error; Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
sendNotificationForError(error);
deferred.reject(error);
} }
) )
.finally(clearMessage); .finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function queryDocumentsPage( export function queryDocumentsPage(
@@ -69,114 +81,164 @@ export function queryDocumentsPage(
documentsIterator: MinimalQueryIterator, documentsIterator: MinimalQueryIterator,
firstItemIndex: number, firstItemIndex: number,
options: any options: any
): Promise<ViewModels.QueryResults> { ): Q.Promise<ViewModels.QueryResults> {
var deferred = Q.defer<ViewModels.QueryResults>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`); const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
return nextPage(documentsIterator, firstItemIndex) Q(nextPage(documentsIterator, firstItemIndex))
.then( .then(
(result: ViewModels.QueryResults) => { (result: ViewModels.QueryResults) => {
const itemCount = (result.documents && result.documents.length) || 0; const itemCount = (result.documents && result.documents.length) || 0;
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`); logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
return result; deferred.resolve(result);
}, },
(error: any) => { (error: any) => {
handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage"); logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
throw error; Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
sendNotificationForError(error);
deferred.reject(error);
} }
) )
.finally(clearMessage); .finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> { export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`); const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
return DataAccessUtilityBase.readDocument(collection, documentId) DataAccessUtilityBase.readDocument(collection, documentId)
.catch((error: any) => { .then(
handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument"); (document: any) => {
throw error; deferred.resolve(document);
}) },
.finally(clearMessage); (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;
} }
export function updateDocument( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,
newDocument: any newDocument: any
): Promise<any> { ): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`); const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
return DataAccessUtilityBase.updateDocument(collection, documentId, newDocument) DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
.then( .then(
(updatedDocument: any) => { (updatedDocument: any) => {
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`); logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
return updatedDocument; deferred.resolve(updatedDocument);
}, },
(error: any) => { (error: any) => {
handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument"); logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
throw error; Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
sendNotificationForError(error);
deferred.reject(error);
} }
) )
.finally(clearMessage); .finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> { export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`); const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
return DataAccessUtilityBase.createDocument(collection, newDocument) DataAccessUtilityBase.createDocument(collection, newDocument)
.then( .then(
(savedDocument: any) => { (savedDocument: any) => {
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`); logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
return savedDocument; deferred.resolve(savedDocument);
}, },
(error: any) => { (error: any) => {
handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument"); logConsoleError(
throw error; `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);
} }
) )
.finally(clearMessage); .finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> { export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`); const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
return DataAccessUtilityBase.deleteDocument(collection, documentId) DataAccessUtilityBase.deleteDocument(collection, documentId)
.then( .then(
(response: any) => { (response: any) => {
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`); logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
return response; deferred.resolve(response);
}, },
(error: any) => { (error: any) => {
handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument"); logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
throw error; Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
sendNotificationForError(error);
deferred.reject(error);
} }
) )
.finally(clearMessage); .finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function deleteConflict( export function deleteConflict(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
conflictId: ConflictId, conflictId: ConflictId,
options?: any options?: any
): Promise<any> { ): Q.Promise<any> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`); const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
return DataAccessUtilityBase.deleteConflict(collection, conflictId, options) DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
.then( .then(
(response: any) => { (response: any) => {
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`); logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
return response; deferred.resolve(response);
}, },
(error: any) => { (error: any) => {
handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict"); logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
throw error; Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
sendNotificationForError(error);
deferred.reject(error);
} }
) )
.finally(clearMessage); .finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function refreshCachedResources(options: any = {}): Promise<void> { export function refreshCachedResources(options: any = {}): Q.Promise<void> {
return DataAccessUtilityBase.refreshCachedResources(options); return DataAccessUtilityBase.refreshCachedResources(options);
} }
export function refreshCachedOffers(): Promise<void> { export function refreshCachedOffers(): Q.Promise<void> {
return DataAccessUtilityBase.refreshCachedOffers(); return DataAccessUtilityBase.refreshCachedOffers();
} }

View File

@@ -1,11 +0,0 @@
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);
};

View File

@@ -1,41 +1,41 @@
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext"; import { configContext, Platform } from "../ConfigContext";
const notificationsPath = () => { const notificationsPath = () => {
switch (configContext.platform) { switch (configContext.platform) {
case Platform.Hosted: case Platform.Hosted:
return "/api/guest/notifications"; return "/api/guest/notifications";
case Platform.Portal: case Platform.Portal:
return "/api/notifications"; return "/api/notifications";
default: default:
throw new Error(`Unknown platform: ${configContext.platform}`); throw new Error(`Unknown platform: ${configContext.platform}`);
} }
}; };
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => { export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator) {
return []; return [];
} }
const databaseAccount = userContext.databaseAccount; const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId; const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${ const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`; }&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token }; const headers = { [authorizationHeader.header]: authorizationHeader.token };
const response = await window.fetch(url, { const response = await window.fetch(url, {
headers headers
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(await response.text()); throw new Error(await response.text());
} }
return (await response.json()) as DataModels.Notification[]; return (await response.json()) as DataModels.Notification[];
}; };

View File

@@ -53,7 +53,7 @@ export class QueriesClient {
return Promise.resolve(collection); return Promise.resolve(collection);
}, },
(error: any) => { (error: any) => {
const stringifiedError: string = error.message; const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to set up account for saving queries: ${stringifiedError}` `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) return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
.then( .then(
(queryIterator: QueryIterator<ItemDefinition & Resource>) => { (queryIterator: QueryIterator<ItemDefinition & Resource>) => {
const fetchQueries = (firstItemIndex: number): Promise<ViewModels.QueryResults> => const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options); queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
return QueryUtils.queryAllPages(fetchQueries).then( return QueryUtils.queryAllPages(fetchQueries).then(
(results: ViewModels.QueryResults) => { (results: ViewModels.QueryResults) => {
@@ -163,7 +163,7 @@ export class QueriesClient {
return Promise.resolve(queries); return Promise.resolve(queries);
}, },
(error: any) => { (error: any) => {
const stringifiedError: string = error.message; const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to fetch saved queries: ${stringifiedError}` `Failed to fetch saved queries: ${stringifiedError}`
@@ -175,7 +175,7 @@ export class QueriesClient {
}, },
(error: any) => { (error: any) => {
// should never get into this state but we handle this regardless // should never get into this state but we handle this regardless
const stringifiedError: string = error.message; const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to fetch saved queries: ${stringifiedError}` `Failed to fetch saved queries: ${stringifiedError}`
@@ -232,7 +232,7 @@ export class QueriesClient {
return Promise.resolve(); return Promise.resolve();
}, },
(error: any) => { (error: any) => {
const stringifiedError: string = error.message; const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to delete query ${query.queryName}: ${stringifiedError}` `Failed to delete query ${query.queryName}: ${stringifiedError}`

View File

@@ -1,4 +1,5 @@
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ErrorParserUtility from "../ErrorParserUtility";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos"; import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest"; import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
@@ -22,20 +23,21 @@ import {
getGremlinGraph getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { refreshCachedResources } from "../DataAccessUtilityBase"; import { refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createDatabase } from "./createDatabase"; import { createDatabase } from "./createDatabase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { handleError } from "../ErrorHandlingUtils";
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
let collection: DataModels.Collection;
const clearMessage = logConsoleProgress( const clearMessage = logConsoleProgress(
`Creating a new container ${params.collectionId} for database ${params.databaseId}` `Creating a new container ${params.collectionId} for database ${params.databaseId}`
); );
try { try {
let collection: DataModels.Collection;
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.createNewDatabase) { if (params.createNewDatabase) {
const createDatabaseParams: DataModels.CreateDatabaseParams = { const createDatabaseParams: DataModels.CreateDatabaseParams = {
@@ -52,16 +54,18 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
} else { } else {
collection = await createCollectionWithSDK(params); collection = await createCollectionWithSDK(params);
} }
logConsoleInfo(`Successfully created container ${params.collectionId}`);
await refreshCachedResources();
return collection;
} catch (error) { } catch (error) {
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection"); const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
throw error; logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
} finally { logError(JSON.stringify(error), "CreateCollection", error.code);
sendNotificationForError(error);
clearMessage(); clearMessage();
throw error;
} }
logConsoleInfo(`Successfully created container ${params.collectionId}`);
await refreshCachedResources();
clearMessage();
return collection;
}; };
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => { const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {

View File

@@ -24,31 +24,36 @@ import {
createUpdateGremlinDatabase, createUpdateGremlinDatabase,
getGremlinDatabase getGremlinDatabase
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase"; import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
let database: DataModels.Database;
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
try { try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) { if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Creating database resources is not allowed for tables accounts"); throw new Error("Creating database resources is not allowed for tables accounts");
} }
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
? createDatabaseWithARM(params) database = await createDatabaseWithARM(params);
: createDatabaseWithSDK(params)); } else {
database = await createDatabaseWithSDK(params);
await refreshCachedResources(); }
await refreshCachedOffers();
logConsoleInfo(`Successfully created database ${params.databaseId}`);
return database;
} catch (error) { } catch (error) {
handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase"); logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
throw error; logError(JSON.stringify(error), "CreateDatabase", error.code);
} finally { sendNotificationForError(error);
clearMessage(); clearMessage();
throw error;
} }
logConsoleInfo(`Successfully created database ${params.databaseId}`);
await refreshCachedResources();
await refreshCachedOffers();
clearMessage();
return database;
} }
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {

View File

@@ -1,78 +1,28 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { import { logError } from "../Logger";
createUpdateSqlStoredProcedure, import { sendNotificationForError } from "./sendNotificationForError";
getSqlStoredProcedure
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createStoredProcedure( export async function createStoredProcedure(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
storedProcedure: StoredProcedureDefinition storedProcedure: StoredProcedureDefinition
): Promise<StoredProcedureDefinition & Resource> { ): Promise<StoredProcedureDefinition & Resource> {
let createdStoredProcedure: StoredProcedureDefinition & Resource;
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
);
if (getResponse?.properties?.resource) {
throw new Error(
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: {
resource: storedProcedure as SqlStoredProcedureResource,
options: {}
}
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,
createSprocParams
);
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.storedProcedures.create(storedProcedure); .scripts.storedProcedures.create(storedProcedure);
return response?.resource; createdStoredProcedure = response.resource;
} catch (error) { } catch (error) {
handleError(error, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure"); logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return createdStoredProcedure;
} }

View File

@@ -1,76 +1,28 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { logError } from "../Logger"; import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError"; import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function createTrigger( export async function createTrigger(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
trigger: TriggerDefinition trigger: TriggerDefinition
): Promise<TriggerDefinition & Resource> { ): Promise<TriggerDefinition & Resource> {
let createdTrigger: TriggerDefinition & Resource;
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
);
if (getResponse?.properties?.resource) {
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: {
resource: trigger as SqlTriggerResource,
options: {}
}
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,
createTriggerParams
);
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.triggers.create(trigger); .scripts.triggers.create(trigger);
return response.resource; createdTrigger = response.resource;
} catch (error) { } catch (error) {
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`); logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
logError(error.message, "CreateTrigger", error.code); logError(JSON.stringify(error), "CreateTrigger", error.code);
sendNotificationForError(error); sendNotificationForError(error);
throw error;
} finally {
clearMessage();
} }
clearMessage();
return createdTrigger;
} }

View File

@@ -1,82 +1,28 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { import { logError } from "../Logger";
createUpdateSqlUserDefinedFunction, import { sendNotificationForError } from "./sendNotificationForError";
getSqlUserDefinedFunction
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createUserDefinedFunction( export async function createUserDefinedFunction(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
userDefinedFunction: UserDefinedFunctionDefinition userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
);
if (getResponse?.properties?.resource) {
throw new Error(
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {}
}
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,
createUDFParams
);
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.userDefinedFunctions.create(userDefinedFunction); .scripts.userDefinedFunctions.create(userDefinedFunction);
return response?.resource; createdUserDefinedFunction = response.resource;
} catch (error) { } catch (error) {
handleError( logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
error, logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
`Error while creating user defined function ${userDefinedFunction.id}`, sendNotificationForError(error);
"CreateUserupdateUserDefinedFunction"
);
throw error;
} finally {
clearMessage();
} }
clearMessage();
return createdUserDefinedFunction;
} }

View File

@@ -5,8 +5,9 @@ import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-0
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase"; import { refreshCachedResources } from "../DataAccessUtilityBase";
@@ -22,14 +23,15 @@ export async function deleteCollection(databaseId: string, collectionId: string)
.container(collectionId) .container(collectionId)
.delete(); .delete();
} }
logConsoleInfo(`Successfully deleted container ${collectionId}`);
await refreshCachedResources();
} catch (error) { } catch (error) {
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection"); logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteCollection", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally {
clearMessage();
} }
logConsoleInfo(`Successfully deleted container ${collectionId}`);
clearMessage();
await refreshCachedResources();
} }
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> { function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {

View File

@@ -4,11 +4,12 @@ import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/s
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase"; import { refreshCachedResources } from "../DataAccessUtilityBase";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
export async function deleteDatabase(databaseId: string): Promise<void> { export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`); const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
@@ -24,14 +25,15 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
.database(databaseId) .database(databaseId)
.delete(); .delete();
} }
logConsoleInfo(`Successfully deleted database ${databaseId}`);
await refreshCachedResources();
} catch (error) { } catch (error) {
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase"); logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteDatabase", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally {
clearMessage();
} }
logConsoleInfo(`Successfully deleted database ${databaseId}`);
clearMessage();
await refreshCachedResources();
} }
function deleteDatabaseWithARM(databaseId: string): Promise<void> { function deleteDatabaseWithARM(databaseId: string): Promise<void> {

View File

@@ -1,10 +1,7 @@
import { AuthType } from "../../AuthType"; import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { logError } from "../Logger";
import { handleError } from "../ErrorHandlingUtils"; import { sendNotificationForError } from "./sendNotificationForError";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteStoredProcedure( export async function deleteStoredProcedure(
databaseId: string, databaseId: string,
@@ -13,30 +10,17 @@ export async function deleteStoredProcedure(
): Promise<void> { ): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`); const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try { try {
if ( await client()
window.authType === AuthType.AAD && .database(databaseId)
!userContext.useSDKOperations && .container(collectionId)
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB .scripts.storedProcedure(storedProcedureId)
) { .delete();
await deleteSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedureId
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedureId)
.delete();
}
} catch (error) { } catch (error) {
handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure"); logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return undefined;
} }

View File

@@ -1,38 +1,22 @@
import { AuthType } from "../../AuthType"; import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { logError } from "../Logger";
import { handleError } from "../ErrorHandlingUtils"; import { sendNotificationForError } from "./sendNotificationForError";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> { export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`); const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try { try {
if ( await client()
window.authType === AuthType.AAD && .database(databaseId)
!userContext.useSDKOperations && .container(collectionId)
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB .scripts.trigger(triggerId)
) { .delete();
await deleteSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
triggerId
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(triggerId)
.delete();
}
} catch (error) { } catch (error) {
handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger"); logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "DeleteTrigger", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return undefined;
} }

View File

@@ -1,38 +1,22 @@
import { AuthType } from "../../AuthType"; import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { logError } from "../Logger";
import { handleError } from "../ErrorHandlingUtils"; import { sendNotificationForError } from "./sendNotificationForError";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> { export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`); const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try { try {
if ( await client()
window.authType === AuthType.AAD && .database(databaseId)
!userContext.useSDKOperations && .container(collectionId)
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB .scripts.userDefinedFunction(id)
) { .delete();
await deleteSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
id
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(id)
.delete();
}
} catch (error) { } catch (error) {
handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction"); logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return undefined;
} }

View File

@@ -1,7 +1,8 @@
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> { export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
let collection: DataModels.Collection; let collection: DataModels.Collection;
@@ -13,7 +14,9 @@ export async function readCollection(databaseId: string, collectionId: string):
.read(); .read();
collection = response.resource as DataModels.Collection; collection = response.resource as DataModels.Collection;
} catch (error) { } catch (error) {
handleError(error, `Error while querying container ${collectionId}`, "ReadCollection"); logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadCollection", error.code);
sendNotificationForError(error);
throw error; throw error;
} }
clearMessage(); clearMessage();

View File

@@ -4,14 +4,15 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
import { HttpHeaders } from "../Constants"; import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { readOffers } from "./readOffers"; import { readOffers } from "./readOffers";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export const readCollectionOffer = async ( export const readCollectionOffer = async (
@@ -56,7 +57,9 @@ export const readCollectionOffer = async (
} }
); );
} catch (error) { } catch (error) {
handleError(error, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer"); logConsoleError(`Error while querying offer for collection ${params.collectionId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadCollectionOffer", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally { } finally {
clearMessage(); clearMessage();

View File

@@ -5,8 +5,9 @@ import { ContainerDefinition, Resource } from "@azure/cosmos";
import { HttpHeaders } from "../Constants"; import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
interface ResourceWithStatistics { interface ResourceWithStatistics {
statistics: DataModels.Statistic[]; statistics: DataModels.Statistic[];
@@ -37,7 +38,9 @@ export const readCollectionQuotaInfo = async (
return quota; return quota;
} catch (error) { } catch (error) {
handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo"); logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally { } finally {
clearMessage(); clearMessage();

View File

@@ -2,16 +2,18 @@ import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> { export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
let collections: DataModels.Collection[];
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`); const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try { try {
if ( if (
@@ -20,20 +22,22 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB && userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table
) { ) {
return await readCollectionsWithARM(databaseId); collections = await readCollectionsWithARM(databaseId);
} else {
const sdkResponse = await client()
.database(databaseId)
.containers.readAll()
.fetchAll();
collections = sdkResponse.resources as DataModels.Collection[];
} }
const sdkResponse = await client()
.database(databaseId)
.containers.readAll()
.fetchAll();
return sdkResponse.resources as DataModels.Collection[];
} catch (error) { } catch (error) {
handleError(error, `Error while querying containers for database ${databaseId}`, "ReadCollections"); logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadCollections", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally {
clearMessage();
} }
clearMessage();
return collections;
} }
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> { async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {

View File

@@ -8,9 +8,10 @@ import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { readOffers } from "./readOffers"; import { readOffers } from "./readOffers";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export const readDatabaseOffer = async ( export const readDatabaseOffer = async (
@@ -47,7 +48,9 @@ export const readDatabaseOffer = async (
} }
); );
} catch (error) { } catch (error) {
handleError(error, `Error while querying offer for database ${params.databaseId}`, "ReadDatabaseOffer"); logConsoleError(`Error while querying offer for database ${params.databaseId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally { } finally {
clearMessage(); clearMessage();

View File

@@ -2,23 +2,24 @@ import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources"; import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> { export async function readDatabases(): Promise<DataModels.Database[]> {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
return [{ id: "TablesDB" } as DataModels.Database];
}
let databases: DataModels.Database[]; let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
try { try {
if ( if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
databases = await readDatabasesWithARM(); databases = await readDatabasesWithARM();
} else { } else {
const sdkResponse = await client() const sdkResponse = await client()
@@ -27,7 +28,9 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
databases = sdkResponse.resources as DataModels.Database[]; databases = sdkResponse.resources as DataModels.Database[];
} }
} catch (error) { } catch (error) {
handleError(error, `Error while querying databases`, "ReadDatabases"); logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadDatabases", error.code);
sendNotificationForError(error);
throw error; throw error;
} }
clearMessage(); clearMessage();

View File

@@ -1,58 +0,0 @@
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;
}

View File

@@ -3,22 +3,20 @@ import { ClientDefaults } from "../Constants";
import { MessageTypes } from "../../Contracts/ExplorerContracts"; import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { Platform, configContext } from "../../ConfigContext"; import { Platform, configContext } from "../../ConfigContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { sendCachedDataMessage } from "../MessageHandler"; import { sendCachedDataMessage } from "../MessageHandler";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export const readOffers = async (): Promise<Offer[]> => { export const readOffers = async (): Promise<Offer[]> => {
const clearMessage = logConsoleProgress(`Querying offers`); const clearMessage = logConsoleProgress(`Querying offers`);
try { try {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
const offers = sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [ return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
userContext.databaseAccount.id, userContext.databaseAccount.id,
ClientDefaults.portalCacheTimeoutMs ClientDefaults.portalCacheTimeoutMs
]); ]);
clearMessage();
return offers;
} }
} catch (error) { } catch (error) {
// If error getting cached Offers, continue on and read via SDK // If error getting cached Offers, continue on and read via SDK
@@ -35,7 +33,9 @@ export const readOffers = async (): Promise<Offer[]> => {
return []; return [];
} }
handleError(error, `Error while querying offers`, "ReadOffers"); logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadOffers", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally { } finally {
clearMessage(); clearMessage();

View File

@@ -1,43 +1,27 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { logError } from "../Logger";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { sendNotificationForError } from "./sendNotificationForError";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readStoredProcedures( export async function readStoredProcedures(
databaseId: string, databaseId: string,
collectionId: string collectionId: string
): Promise<(StoredProcedureDefinition & Resource)[]> { ): Promise<(StoredProcedureDefinition & Resource)[]> {
let sprocs: (StoredProcedureDefinition & Resource)[];
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.storedProcedures.readAll() .scripts.storedProcedures.readAll()
.fetchAll(); .fetchAll();
return response?.resources; sprocs = response.resources;
} catch (error) { } catch (error) {
handleError(error, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures"); logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return sprocs;
} }

View File

@@ -1,43 +1,27 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos"; import { Resource, TriggerDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { logError } from "../Logger";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers( export async function readTriggers(
databaseId: string, databaseId: string,
collectionId: string collectionId: string
): Promise<(TriggerDefinition & Resource)[]> { ): Promise<(TriggerDefinition & Resource)[]> {
let triggers: (TriggerDefinition & Resource)[];
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlTriggers(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.triggers.readAll() .scripts.triggers.readAll()
.fetchAll(); .fetchAll();
return response?.resources; triggers = response.resources;
} catch (error) { } catch (error) {
handleError(error, `Failed to query triggers for container ${collectionId}`, "ReadTriggers"); logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "ReadTriggers", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return triggers;
} }

View File

@@ -1,47 +1,27 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { logError } from "../Logger";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { sendNotificationForError } from "./sendNotificationForError";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readUserDefinedFunctions( export async function readUserDefinedFunctions(
databaseId: string, databaseId: string,
collectionId: string collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> { ): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
let udfs: (UserDefinedFunctionDefinition & Resource)[];
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`); const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.userDefinedFunctions.readAll() .scripts.userDefinedFunctions.readAll()
.fetchAll(); .fetchAll();
return response?.resources; udfs = response.resources;
} catch (error) { } catch (error) {
handleError( logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
error, logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
`Failed to query user defined functions for container ${collectionId}`, sendNotificationForError(error);
"ReadUserDefinedFunctions"
);
throw error;
} finally {
clearMessage();
} }
clearMessage();
return udfs;
} }

View File

@@ -2,7 +2,7 @@ import * as Constants from "../Constants";
import { sendMessage } from "../MessageHandler"; import { sendMessage } from "../MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts"; import { MessageTypes } from "../../Contracts/ExplorerContracts";
export interface CosmosError { interface CosmosError {
code: number; code: number;
message?: string; message?: string;
} }

View File

@@ -3,10 +3,7 @@ import { Collection } from "../../Contracts/DataModels";
import { ContainerDefinition } from "@azure/cosmos"; import { ContainerDefinition } from "@azure/cosmos";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { import {
CreateUpdateOptions,
ExtendedResourceProperties, ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters,
MongoDBCollectionResource,
SqlContainerCreateUpdateParameters, SqlContainerCreateUpdateParameters,
SqlContainerResource SqlContainerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types"; } from "../../Utils/arm/generatedClients/2020-04-01/types";
@@ -26,9 +23,10 @@ import {
getGremlinGraph getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { refreshCachedResources } from "../DataAccessUtilityBase"; import { refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function updateCollection( export async function updateCollection(
@@ -53,7 +51,6 @@ export async function updateCollection(
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.replace(newCollection as ContainerDefinition, options); .replace(newCollection as ContainerDefinition, options);
collection = sdkResponse.resource as Collection; collection = sdkResponse.resource as Collection;
} }
@@ -61,7 +58,9 @@ export async function updateCollection(
await refreshCachedResources(); await refreshCachedResources();
return collection; return collection;
} catch (error) { } catch (error) {
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection"); logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally { } finally {
clearMessage(); clearMessage();
@@ -81,6 +80,15 @@ async function updateCollectionWithARM(
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case DefaultAccountExperienceType.DocumentDB:
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.MongoDB:
return updateMongoDBCollection(
databaseId,
collectionId,
subscriptionId,
resourceGroup,
accountName,
newCollection
);
case DefaultAccountExperienceType.Cassandra: case DefaultAccountExperienceType.Cassandra:
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection); return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Graph: case DefaultAccountExperienceType.Graph:
@@ -117,35 +125,26 @@ async function updateSqlContainer(
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`); throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
} }
export async function updateMongoDBCollectionThroughRP( async function updateMongoDBCollection(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
newCollection: MongoDBCollectionResource, subscriptionId: string,
updateOptions?: CreateUpdateOptions resourceGroup: string,
): Promise<MongoDBCollectionResource> { accountName: string,
const subscriptionId = userContext.subscriptionId; newCollection: Collection
const resourceGroup = userContext.resourceGroup; ): Promise<Collection> {
const accountName = userContext.databaseAccount.name;
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId); const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) { if (getResponse && getResponse.properties && getResponse.properties.resource) {
const updateParams: MongoDBCollectionCreateUpdateParameters = { getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
properties: {
resource: newCollection,
options: updateOptions
}
};
const updateResponse = await createUpdateMongoDBCollection( const updateResponse = await createUpdateMongoDBCollection(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
accountName, accountName,
databaseId, databaseId,
collectionId, collectionId,
updateParams getResponse as SqlContainerCreateUpdateParameters
); );
return updateResponse && (updateResponse.properties.resource as Collection);
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
} }
throw new Error( throw new Error(

View File

@@ -6,11 +6,12 @@ import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types"; import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logError } from "../Logger";
import { readCollectionOffer } from "./readCollectionOffer"; import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer"; import { readDatabaseOffer } from "./readDatabaseOffer";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase"; import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { import {
updateSqlDatabaseThroughput, updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale, migrateSqlDatabaseToAutoscale,
@@ -75,7 +76,9 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`); logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
return updatedOffer; return updatedOffer;
} catch (error) { } catch (error) {
handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection"); logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
throw error; throw error;
} finally { } finally {
clearMessage(); clearMessage();
@@ -403,14 +406,10 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
const options: RequestOptions = {}; const options: RequestOptions = {};
if (params.migrateToAutoPilot) { if (params.migrateToAutoPilot) {
options.initialHeaders = { options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
[HttpHeaders.migrateOfferToAutopilot]: "true"
};
delete newOffer.content.offerAutopilotSettings; delete newOffer.content.offerAutopilotSettings;
} else if (params.migrateToManual) { } else if (params.migrateToManual) {
options.initialHeaders = { options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
[HttpHeaders.migrateOfferToManualThroughput]: "true"
};
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 }; newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
} }

View File

@@ -1,72 +1,29 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { import { logError } from "../Logger";
createUpdateSqlStoredProcedure, import { sendNotificationForError } from "./sendNotificationForError";
getSqlStoredProcedure
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateStoredProcedure( export async function updateStoredProcedure(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
storedProcedure: StoredProcedureDefinition storedProcedure: StoredProcedureDefinition
): Promise<StoredProcedureDefinition & Resource> { ): Promise<StoredProcedureDefinition & Resource> {
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`); const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
);
if (getResponse?.properties?.resource) {
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: {
resource: storedProcedure as SqlStoredProcedureResource,
options: {}
}
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,
createSprocParams
);
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.storedProcedure(storedProcedure.id) .scripts.storedProcedure(storedProcedure.id)
.replace(storedProcedure); .replace(storedProcedure);
return response?.resource; updatedStoredProcedure = response.resource;
} catch (error) { } catch (error) {
handleError(error, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure"); logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return updatedStoredProcedure;
} }

View File

@@ -1,69 +1,29 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { TriggerDefinition } from "@azure/cosmos"; import { TriggerDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources"; import { logError } from "../Logger";
import { handleError } from "../ErrorHandlingUtils"; import { sendNotificationForError } from "./sendNotificationForError";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateTrigger( export async function updateTrigger(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
trigger: TriggerDefinition trigger: TriggerDefinition
): Promise<TriggerDefinition> { ): Promise<TriggerDefinition> {
let updatedTrigger: TriggerDefinition;
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`); const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
);
if (getResponse?.properties?.resource) {
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: {
resource: trigger as SqlTriggerResource,
options: {}
}
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,
createTriggerParams
);
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
}
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.trigger(trigger.id) .scripts.trigger(trigger.id)
.replace(trigger); .replace(trigger);
return response?.resource; updatedTrigger = response.resource;
} catch (error) { } catch (error) {
handleError(error, `Error while updating trigger ${trigger.id}`, "UpdateTrigger"); logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
throw error; logError(JSON.stringify(error), "UpdateTrigger", error.code);
} finally { sendNotificationForError(error);
clearMessage();
} }
clearMessage();
return updatedTrigger;
} }

View File

@@ -1,76 +1,29 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos"; import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { import { logError } from "../Logger";
createUpdateSqlUserDefinedFunction, import { sendNotificationForError } from "./sendNotificationForError";
getSqlUserDefinedFunction
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateUserDefinedFunction( export async function updateUserDefinedFunction(
databaseId: string, databaseId: string,
collectionId: string, collectionId: string,
userDefinedFunction: UserDefinedFunctionDefinition userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> { ): Promise<UserDefinedFunctionDefinition & Resource> {
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`); const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
try { try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
);
if (getResponse?.properties?.resource) {
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {}
}
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,
createUDFParams
);
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
}
const response = await client() const response = await client()
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.scripts.userDefinedFunction(userDefinedFunction.id) .scripts.userDefinedFunction(userDefinedFunction.id)
.replace(userDefinedFunction); .replace(userDefinedFunction);
return response?.resource; updatedUserDefinedFunction = response.resource;
} catch (error) { } catch (error) {
handleError( logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
error, logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
`Error while updating user defined function ${userDefinedFunction.id}`, sendNotificationForError(error);
"UpdateUserupdateUserDefinedFunction"
);
throw error;
} finally {
clearMessage();
} }
clearMessage();
return updatedUserDefinedFunction;
} }

View File

@@ -5,6 +5,7 @@ import {
TriggerDefinition, TriggerDefinition,
UserDefinedFunctionDefinition UserDefinedFunctionDefinition
} from "@azure/cosmos"; } from "@azure/cosmos";
import Q from "q";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
@@ -106,7 +107,7 @@ export interface CollectionBase extends TreeNode {
onDocumentDBDocumentsClick(): void; onDocumentDBDocumentsClick(): void;
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void; onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
expandCollection(): Promise<any>; expandCollection(): Q.Promise<any>;
collapseCollection(): void; collapseCollection(): void;
getDatabase(): Database; getDatabase(): Database;
} }
@@ -171,7 +172,7 @@ export interface Collection extends CollectionBase {
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void; onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
onDrop(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Promise<UploadDetails>; uploadFiles(fileList: FileList): Q.Promise<UploadDetails>;
getLabel(): string; getLabel(): string;
} }
@@ -288,10 +289,6 @@ export interface DocumentsTabOptions extends TabOptions {
resourceTokenPartitionKey?: string; resourceTokenPartitionKey?: string;
} }
export interface SettingsTabV2Options extends TabOptions {
getPendingNotification: Promise<DataModels.Notification>;
}
export interface ConflictsTabOptions extends TabOptions { export interface ConflictsTabOptions extends TabOptions {
partitionKey: DataModels.PartitionKey; partitionKey: DataModels.PartitionKey;
conflictIds: ko.ObservableArray<ConflictId>; conflictIds: ko.ObservableArray<ConflictId>;

View File

@@ -1,14 +0,0 @@
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();
});
});

View File

@@ -1,36 +0,0 @@
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}
</>
);
}
}

View File

@@ -1,30 +0,0 @@
// 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>
`;

View File

@@ -57,7 +57,7 @@ class EditorViewModel extends JsonEditorViewModel {
} }
} }
protected async getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> { protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return ErrorMarkProvider.getErrorMark(input); return ErrorMarkProvider.getErrorMark(input);
} }
} }

View File

@@ -1,3 +1,4 @@
import Q from "q";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
@@ -106,8 +107,8 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected registerCompletionItemProvider() {} protected registerCompletionItemProvider() {}
// Interface. Will be implemented in children editor view model such as EditorViewModel. // Interface. Will be implemented in children editor view model such as EditorViewModel.
protected getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> { protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return new Promise(() => {}); return Q.Promise(() => {});
} }
protected getEditorLanguage(): string { protected getEditorLanguage(): string {

View File

@@ -18,9 +18,7 @@ describe("GalleryCardComponent", () => {
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0,
newCellId: undefined, newCellId: undefined
policyViolations: undefined,
pendingScanJobIds: undefined
}, },
isFavorite: false, isFavorite: false,
showDownload: true, showDownload: true,

View File

@@ -1,7 +1,6 @@
import { import {
Dropdown, Dropdown,
FocusZone, FocusZone,
FontWeights,
IDropdownOption, IDropdownOption,
IPageSpecification, IPageSpecification,
IPivotItemProps, IPivotItemProps,
@@ -12,8 +11,7 @@ import {
Pivot, Pivot,
PivotItem, PivotItem,
SearchBox, SearchBox,
Stack, Stack
Text
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
@@ -153,7 +151,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined. // explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
// Displaying code of conduct component on gallery load should not be the default behavior. // Displaying code of conduct component on gallery load should not be the default behavior.
if (this.state.isCodeOfConductAccepted !== false) { if (this.state.isCodeOfConductAccepted !== false) {
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks)); tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
} }
} }
@@ -199,59 +197,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo { private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
return { return {
tab, tab,
content: this.createSearchBarHeader(this.createCardsTabContent(data)) content: this.createTabContent(data)
}; };
} }
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
return {
tab,
content: this.createPublishedNotebooksTabContent(data)
};
};
private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => {
const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data);
const content = (
<Stack tokens={{ childrenGap: 10 }}>
{published?.length > 0 &&
this.createPublishedNotebooksSectionContent(
undefined,
"You have successfully published the following notebook(s) to public gallery and shared with other Azure Cosmos DB users.",
this.createCardsTabContent(published)
)}
{underReview?.length > 0 &&
this.createPublishedNotebooksSectionContent(
"Under Review",
"Content of a notebook you published is currently being scanned for illegal content. It will not be available to public gallery until the review is completed (may take a few days)",
this.createCardsTabContent(underReview)
)}
{removed?.length > 0 &&
this.createPublishedNotebooksSectionContent(
"Removed",
"These notebooks were found to contain illegal content and has been taken down.",
this.createPolicyViolationsListContent(removed)
)}
</Stack>
);
return this.createSearchBarHeader(content);
};
private createPublishedNotebooksSectionContent = (
title: string,
description: string,
content: JSX.Element
): JSX.Element => {
return (
<Stack tokens={{ childrenGap: 5 }}>
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
{description && <Text>{description}</Text>}
{content}
</Stack>
);
};
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element { private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
return acceptedCodeOfConduct === false ? ( return acceptedCodeOfConduct === false ? (
<CodeOfConductComponent <CodeOfConductComponent
@@ -261,11 +210,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}} }}
/> />
) : ( ) : (
this.createSearchBarHeader(this.createCardsTabContent(data)) this.createTabContent(data)
); );
} }
private createSearchBarHeader(content: JSX.Element): JSX.Element { private createTabContent(data: IGalleryItem[]): JSX.Element {
return ( return (
<Stack tokens={{ childrenGap: 10 }}> <Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}> <Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
@@ -284,7 +233,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
</Stack.Item> </Stack.Item>
)} )}
</Stack> </Stack>
<Stack.Item>{content}</Stack.Item> {data && this.createCardsTabContent(data)}
</Stack> </Stack>
); );
} }
@@ -302,25 +251,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
} }
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
return (
<table>
<tbody>
<tr>
<th>Name</th>
<th>Policy violations</th>
</tr>
{data.map(item => (
<tr key={`policy-violations-tr-${item.id}`}>
<td>{item.name}</td>
<td>{item.policyViolations.join(", ")}</td>
</tr>
))}
</tbody>
</table>
);
}
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void { private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
switch (tab) { switch (tab) {
case GalleryTab.OfficialSamples: case GalleryTab.OfficialSamples:

View File

@@ -29,7 +29,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
<Stack.Item> <Stack.Item>
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)} {this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
</Stack.Item> </Stack.Item>
{this.props.onReportAbuseClick && ( {this.props.onReportAbuseClick !== undefined && (
<Stack.Item> <Stack.Item>
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())} {this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
</Stack.Item> </Stack.Item>

View File

@@ -81,21 +81,6 @@ exports[`GalleryViewerComponent renders 1`] = `
<InfoComponent /> <InfoComponent />
</StackItem> </StackItem>
</Stack> </Stack>
<StackItem>
<FocusZone
direction={2}
isCircularNavigation={false}
shouldRaiseClicks={true}
>
<List
getPageSpecification={[Function]}
onRenderCell={[Function]}
renderedWindowsAhead={3}
renderedWindowsBehind={2}
startIndex={0}
/>
</FocusZone>
</StackItem>
</Stack> </Stack>
</PivotItem> </PivotItem>
</StyledPivotBase> </StyledPivotBase>

View File

@@ -18,9 +18,7 @@ describe("NotebookMetadataComponent", () => {
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0,
newCellId: undefined, newCellId: undefined
policyViolations: undefined,
pendingScanJobIds: undefined
}, },
isFavorite: false, isFavorite: false,
downloadButtonText: "Download", downloadButtonText: "Download",
@@ -50,9 +48,7 @@ describe("NotebookMetadataComponent", () => {
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0,
newCellId: undefined, newCellId: undefined
policyViolations: undefined,
pendingScanJobIds: undefined
}, },
isFavorite: true, isFavorite: true,
downloadButtonText: "Download", downloadButtonText: "Download",

View File

@@ -8,10 +8,7 @@ import * as DataModels from "../../../Contracts/DataModels";
import ko from "knockout"; import ko from "knockout";
import { TtlType, isDirty } from "./SettingsUtils"; import { TtlType, isDirty } from "./SettingsUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
jest.mock("../../../Common/dataAccess/readMongoDBCollection", () => ({ import { updateCollection } from "../../../Common/dataAccess/updateCollection";
getMongoDBCollectionIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
}));
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
jest.mock("../../../Common/dataAccess/updateCollection", () => ({ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
updateCollection: jest.fn().mockReturnValue({ updateCollection: jest.fn().mockReturnValue({
id: undefined, id: undefined,
@@ -21,16 +18,9 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
changeFeedPolicy: undefined, changeFeedPolicy: undefined,
analyticalStorageTtl: undefined, analyticalStorageTtl: undefined,
geospatialConfig: 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 { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
jest.mock("../../../Common/dataAccess/updateOffer", () => ({ jest.mock("../../../Common/dataAccess/updateOffer", () => ({
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer) updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
})); }));
@@ -45,8 +35,7 @@ describe("SettingsComponent", () => {
node: undefined, node: undefined,
hashLocation: "settings", hashLocation: "settings",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: undefined, onUpdateTabsButtons: undefined
getPendingNotification: Promise.resolve(undefined)
}) })
}; };
@@ -199,17 +188,13 @@ describe("SettingsComponent", () => {
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true); expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
}); });
it("save calls updateCollection, updateMongoDBCollectionThroughRP and updateOffer", async () => { it("save calls updateCollection and updateOffer", async () => {
const wrapper = shallow(<SettingsComponent {...baseProps} />); const wrapper = shallow(<SettingsComponent {...baseProps} />);
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true, isMongoIndexingPolicySaveable: true }); wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true });
wrapper.update(); wrapper.update();
const settingsComponentInstance = wrapper.instance() as SettingsComponent; const settingsComponentInstance = wrapper.instance() as SettingsComponent;
settingsComponentInstance.mongoDBCollectionResource = {
id: "id"
};
await settingsComponentInstance.onSaveClick(); await settingsComponentInstance.onSaveClick();
expect(updateCollection).toBeCalled(); expect(updateCollection).toBeCalled();
expect(updateMongoDBCollectionThroughRP).toBeCalled();
expect(updateOffer).toBeCalled(); expect(updateOffer).toBeCalled();
}); });

View File

@@ -11,17 +11,13 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit"; import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
import SettingsTab from "../../Tabs/SettingsTabV2"; import SettingsTab from "../../Tabs/SettingsTabV2";
import { throughputUnit } from "./SettingsRenderUtils"; import { throughputUnit } from "./SettingsRenderUtils";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent"; import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
import {
MongoIndexingPolicyComponent,
MongoIndexingPolicyComponentProps
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
import { import {
getMaxRUs, getMaxRUs,
hasDatabaseSharedThroughput, hasDatabaseSharedThroughput,
@@ -31,11 +27,8 @@ import {
SettingsV2TabTypes, SettingsV2TabTypes,
getTabTitle, getTabTitle,
isDirty, isDirty,
AddMongoIndexProps,
MongoIndexTypes,
parseConflictResolutionMode, parseConflictResolutionMode,
parseConflictResolutionProcedure, parseConflictResolutionProcedure
getMongoNotification
} from "./SettingsUtils"; } from "./SettingsUtils";
import { import {
ConflictResolutionComponent, ConflictResolutionComponent,
@@ -45,11 +38,6 @@ import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubCo
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react"; import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent"; 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 { interface SettingsV2TabInfo {
tab: SettingsV2TabTypes; tab: SettingsV2TabTypes;
@@ -96,13 +84,6 @@ export interface SettingsComponentState {
shouldDiscardIndexingPolicy: boolean; shouldDiscardIndexingPolicy: boolean;
isIndexingPolicyDirty: boolean; isIndexingPolicyDirty: boolean;
isMongoIndexingPolicySaveable: boolean;
isMongoIndexingPolicyDiscardable: boolean;
currentMongoIndexes: MongoIndex[];
indexesToDrop: number[];
indexesToAdd: AddMongoIndexProps[];
indexTransformationProgress: number;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
conflictResolutionPolicyPath: string; conflictResolutionPolicyPath: string;
@@ -117,17 +98,17 @@ export interface SettingsComponentState {
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> { export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
private static readonly sixMonthsInSeconds = 15768000; private static readonly sixMonthsInSeconds = 15768000;
private saveSettingsButton: ButtonV2;
private discardSettingsChangesButton: ButtonV2;
private isAnalyticalStorageEnabled: boolean; public saveSettingsButton: ButtonV2;
public discardSettingsChangesButton: ButtonV2;
public isAnalyticalStorageEnabled: boolean;
private collection: ViewModels.Collection; private collection: ViewModels.Collection;
private container: Explorer; private container: Explorer;
private changeFeedPolicyVisible: boolean; private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean; private isFixedContainer: boolean;
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[]; private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
private shouldShowIndexingPolicyEditor: boolean; private shouldShowIndexingPolicyEditor: boolean;
public mongoDBCollectionResource: MongoDBCollectionResource;
constructor(props: SettingsComponentProps) { constructor(props: SettingsComponentProps) {
super(props); super(props);
@@ -177,13 +158,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
shouldDiscardIndexingPolicy: false, shouldDiscardIndexingPolicy: false,
isIndexingPolicyDirty: false, isIndexingPolicyDirty: false,
indexesToDrop: [],
indexesToAdd: [],
currentMongoIndexes: undefined,
isMongoIndexingPolicySaveable: false,
isMongoIndexingPolicyDiscardable: false,
indexTransformationProgress: undefined,
conflictResolutionPolicyMode: undefined, conflictResolutionPolicyMode: undefined,
conflictResolutionPolicyModeBaseline: undefined, conflictResolutionPolicyModeBaseline: undefined,
conflictResolutionPolicyPath: undefined, conflictResolutionPolicyPath: undefined,
@@ -212,7 +186,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
componentDidMount(): void { componentDidMount(): void {
this.loadMongoIndexes();
this.setAutoPilotStates(); this.setAutoPilotStates();
this.setBaseline(); this.setBaseline();
if (this.props.settingsTab.isActive()) { if (this.props.settingsTab.isActive()) {
@@ -226,35 +199,6 @@ 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 => { public isSaveSettingsButtonEnabled = (): boolean => {
if (this.isOfferReplacePending()) { if (this.isOfferReplacePending()) {
return false; return false;
@@ -264,8 +208,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isScaleSaveable || this.state.isScaleSaveable ||
this.state.isSubSettingsSaveable || this.state.isSubSettingsSaveable ||
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
); );
}; };
@@ -274,8 +217,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isScaleDiscardable || this.state.isScaleDiscardable ||
this.state.isSubSettingsDiscardable || this.state.isSubSettingsDiscardable ||
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
); );
}; };
@@ -394,27 +336,6 @@ 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) { if (this.state.isScaleSaveable) {
const newThroughput = this.state.throughput; const newThroughput = this.state.throughput;
const newOffer: DataModels.Offer = { ...this.collection.offer() }; const newOffer: DataModels.Offer = { ...this.collection.offer() };
@@ -471,18 +392,33 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
throughput: newThroughput, throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false offerIsRUPerMinuteThroughputEnabled: false
}; };
try {
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
this.collection.offer().content.offerThroughput = originalThroughputValue; this.collection.offer().content.offerThroughput = originalThroughputValue;
this.setState({ this.setState({
isScaleSaveable: false, throughput: originalThroughputValue,
isScaleDiscardable: false, throughputBaseline: originalThroughputValue,
throughput: originalThroughputValue, initialNotification: {
throughputBaseline: originalThroughputValue, description: `Throughput update for ${newThroughput} ${throughputUnit}`
initialNotification: { } as DataModels.Notification
description: `Throughput update for ${newThroughput} ${throughputUnit}` });
} as DataModels.Notification this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
}); } catch (error) {
traceFailure(
Action.SettingsV2Updated,
{
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.collection && this.collection.databaseId,
collectionName: this.collection && this.collection.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
error: error
},
startKey
);
throw error;
}
} else { } else {
const updateOfferParams: DataModels.UpdateOfferParams = { const updateOfferParams: DataModels.UpdateOfferParams = {
databaseId: this.collection.databaseId, databaseId: this.collection.databaseId,
@@ -521,8 +457,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
Action.SettingsV2Updated, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name, databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle() tabTitle: this.props.settingsTab.tabTitle()
@@ -537,12 +471,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
Action.SettingsV2Updated, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name, databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle()
error: reason
}, },
startKey startKey
); );
@@ -561,8 +492,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline, timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
geospatialConfigType: this.state.geospatialConfigTypeBaseline, geospatialConfigType: this.state.geospatialConfigTypeBaseline,
indexingPolicyContent: this.state.indexingPolicyContentBaseline, indexingPolicyContent: this.state.indexingPolicyContentBaseline,
indexesToAdd: [],
indexesToDrop: [],
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline, conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline, conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline, conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
@@ -577,23 +506,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isSubSettingsSaveable: false, isSubSettingsSaveable: false,
isSubSettingsDiscardable: false, isSubSettingsDiscardable: false,
isIndexingPolicyDirty: false, isIndexingPolicyDirty: false,
isMongoIndexingPolicySaveable: false,
isMongoIndexingPolicyDiscardable: false,
isConflictResolutionDirty: 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 => private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
this.setState({ isScaleSaveable: isScaleSaveable }); this.setState({ isScaleSaveable: isScaleSaveable });
@@ -623,36 +539,6 @@ 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 => private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
this.setState({ conflictResolutionPolicyMode: newMode }); this.setState({ conflictResolutionPolicyMode: newMode });
@@ -691,12 +577,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void => private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty }); this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
private onMongoIndexingPolicySaveableChange = (isMongoIndexingPolicySaveable: boolean): void =>
this.setState({ isMongoIndexingPolicySaveable });
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
this.setState({ isMongoIndexingPolicyDiscardable });
public getAnalyticalStorageTtl = (): number => { public getAnalyticalStorageTtl = (): number => {
if (this.isAnalyticalStorageEnabled) { if (this.isAnalyticalStorageEnabled) {
if (this.state.analyticalStorageTtlSelection === TtlType.On) { if (this.state.analyticalStorageTtlSelection === TtlType.On) {
@@ -919,20 +799,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange 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 = { const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
collection: this.collection, collection: this.collection,
container: this.container, container: this.container,
@@ -949,7 +815,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}; };
const tabs: SettingsV2TabInfo[] = []; const tabs: SettingsV2TabInfo[] = [];
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) { if (!hasDatabaseSharedThroughput(this.collection)) {
tabs.push({ tabs.push({
tab: SettingsV2TabTypes.ScaleTab, tab: SettingsV2TabTypes.ScaleTab,
content: <ScaleComponent {...scaleComponentProps} /> content: <ScaleComponent {...scaleComponentProps} />
@@ -966,15 +832,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
tab: SettingsV2TabTypes.IndexingPolicyTab, tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} /> 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()) { if (this.hasConflictResolution()) {

View File

@@ -4,11 +4,17 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent"; import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
export class SettingsComponentAdapter implements ReactAdapter { export class SettingsComponentAdapter implements ReactAdapter {
public parameters: ko.Computed<boolean>; public parameters: ko.Observable<number>;
constructor(private props: SettingsComponentProps) {} constructor(private props: SettingsComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element { public renderComponent(): JSX.Element {
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>; return <SettingsComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
} }
} }

View File

@@ -15,11 +15,7 @@ import {
getToolTipContainer, getToolTipContainer,
conflictResolutionCustomToolTip, conflictResolutionCustomToolTip,
changeFeedPolicyToolTip, changeFeedPolicyToolTip,
conflictResolutionLwwTooltip, conflictResolutionLwwTooltip
mongoIndexingPolicyDisclaimer,
mongoIndexingPolicyAADError,
mongoIndexTransformationRefreshingMessage,
renderMongoIndexTransformationRefreshMessage
} from "./SettingsRenderUtils"; } from "./SettingsRenderUtils";
class SettingsRenderUtilsTestComponent extends React.Component { class SettingsRenderUtilsTestComponent extends React.Component {
@@ -49,16 +45,6 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{conflictResolutionLwwTooltip} {conflictResolutionLwwTooltip}
{conflictResolutionCustomToolTip} {conflictResolutionCustomToolTip}
{changeFeedPolicyToolTip} {changeFeedPolicyToolTip}
{mongoIndexingPolicyDisclaimer}
{mongoIndexingPolicyAADError}
{mongoIndexTransformationRefreshingMessage}
{renderMongoIndexTransformationRefreshMessage(0, () => {
return;
})}
{renderMongoIndexTransformationRefreshMessage(90, () => {
return;
})}
</> </>
); );
} }

View File

@@ -21,24 +21,13 @@ import {
Link, Link,
Text, Text,
IMessageBarStyles, IMessageBarStyles,
ITextStyles, ITextStyles
IDetailsRowStyles,
IStackStyles,
IIconStyles,
IDetailsListStyles,
IDropdownStyles,
ISeparatorStyles,
MessageBar,
MessageBarType,
Stack,
Spinner,
SpinnerSize
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { isDirtyTypes, isDirty } from "./SettingsUtils"; import { isDirtyTypes, isDirty } from "./SettingsUtils";
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } }; const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const spendAckCheckBoxStyle: ICheckboxStyles = {
label: { label: {
margin: 0, margin: 0,
padding: "2 0 2 0" padding: "2 0 2 0"
@@ -56,20 +45,6 @@ export const titleAndInputStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 5 } 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> = { export const checkBoxAndInputStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 10 } tokens: { childrenGap: 10 }
}; };
@@ -78,54 +53,6 @@ export const toolTipLabelStackTokens: IStackTokens = {
childrenGap: 6 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 messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
export const throughputUnit = "RU/s"; export const throughputUnit = "RU/s";
@@ -386,56 +313,6 @@ export const changeFeedPolicyToolTip: JSX.Element = (
</Text> </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> => ({ export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
fieldGroup: { fieldGroup: {
height: 25, height: 25,

View File

@@ -1,24 +0,0 @@
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();
});
});

View File

@@ -1,103 +0,0 @@
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>
);
}
}

View File

@@ -1,122 +0,0 @@
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
);
}
);
});
});

View File

@@ -1,369 +0,0 @@
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} />;
}
}
}

View File

@@ -1,83 +0,0 @@
// 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>
`;

View File

@@ -1,137 +0,0 @@
// 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>
`;

View File

@@ -5,6 +5,7 @@ import { container, collection } from "../TestUtils";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component"; import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import * as Constants from "../../../../Common/Constants"; import * as Constants from "../../../../Common/Constants";
import { PlatformType } from "../../../../PlatformType";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import { throughputUnit } from "../SettingsRenderUtils"; import { throughputUnit } from "../SettingsRenderUtils";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
@@ -12,6 +13,7 @@ import ko from "knockout";
describe("ScaleComponent", () => { describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer(); const nonNationalCloudContainer = new Explorer();
nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal;
nonNationalCloudContainer.isRunningOnNationalCloud = () => false; nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
const targetThroughput = 6000; const targetThroughput = 6000;
@@ -117,7 +119,7 @@ describe("ScaleComponent", () => {
it("getThroughputTitle", () => { it("getThroughputTitle", () => {
let scaleComponent = new ScaleComponent(baseProps); let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)"); expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - 40,000 RU/s)");
let newProps = { ...baseProps, container: nonNationalCloudContainer }; let newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps); scaleComponent = new ScaleComponent(newProps);
@@ -130,7 +132,7 @@ describe("ScaleComponent", () => {
it("canThroughputExceedMaximumValue", () => { it("canThroughputExceedMaximumValue", () => {
let scaleComponent = new ScaleComponent(baseProps); let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true); expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(false);
const newProps = { ...baseProps, container: nonNationalCloudContainer }; const newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps); scaleComponent = new ScaleComponent(newProps);

View File

@@ -5,6 +5,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { PlatformType } from "../../../../PlatformType";
import { import {
getTextFieldStyles, getTextFieldStyles,
subComponentStackProps, subComponentStackProps,
@@ -77,7 +78,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}; };
public getMaxRUThroughputInputLimit = (): number => { public getMaxRUThroughputInputLimit = (): number => {
if (configContext.platform === Platform.Hosted && this.props.collection.partitionKey) { if (this.props.container?.getPlatformType() === PlatformType.Hosted && this.props.collection.partitionKey) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million; return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
} }
@@ -98,11 +99,12 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}; };
public canThroughputExceedMaximumValue = (): boolean => { public canThroughputExceedMaximumValue = (): boolean => {
return ( const isPublicAzurePortal: boolean =
!this.props.isFixedContainer && this.props.container.getPlatformType() === PlatformType.Portal &&
configContext.platform === Platform.Portal && !this.props.container.isRunningOnNationalCloud();
!this.props.container.isRunningOnNationalCloud() const hasPartitionKey = !!this.props.collection.partitionKey;
);
return isPublicAzurePortal && hasPartitionKey;
}; };
public getInitialNotificationElement = (): JSX.Element => { public getInitialNotificationElement = (): JSX.Element => {

View File

@@ -3,7 +3,7 @@ import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import { import {
getTextFieldStyles, getTextFieldStyles,
getToolTipContainer, getToolTipContainer,
noLeftPaddingCheckBoxStyle, spendAckCheckBoxStyle,
titleAndInputStackProps, titleAndInputStackProps,
checkBoxAndInputStackProps, checkBoxAndInputStackProps,
getChoiceGroupStyles, getChoiceGroupStyles,
@@ -142,7 +142,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep; this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum; this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
this.autoPilotInputMaxValue = this.props.isFixed ? this.props.maximum : Int32.Max; this.autoPilotInputMaxValue = Int32.Max;
} }
public hasProvisioningTypeChanged = (): boolean => public hasProvisioningTypeChanged = (): boolean =>
@@ -278,13 +278,12 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
{this.props.spendAckVisible && ( {this.props.spendAckVisible && (
<Checkbox <Checkbox
id="spendAckCheckBox" id="spendAckCheckBox"
styles={noLeftPaddingCheckBoxStyle} styles={spendAckCheckBoxStyle}
label={this.props.spendAckText} label={this.props.spendAckText}
checked={this.state.spendAckChecked} checked={this.state.spendAckChecked}
onChange={this.onSpendAckChecked} 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>}
</> </>
); );
@@ -317,21 +316,21 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
{this.props.spendAckVisible && ( {this.props.spendAckVisible && (
<Checkbox <Checkbox
id="spendAckCheckBox" id="spendAckCheckBox"
styles={noLeftPaddingCheckBoxStyle} styles={spendAckCheckBoxStyle}
label={this.props.spendAckText} label={this.props.spendAckText}
checked={this.state.spendAckChecked} checked={this.state.spendAckChecked}
onChange={this.onSpendAckChecked} 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>} {this.props.isFixed && <p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>}
</Stack> </Stack>
); );
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<Stack {...checkBoxAndInputStackProps}> <Stack {...checkBoxAndInputStackProps}>
{this.renderThroughputModeChoices()} {!this.props.isFixed && this.renderThroughputModeChoices()}
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()} {this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
</Stack> </Stack>

View File

@@ -39,13 +39,13 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
} }
> >
<ThroughputInputAutoPilotV3Component <ThroughputInputAutoPilotV3Component
canExceedMaximumValue={true} canExceedMaximumValue={false}
getThroughputWarningMessage={[Function]} getThroughputWarningMessage={[Function]}
isAutoPilotSelected={false} isAutoPilotSelected={false}
isEmulator={false} isEmulator={false}
isEnabled={true} isEnabled={true}
isFixed={false} isFixed={false}
label="Throughput (6,000 - unlimited RU/s)" label="Throughput (6,000 - 40,000 RU/s)"
maxAutoPilotThroughput={4000} maxAutoPilotThroughput={4000}
maxAutoPilotThroughputBaseline={4000} maxAutoPilotThroughputBaseline={4000}
maximum={40000} maximum={40000}

View File

@@ -2,20 +2,12 @@ import { collection, container } from "./TestUtils";
import { import {
getMaxRUs, getMaxRUs,
getMinRUs, getMinRUs,
getMongoIndexType,
getMongoNotification,
getSanitizedInputValue, getSanitizedInputValue,
hasDatabaseSharedThroughput, hasDatabaseSharedThroughput,
isDirty, isDirty,
isDirtyTypes, isDirtyTypes,
MongoIndexTypes,
MongoNotificationType,
parseConflictResolutionMode, parseConflictResolutionMode,
parseConflictResolutionProcedure, parseConflictResolutionProcedure
MongoWildcardPlaceHolder,
getMongoIndexTypeText,
SingleFieldText,
WildcardText
} from "./SettingsUtils"; } from "./SettingsUtils";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
@@ -99,43 +91,7 @@ describe("SettingsUtils", () => {
it("getSanitizedInputValue", () => { it("getSanitizedInputValue", () => {
const max = 100; const max = 100;
expect(getSanitizedInputValue("", max)).toEqual(0); expect(getSanitizedInputValue("", max)).toEqual(0);
expect(getSanitizedInputValue("999", max)).toEqual(100); expect(getSanitizedInputValue("999", max)).toEqual(99);
expect(getSanitizedInputValue("10", max)).toEqual(10); 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);
});
}); });

View File

@@ -5,17 +5,12 @@ import * as SharedConstants from "../../../Shared/Constants";
import * as PricingUtils from "../../../Utils/PricingUtils"; import * as PricingUtils from "../../../Utils/PricingUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
const zeroValue = 0; const zeroValue = 0;
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
export const TtlOff = "off"; export const TtlOff = "off";
export const TtlOn = "on"; export const TtlOn = "on";
export const TtlOnNoDefault = "on-nodefault"; 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 { export enum ChangeFeedPolicyState {
Off = "Off", Off = "Off",
@@ -33,17 +28,6 @@ export enum GeospatialConfigType {
Geometry = "Geometry" Geometry = "Geometry"
} }
export enum MongoIndexTypes {
Single = "Single",
Wildcard = "Wildcard"
}
export interface AddMongoIndexProps {
mongoIndex: MongoIndex;
type: MongoIndexTypes;
notification: MongoNotificationMessage;
}
export enum SettingsV2TabTypes { export enum SettingsV2TabTypes {
ScaleTab, ScaleTab,
ConflictResolutionTab, ConflictResolutionTab,
@@ -56,16 +40,6 @@ export interface IsComponentDirtyResult {
isDiscardable: boolean; isDiscardable: boolean;
} }
export enum MongoNotificationType {
Warning = "Warning",
Error = "Error"
}
export interface MongoNotificationMessage {
type: MongoNotificationType;
message: string;
}
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => { export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
const database: ViewModels.Database = collection.getDatabase(); const database: ViewModels.Database = collection.getDatabase();
return database?.isDatabaseShared() && !collection.offer(); return database?.isDatabaseShared() && !collection.offer();
@@ -80,7 +54,7 @@ export const getMaxRUs = (collection: ViewModels.Collection, container: Explorer
const numPartitionsFromOffer: number = const numPartitionsFromOffer: number =
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions; collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo()?.numPartitions; const numPartitionsFromQuotaInfo: number = collection?.quotaInfo().numPartitions;
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1; const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
@@ -105,7 +79,7 @@ export const getMinRUs = (collection: ViewModels.Collection, container: Explorer
return collectionThroughputInfo.minimumRUForCollection; return collectionThroughputInfo.minimumRUForCollection;
} }
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo()?.numPartitions; const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo().numPartitions;
if (!numPartitions || numPartitions === 1) { if (!numPartitions || numPartitions === 1) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs400; return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
@@ -157,12 +131,13 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
}; };
export const getSanitizedInputValue = (newValueString: string, max: number): number => { export const getSanitizedInputValue = (newValueString: string, max: number): number => {
const newValue = parseInt(newValueString); let newValue = parseInt(newValueString);
if (isNaN(newValue)) { if (isNaN(newValue)) {
return zeroValue; newValue = zeroValue;
} else if (newValue > max) {
newValue = Math.floor(newValue / 10);
} }
// make sure new value does not exceed the maximum throughput return newValue;
return Math.min(newValue, max);
}; };
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => { export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
@@ -205,48 +180,3 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
throw new Error(`Unknown tab ${tab}`); 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;
};

View File

@@ -983,7 +983,6 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function], "isLinkInjectionEnabled": [Function],
"isMongoIndexEditorEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function], "isNotificationConsoleExpanded": [Function],
@@ -2290,7 +2289,6 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function], "isLinkInjectionEnabled": [Function],
"isMongoIndexEditorEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function], "isNotificationConsoleExpanded": [Function],
@@ -3610,7 +3608,6 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function], "isLinkInjectionEnabled": [Function],
"isMongoIndexEditorEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function], "isNotificationConsoleExpanded": [Function],
@@ -4917,7 +4914,6 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function], "isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function], "isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function], "isLinkInjectionEnabled": [Function],
"isMongoIndexEditorEnabled": [Function],
"isNotebookEnabled": [Function], "isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function], "isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function], "isNotificationConsoleExpanded": [Function],

View File

@@ -310,59 +310,5 @@ 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. 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>
<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> </Fragment>
`; `;

View File

@@ -45,7 +45,7 @@
<input <input
class="throughputModeRadio nonFirstRadio" class="throughputModeRadio nonFirstRadio"
aria-label="Manual mode" aria-label="Provisioned Throughput mode"
type="radio" type="radio"
role="radio" role="radio"
tabindex="0" tabindex="0"

View File

@@ -44,7 +44,7 @@
<input <input
class="throughputModeRadio nonFirstRadio" class="throughputModeRadio nonFirstRadio"
aria-label="Manual mode" aria-label="Provisioned Throughput mode"
type="radio" type="radio"
role="radio" role="radio"
tabindex="0" tabindex="0"
@@ -119,10 +119,6 @@
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span> <span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
</p> </p>
<!-- /ko --> <!-- /ko -->
<!-- ko if: isFixed -->
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
<!-- /ko -->
</div> </div>
<div data-bind="visible: !isAutoPilotSelected()"> <div data-bind="visible: !isAutoPilotSelected()">

View File

@@ -3,6 +3,7 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
jest.mock("../../Common/dataAccess/createCollection"); jest.mock("../../Common/dataAccess/createCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Q from "q";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { createDocument } from "../../Common/DocumentClientUtilityBase"; import { createDocument } from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
@@ -20,7 +21,7 @@ describe("ContainerSampleGenerator", () => {
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false); explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorerStub.findDatabaseWithId = () => database; explorerStub.findDatabaseWithId = () => database;
explorerStub.refreshAllDatabases = () => Promise.resolve(); explorerStub.refreshAllDatabases = () => Q.resolve();
return explorerStub; return explorerStub;
}; };

View File

@@ -70,7 +70,7 @@ export class ContainerSampleGenerator {
if (!collection) { if (!collection) {
throw new Error("No container to populate"); throw new Error("No container to populate");
} }
const promises: Promise<any>[] = []; const promises: Q.Promise<any>[] = [];
if (this.container.isPreferredApiGraph()) { if (this.container.isPreferredApiGraph()) {
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries // For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries

View File

@@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { configContext, Platform, updateConfigContext } from "../ConfigContext"; import { configContext, updateConfigContext } from "../ConfigContext";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
@@ -58,6 +58,7 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager"; import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter"; import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { PlatformType } from "../PlatformType";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane"; import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
import { RenewAdHocAccessPane } from "./Panes/RenewAdHocAccessPane"; import { RenewAdHocAccessPane } from "./Panes/RenewAdHocAccessPane";
@@ -206,7 +207,6 @@ export default class Explorer {
public isCodeOfConductEnabled: ko.Computed<boolean>; public isCodeOfConductEnabled: ko.Computed<boolean>;
public isLinkInjectionEnabled: ko.Computed<boolean>; public isLinkInjectionEnabled: ko.Computed<boolean>;
public isSettingsV2Enabled: ko.Observable<boolean>; public isSettingsV2Enabled: ko.Observable<boolean>;
public isMongoIndexEditorEnabled: ko.Observable<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>; public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isCopyNotebookPaneEnabled: ko.Observable<boolean>; public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
@@ -217,7 +217,7 @@ export default class Explorer {
public shouldShowShareDialogContents: ko.Observable<boolean>; public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<AdHocAccessData>; public shareAccessData: ko.Observable<AdHocAccessData>;
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Promise<void>; public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
public renewTokenError: ko.Observable<string>; public renewTokenError: ko.Observable<string>;
public tokenForRenewal: ko.Observable<string>; public tokenForRenewal: ko.Observable<string>;
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>; public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
@@ -413,8 +413,8 @@ export default class Explorer {
this.isLinkInjectionEnabled = ko.computed<boolean>(() => this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableLinkInjection) this.isFeatureEnabled(Constants.Features.enableLinkInjection)
); );
//this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
this.isSettingsV2Enabled = ko.observable(false); this.isSettingsV2Enabled = ko.observable(false);
this.isMongoIndexEditorEnabled = ko.observable(false);
this.isGitHubPaneEnabled = ko.observable<boolean>(false); this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false); this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false); this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
@@ -565,7 +565,9 @@ export default class Explorer {
this.isHostedDataExplorerEnabled = ko.computed<boolean>( this.isHostedDataExplorerEnabled = ko.computed<boolean>(
() => () =>
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph() this.getPlatformType() === PlatformType.Portal &&
!this.isRunningOnNationalCloud() &&
!this.isPreferredApiGraph()
); );
this.isRightPanelV2Enabled = ko.computed<boolean>(() => this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableRightPanelV2) this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
@@ -1120,8 +1122,8 @@ export default class Explorer {
"Initiating connection to account" "Initiating connection to account"
); );
this.renewExplorerShareAccess(this, this.tokenForRenewal()) this.renewExplorerShareAccess(this, this.tokenForRenewal())
.catch((error: any) => { .fail((error: any) => {
const stringifiedError: string = error.message; const stringifiedError: string = JSON.stringify(error);
this.renewTokenError("Invalid connection string specified"); this.renewTokenError("Invalid connection string specified");
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
@@ -1150,34 +1152,43 @@ export default class Explorer {
NotificationConsoleUtils.clearInProgressMessageWithId(id); NotificationConsoleUtils.clearInProgressMessageWithId(id);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to generate share url: ${error.message}` `Failed to generate share url: ${JSON.stringify(error)}`
); );
console.error(error); console.error(error);
} }
); );
} }
public async renewShareAccess(token: string): Promise<void> { public renewShareAccess(token: string): Q.Promise<void> {
if (!this.renewExplorerShareAccess) { if (!this.renewExplorerShareAccess) {
throw "Not implemented"; return Q.reject("Not implemented");
} }
const deferred: Q.Deferred<void> = Q.defer<void>();
const id: string = NotificationConsoleUtils.logConsoleMessage( const id: string = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
"Initiating connection to account" "Initiating connection to account"
); );
return this.renewExplorerShareAccess(this, token) this.renewExplorerShareAccess(this, token)
.then( .then(
() => { () => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful"); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful");
this.renewAdHocAccessPane && this.renewAdHocAccessPane.close(); this.renewAdHocAccessPane && this.renewAdHocAccessPane.close();
deferred.resolve();
}, },
(error: any) => { (error: any) => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`); NotificationConsoleUtils.logConsoleMessage(
throw error; ConsoleDataType.Error,
`Failed to connect: ${JSON.stringify(error)}`
);
deferred.reject(error);
} }
) )
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); .finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
} }
public displayGuestAccessTokenRenewalPrompt(): void { public displayGuestAccessTokenRenewalPrompt(): void {
@@ -1372,19 +1383,24 @@ export default class Explorer {
} }
} }
public async refreshDatabaseForResourceToken(): Promise<any> { public refreshDatabaseForResourceToken(): Q.Promise<any> {
const databaseId = this.resourceTokenDatabaseId(); const databaseId = this.resourceTokenDatabaseId();
const collectionId = this.resourceTokenCollectionId(); const collectionId = this.resourceTokenCollectionId();
if (!databaseId || !collectionId) { if (!databaseId || !collectionId) {
throw new Error(); return Q.reject();
} }
const collection = await readCollection(databaseId, collectionId); const deferred: Q.Deferred<void> = Q.defer();
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection)); readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
this.selectedNode(this.resourceTokenCollection()); this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
this.selectedNode(this.resourceTokenCollection());
deferred.resolve();
});
return deferred.promise;
} }
public refreshAllDatabases(isInitialLoad?: boolean): Promise<any> { public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
this.isRefreshingExplorer(true); this.isRefreshingExplorer(true);
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, { const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name, databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
@@ -1401,85 +1417,89 @@ export default class Explorer {
} }
// TODO: Refactor // TODO: Refactor
const deferred: Q.Deferred<any> = Q.defer();
this._setLoadingStatusText("Fetching databases..."); this._setLoadingStatusText("Fetching databases...");
return readDatabases() readDatabases().then(
.then( (databases: DataModels.Database[]) => {
(databases: DataModels.Database[]) => { this._setLoadingStatusText("Successfully fetched databases.");
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) {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabases, Action.LoadResourceTree,
{ {
databaseAccountName: this.databaseAccount().name, databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(), defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}, },
startKey resourceTreeStartKey
); );
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode(); }
const deltaDatabases = this.getDeltaDatabases(databases); },
this.addDatabasesToList(deltaDatabases.toAdd); reason => {
this.deleteDatabasesFromList(deltaDatabases.toDelete); if (resourceTreeStartKey != null) {
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( TelemetryProcessor.traceFailure(
Action.LoadDatabases, Action.LoadResourceTree,
{ {
databaseAccountName: this.databaseAccount().name, databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(), defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
error: error.message error: reason
}, },
startKey resourceTreeStartKey
); );
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 => { public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
@@ -1516,7 +1536,7 @@ export default class Explorer {
this.isRefreshingExplorer(false); this.isRefreshingExplorer(false);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Error while refreshing data: ${error.message}` `Error while refreshing data: ${JSON.stringify(error)}`
); );
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.LoadDatabases, Action.LoadDatabases,
@@ -1583,7 +1603,7 @@ export default class Explorer {
return Promise.all(sparkPromises).then(() => workspaceItems); return Promise.all(sparkPromises).then(() => workspaceItems);
} catch (error) { } catch (error) {
Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync"); Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error.message); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, JSON.stringify(error));
return Promise.resolve([]); return Promise.resolve([]);
} }
} }
@@ -1621,7 +1641,7 @@ export default class Explorer {
Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync"); Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to get notebook workspace connection info: ${error.message}` `Failed to get notebook workspace connection info: ${JSON.stringify(error)}`
); );
throw error; throw error;
} finally { } finally {
@@ -1698,7 +1718,7 @@ export default class Explorer {
} }
} catch (error) { } catch (error) {
Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning"); Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${error.message}`); NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${JSON.stringify(error)}`);
} finally { } finally {
clearMessage && clearMessage(); clearMessage && clearMessage();
} }
@@ -1773,13 +1793,13 @@ export default class Explorer {
const message: any = event.data.data; const message: any = event.data.data;
const inputs: ViewModels.DataExplorerInputsFrame = message.inputs; const inputs: ViewModels.DataExplorerInputsFrame = message.inputs;
const isRunningInPortal = configContext.platform === Platform.Portal; const isRunningInPortal = window.dataExplorerPlatform == PlatformType.Portal;
const isRunningInDevMode = process.env.NODE_ENV === "development"; const isRunningInDevMode = process.env.NODE_ENV === "development";
if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) { if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) {
inputs.extensionEndpoint = configContext.PROXY_PATH; inputs.extensionEndpoint = configContext.PROXY_PATH;
} }
const initPromise: Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Promise.resolve(); const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
initPromise.then(() => { initPromise.then(() => {
const openAction: ActionContracts.DataExplorerAction = message.openAction; const openAction: ActionContracts.DataExplorerAction = message.openAction;
@@ -1873,7 +1893,7 @@ export default class Explorer {
return false; return false;
} }
public async initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Promise<void> { public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
if (inputs != null) { if (inputs != null) {
const authorizationToken = inputs.authorizationToken || ""; const authorizationToken = inputs.authorizationToken || "";
const masterKey = inputs.masterKey || ""; const masterKey = inputs.masterKey || "";
@@ -1923,6 +1943,7 @@ export default class Explorer {
this.isAccountReady(true); this.isAccountReady(true);
} }
return Q();
} }
public setFeatureFlagsFromFlights(flights: readonly string[]): void { public setFeatureFlagsFromFlights(flights: readonly string[]): void {
@@ -1933,10 +1954,6 @@ export default class Explorer {
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) { if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
this.isSettingsV2Enabled(true); this.isSettingsV2Enabled(true);
} }
if (flights.indexOf(Constants.Flights.MongoIndexEditor) !== -1) {
this.isMongoIndexEditorEnabled(true);
}
} }
public findSelectedCollection(): ViewModels.Collection { public findSelectedCollection(): ViewModels.Collection {
@@ -1992,6 +2009,10 @@ export default class Explorer {
this._panes.forEach((pane: ContextualPaneBase) => pane.close()); this._panes.forEach((pane: ContextualPaneBase) => pane.close());
} }
public getPlatformType(): PlatformType {
return window.dataExplorerPlatform;
}
public isRunningOnNationalCloud(): boolean { public isRunningOnNationalCloud(): boolean {
return ( return (
this.serverId() === Constants.ServerIds.blackforest || this.serverId() === Constants.ServerIds.blackforest ||
@@ -2040,7 +2061,7 @@ export default class Explorer {
// we reload collections for all databases so the resource tree reflects any collection-level changes // we reload collections for all databases so the resource tree reflects any collection-level changes
// i.e addition of stored procedures, etc. // i.e addition of stored procedures, etc.
const deferred: Q.Deferred<void> = Q.defer<void>(); const deferred: Q.Deferred<void> = Q.defer<void>();
let loadCollectionPromises: Promise<void>[] = []; let loadCollectionPromises: Q.Promise<void>[] = [];
// If the user has a lot of databases, only load expanded databases. // If the user has a lot of databases, only load expanded databases.
const databasesToLoad = const databasesToLoad =
@@ -2079,7 +2100,7 @@ export default class Explorer {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name, databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(), defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
trace: error.message trace: JSON.stringify(error)
}, },
startKey startKey
); );
@@ -2424,7 +2445,7 @@ export default class Explorer {
return true; return true;
} }
public async renameNotebook(notebookFile: NotebookContentItem): Promise<NotebookContentItem> { public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to rename notebook, but notebook is not enabled"; const error = "Attempt to rename notebook, but notebook is not enabled";
Logger.logError(error, "Explorer/renameNotebook"); Logger.logError(error, "Explorer/renameNotebook");
@@ -2441,35 +2462,39 @@ export default class Explorer {
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
throw new Error(); return Q.reject();
} }
const originalPath = notebookFile.path; const originalPath = notebookFile.path;
const newNotebookFile = await this.stringInputPane.openWithOptions<NotebookContentItem>({ const result = this.stringInputPane
errorMessage: "Could not rename notebook", .openWithOptions<NotebookContentItem>({
inProgressMessage: "Renaming notebook to", errorMessage: "Could not rename notebook",
successMessage: "Renamed notebook to", inProgressMessage: "Renaming notebook to",
inputLabel: "Enter new notebook name", successMessage: "Renamed notebook to",
paneTitle: "Rename Notebook", inputLabel: "Enter new notebook name",
submitButtonLabel: "Rename", paneTitle: "Rename Notebook",
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"), submitButtonLabel: "Rename",
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input) defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
}); onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
const notebookTabs = this.tabsManager.getTabs( })
ViewModels.CollectionTabKind.NotebookV2, .then(newNotebookFile => {
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath) const notebookTabs = this.tabsManager.getTabs(
); ViewModels.CollectionTabKind.NotebookV2,
notebookTabs.forEach(tab => { (tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
tab.tabTitle(newNotebookFile.name); );
tab.tabPath(newNotebookFile.path); notebookTabs.forEach(tab => {
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path); tab.tabTitle(newNotebookFile.name);
}); tab.tabPath(newNotebookFile.path);
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
this.resourceTree.triggerRender(); return newNotebookFile;
return newNotebookFile; });
result.then(() => this.resourceTree.triggerRender());
return result;
} }
public async onCreateDirectory(parent: NotebookContentItem): Promise<NotebookContentItem> { public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create notebook directory, but notebook is not enabled"; const error = "Attempt to create notebook directory, but notebook is not enabled";
Logger.logError(error, "Explorer/onCreateDirectory"); Logger.logError(error, "Explorer/onCreateDirectory");
@@ -2477,7 +2502,7 @@ export default class Explorer {
throw new Error(error); throw new Error(error);
} }
const result = await this.stringInputPane.openWithOptions<NotebookContentItem>({ const result = this.stringInputPane.openWithOptions<NotebookContentItem>({
errorMessage: "Could not create directory ", errorMessage: "Could not create directory ",
inProgressMessage: "Creating directory ", inProgressMessage: "Creating directory ",
successMessage: "Created directory ", successMessage: "Created directory ",
@@ -2487,7 +2512,7 @@ export default class Explorer {
defaultInput: "", defaultInput: "",
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input) onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input)
}); });
this.resourceTree.triggerRender(); result.then(() => this.resourceTree.triggerRender());
return result; return result;
} }
@@ -2537,7 +2562,7 @@ export default class Explorer {
(error: any) => { (error: any) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Could not download notebook ${error.message}` `Could not download notebook ${JSON.stringify(error)}`
); );
clearMessage(); clearMessage();

View File

@@ -378,7 +378,8 @@ export class D3ForceGraph implements GraphRenderer {
* @param targetPosition * @param targetPosition
* @return promise with shift offset * @return promise with shift offset
*/ */
private async shiftGraph(targetPosition: Point2D): Promise<Point2D> { private shiftGraph(targetPosition: Point2D): Q.Promise<Point2D> {
const deferred: Q.Deferred<Point2D> = Q.defer<Point2D>();
const offset = { x: this.width / 2 - targetPosition.x, y: this.height / 2 - targetPosition.y }; const offset = { x: this.width / 2 - targetPosition.x, y: this.height / 2 - targetPosition.y };
this.viewCenter = targetPosition; this.viewCenter = targetPosition;
@@ -390,15 +391,18 @@ export class D3ForceGraph implements GraphRenderer {
.translate(-targetPosition.x, -targetPosition.y); .translate(-targetPosition.x, -targetPosition.y);
}; };
const transition = this.zoomBackground this.zoomBackground
.transition() .transition()
.duration(D3ForceGraph.TRANSITION_STEP1_MS) .duration(D3ForceGraph.TRANSITION_STEP1_MS)
.call(this.zoom.transform, transform); .call(this.zoom.transform, transform)
.on("end", () => {
await new Promise(resolve => transition.on("end", resolve)); deferred.resolve(offset);
return offset; });
} else {
deferred.resolve(null);
} }
return null;
return deferred.promise;
} }
private onGraphDataUpdate(graph: GraphData<D3Node, D3Link>) { private onGraphDataUpdate(graph: GraphData<D3Node, D3Link>) {
@@ -431,7 +435,7 @@ export class D3ForceGraph implements GraphRenderer {
} }
} }
private animateRemoveExitSelections(): Promise<void> { private animateRemoveExitSelections(): Q.Promise<void> {
const deferred1 = Q.defer<void>(); const deferred1 = Q.defer<void>();
const deferred2 = Q.defer<void>(); const deferred2 = Q.defer<void>();
const linkExitSelection = this.linkSelection.exit(); const linkExitSelection = this.linkSelection.exit();
@@ -504,7 +508,7 @@ export class D3ForceGraph implements GraphRenderer {
deferred2.resolve(); deferred2.resolve();
} }
return Promise.all([deferred1.promise, deferred2.promise]).then(undefined); return Q.allSettled([deferred1.promise, deferred2.promise]).then(undefined);
} }
/** /**

View File

@@ -892,7 +892,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
backendPromise.then( backendPromise.then(
(result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge), (result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge),
(error: any) => { (error: any) => {
const errorMsg = `Failure in submitting query: ${query}: ${error.message}`; const errorMsg = `Failure in submitting query: ${query}: ${JSON.stringify(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg); GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({ this.setState({
filterQueryError: errorMsg filterQueryError: errorMsg
@@ -1826,7 +1826,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
promise promise
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result)) .then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
.catch((error: any) => { .catch((error: any) => {
const errorMsg = `Failed to process query result: ${error.message}`; const errorMsg = `Failed to process query result: ${JSON.stringify(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg); GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({ this.setState({
filterQueryError: errorMsg filterQueryError: errorMsg

View File

@@ -57,9 +57,9 @@ export class GremlinClient {
this.flushResult(result.requestId); this.flushResult(result.requestId);
} }
}, },
failureCallback: (result: Result, error: any) => { failureCallback: (result: Result, error: string) => {
if (typeof error !== "string") { if (typeof error !== "string") {
error = error.message; error = JSON.stringify(error);
} }
const requestId = result.requestId; const requestId = result.requestId;

View File

@@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import { mount, ReactWrapper } from "enzyme"; import { mount, ReactWrapper } from "enzyme";
import * as Q from "q";
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent"; import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer"; import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
@@ -40,11 +41,11 @@ describe("Property pane", () => {
node: highlightedNode, node: highlightedNode,
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null, getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
collectionPartitionKeyProperty: null, collectionPartitionKeyProperty: null,
updateVertexProperties: (editedProperties: EditedProperties): Promise<void> => Promise.resolve(), updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
selectNode: (id: string): void => {}, selectNode: (id: string): void => {},
updatePossibleVertices: async (): Promise<PossibleVertex[]> => null, updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
possibleEdgeLabels: null, possibleEdgeLabels: null,
editGraphEdges: async (editedEdges: EditedEdges): Promise<any> => undefined, editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
deleteHighlightedNode: (): void => {}, deleteHighlightedNode: (): void => {},
onModeChanged: (newMode: Mode): void => {}, onModeChanged: (newMode: Mode): void => {},
viewMode: Mode.READONLY_PROP viewMode: Mode.READONLY_PROP

View File

@@ -36,11 +36,11 @@ export interface NodePropertiesComponentProps {
node: GraphHighlightedNodeData; node: GraphHighlightedNodeData;
getPkIdFromNodeData: (v: GraphHighlightedNodeData) => string; getPkIdFromNodeData: (v: GraphHighlightedNodeData) => string;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
updateVertexProperties: (editedProperties: EditedProperties) => Promise<void>; updateVertexProperties: (editedProperties: EditedProperties) => Q.Promise<void>;
selectNode: (id: string) => void; selectNode: (id: string) => void;
updatePossibleVertices: () => Promise<PossibleVertex[]>; updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
possibleEdgeLabels: Item[]; possibleEdgeLabels: Item[];
editGraphEdges: (editedEdges: EditedEdges) => Promise<any>; editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<any>;
deleteHighlightedNode: () => void; deleteHighlightedNode: () => void;
onModeChanged: (newMode: Mode) => void; onModeChanged: (newMode: Mode) => void;
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it

View File

@@ -1,4 +1,5 @@
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { PlatformType } from "../../../PlatformType";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../../Common/Constants"; import { Areas } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -158,7 +159,7 @@ export class CommandBarComponentButtonFactory {
public static createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { public static createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];
if (configContext.platform === Platform.Hosted) { if (window.dataExplorerPlatform === PlatformType.Hosted) {
return buttons; return buttons;
} }

View File

@@ -47,7 +47,7 @@ export default function configureStore(
onTraceFailure(title, `${error.message} ${JSON.stringify(error.stack)}`); onTraceFailure(title, `${error.message} ${JSON.stringify(error.stack)}`);
console.error(error); console.error(error);
} else { } else {
onTraceFailure(title, error.message); onTraceFailure(title, JSON.stringify(error));
} }
}; };

View File

@@ -106,7 +106,3 @@
.expanded::before { .expanded::before {
content: ''; content: '';
} }
.monaco-editor .monaco-list .main {
background-color: transparent;
}

View File

@@ -276,7 +276,7 @@
<!-- Fixed option button - Start --> <!-- Fixed option button - Start -->
<div class="tab"> <div class="tab">
<input type="radio" id="tab1" name="storage" value="10" class="radio" data-bind="checked: storage"> <input type="radio" id="tab1" name="storage" value="10" class="radio" data-bind="checked: storage">
<label for="tab1">Fixed (20 GB)</label> <label for="tab1">Fixed (10 GB)</label>
</div> </div>
<!-- Fixed option button - End --> <!-- Fixed option button - End -->

View File

@@ -14,6 +14,8 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent"; import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { HashMap } from "../../Common/HashMap";
import { PlatformType } from "../../PlatformType";
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase"; import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
import { createCollection } from "../../Common/dataAccess/createCollection"; import { createCollection } from "../../Common/dataAccess/createCollection";
@@ -325,7 +327,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
if ( if (
configContext.platform !== Platform.Emulator && configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() && !this.container.isTryCosmosDBSubscription() &&
configContext.platform !== Platform.Portal this.container.getPlatformType() !== PlatformType.Portal
) { ) {
const offerThroughput: number = this._getThroughput(); const offerThroughput: number = this._getThroughput();
return offerThroughput <= 100000; return offerThroughput <= 100000;

View File

@@ -74,8 +74,8 @@
<input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]" <input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'" title="May not end with space nor contain characters '\' '/' '#' '?'"
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { 'aria-label': databaseIdLabel, 'placeholder': databaseIdPlaceHolder }" size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { placeholder: databaseIdPlaceHolder }"
autofocus> aria-label="Database id" autofocus>
<!-- Database provisioned throughput - Start --> <!-- Database provisioned throughput - Start -->
<!-- ko if: canConfigureThroughput --> <!-- ko if: canConfigureThroughput -->

View File

@@ -11,6 +11,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { createDatabase } from "../../Common/dataAccess/createDatabase"; import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { PlatformType } from "../../PlatformType";
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
export default class AddDatabasePane extends ContextualPaneBase { export default class AddDatabasePane extends ContextualPaneBase {
@@ -182,7 +183,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
if ( if (
configContext.platform !== Platform.Emulator && configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() && !this.container.isTryCosmosDBSubscription() &&
configContext.platform !== Platform.Portal this.container.getPlatformType() !== PlatformType.Portal
) { ) {
const offerThroughput: number = this.throughput(); const offerThroughput: number = this.throughput();
return offerThroughput <= 100000; return offerThroughput <= 100000;

View File

@@ -70,7 +70,8 @@ export class BrowseQueriesPane extends ContextualPaneBase {
}, },
startKey startKey
); );
this.formErrors(`Failed to setup a collection for saved queries: ${error.message}`); this.formErrors("Failed to setup a collection for saved queries");
this.formErrors(`Failed to setup a collection for saved queries: ${JSON.stringify(error)}`);
} finally { } finally {
this.isExecuting(false); this.isExecuting(false);
} }

View File

@@ -350,7 +350,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} }
this.isExecuting(true); this.isExecuting(true);
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
let createTableAndKeyspacePromise: Promise<any>; let createTableAndKeyspacePromise: Q.Promise<any>;
const toCreateKeyspace: boolean = this.keyspaceCreateNew(); const toCreateKeyspace: boolean = this.keyspaceCreateNew();
const useAutoPilotForKeyspace: boolean = const useAutoPilotForKeyspace: boolean =
(!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput()) || (!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput()) ||

View File

@@ -1,6 +1,7 @@
jest.mock("../../Common/dataAccess/deleteCollection"); jest.mock("../../Common/dataAccess/deleteCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as sinon from "sinon"; import * as sinon from "sinon";
import Q from "q";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -57,7 +58,7 @@ describe("Delete Collection Confirmation Pane", () => {
it("should return true if last collection and database does not have shared throughput else false", () => { it("should return true if last collection and database does not have shared throughput else false", () => {
let fakeExplorer = new Explorer(); let fakeExplorer = new Explorer();
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false); fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.refreshAllDatabases = () => Promise.resolve(); fakeExplorer.refreshAllDatabases = () => Q.resolve();
let pane = new DeleteCollectionConfirmationPane({ let pane = new DeleteCollectionConfirmationPane({
id: "deletecollectionconfirmationpane", id: "deletecollectionconfirmationpane",
@@ -118,7 +119,7 @@ describe("Delete Collection Confirmation Pane", () => {
fakeExplorer.selectedNode = ko.observable<TreeNode>(); fakeExplorer.selectedNode = ko.observable<TreeNode>();
fakeExplorer.isLastCollection = () => true; fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared = () => false; fakeExplorer.isSelectedDatabaseShared = () => false;
fakeExplorer.refreshAllDatabases = () => Promise.resolve(); fakeExplorer.refreshAllDatabases = () => Q.resolve();
let pane = new DeleteCollectionConfirmationPane({ let pane = new DeleteCollectionConfirmationPane({
id: "deletecollectionconfirmationpane", id: "deletecollectionconfirmationpane",

View File

@@ -1,4 +1,5 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";

View File

@@ -1,6 +1,7 @@
jest.mock("../../Common/dataAccess/deleteDatabase"); jest.mock("../../Common/dataAccess/deleteDatabase");
jest.mock("../../Shared/Telemetry/TelemetryProcessor"); jest.mock("../../Shared/Telemetry/TelemetryProcessor");
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -90,7 +91,7 @@ describe("Delete Database Confirmation Pane", () => {
collections: ko.observableArray<ViewModels.Collection>() collections: ko.observableArray<ViewModels.Collection>()
} as ViewModels.Database; } as ViewModels.Database;
}; };
fakeExplorer.refreshAllDatabases = () => Promise.resolve(); fakeExplorer.refreshAllDatabases = () => Q.resolve();
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false); fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.selectedDatabaseId = ko.computed<string>(() => selectedDatabaseId); fakeExplorer.selectedDatabaseId = ko.computed<string>(() => selectedDatabaseId);
fakeExplorer.isSelectedDatabaseShared = () => false; fakeExplorer.isSelectedDatabaseShared = () => false;

View File

@@ -1,4 +1,5 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -30,7 +31,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
this.resetData(); this.resetData();
} }
public async submit(): Promise<any> { public submit(): Q.Promise<any> {
if (!this._isValid()) { if (!this._isValid()) {
const selectedDatabase: ViewModels.Database = this.container.findSelectedDatabase(); const selectedDatabase: ViewModels.Database = this.container.findSelectedDatabase();
this.formErrors("Input database name does not match the selected database"); this.formErrors("Input database name does not match the selected database");
@@ -38,7 +39,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
ConsoleDataType.Error, ConsoleDataType.Error,
`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}: ${this.formErrors()}` `Error while deleting collection ${selectedDatabase && selectedDatabase.id()}: ${this.formErrors()}`
); );
return; return Q.resolve();
} }
this.formErrors(""); this.formErrors("");
@@ -52,7 +53,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
paneTitle: this.title() paneTitle: this.title()
}); });
// TODO: Should not be a Q promise anymore, but the Cassandra code requires it // TODO: Should not be a Q promise anymore, but the Cassandra code requires it
let promise: Promise<any>; let promise: Q.Promise<any>;
if (this.container.isPreferredApiCassandra()) { if (this.container.isPreferredApiCassandra()) {
promise = (<CassandraAPIDataClient>this.container.tableDataClient).deleteTableOrKeyspace( promise = (<CassandraAPIDataClient>this.container.tableDataClient).deleteTableOrKeyspace(
this.container.databaseAccount().properties.cassandraEndpoint, this.container.databaseAccount().properties.cassandraEndpoint,
@@ -61,7 +62,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
this.container this.container
); );
} else { } else {
promise = deleteDatabase(selectedDatabase.id()); promise = Q(deleteDatabase(selectedDatabase.id()));
} }
return promise.then( return promise.then(
() => { () => {

View File

@@ -1,4 +1,5 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
@@ -96,30 +97,33 @@ export class LoadQueryPane extends ContextualPaneBase {
return true; return true;
}; };
public async loadQueryFromFile(file: File): Promise<void> { public loadQueryFromFile(file: File): Q.Promise<void> {
const selectedCollection: ViewModels.Collection = this.container && this.container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.container && this.container.findSelectedCollection();
if (!selectedCollection) { if (!selectedCollection) {
// should never get into this state // should never get into this state
Logger.logError("No collection was selected", "LoadQueryPane.loadQueryFromFile"); Logger.logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
throw new Error("No collection was selected"); return Q.reject("No collection was selected");
} else if (this.container.isPreferredApiMongoDB()) { } else if (this.container.isPreferredApiMongoDB()) {
selectedCollection.onNewMongoQueryClick(selectedCollection, null); selectedCollection.onNewMongoQueryClick(selectedCollection, null);
} else { } else {
selectedCollection.onNewQueryClick(selectedCollection, null); selectedCollection.onNewQueryClick(selectedCollection, null);
} }
const deferred: Q.Deferred<void> = Q.defer<void>();
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (evt: any): void => { reader.onload = (evt: any): void => {
const fileData: string = evt.target.result; const fileData: string = evt.target.result;
const queryTab = this.container.tabsManager.activeTab() as QueryTab; const queryTab = this.container.tabsManager.activeTab() as QueryTab;
queryTab.initialEditorContent(fileData); queryTab.initialEditorContent(fileData);
queryTab.sqlQueryEditorContent(fileData); queryTab.sqlQueryEditorContent(fileData);
deferred.resolve();
}; };
reader.onerror = (evt: ProgressEvent): void => { reader.onerror = (evt: ProgressEvent): void => {
throw new Error((evt as any).error.message); deferred.reject((evt as any).error.message);
}; };
reader.readAsText(file); reader.readAsText(file);
return deferred.promise;
} }
private updateSelectedFilesTitle(fileList: FileList) { private updateSelectedFilesTitle(fileList: FileList) {

View File

@@ -140,7 +140,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
} }
public async submit(): Promise<void> { public async submit(): Promise<void> {
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${this.name} to gallery`); const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Publishing ${this.name} to gallery`
);
this.isExecuting = true; this.isExecuting = true;
this.triggerRender(); this.triggerRender();
@@ -158,16 +161,8 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.content, this.content,
this.isLinkInjectionEnabled this.isLinkInjectionEnabled
); );
if (response.data) {
const data = response.data; NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Published ${name} to gallery`);
if (data) {
if (data.pendingScanJobIds?.length > 0) {
NotificationConsoleUtils.logConsoleInfo(
`Content of ${this.name} is currently being scanned for illegal content. It will not be available in the public gallery until the review is complete (may take a few days).`
);
} else {
NotificationConsoleUtils.logConsoleInfo(`Published ${this.name} to gallery`);
}
} }
} catch (error) { } catch (error) {
this.formError = `Failed to publish ${this.name} to gallery`; this.formError = `Failed to publish ${this.name} to gallery`;
@@ -175,10 +170,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
const message = `${this.formError}: ${this.formErrorDetail}`; const message = `${this.formError}: ${this.formErrorDetail}`;
Logger.logError(message, "PublishNotebookPaneAdapter/submit"); Logger.logError(message, "PublishNotebookPaneAdapter/submit");
NotificationConsoleUtils.logConsoleError(message); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
return; return;
} finally { } finally {
clearPublishingMessage(); NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
this.isExecuting = false; this.isExecuting = false;
this.triggerRender(); this.triggerRender();
} }

View File

@@ -296,9 +296,7 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
downloads: 0, downloads: 0,
favorites: 0, favorites: 0,
views: 0, views: 0,
newCellId: undefined, newCellId: undefined
policyViolations: undefined,
pendingScanJobIds: undefined
}} }}
isFavorite={false} isFavorite={false}
showDownload={true} showDownload={true}

View File

@@ -82,7 +82,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
this.container this.container
.renewShareAccess(this.accessKey()) .renewShareAccess(this.accessKey())
.fail((error: any) => { .fail((error: any) => {
const errorMessage: string = error.message; const errorMessage: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`);
this.formErrors(errorMessage); this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage); this.formErrorsDetails(errorMessage);

View File

@@ -88,7 +88,7 @@ export class SaveQueryPane extends ContextualPaneBase {
(error: any) => { (error: any) => {
this.isExecuting(false); this.isExecuting(false);
if (typeof error != "string") { if (typeof error != "string") {
error = error.message; error = JSON.stringify(error);
} }
this.formErrors("Failed to save query"); this.formErrors("Failed to save query");
this.formErrorsDetails(`Failed to save query: ${error}`); this.formErrorsDetails(`Failed to save query: ${error}`);
@@ -143,7 +143,7 @@ export class SaveQueryPane extends ContextualPaneBase {
startKey startKey
); );
this.formErrors("Failed to setup a container for saved queries"); this.formErrors("Failed to setup a container for saved queries");
this.formErrors(`Failed to setup a container for saved queries: ${error.message}`); this.formErrors(`Failed to setup a container for saved queries: ${JSON.stringify(error)}`);
} finally { } finally {
this.isExecuting(false); this.isExecuting(false);
} }

View File

@@ -85,7 +85,7 @@ export class SetupNotebooksPane extends ContextualPaneBase {
"Successfully created a default notebook workspace for the account" "Successfully created a default notebook workspace for the account"
); );
} catch (error) { } catch (error) {
const errorMessage = typeof error == "string" ? error : error.message; const errorMessage = typeof error == "string" ? error : JSON.stringify(error);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.CreateNotebookWorkspace, Action.CreateNotebookWorkspace,
{ {

View File

@@ -103,8 +103,6 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
"isSample": false, "isSample": false,
"name": "SampleNotebook.ipynb", "name": "SampleNotebook.ipynb",
"newCellId": undefined, "newCellId": undefined,
"pendingScanJobIds": undefined,
"policyViolations": undefined,
"tags": Array [ "tags": Array [
"", "",
], ],

Some files were not shown because too many files have changed in this diff Show More