mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-03-11 04:17:25 +00:00
Merge branch 'master' into generated-clients-strict-compile
This commit is contained in:
commit
c4fd56ecb0
@ -41,6 +41,7 @@ module.exports = {
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"no-null/no-null": "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"
|
||||
}
|
||||
};
|
||||
|
@ -7,7 +7,7 @@
|
||||
"test": "cypress run",
|
||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
||||
"test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"",
|
||||
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
|
||||
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless",
|
||||
"test:debug": "cypress open"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
42
package-lock.json
generated
42
package-lock.json
generated
@ -5,13 +5,14 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@azure/cosmos": {
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.7.4.tgz",
|
||||
"integrity": "sha512-IbSEadapQDajSCXj7gUc8OklkOd/oAY4w7XBLHouWc4iKQTtntb2DmGjhrbh2W5Ku+pmBSr1GTApCjQ55iIjlQ==",
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.9.0.tgz",
|
||||
"integrity": "sha512-SA+QB54I8Dvg/ZolHpsEDLK/sbSB9sFmSU1ElnMTFw88TVik+LYHq4o/srU2TY6Gr1BketjPmgLVEqrmnRvjkw==",
|
||||
"requires": {
|
||||
"@types/debug": "^4.1.4",
|
||||
"debug": "^4.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"jsbi": "^3.1.3",
|
||||
"node-abort-controller": "^1.0.4",
|
||||
"node-fetch": "^2.6.0",
|
||||
"os-name": "^3.1.0",
|
||||
@ -22,14 +23,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
|
||||
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz",
|
||||
"integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q=="
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
|
||||
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -10132,9 +10133,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"canvas": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.0.tgz",
|
||||
"integrity": "sha512-bEO9f1ThmbknLPxCa8Es7obPlN9W3stB1bo7njlhOFKIdUTldeTqXCh9YclCPAi2pSQs84XA0jq/QEZXSzgyMw==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
|
||||
"integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
|
||||
"requires": {
|
||||
"nan": "^2.14.0",
|
||||
"node-pre-gyp": "^0.11.0",
|
||||
@ -20204,6 +20205,11 @@
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"jsbi": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.3.tgz",
|
||||
"integrity": "sha512-nBJqA0C6Qns+ZxurbEoIR56wyjiUszpNy70FHvxO5ervMoCbZVE3z3kxr5nKGhlxr/9MhKTSUBs7cAwwuf3g9w=="
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
@ -21534,9 +21540,9 @@
|
||||
}
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
|
||||
"integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
|
||||
"integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
|
||||
"requires": {
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.4.4",
|
||||
@ -24630,9 +24636,9 @@
|
||||
"integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk="
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
|
||||
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "3.1.0",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"description": "Cosmos Explorer",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@azure/cosmos": "3.7.4",
|
||||
"@azure/cosmos": "3.9.0",
|
||||
"@azure/cosmos-language-service": "0.0.4",
|
||||
"@jupyterlab/services": "4.2.0",
|
||||
"@jupyterlab/terminal": "1.2.1",
|
||||
@ -42,7 +42,7 @@
|
||||
"applicationinsights": "1.8.0",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"bootstrap": "3.4.1",
|
||||
"canvas": "2.6.0",
|
||||
"canvas": "2.6.1",
|
||||
"clean-webpack-plugin": "0.1.19",
|
||||
"copy-webpack-plugin": "6.0.2",
|
||||
"crossroads": "0.12.2",
|
||||
|
@ -3,8 +3,8 @@
|
||||
"offerThroughput": 400,
|
||||
"databaseLevelThroughput": false,
|
||||
"collectionId": "Persons",
|
||||
"rupmEnabled": false,
|
||||
"partitionKey": { "kind": "Hash", "paths": ["/firstname"] },
|
||||
"createNewDatabase": true,
|
||||
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
|
||||
"data": [
|
||||
{
|
||||
"firstname": "Eva",
|
||||
@ -23,4 +23,4 @@
|
||||
"age": 23
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,7 @@ export class Features {
|
||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
||||
public static readonly ttl90Days = "ttl90days";
|
||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
|
@ -6,19 +6,14 @@ import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Q from "q";
|
||||
import {
|
||||
ConflictDefinition,
|
||||
ContainerDefinition,
|
||||
ContainerResponse,
|
||||
DatabaseResponse,
|
||||
FeedOptions,
|
||||
ItemDefinition,
|
||||
PartitionKeyDefinition,
|
||||
QueryIterator,
|
||||
Resource,
|
||||
TriggerDefinition
|
||||
TriggerDefinition,
|
||||
OfferDefinition
|
||||
} from "@azure/cosmos";
|
||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||
import { client } from "./CosmosClient";
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
import { sendCachedDataMessage } from "./MessageHandler";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
@ -202,23 +197,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
|
||||
return [partitionKeyValue];
|
||||
}
|
||||
|
||||
export function updateCollection(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
newCollection: DataModels.Collection,
|
||||
options: any = {}
|
||||
): Q.Promise<DataModels.Collection> {
|
||||
return Q(
|
||||
client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.replace(newCollection as ContainerDefinition, options)
|
||||
.then(async (response: ContainerResponse) => {
|
||||
return refreshCachedResources().then(() => response.resource as DataModels.Collection);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function updateDocument(
|
||||
collection: ViewModels.CollectionBase,
|
||||
documentId: DocumentId,
|
||||
@ -244,7 +222,8 @@ export function updateOffer(
|
||||
return Q(
|
||||
client()
|
||||
.offer(offer.id)
|
||||
.replace(newOffer, options)
|
||||
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
|
||||
.replace((newOffer as unknown) as OfferDefinition, options)
|
||||
.then(response => {
|
||||
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
|
||||
})
|
||||
@ -454,6 +433,10 @@ export function readCollectionQuotaInfo(
|
||||
}
|
||||
|
||||
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
||||
if (options.isServerless) {
|
||||
return Q([]); // Reading offers is not supported for serverless accounts
|
||||
}
|
||||
|
||||
try {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
|
||||
@ -469,6 +452,13 @@ export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
|
||||
.offers.readAll()
|
||||
.fetchAll()
|
||||
.then(response => response.resources)
|
||||
.catch(error => {
|
||||
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
||||
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -487,89 +477,6 @@ export function readOffer(requestedResource: DataModels.Offer, options: any): Q.
|
||||
);
|
||||
}
|
||||
|
||||
export function getOrCreateDatabaseAndCollection(
|
||||
request: DataModels.CreateDatabaseAndCollectionRequest,
|
||||
options: any
|
||||
): Q.Promise<DataModels.Collection> {
|
||||
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
|
||||
const {
|
||||
databaseId,
|
||||
databaseLevelThroughput,
|
||||
collectionId,
|
||||
partitionKey,
|
||||
indexingPolicy,
|
||||
uniqueKeyPolicy,
|
||||
offerThroughput,
|
||||
analyticalStorageTtl,
|
||||
hasAutoPilotV2FeatureFlag
|
||||
} = request;
|
||||
|
||||
const createBody: DatabaseRequest = {
|
||||
id: databaseId
|
||||
};
|
||||
|
||||
// TODO: replace when SDK support autopilot
|
||||
const initialHeaders = request.autoPilot
|
||||
? !hasAutoPilotV2FeatureFlag
|
||||
? {
|
||||
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({
|
||||
maxThroughput: request.autoPilot.maxThroughput
|
||||
})
|
||||
}
|
||||
: {
|
||||
[Constants.HttpHeaders.autoPilotTier]: request.autoPilot.autopilotTier
|
||||
}
|
||||
: undefined;
|
||||
if (databaseLevelThroughput) {
|
||||
if (request.autoPilot) {
|
||||
databaseOptions.initialHeaders = initialHeaders;
|
||||
}
|
||||
createBody.throughput = offerThroughput;
|
||||
}
|
||||
|
||||
return Q(
|
||||
client()
|
||||
.databases.createIfNotExists(createBody, databaseOptions)
|
||||
.then(response => {
|
||||
return response.database.containers.create(
|
||||
{
|
||||
id: collectionId,
|
||||
partitionKey: (partitionKey || undefined) as PartitionKeyDefinition,
|
||||
indexingPolicy: indexingPolicy ? indexingPolicy : undefined,
|
||||
uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined,
|
||||
analyticalStorageTtl: analyticalStorageTtl,
|
||||
throughput: databaseLevelThroughput || request.autoPilot ? undefined : offerThroughput
|
||||
} as ContainerRequest, // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||
{
|
||||
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders
|
||||
}
|
||||
);
|
||||
})
|
||||
.then(containerResponse => containerResponse.resource as DataModels.Collection)
|
||||
.finally(() => refreshCachedResources(options))
|
||||
);
|
||||
}
|
||||
|
||||
export function createDatabase(
|
||||
request: DataModels.CreateDatabaseRequest,
|
||||
options: any
|
||||
): Q.Promise<DataModels.Database> {
|
||||
var deferred = Q.defer<DataModels.Database>();
|
||||
|
||||
_createDatabase(request, options).then(
|
||||
(createdDatabase: DataModels.Database) => {
|
||||
refreshCachedOffers().then(() => {
|
||||
deferred.resolve(createdDatabase);
|
||||
});
|
||||
},
|
||||
_createDatabaseError => {
|
||||
deferred.reject(_createDatabaseError);
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
export function refreshCachedOffers(): Q.Promise<void> {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
||||
@ -598,33 +505,3 @@ export function queryConflicts(
|
||||
.conflicts.query(query, options);
|
||||
return Q(documentsIterator);
|
||||
}
|
||||
|
||||
function _createDatabase(request: DataModels.CreateDatabaseRequest, options: any = {}): Q.Promise<DataModels.Database> {
|
||||
const { databaseId, databaseLevelThroughput, offerThroughput, autoPilot, hasAutoPilotV2FeatureFlag } = request;
|
||||
const createBody: DatabaseRequest = { id: databaseId };
|
||||
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
|
||||
// TODO: replace when SDK support autopilot
|
||||
const initialHeaders = autoPilot
|
||||
? !hasAutoPilotV2FeatureFlag
|
||||
? {
|
||||
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ maxThroughput: autoPilot.maxThroughput })
|
||||
}
|
||||
: {
|
||||
[Constants.HttpHeaders.autoPilotTier]: autoPilot.autopilotTier
|
||||
}
|
||||
: undefined;
|
||||
if (!!databaseLevelThroughput) {
|
||||
if (autoPilot) {
|
||||
databaseOptions.initialHeaders = initialHeaders;
|
||||
}
|
||||
createBody.throughput = offerThroughput;
|
||||
}
|
||||
|
||||
return Q(
|
||||
client()
|
||||
.databases.create(createBody, databaseOptions)
|
||||
.then((response: DatabaseResponse) => {
|
||||
return refreshCachedResources(databaseOptions).then(() => response.resource);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as Constants from "./Constants";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import Q from "q";
|
||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
@ -266,42 +265,6 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
export function updateCollection(
|
||||
databaseId: string,
|
||||
collection: ViewModels.Collection,
|
||||
newCollection: DataModels.Collection
|
||||
): Q.Promise<DataModels.Collection> {
|
||||
var deferred = Q.defer<any>();
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Updating container ${collection.id()}`
|
||||
);
|
||||
DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection)
|
||||
.then(
|
||||
(replacedCollection: DataModels.Collection) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully updated container ${collection.id()}`
|
||||
);
|
||||
deferred.resolve(replacedCollection);
|
||||
},
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Failed to update container ${collection.id()}: ${JSON.stringify(error)}`
|
||||
);
|
||||
Logger.logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
export function updateDocument(
|
||||
collection: ViewModels.CollectionBase,
|
||||
documentId: DocumentId,
|
||||
@ -892,70 +855,3 @@ export function readOffer(
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
export function getOrCreateDatabaseAndCollection(
|
||||
request: DataModels.CreateDatabaseAndCollectionRequest,
|
||||
options: any = {}
|
||||
): Q.Promise<DataModels.Collection> {
|
||||
const deferred: Q.Deferred<DataModels.Collection> = Q.defer<DataModels.Collection>();
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Creating a new container ${request.collectionId} for database ${request.databaseId}`
|
||||
);
|
||||
|
||||
DataAccessUtilityBase.getOrCreateDatabaseAndCollection(request, options)
|
||||
.then(
|
||||
(collection: DataModels.Collection) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully created container ${request.collectionId}`
|
||||
);
|
||||
deferred.resolve(collection);
|
||||
},
|
||||
(error: any) => {
|
||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while creating container ${request.collectionId}:\n ${sanitizedError}`
|
||||
);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
export function createDatabase(
|
||||
request: DataModels.CreateDatabaseRequest,
|
||||
options: any = {}
|
||||
): Q.Promise<DataModels.Database> {
|
||||
const deferred: Q.Deferred<DataModels.Database> = Q.defer<DataModels.Database>();
|
||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
`Creating a new database ${request.databaseId}`
|
||||
);
|
||||
|
||||
DataAccessUtilityBase.createDatabase(request, options)
|
||||
.then(
|
||||
(database: DataModels.Database) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
`Successfully created database ${request.databaseId}`
|
||||
);
|
||||
deferred.resolve(database);
|
||||
},
|
||||
(error: any) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while creating database ${request.databaseId}:\n ${JSON.stringify(error)}`
|
||||
);
|
||||
sendNotificationForError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
)
|
||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||
import queryString from "querystring";
|
||||
import { AuthType } from "../AuthType";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import * as DataExplorerConstants from "../Common/Constants";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
@ -285,43 +284,35 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
|
||||
}
|
||||
|
||||
export function createMongoCollectionWithProxy(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
offerThroughput: number,
|
||||
shardKey: string,
|
||||
createDatabase: boolean,
|
||||
sharedThroughput: boolean,
|
||||
isSharded: boolean,
|
||||
autopilotOptions?: DataModels.RpOptions
|
||||
params: DataModels.CreateCollectionParams
|
||||
): Promise<DataModels.Collection> {
|
||||
const databaseAccount = userContext.databaseAccount;
|
||||
const params: DataModels.MongoParameters = {
|
||||
const shardKey: string = params.partitionKey?.paths[0];
|
||||
const mongoParams: DataModels.MongoParameters = {
|
||||
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
||||
db: databaseId,
|
||||
coll: collectionId,
|
||||
db: params.databaseId,
|
||||
coll: params.collectionId,
|
||||
pk: shardKey,
|
||||
offerThroughput,
|
||||
cd: createDatabase,
|
||||
st: sharedThroughput,
|
||||
is: isSharded,
|
||||
offerThroughput: params.offerThroughput,
|
||||
cd: params.createNewDatabase,
|
||||
st: params.databaseLevelThroughput,
|
||||
is: !!shardKey,
|
||||
rid: "",
|
||||
rtype: "colls",
|
||||
sid: userContext.subscriptionId,
|
||||
rg: userContext.resourceGroup,
|
||||
dba: databaseAccount.name,
|
||||
isAutoPilot: false
|
||||
isAutoPilot: !!params.autoPilotMaxThroughput,
|
||||
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
|
||||
};
|
||||
|
||||
if (autopilotOptions) {
|
||||
params.isAutoPilot = true;
|
||||
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
|
||||
}
|
||||
|
||||
const endpoint = getEndpoint(databaseAccount);
|
||||
|
||||
return window
|
||||
.fetch(
|
||||
`${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`,
|
||||
`${endpoint}/createCollection?${queryString.stringify(
|
||||
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
|
||||
)}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
@ -335,7 +326,7 @@ export function createMongoCollectionWithProxy(
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
return errorHandling(response, "creating collection", params);
|
||||
return errorHandling(response, "creating collection", mongoParams);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,8 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import { QueryUtils } from "../Utils/QueryUtils";
|
||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
import {
|
||||
createDocument,
|
||||
deleteDocument,
|
||||
getOrCreateDatabaseAndCollection,
|
||||
queryDocuments,
|
||||
queryDocumentsPage
|
||||
} from "./DocumentClientUtilityBase";
|
||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||
import { createCollection } from "./dataAccess/createCollection";
|
||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
||||
import * as Logger from "./Logger";
|
||||
|
||||
@ -41,12 +36,13 @@ export class QueriesClient {
|
||||
ConsoleDataType.InProgress,
|
||||
"Setting up account for saving queries"
|
||||
);
|
||||
return getOrCreateDatabaseAndCollection({
|
||||
return createCollection({
|
||||
collectionId: SavedQueries.CollectionName,
|
||||
createNewDatabase: true,
|
||||
databaseId: SavedQueries.DatabaseName,
|
||||
partitionKey: QueriesClient.PartitionKey,
|
||||
offerThroughput: SavedQueries.OfferThroughput,
|
||||
databaseLevelThroughput: undefined
|
||||
databaseLevelThroughput: false
|
||||
})
|
||||
.then(
|
||||
(collection: DataModels.Collection) => {
|
||||
|
81
src/Common/dataAccess/createCollection.test.ts
Normal file
81
src/Common/dataAccess/createCollection.test.ts
Normal file
@ -0,0 +1,81 @@
|
||||
jest.mock("../../Utils/arm/request");
|
||||
jest.mock("../CosmosClient");
|
||||
jest.mock("../DataAccessUtilityBase");
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { armRequest } from "../../Utils/arm/request";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createCollection, constructRpOptions } from "./createCollection";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
|
||||
describe("createCollection", () => {
|
||||
const createCollectionParams: CreateCollectionParams = {
|
||||
createNewDatabase: false,
|
||||
collectionId: "testContainer",
|
||||
databaseId: "testDatabase",
|
||||
databaseLevelThroughput: true,
|
||||
offerThroughput: 400
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
name: "test"
|
||||
} as DatabaseAccount,
|
||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||
});
|
||||
});
|
||||
|
||||
it("should call ARM if logged in with AAD", async () => {
|
||||
window.authType = AuthType.AAD;
|
||||
await createCollection(createCollectionParams);
|
||||
expect(armRequest).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call SDK if not logged in with non-AAD method", async () => {
|
||||
window.authType = AuthType.MasterKey;
|
||||
(client as jest.Mock).mockReturnValue({
|
||||
databases: {
|
||||
createIfNotExists: () => {
|
||||
return {
|
||||
database: {
|
||||
containers: {
|
||||
create: () => ({})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
await createCollection(createCollectionParams);
|
||||
expect(client).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("constructRpOptions should return the correct options", () => {
|
||||
expect(constructRpOptions(createCollectionParams)).toEqual({});
|
||||
|
||||
const manualThroughputParams: CreateCollectionParams = {
|
||||
createNewDatabase: false,
|
||||
collectionId: "testContainer",
|
||||
databaseId: "testDatabase",
|
||||
databaseLevelThroughput: false,
|
||||
offerThroughput: 400
|
||||
};
|
||||
expect(constructRpOptions(manualThroughputParams)).toEqual({ throughput: 400 });
|
||||
|
||||
const autoPilotThroughputParams: CreateCollectionParams = {
|
||||
createNewDatabase: false,
|
||||
collectionId: "testContainer",
|
||||
databaseId: "testDatabase",
|
||||
databaseLevelThroughput: false,
|
||||
offerThroughput: 400,
|
||||
autoPilotMaxThroughput: 4000
|
||||
};
|
||||
expect(constructRpOptions(autoPilotThroughputParams)).toEqual({
|
||||
autoscaleSettings: {
|
||||
maxThroughput: 4000
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
371
src/Common/dataAccess/createCollection.ts
Normal file
371
src/Common/dataAccess/createCollection.ts
Normal file
@ -0,0 +1,371 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
|
||||
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import {
|
||||
createUpdateCassandraTable,
|
||||
getCassandraTable
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import {
|
||||
createUpdateMongoDBCollection,
|
||||
getMongoDBCollection
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import {
|
||||
createUpdateGremlinGraph,
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { createDatabase } from "./createDatabase";
|
||||
|
||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
let collection: DataModels.Collection;
|
||||
const clearMessage = logConsoleProgress(
|
||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||
);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
if (params.createNewDatabase) {
|
||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
||||
databaseId: params.databaseId,
|
||||
databaseLevelThroughput: params.databaseLevelThroughput,
|
||||
offerThroughput: params.offerThroughput
|
||||
};
|
||||
await createDatabase(createDatabaseParams);
|
||||
}
|
||||
collection = await createCollectionWithARM(params);
|
||||
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
|
||||
collection = await createMongoCollectionWithProxy(params);
|
||||
} else {
|
||||
collection = await createCollectionWithSDK(params);
|
||||
}
|
||||
} catch (error) {
|
||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
clearMessage();
|
||||
throw error;
|
||||
}
|
||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||
await refreshCachedResources();
|
||||
clearMessage();
|
||||
return collection;
|
||||
};
|
||||
|
||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
const defaultExperience = userContext.defaultExperience;
|
||||
switch (defaultExperience) {
|
||||
case DefaultAccountExperienceType.DocumentDB:
|
||||
return createSqlContainer(params);
|
||||
case DefaultAccountExperienceType.MongoDB:
|
||||
return createMongoCollection(params);
|
||||
case DefaultAccountExperienceType.Cassandra:
|
||||
return createCassandraTable(params);
|
||||
case DefaultAccountExperienceType.Graph:
|
||||
return createGraph(params);
|
||||
case DefaultAccountExperienceType.Table:
|
||||
return createTable(params);
|
||||
default:
|
||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||
}
|
||||
};
|
||||
|
||||
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getSqlContainer(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.SqlContainerResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
if (params.analyticalStorageTtl) {
|
||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||
}
|
||||
if (params.indexingPolicy) {
|
||||
resource.indexingPolicy = params.indexingPolicy;
|
||||
}
|
||||
if (params.partitionKey) {
|
||||
resource.partitionKey = params.partitionKey;
|
||||
}
|
||||
if (params.uniqueKeyPolicy) {
|
||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateSqlContainer(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getMongoDBCollection(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.MongoDBCollectionResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
if (params.analyticalStorageTtl) {
|
||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||
}
|
||||
if (params.partitionKey) {
|
||||
const partitionKeyPath: string = params.partitionKey.paths[0];
|
||||
resource.shardKey = { [partitionKeyPath]: "Hash" };
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateMongoDBCollection(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getCassandraTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.CassandraTableResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
if (params.analyticalStorageTtl) {
|
||||
resource.analyticalStorageTtl = params.analyticalStorageTtl;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateCassandraTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getGremlinGraph(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.GremlinGraphResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
|
||||
if (params.indexingPolicy) {
|
||||
resource.indexingPolicy = params.indexingPolicy;
|
||||
}
|
||||
if (params.partitionKey) {
|
||||
resource.partitionKey = params.partitionKey;
|
||||
}
|
||||
if (params.uniqueKeyPolicy) {
|
||||
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
|
||||
}
|
||||
|
||||
const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateGremlinGraph(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
try {
|
||||
const getResponse = await getTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.collectionId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
|
||||
const resource: ARMTypes.TableResource = {
|
||||
id: params.collectionId
|
||||
};
|
||||
|
||||
const rpPayload: ARMTypes.TableCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource,
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
const createResponse = await createUpdateTable(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.collectionId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Collection);
|
||||
};
|
||||
|
||||
export const constructRpOptions = (params: DataModels.CreateDatabaseParams): ARMTypes.CreateUpdateOptions => {
|
||||
if (params.databaseLevelThroughput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
return {
|
||||
autoscaleSettings: {
|
||||
maxThroughput: params.autoPilotMaxThroughput
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
throughput: params.offerThroughput
|
||||
};
|
||||
};
|
||||
|
||||
const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||
const createCollectionBody: ContainerRequest = {
|
||||
id: params.collectionId,
|
||||
partitionKey: params.partitionKey || undefined,
|
||||
indexingPolicy: params.indexingPolicy || undefined,
|
||||
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
||||
analyticalStorageTtl: params.analyticalStorageTtl
|
||||
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||
const collectionOptions: RequestOptions = {};
|
||||
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
||||
|
||||
if (params.databaseLevelThroughput) {
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
createDatabaseBody.maxThroughput = params.autoPilotMaxThroughput;
|
||||
} else {
|
||||
createDatabaseBody.throughput = params.offerThroughput;
|
||||
}
|
||||
} else {
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
createCollectionBody.maxThroughput = params.autoPilotMaxThroughput;
|
||||
} else {
|
||||
createCollectionBody.throughput = params.offerThroughput;
|
||||
}
|
||||
}
|
||||
|
||||
const databaseResponse: DatabaseResponse = await client().databases.createIfNotExists(createDatabaseBody);
|
||||
const collectionResponse: ContainerResponse = await databaseResponse?.database.containers.create(
|
||||
createCollectionBody,
|
||||
collectionOptions
|
||||
);
|
||||
return collectionResponse?.resource as DataModels.Collection;
|
||||
};
|
251
src/Common/dataAccess/createDatabase.ts
Normal file
251
src/Common/dataAccess/createDatabase.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { DatabaseResponse } from "@azure/cosmos";
|
||||
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import {
|
||||
CassandraKeyspaceCreateUpdateParameters,
|
||||
GremlinDatabaseCreateUpdateParameters,
|
||||
MongoDBDatabaseCreateUpdateParameters,
|
||||
SqlDatabaseCreateUpdateParameters,
|
||||
CreateUpdateOptions
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import {
|
||||
createUpdateCassandraKeyspace,
|
||||
getCassandraKeyspace
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import {
|
||||
createUpdateMongoDBDatabase,
|
||||
getMongoDBDatabase
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import {
|
||||
createUpdateGremlinDatabase,
|
||||
getGremlinDatabase
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
let database: DataModels.Database;
|
||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
database = await createDatabaseWithARM(params);
|
||||
} else {
|
||||
database = await createDatabaseWithSDK(params);
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
||||
sendNotificationForError(error);
|
||||
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> {
|
||||
const defaultExperience = userContext.defaultExperience;
|
||||
switch (defaultExperience) {
|
||||
case DefaultAccountExperienceType.DocumentDB:
|
||||
return createSqlDatabase(params);
|
||||
case DefaultAccountExperienceType.MongoDB:
|
||||
return createMongoDatabase(params);
|
||||
case DefaultAccountExperienceType.Cassandra:
|
||||
return createCassandraKeyspace(params);
|
||||
case DefaultAccountExperienceType.Graph:
|
||||
return createGremlineDatabase(params);
|
||||
default:
|
||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
try {
|
||||
const getResponse = await getSqlDatabase(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: SqlDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
},
|
||||
options
|
||||
}
|
||||
};
|
||||
const createResponse = await createUpdateSqlDatabase(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
||||
}
|
||||
|
||||
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
try {
|
||||
const getResponse = await getMongoDBDatabase(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
},
|
||||
options
|
||||
}
|
||||
};
|
||||
const createResponse = await createUpdateMongoDBDatabase(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
||||
}
|
||||
|
||||
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
try {
|
||||
const getResponse = await getCassandraKeyspace(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
},
|
||||
options
|
||||
}
|
||||
};
|
||||
const createResponse = await createUpdateCassandraKeyspace(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
||||
}
|
||||
|
||||
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
try {
|
||||
const getResponse = await getGremlinDatabase(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId
|
||||
);
|
||||
if (getResponse?.properties?.resource) {
|
||||
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "NotFound") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const options: CreateUpdateOptions = constructRpOptions(params);
|
||||
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
|
||||
properties: {
|
||||
resource: {
|
||||
id: params.databaseId
|
||||
},
|
||||
options
|
||||
}
|
||||
};
|
||||
const createResponse = await createUpdateGremlinDatabase(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
params.databaseId,
|
||||
rpPayload
|
||||
);
|
||||
return createResponse && (createResponse.properties.resource as DataModels.Database);
|
||||
}
|
||||
|
||||
async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||
const createBody: DatabaseRequest = { id: params.databaseId };
|
||||
|
||||
if (params.databaseLevelThroughput) {
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
createBody.maxThroughput = params.autoPilotMaxThroughput;
|
||||
} else {
|
||||
createBody.throughput = params.offerThroughput;
|
||||
}
|
||||
}
|
||||
|
||||
const response: DatabaseResponse = await client().databases.create(createBody);
|
||||
return response.resource;
|
||||
}
|
||||
|
||||
function constructRpOptions(params: DataModels.CreateDatabaseParams): CreateUpdateOptions {
|
||||
if (!params.databaseLevelThroughput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (params.autoPilotMaxThroughput) {
|
||||
return {
|
||||
autoscaleSettings: {
|
||||
maxThroughput: params.autoPilotMaxThroughput
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
throughput: params.offerThroughput
|
||||
};
|
||||
}
|
@ -15,7 +15,7 @@ import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD) {
|
||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||
await deleteCollectionWithARM(databaseId, collectionId);
|
||||
} else {
|
||||
await client()
|
||||
|
@ -15,7 +15,11 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||
|
||||
try {
|
||||
if (window.authType === AuthType.AAD) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
||||
!userContext.useSDKOperations
|
||||
) {
|
||||
await deleteDatabaseWithARM(databaseId);
|
||||
} else {
|
||||
await client()
|
||||
|
@ -16,7 +16,12 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
||||
let collections: DataModels.Collection[];
|
||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
collections = await readCollectionsWithARM(databaseId);
|
||||
} else {
|
||||
const sdkResponse = await client()
|
||||
|
@ -15,7 +15,13 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||
let databases: DataModels.Database[];
|
||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||
try {
|
||||
if (window.authType === AuthType.AAD) {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
!userContext.useSDKOperations &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
|
||||
) {
|
||||
databases = await readDatabasesWithARM();
|
||||
} else {
|
||||
const sdkResponse = await client()
|
||||
|
225
src/Common/dataAccess/updateCollection.ts
Normal file
225
src/Common/dataAccess/updateCollection.ts
Normal file
@ -0,0 +1,225 @@
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { Collection } from "../../Contracts/DataModels";
|
||||
import { ContainerDefinition } from "@azure/cosmos";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import {
|
||||
ExtendedResourceProperties,
|
||||
SqlContainerCreateUpdateParameters,
|
||||
SqlContainerResource
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import { client } from "../CosmosClient";
|
||||
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||
import {
|
||||
createUpdateCassandraTable,
|
||||
getCassandraTable
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
import {
|
||||
createUpdateMongoDBCollection,
|
||||
getMongoDBCollection
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||
import {
|
||||
createUpdateGremlinGraph,
|
||||
getGremlinGraph
|
||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { logError } from "../Logger";
|
||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
||||
import { sendNotificationForError } from "./sendNotificationForError";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export async function updateCollection(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
newCollection: Collection,
|
||||
options: RequestOptions = {}
|
||||
): Promise<Collection> {
|
||||
let collection: Collection;
|
||||
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
|
||||
|
||||
try {
|
||||
if (
|
||||
window.authType === AuthType.AAD &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||
) {
|
||||
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
||||
} else {
|
||||
const sdkResponse = await client()
|
||||
.database(databaseId)
|
||||
.container(collectionId)
|
||||
.replace(newCollection as ContainerDefinition, options);
|
||||
collection = sdkResponse.resource as Collection;
|
||||
}
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
||||
sendNotificationForError(error);
|
||||
throw error;
|
||||
}
|
||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||
clearMessage();
|
||||
await refreshCachedResources();
|
||||
return collection;
|
||||
}
|
||||
|
||||
async function updateCollectionWithARM(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
const subscriptionId = userContext.subscriptionId;
|
||||
const resourceGroup = userContext.resourceGroup;
|
||||
const accountName = userContext.databaseAccount.name;
|
||||
const defaultExperience = userContext.defaultExperience;
|
||||
|
||||
switch (defaultExperience) {
|
||||
case DefaultAccountExperienceType.DocumentDB:
|
||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.MongoDB:
|
||||
return updateMongoDBCollection(
|
||||
databaseId,
|
||||
collectionId,
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
newCollection
|
||||
);
|
||||
case DefaultAccountExperienceType.Cassandra:
|
||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.Graph:
|
||||
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
case DefaultAccountExperienceType.Table:
|
||||
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||
default:
|
||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSqlContainer(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateResponse = await createUpdateSqlContainer(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
databaseId,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
}
|
||||
|
||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||
}
|
||||
|
||||
async function updateMongoDBCollection(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateResponse = await createUpdateMongoDBCollection(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
databaseId,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`MongoDB collection to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
|
||||
);
|
||||
}
|
||||
|
||||
async function updateCassandraTable(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateResponse = await createUpdateCassandraTable(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
databaseId,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cassandra table to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
|
||||
);
|
||||
}
|
||||
|
||||
async function updateGremlinGraph(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateResponse = await createUpdateGremlinGraph(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
databaseId,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
}
|
||||
|
||||
throw new Error(`Gremlin graph to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||
}
|
||||
|
||||
async function updateTable(
|
||||
collectionId: string,
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
accountName: string,
|
||||
newCollection: Collection
|
||||
): Promise<Collection> {
|
||||
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
|
||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||
const updateResponse = await createUpdateTable(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
collectionId,
|
||||
getResponse as SqlContainerCreateUpdateParameters
|
||||
);
|
||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||
}
|
||||
|
||||
throw new Error(`Table to update does not exist. Table id: ${collectionId}`);
|
||||
}
|
@ -80,12 +80,20 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
// Allow override of any config value with URL query parameters
|
||||
// Allow override of platform value with URL query parameter
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.forEach((value, key) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(configContext as any)[key] = value;
|
||||
});
|
||||
if (params.has("platform")) {
|
||||
const platform = params.get("platform");
|
||||
switch (platform) {
|
||||
default:
|
||||
console.log("Invalid platform query parameter given, ignoring");
|
||||
break;
|
||||
case Platform.Portal:
|
||||
case Platform.Hosted:
|
||||
case Platform.Emulator:
|
||||
updateConfigContext({ platform });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("No configuration file found using defaults");
|
||||
}
|
||||
|
@ -153,7 +153,14 @@ export interface KeyResource {
|
||||
Token: string;
|
||||
}
|
||||
|
||||
export interface IndexingPolicy {}
|
||||
export interface IndexingPolicy {
|
||||
automatic: boolean;
|
||||
indexingMode: string;
|
||||
includedPaths: any;
|
||||
excludedPaths: any;
|
||||
compositeIndexes?: any;
|
||||
spatialIndexes?: any;
|
||||
}
|
||||
|
||||
export interface PartitionKey {
|
||||
paths: string[];
|
||||
@ -320,12 +327,24 @@ export interface AutoPilotOfferSettings {
|
||||
targetMaxThroughput?: number;
|
||||
}
|
||||
|
||||
export interface CreateDatabaseRequest {
|
||||
export interface CreateDatabaseParams {
|
||||
autoPilotMaxThroughput?: number;
|
||||
databaseId: string;
|
||||
databaseLevelThroughput?: boolean;
|
||||
offerThroughput?: number;
|
||||
autoPilot?: AutoPilotCreationSettings;
|
||||
hasAutoPilotV2FeatureFlag?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateCollectionParams {
|
||||
createNewDatabase: boolean;
|
||||
collectionId: string;
|
||||
databaseId: string;
|
||||
databaseLevelThroughput: boolean;
|
||||
offerThroughput: number;
|
||||
analyticalStorageTtl?: number;
|
||||
autoPilotMaxThroughput?: number;
|
||||
indexingPolicy?: IndexingPolicy;
|
||||
partitionKey?: PartitionKey;
|
||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||
}
|
||||
|
||||
export interface SharedThroughputRange {
|
||||
|
@ -1,4 +1,5 @@
|
||||
jest.mock("../../Common/DocumentClientUtilityBase");
|
||||
jest.mock("../../Common/dataAccess/createCollection");
|
||||
import * as ko from "knockout";
|
||||
import * as sinon from "sinon";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@ -33,8 +34,8 @@ describe("ContainerSampleGenerator", () => {
|
||||
databaseId: sampleDatabaseId,
|
||||
offerThroughput: 400,
|
||||
databaseLevelThroughput: false,
|
||||
createNewDatabase: true,
|
||||
collectionId: sampleCollectionId,
|
||||
rupmEnabled: false,
|
||||
data: [
|
||||
{
|
||||
firstname: "Eva",
|
||||
@ -99,8 +100,8 @@ describe("ContainerSampleGenerator", () => {
|
||||
databaseId: sampleDatabaseId,
|
||||
offerThroughput: 400,
|
||||
databaseLevelThroughput: false,
|
||||
createNewDatabase: true,
|
||||
collectionId: sampleCollectionId,
|
||||
rupmEnabled: false,
|
||||
data: [
|
||||
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
|
||||
]
|
||||
|
@ -1,4 +1,3 @@
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import GraphTab from ".././Tabs/GraphTab";
|
||||
@ -6,10 +5,11 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
|
||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import { createDocument, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
|
||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||
data: any[];
|
||||
}
|
||||
|
||||
@ -54,18 +54,11 @@ export class ContainerSampleGenerator {
|
||||
}
|
||||
|
||||
private async createContainerAsync(): Promise<ViewModels.Collection> {
|
||||
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
||||
const createRequest: DataModels.CreateCollectionParams = {
|
||||
...this.sampleDataFile
|
||||
};
|
||||
|
||||
const options: any = {};
|
||||
if (this.container.isPreferredApiMongoDB()) {
|
||||
options.initialHeaders = options.initialHeaders || {};
|
||||
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
|
||||
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
|
||||
}
|
||||
|
||||
await getOrCreateDatabaseAndCollection(createRequest, options);
|
||||
await createCollection(createRequest);
|
||||
await this.container.refreshAllDatabases();
|
||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||
if (!database) {
|
||||
|
@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
|
||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
|
||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import { configContext, updateConfigContext } from "../ConfigContext";
|
||||
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
|
||||
@ -975,6 +975,10 @@ export default class Explorer {
|
||||
this.sparkClusterConnectionInfo.valueHasMutated();
|
||||
}
|
||||
|
||||
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
|
||||
updateUserContext({ useSDKOperations: true });
|
||||
}
|
||||
|
||||
featureSubcription.dispose();
|
||||
});
|
||||
|
||||
@ -1475,38 +1479,33 @@ export default class Explorer {
|
||||
);
|
||||
};
|
||||
|
||||
if (this.isServerlessEnabled()) {
|
||||
// Serverless accounts don't support offers call
|
||||
refreshDatabases();
|
||||
} else {
|
||||
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
||||
this._setLoadingStatusText("Fetching offers...");
|
||||
offerPromise.then(
|
||||
(offers: DataModels.Offer[]) => {
|
||||
this._setLoadingStatusText("Successfully fetched offers.");
|
||||
refreshDatabases(offers);
|
||||
},
|
||||
error => {
|
||||
this._setLoadingStatusText("Failed to fetch offers.");
|
||||
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)}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers({ isServerless: this.isServerlessEnabled() });
|
||||
this._setLoadingStatusText("Fetching offers...");
|
||||
offerPromise.then(
|
||||
(offers: DataModels.Offer[]) => {
|
||||
this._setLoadingStatusText("Successfully fetched offers.");
|
||||
refreshDatabases(offers);
|
||||
},
|
||||
error => {
|
||||
this._setLoadingStatusText("Failed to fetch offers.");
|
||||
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(
|
||||
() => {
|
||||
@ -1954,12 +1953,17 @@ export default class Explorer {
|
||||
|
||||
this._importExplorerConfigComplete = true;
|
||||
|
||||
updateConfigContext({
|
||||
ARM_ENDPOINT: this.armEndpoint()
|
||||
});
|
||||
|
||||
updateUserContext({
|
||||
authorizationToken,
|
||||
masterKey,
|
||||
databaseAccount
|
||||
databaseAccount,
|
||||
resourceGroup: inputs.resourceGroup,
|
||||
subscriptionId: inputs.subscriptionId
|
||||
});
|
||||
updateUserContext({ resourceGroup: inputs.resourceGroup, subscriptionId: inputs.subscriptionId });
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.LoadDatabaseAccount,
|
||||
{
|
||||
|
@ -87,13 +87,31 @@ describe("getPkIdFromDocumentId", () => {
|
||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
||||
});
|
||||
|
||||
it("should create pkid pair from partitioned graph (pk as number)", () => {
|
||||
const doc = createFakeDoc({ id: "id", mypk: 234 });
|
||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[234, 'id']");
|
||||
});
|
||||
|
||||
it("should create pkid pair from partitioned graph (pk as boolean)", () => {
|
||||
const doc = createFakeDoc({ id: "id", mypk: true });
|
||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[true, 'id']");
|
||||
});
|
||||
|
||||
it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
|
||||
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
|
||||
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
|
||||
});
|
||||
|
||||
it("should error if id is not a string", () => {
|
||||
const doc = createFakeDoc({ id: { foo: 1 } });
|
||||
it("should error if id is not a string or number", () => {
|
||||
let doc = createFakeDoc({ id: { foo: 1 } });
|
||||
try {
|
||||
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
||||
expect(true).toBe(false);
|
||||
} catch (e) {
|
||||
expect(true).toBe(true);
|
||||
}
|
||||
|
||||
doc = createFakeDoc({ id: true });
|
||||
try {
|
||||
GraphExplorer.getPkIdFromDocumentId(doc, undefined);
|
||||
expect(true).toBe(false);
|
||||
@ -102,16 +120,8 @@ describe("getPkIdFromDocumentId", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("should error if pk not string nor non-empty array", () => {
|
||||
let doc = createFakeDoc({ mypk: { foo: 1 } });
|
||||
|
||||
try {
|
||||
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
||||
} catch (e) {
|
||||
expect(true).toBe(true);
|
||||
}
|
||||
|
||||
doc = createFakeDoc({ mypk: [] });
|
||||
it("should error if pk is empty array", () => {
|
||||
let doc = createFakeDoc({ mypk: [] });
|
||||
try {
|
||||
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
|
||||
expect(true).toBe(false);
|
||||
|
@ -1371,7 +1371,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||
if (typeof pk !== "string") {
|
||||
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
||||
if (Array.isArray(pk) && pk.length > 0) {
|
||||
// pk is [{ id: 'id', _value: 'value' }]
|
||||
pk = pk[0]["_value"];
|
||||
|
@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
|
||||
actions.fetchContentFulfilled({
|
||||
filepath: undefined,
|
||||
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
||||
kernelRef: createKernelRef(),
|
||||
kernelRef: undefined, // must be undefined or it will be auto-started by the epic
|
||||
contentRef: this.contentRef
|
||||
})
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as Immutable from "immutable";
|
||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||
import { Subject } from "rxjs";
|
||||
import { Subject, empty } from "rxjs";
|
||||
import { toArray } from "rxjs/operators";
|
||||
import { makeNotebookRecord } from "@nteract/commutable";
|
||||
import { actions, state } from "@nteract/core";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { CdbAppState, makeCdbRecord } from "./types";
|
||||
import { launchWebSocketKernelEpic } from "./epics";
|
||||
import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
|
||||
import { sessions } from "rx-jupyter";
|
||||
@ -74,46 +74,47 @@ describe("Extract kernel from notebook", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
app: state.makeAppRecord({
|
||||
host: state.makeJupyterHostRecord({
|
||||
type: "jupyter",
|
||||
token: "eh",
|
||||
basePath: "/"
|
||||
})
|
||||
}),
|
||||
comms: state.makeCommsRecord(),
|
||||
config: Immutable.Map({}),
|
||||
core: state.makeStateRecord({
|
||||
kernelRef: "fake",
|
||||
entities: state.makeEntitiesRecord({
|
||||
contents: state.makeContentsRecord({
|
||||
byRef: Immutable.Map({
|
||||
fakeContentRef: state.makeNotebookContentRecord()
|
||||
})
|
||||
}),
|
||||
kernels: state.makeKernelsRecord({
|
||||
byRef: Immutable.Map({
|
||||
fake: state.makeRemoteKernelRecord({
|
||||
type: "websocket",
|
||||
channels: new Subject<any>(),
|
||||
kernelSpecName: "fancy",
|
||||
id: "0"
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
cdb: makeCdbRecord({
|
||||
databaseAccountName: "dbAccountName",
|
||||
defaultExperience: "defaultExperience"
|
||||
})
|
||||
};
|
||||
|
||||
describe("launchWebSocketKernelEpic", () => {
|
||||
const createSpy = sinon.spy(sessions, "create");
|
||||
|
||||
const contentRef = "fakeContentRef";
|
||||
const kernelRef = "fake";
|
||||
const initialState = {
|
||||
app: state.makeAppRecord({
|
||||
host: state.makeJupyterHostRecord({
|
||||
type: "jupyter",
|
||||
token: "eh",
|
||||
basePath: "/"
|
||||
})
|
||||
}),
|
||||
comms: state.makeCommsRecord(),
|
||||
config: Immutable.Map({}),
|
||||
core: state.makeStateRecord({
|
||||
kernelRef: "fake",
|
||||
entities: state.makeEntitiesRecord({
|
||||
contents: state.makeContentsRecord({
|
||||
byRef: Immutable.Map({
|
||||
fakeContentRef: state.makeNotebookContentRecord()
|
||||
})
|
||||
}),
|
||||
kernels: state.makeKernelsRecord({
|
||||
byRef: Immutable.Map({
|
||||
fake: state.makeRemoteKernelRecord({
|
||||
type: "websocket",
|
||||
channels: new Subject<any>(),
|
||||
kernelSpecName: "fancy",
|
||||
id: "0"
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
cdb: makeCdbRecord({
|
||||
databaseAccountName: "dbAccountName",
|
||||
defaultExperience: "defaultExperience"
|
||||
})
|
||||
};
|
||||
|
||||
it("launches remote kernels", async () => {
|
||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||
@ -490,3 +491,55 @@ describe("launchWebSocketKernelEpic", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("autoStartKernelEpic", () => {
|
||||
const contentRef = "fakeContentRef";
|
||||
const kernelRef = "fake";
|
||||
|
||||
it("automatically starts kernel when content fetch is successful if kernelRef is defined", async () => {
|
||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||
|
||||
const action$ = ActionsObservable.of(
|
||||
actions.fetchContentFulfilled({
|
||||
contentRef,
|
||||
kernelRef,
|
||||
filepath: "filepath",
|
||||
model: {}
|
||||
})
|
||||
);
|
||||
|
||||
const responseActions = await autoStartKernelEpic(action$, state$)
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
expect(responseActions).toMatchObject([
|
||||
{
|
||||
type: actions.RESTART_KERNEL,
|
||||
payload: {
|
||||
contentRef,
|
||||
kernelRef,
|
||||
outputHandling: "None"
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("Don't start kernel when content fetch is successful if kernelRef is not defined", async () => {
|
||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||
|
||||
const action$ = ActionsObservable.of(
|
||||
actions.fetchContentFulfilled({
|
||||
contentRef,
|
||||
kernelRef: undefined,
|
||||
filepath: "filepath",
|
||||
model: {}
|
||||
})
|
||||
);
|
||||
|
||||
const responseActions = await autoStartKernelEpic(action$, state$)
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
expect(responseActions).toMatchObject([]);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||
import { webSocket } from "rxjs/webSocket";
|
||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||
import { ofType } from "redux-observable";
|
||||
@ -77,7 +77,7 @@ const addInitialCodeCellEpic = (
|
||||
|
||||
// If it's not a notebook, we shouldn't be here
|
||||
if (!model || model.type !== "notebook") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const cellOrder = selectors.notebook.cellOrder(model);
|
||||
@ -90,7 +90,40 @@ const addInitialCodeCellEpic = (
|
||||
);
|
||||
}
|
||||
|
||||
return empty();
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically start kernel if kernelRef is present.
|
||||
* The kernel is normally lazy-started when a cell is being executed, but a running kernel is
|
||||
* required for code completion to work.
|
||||
* For notebook viewer, there is no kernel
|
||||
* @param action$
|
||||
* @param state$
|
||||
*/
|
||||
export const autoStartKernelEpic = (
|
||||
action$: ActionsObservable<actions.FetchContentFulfilled>,
|
||||
state$: StateObservable<AppState>
|
||||
): Observable<{} | actions.CreateCellBelow> => {
|
||||
return action$.pipe(
|
||||
ofType(actions.FETCH_CONTENT_FULFILLED),
|
||||
mergeMap(action => {
|
||||
const state = state$.value;
|
||||
const { contentRef, kernelRef } = action.payload;
|
||||
|
||||
if (!kernelRef) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return of(
|
||||
actions.restartKernel({
|
||||
contentRef,
|
||||
kernelRef,
|
||||
outputHandling: "None"
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -288,7 +321,7 @@ export const launchWebSocketKernelEpic = (
|
||||
const state = state$.value;
|
||||
const host = selectors.currentHost(state);
|
||||
if (host.type !== "jupyter") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||
serverConfig.userPuid = getUserPuid();
|
||||
@ -299,7 +332,7 @@ export const launchWebSocketKernelEpic = (
|
||||
|
||||
const content = selectors.content(state, { contentRef });
|
||||
if (!content || content.type !== "notebook") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
let kernelSpecToLaunch = kernelSpecName;
|
||||
@ -513,26 +546,26 @@ const changeWebSocketKernelEpic = (
|
||||
const state = state$.value;
|
||||
const host = selectors.currentHost(state);
|
||||
if (host.type !== "jupyter") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||
if (!oldKernelRef) {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
||||
if (!oldKernel || oldKernel.type !== "websocket") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
const { sessionId } = oldKernel;
|
||||
if (!sessionId) {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const content = selectors.content(state, { contentRef });
|
||||
if (!content || content.type !== "notebook") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
const {
|
||||
filepath,
|
||||
@ -593,7 +626,7 @@ const focusInitialCodeCellEpic = (
|
||||
|
||||
// If it's not a notebook, we shouldn't be here
|
||||
if (!model || model.type !== "notebook") {
|
||||
return empty();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const cellOrder = selectors.notebook.cellOrder(model);
|
||||
@ -608,7 +641,7 @@ const focusInitialCodeCellEpic = (
|
||||
);
|
||||
}
|
||||
|
||||
return empty();
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -661,7 +694,7 @@ const notificationsToUserEpic = (
|
||||
break;
|
||||
}
|
||||
}
|
||||
return empty();
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -701,7 +734,7 @@ const handleKernelConnectionLostEpic = (
|
||||
if (explorer) {
|
||||
explorer.showOkModalDialog("kernel restarts", msg);
|
||||
}
|
||||
return of(empty());
|
||||
return of(EMPTY);
|
||||
}
|
||||
|
||||
return concat(
|
||||
@ -814,7 +847,7 @@ const closeUnsupportedMimetypesEpic = (
|
||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
}
|
||||
return empty();
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -842,13 +875,14 @@ const closeContentFailedToFetchEpic = (
|
||||
explorer.showOkModalDialog("Failure to load", msg);
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||
}
|
||||
return empty();
|
||||
return EMPTY;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const allEpics = [
|
||||
addInitialCodeCellEpic,
|
||||
autoStartKernelEpic,
|
||||
focusInitialCodeCellEpic,
|
||||
notificationsToUserEpic,
|
||||
launchWebSocketKernelEpic,
|
||||
|
@ -9,18 +9,15 @@ import * as PricingUtils from "../../Utils/PricingUtils";
|
||||
import * as SharedConstants from "../../Shared/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import editable from "../../Common/EditableUtility";
|
||||
import EnvironmentUtility from "../../Common/EnvironmentUtility";
|
||||
import Q from "q";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { createMongoCollectionWithARM, createMongoCollectionWithProxy } from "../../Common/MongoProxyClient";
|
||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||
import { HashMap } from "../../Common/HashMap";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
import { refreshCachedResources, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
|
||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||
isPreferredApiTable: ko.Computed<boolean>;
|
||||
@ -811,7 +808,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
|
||||
let collectionId: string = this.collectionId().trim();
|
||||
let rupm: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
let indexingPolicy: DataModels.IndexingPolicy;
|
||||
// todo - remove mongo indexing policy ticket # 616274
|
||||
@ -828,130 +824,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
this.formErrors("");
|
||||
|
||||
this.isExecuting(true);
|
||||
|
||||
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
|
||||
const databaseLevelThroughput: boolean = this.databaseCreateNew()
|
||||
? this.databaseCreateNewShared()
|
||||
: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared();
|
||||
const autoPilotMaxThroughput: number = databaseLevelThroughput
|
||||
? this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()
|
||||
: this.isAutoPilotSelected() && this.autoPilotThroughput();
|
||||
const createCollectionParams: DataModels.CreateCollectionParams = {
|
||||
createNewDatabase: this.databaseCreateNew(),
|
||||
collectionId,
|
||||
databaseId,
|
||||
databaseLevelThroughput,
|
||||
offerThroughput,
|
||||
databaseLevelThroughput: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(),
|
||||
rupmEnabled: rupm,
|
||||
partitionKey,
|
||||
indexingPolicy,
|
||||
uniqueKeyPolicy,
|
||||
autoPilot,
|
||||
analyticalStorageTtl: this._getAnalyticalStorageTtl(),
|
||||
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag()
|
||||
autoPilotMaxThroughput,
|
||||
indexingPolicy,
|
||||
partitionKey,
|
||||
uniqueKeyPolicy
|
||||
};
|
||||
|
||||
const options: any = {};
|
||||
if (this.container.isPreferredApiMongoDB()) {
|
||||
options.initialHeaders = options.initialHeaders || {};
|
||||
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
|
||||
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
|
||||
}
|
||||
|
||||
const databaseCreateNew = this.databaseCreateNew();
|
||||
const useDatabaseSharedOffer = this.shouldUseDatabaseThroughput();
|
||||
const isSharded: boolean = !!partitionKeyPath;
|
||||
const autopilotSettings: DataModels.RpOptions = this._getAutopilotSettings();
|
||||
|
||||
let createCollectionFunc: () => Q.Promise<DataModels.Collection | DataModels.CreateCollectionWithRpResponse>;
|
||||
|
||||
if (this.container.isPreferredApiMongoDB()) {
|
||||
const isFixedCollectionWithSharedThroughputBeingCreated =
|
||||
this.container.isFixedCollectionWithSharedThroughputSupported() &&
|
||||
!this.isUnlimitedStorageSelected() &&
|
||||
this.databaseHasSharedOffer();
|
||||
const isAadUser = EnvironmentUtility.isAadUser();
|
||||
|
||||
// note: v3 autopilot not supported yet for Mongo fixed collections (only tier supported)
|
||||
if (!isAadUser || isFixedCollectionWithSharedThroughputBeingCreated) {
|
||||
createCollectionFunc = () =>
|
||||
Q(
|
||||
createMongoCollectionWithProxy(
|
||||
databaseId,
|
||||
collectionId,
|
||||
offerThroughput,
|
||||
partitionKeyPath,
|
||||
databaseCreateNew,
|
||||
useDatabaseSharedOffer,
|
||||
isSharded,
|
||||
autopilotSettings
|
||||
)
|
||||
);
|
||||
} else {
|
||||
createCollectionFunc = () =>
|
||||
Q(
|
||||
createMongoCollectionWithARM(
|
||||
this.container.armEndpoint(),
|
||||
databaseId,
|
||||
this._getAnalyticalStorageTtl(),
|
||||
collectionId,
|
||||
offerThroughput,
|
||||
partitionKeyPath,
|
||||
databaseCreateNew,
|
||||
useDatabaseSharedOffer,
|
||||
isSharded,
|
||||
autopilotSettings
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (this.container.isPreferredApiTable() && EnvironmentUtility.isAadUser()) {
|
||||
createCollectionFunc = () =>
|
||||
Q(
|
||||
AddCollectionUtility.Utilities.createAzureTableWithARM(
|
||||
this.container.armEndpoint(),
|
||||
createRequest,
|
||||
autopilotSettings
|
||||
)
|
||||
);
|
||||
} else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) {
|
||||
createCollectionFunc = () =>
|
||||
Q(
|
||||
AddCollectionUtility.CreateCollectionUtilities.createGremlinGraph(
|
||||
this.container.armEndpoint(),
|
||||
databaseId,
|
||||
collectionId,
|
||||
indexingPolicy,
|
||||
offerThroughput,
|
||||
partitionKeyPath,
|
||||
partitionKey.version,
|
||||
databaseCreateNew,
|
||||
useDatabaseSharedOffer,
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
autopilotSettings
|
||||
)
|
||||
);
|
||||
} else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) {
|
||||
createCollectionFunc = () =>
|
||||
Q(
|
||||
AddCollectionUtility.CreateSqlCollectionUtilities.createSqlCollection(
|
||||
this.container.armEndpoint(),
|
||||
databaseId,
|
||||
this._getAnalyticalStorageTtl(),
|
||||
collectionId,
|
||||
indexingPolicy,
|
||||
offerThroughput,
|
||||
partitionKeyPath,
|
||||
partitionKey.version,
|
||||
databaseCreateNew,
|
||||
useDatabaseSharedOffer,
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount.name,
|
||||
uniqueKeyPolicy,
|
||||
autopilotSettings
|
||||
)
|
||||
);
|
||||
} else {
|
||||
createCollectionFunc = () => getOrCreateDatabaseAndCollection(createRequest, options);
|
||||
}
|
||||
|
||||
createCollectionFunc().then(
|
||||
createCollection(createCollectionParams).then(
|
||||
() => {
|
||||
this.isExecuting(false);
|
||||
this.close();
|
||||
@ -1234,35 +1128,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
private _getAutopilotSettings(): DataModels.RpOptions {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.databaseCreateNewShared() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.databaseCreateNewShared() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.selectedSharedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.sharedAutoPilotThroughput() * 1 }
|
||||
}
|
||||
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedSharedAutoPilotTier().toString() };
|
||||
}
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.autoPilotThroughput() * 1 }
|
||||
}
|
||||
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _calculateNumberOfPartitions(): number {
|
||||
// Note: this will not validate properly on accounts that have been set up for custom partitioning,
|
||||
|
@ -14,8 +14,8 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||
import { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
|
||||
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
import { refreshCachedOffers, refreshCachedResources, createDatabase } from "../../Common/DocumentClientUtilityBase";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
export default class AddDatabasePane extends ContextualPaneBase {
|
||||
@ -304,76 +304,23 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
this.formErrors("");
|
||||
this.isExecuting(true);
|
||||
|
||||
const createDatabaseParameters: DataModels.RpParameters = {
|
||||
db: addDatabasePaneStartMessage.database.id,
|
||||
st: addDatabasePaneStartMessage.database.shared,
|
||||
offerThroughput: addDatabasePaneStartMessage.offerThroughput,
|
||||
sid: userContext.subscriptionId,
|
||||
rg: userContext.resourceGroup,
|
||||
dba: addDatabasePaneStartMessage.databaseAccountName
|
||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||
autoPilotMaxThroughput: this.maxAutoPilotThroughputSet(),
|
||||
databaseId: addDatabasePaneStartMessage.database.id,
|
||||
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||
offerThroughput: addDatabasePaneStartMessage.offerThroughput
|
||||
};
|
||||
|
||||
const autopilotSettings = this._getAutopilotSettings();
|
||||
|
||||
if (this.container.isPreferredApiCassandra()) {
|
||||
this._createKeyspace(createDatabaseParameters, autopilotSettings, startKey);
|
||||
} else if (this.container.isPreferredApiMongoDB() && EnvironmentUtility.isAadUser()) {
|
||||
this._createMongoDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
||||
} else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) {
|
||||
this._createGremlinDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
||||
} else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) {
|
||||
this._createSqlDatabase(createDatabaseParameters, autopilotSettings, startKey);
|
||||
} else {
|
||||
this._createDatabase(offerThroughput, startKey);
|
||||
}
|
||||
}
|
||||
|
||||
private _createSqlDatabase(
|
||||
createDatabaseParameters: DataModels.RpParameters,
|
||||
autoPilotSettings: DataModels.RpOptions,
|
||||
startKey: number
|
||||
) {
|
||||
AddDbUtilities.createSqlDatabase(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings).then(
|
||||
() => {
|
||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
||||
});
|
||||
createDatabase(createDatabaseParams).then(
|
||||
(database: DataModels.Database) => {
|
||||
this._onCreateDatabaseSuccess(offerThroughput, startKey);
|
||||
},
|
||||
(reason: any) => {
|
||||
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _createMongoDatabase(
|
||||
createDatabaseParameters: DataModels.RpParameters,
|
||||
autoPilotSettings: DataModels.RpOptions,
|
||||
startKey: number
|
||||
) {
|
||||
AddDbUtilities.createMongoDatabaseWithARM(
|
||||
this.container.armEndpoint(),
|
||||
createDatabaseParameters,
|
||||
autoPilotSettings
|
||||
).then(() => {
|
||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _createGremlinDatabase(
|
||||
createDatabaseParameters: DataModels.RpParameters,
|
||||
autoPilotSettings: DataModels.RpOptions,
|
||||
startKey: number
|
||||
) {
|
||||
AddDbUtilities.createGremlinDatabase(
|
||||
this.container.armEndpoint(),
|
||||
createDatabaseParameters,
|
||||
autoPilotSettings
|
||||
).then(() => {
|
||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||
this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public resetData() {
|
||||
this.databaseId("");
|
||||
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
||||
@ -396,72 +343,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _createDatabase(offerThroughput: number, telemetryStartKey: number): void {
|
||||
const autoPilot: DataModels.AutoPilotCreationSettings = this._isAutoPilotSelectedAndWhatTier();
|
||||
const createRequest: DataModels.CreateDatabaseRequest = {
|
||||
databaseId: this.databaseId().trim(),
|
||||
offerThroughput,
|
||||
databaseLevelThroughput: this.databaseCreateNewShared(),
|
||||
autoPilot,
|
||||
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag()
|
||||
};
|
||||
createDatabase(createRequest).then(
|
||||
(database: DataModels.Database) => {
|
||||
this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey);
|
||||
},
|
||||
(reason: any) => {
|
||||
this._onCreateDatabaseFailure(reason, offerThroughput, reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _createKeyspace(
|
||||
createDatabaseParameters: DataModels.RpParameters,
|
||||
autoPilotSettings: DataModels.RpOptions,
|
||||
startKey: number
|
||||
): void {
|
||||
if (EnvironmentUtility.isAadUser()) {
|
||||
this._createKeyspaceUsingRP(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings, startKey);
|
||||
} else {
|
||||
this._createKeyspaceUsingProxy(createDatabaseParameters.offerThroughput, startKey);
|
||||
}
|
||||
}
|
||||
|
||||
private _createKeyspaceUsingProxy(offerThroughput: number, telemetryStartKey: number): void {
|
||||
const provisionThroughputQueryPart: string = this.databaseCreateNewShared()
|
||||
? `AND cosmosdb_provisioned_throughput=${offerThroughput}`
|
||||
: "";
|
||||
const createKeyspaceQuery: string = `CREATE KEYSPACE ${this.databaseId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 } ${provisionThroughputQueryPart};`;
|
||||
(this.container.tableDataClient as CassandraAPIDataClient)
|
||||
.createKeyspace(
|
||||
this.container.databaseAccount().properties.cassandraEndpoint,
|
||||
this.container.databaseAccount().id,
|
||||
this.container,
|
||||
createKeyspaceQuery
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey);
|
||||
},
|
||||
(reason: any) => {
|
||||
this._onCreateDatabaseFailure(reason, offerThroughput, telemetryStartKey);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _createKeyspaceUsingRP(
|
||||
armEndpoint: string,
|
||||
createKeyspaceParameters: DataModels.RpParameters,
|
||||
autoPilotSettings: DataModels.RpOptions,
|
||||
startKey: number
|
||||
): void {
|
||||
AddDbUtilities.createCassandraKeyspace(armEndpoint, createKeyspaceParameters, autoPilotSettings).then(() => {
|
||||
Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => {
|
||||
this._onCreateDatabaseSuccess(createKeyspaceParameters.offerThroughput, startKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
|
||||
this.isExecuting(false);
|
||||
this.close();
|
||||
@ -582,20 +463,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _getAutopilotSettings(): DataModels.RpOptions {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
[Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.maxAutoPilotThroughputSet() * 1 }
|
||||
}
|
||||
: { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _updateThroughputLimitByDatabase() {
|
||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
||||
this.throughput(throughputDefaults.shared);
|
||||
|
@ -16,7 +16,7 @@ export interface GenericRightPaneProps {
|
||||
onSubmit: () => void;
|
||||
submitButtonText: string;
|
||||
title: string;
|
||||
isSubmitButtonVisible?: boolean;
|
||||
isSubmitButtonHidden?: boolean;
|
||||
}
|
||||
|
||||
export interface GenericRightPaneState {
|
||||
@ -108,7 +108,7 @@ export class GenericRightPaneComponent extends React.Component<GenericRightPaneP
|
||||
<div className="paneFooter">
|
||||
<div className="leftpanel-okbut">
|
||||
<PrimaryButton
|
||||
style={{ visibility: this.props.isSubmitButtonVisible ? "visible" : "hidden" }}
|
||||
style={{ visibility: this.props.isSubmitButtonHidden ? "hidden" : "visible" }}
|
||||
ariaLabel="Submit"
|
||||
title="Submit"
|
||||
onClick={this.props.onSubmit}
|
||||
|
@ -52,7 +52,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
||||
submitButtonText: "Publish",
|
||||
onClose: () => this.close(),
|
||||
onSubmit: () => this.submit(),
|
||||
isSubmitButtonVisible: this.isCodeOfConductAccepted
|
||||
isSubmitButtonHidden: !this.isCodeOfConductAccepted
|
||||
};
|
||||
|
||||
const publishNotebookPaneProps: PublishNotebookPaneProps = {
|
||||
|
@ -285,7 +285,7 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
|
||||
<GalleryCardComponent
|
||||
data={{
|
||||
id: undefined,
|
||||
name: this.props.notebookName,
|
||||
name: this.state.notebookName,
|
||||
description: this.state.notebookDescription,
|
||||
gitSha: undefined,
|
||||
tags: this.state.notebookTags.split(","),
|
||||
|
@ -39,7 +39,6 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
|
||||
formErrorDetail: this.formErrorDetail,
|
||||
id: "uploaditemspane",
|
||||
isExecuting: this.isExecuting,
|
||||
isSubmitButtonVisible: true,
|
||||
title: "Upload Items",
|
||||
submitButtonText: "Upload",
|
||||
onClose: () => this.close(),
|
||||
|
@ -45,7 +45,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<img src={item.iconSrc} alt={item.title} />
|
||||
<img src={item.iconSrc} alt="" />
|
||||
<div className="legendContainer">
|
||||
<div className="legend">{item.title}</div>
|
||||
<div className="description">{item.description}</div>
|
||||
@ -66,7 +66,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<img src={item.iconSrc} alt={item.title} />
|
||||
<img src={item.iconSrc} alt="" />
|
||||
<span className="oneLineContent" title={item.info}>
|
||||
{item.title}
|
||||
</span>
|
||||
@ -79,7 +79,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
||||
<ul>
|
||||
{this.props.recentItems.map((item: SplashScreenItem, index: number) => (
|
||||
<li key={`${item.title}${item.description}${index}`}>
|
||||
<img src={item.iconSrc} alt={item.title} />
|
||||
<img src={item.iconSrc} alt="" />
|
||||
<span className="twoLineContent">
|
||||
<Link onClick={item.onClick} title={item.info}>
|
||||
{item.title}
|
||||
|
@ -147,6 +147,30 @@ export default class NotebookTabV2 extends TabsBase {
|
||||
const cellCodeType = "code";
|
||||
const cellMarkdownType = "markdown";
|
||||
const cellRawType = "raw";
|
||||
|
||||
const saveButtonChildren = [];
|
||||
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
saveButtonChildren.push({
|
||||
iconName: "Copy",
|
||||
onCommandClick: () => this.copyNotebook(),
|
||||
commandButtonLabel: copyToLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: copyToLabel
|
||||
});
|
||||
}
|
||||
|
||||
if (this.container.isGalleryPublishEnabled()) {
|
||||
saveButtonChildren.push({
|
||||
iconName: "PublishContent",
|
||||
onCommandClick: async () => await this.publishToGallery(),
|
||||
commandButtonLabel: publishLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: publishLabel
|
||||
});
|
||||
}
|
||||
|
||||
let buttons: CommandButtonComponentProps[] = [
|
||||
{
|
||||
iconSrc: SaveIcon,
|
||||
@ -156,34 +180,17 @@ export default class NotebookTabV2 extends TabsBase {
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: saveLabel,
|
||||
children: this.container.isGalleryPublishEnabled()
|
||||
? [
|
||||
{
|
||||
iconName: "Save",
|
||||
onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
|
||||
commandButtonLabel: saveLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: saveLabel
|
||||
},
|
||||
{
|
||||
iconName: "Copy",
|
||||
onCommandClick: () => this.copyNotebook(),
|
||||
commandButtonLabel: copyToLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: copyToLabel
|
||||
},
|
||||
{
|
||||
iconName: "PublishContent",
|
||||
onCommandClick: async () => await this.publishToGallery(),
|
||||
commandButtonLabel: publishLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: publishLabel
|
||||
}
|
||||
]
|
||||
: undefined
|
||||
children: saveButtonChildren.length && [
|
||||
{
|
||||
iconName: "Save",
|
||||
onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
|
||||
commandButtonLabel: saveLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: saveLabel
|
||||
},
|
||||
...saveButtonChildren
|
||||
]
|
||||
},
|
||||
{
|
||||
iconSrc: null,
|
||||
|
@ -7,6 +7,7 @@ import Database from "../Tree/Database";
|
||||
import Explorer from "../Explorer";
|
||||
import SettingsTab from "../Tabs/SettingsTab";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
import { IndexingPolicies } from "../../Shared/Constants";
|
||||
|
||||
describe("Settings tab", () => {
|
||||
const baseCollection: DataModels.Collection = {
|
||||
@ -16,7 +17,7 @@ describe("Settings tab", () => {
|
||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||
conflictResolutionPath: "/_ts"
|
||||
},
|
||||
indexingPolicy: {},
|
||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
@ -51,7 +52,7 @@ describe("Settings tab", () => {
|
||||
defaultTtl: 200,
|
||||
partitionKey: null,
|
||||
conflictResolutionPolicy: null,
|
||||
indexingPolicy: {},
|
||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
@ -345,7 +346,6 @@ describe("Settings tab", () => {
|
||||
|
||||
const offer: DataModels.Offer = null;
|
||||
const defaultTtl = 200;
|
||||
const indexingPolicy = {};
|
||||
const database = new Database(explorer, baseDatabase, null);
|
||||
const conflictResolutionPolicy = {
|
||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||
@ -367,7 +367,7 @@ describe("Settings tab", () => {
|
||||
}
|
||||
: null,
|
||||
conflictResolutionPolicy: conflictResolutionPolicy,
|
||||
indexingPolicy: indexingPolicy,
|
||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
|
@ -17,7 +17,8 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||
import Explorer from "../Explorer";
|
||||
import { updateOffer, updateCollection } from "../../Common/DocumentClientUtilityBase";
|
||||
import { updateOffer } from "../../Common/DocumentClientUtilityBase";
|
||||
import { updateCollection } from "../../Common/dataAccess/updateCollection";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||
@ -1009,8 +1010,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
);
|
||||
}
|
||||
|
||||
public onSaveClick = (): Q.Promise<any> => {
|
||||
let promises: Q.Promise<void>[] = [];
|
||||
public onSaveClick = async (): Promise<any> => {
|
||||
this.isExecutionError(false);
|
||||
|
||||
this.isExecuting(true);
|
||||
@ -1023,50 +1023,60 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
const newCollectionAttributes: any = {};
|
||||
|
||||
if (this.shouldUpdateCollection()) {
|
||||
let defaultTtl: number;
|
||||
switch (this.timeToLive()) {
|
||||
case "on":
|
||||
defaultTtl = Number(this.timeToLiveSeconds());
|
||||
break;
|
||||
case "on-nodefault":
|
||||
defaultTtl = -1;
|
||||
break;
|
||||
case "off":
|
||||
default:
|
||||
defaultTtl = undefined;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (this.shouldUpdateCollection()) {
|
||||
let defaultTtl: number;
|
||||
switch (this.timeToLive()) {
|
||||
case "on":
|
||||
defaultTtl = Number(this.timeToLiveSeconds());
|
||||
break;
|
||||
case "on-nodefault":
|
||||
defaultTtl = -1;
|
||||
break;
|
||||
case "off":
|
||||
default:
|
||||
defaultTtl = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
newCollectionAttributes.defaultTtl = defaultTtl;
|
||||
newCollectionAttributes.defaultTtl = defaultTtl;
|
||||
|
||||
newCollectionAttributes.indexingPolicy = this.indexingPolicyContent();
|
||||
newCollectionAttributes.indexingPolicy = this.indexingPolicyContent();
|
||||
|
||||
newCollectionAttributes.changeFeedPolicy =
|
||||
this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On
|
||||
? ({
|
||||
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration
|
||||
} as DataModels.ChangeFeedPolicy)
|
||||
newCollectionAttributes.changeFeedPolicy =
|
||||
this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On
|
||||
? ({
|
||||
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration
|
||||
} as DataModels.ChangeFeedPolicy)
|
||||
: undefined;
|
||||
|
||||
newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled
|
||||
? this.analyticalStorageTtlSelection() === "on"
|
||||
? Number(this.analyticalStorageTtlSeconds())
|
||||
: Constants.AnalyticalStorageTtl.Infinite
|
||||
: undefined;
|
||||
|
||||
newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled
|
||||
? this.analyticalStorageTtlSelection() === "on"
|
||||
? Number(this.analyticalStorageTtlSeconds())
|
||||
: Constants.AnalyticalStorageTtl.Infinite
|
||||
: undefined;
|
||||
newCollectionAttributes.geospatialConfig = {
|
||||
type: this.geospatialConfigType()
|
||||
};
|
||||
|
||||
newCollectionAttributes.geospatialConfig = {
|
||||
type: this.geospatialConfigType()
|
||||
};
|
||||
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
|
||||
if (!!conflictResolutionChanges) {
|
||||
newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges;
|
||||
}
|
||||
|
||||
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
|
||||
if (!!conflictResolutionChanges) {
|
||||
newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges;
|
||||
}
|
||||
const newCollection: DataModels.Collection = _.extend(
|
||||
{},
|
||||
this.collection.rawDataModel,
|
||||
newCollectionAttributes
|
||||
);
|
||||
const updatedCollection: DataModels.Collection = await updateCollection(
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newCollection
|
||||
);
|
||||
|
||||
const newCollection: DataModels.Collection = _.extend({}, this.collection.rawDataModel, newCollectionAttributes);
|
||||
const updateCollectionPromise = updateCollection(this.collection.databaseId, this.collection, newCollection).then(
|
||||
(updatedCollection: DataModels.Collection) => {
|
||||
if (updatedCollection) {
|
||||
this.collection.rawDataModel = updatedCollection;
|
||||
this.collection.defaultTtl(updatedCollection.defaultTtl);
|
||||
this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl);
|
||||
@ -1076,164 +1086,133 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||
}
|
||||
);
|
||||
|
||||
promises.push(updateCollectionPromise);
|
||||
}
|
||||
|
||||
if (
|
||||
this.throughput.editableIsDirty() ||
|
||||
this.rupm.editableIsDirty() ||
|
||||
this._isAutoPilotDirty() ||
|
||||
this._hasProvisioningTypeChanged()
|
||||
) {
|
||||
const newThroughput = this.throughput();
|
||||
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
|
||||
const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
|
||||
|
||||
if (newOffer.content) {
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
|
||||
} else {
|
||||
newOffer = _.extend({}, newOffer, {
|
||||
content: {
|
||||
offerThroughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
maxThroughput: this.autoPilotThroughput()
|
||||
};
|
||||
} else {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
tier: this.selectedAutoPilotTier()
|
||||
};
|
||||
}
|
||||
|
||||
// user has changed from provisioned --> autoscale
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
} else {
|
||||
delete newOffer.content.offerThroughput;
|
||||
}
|
||||
} else {
|
||||
this.isAutoPilotSelected(false);
|
||||
this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
// user has changed from autoscale --> provisioned
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||
} else {
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
this.container != null
|
||||
this.throughput.editableIsDirty() ||
|
||||
this.rupm.editableIsDirty() ||
|
||||
this._isAutoPilotDirty() ||
|
||||
this._hasProvisioningTypeChanged()
|
||||
) {
|
||||
const requestPayload = {
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
databaseAccountName: userContext.databaseAccount.name,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
};
|
||||
const updateOfferBeyondLimitPromise = updateOfferThroughputBeyondLimit(requestPayload).then(
|
||||
() => {
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
this.throughput(originalThroughputValue);
|
||||
this.notificationStatusInfo(
|
||||
throughputApplyDelayedMessage(
|
||||
this.isAutoPilotSelected(),
|
||||
originalThroughputValue,
|
||||
this._getThroughputUnit(),
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newThroughput
|
||||
)
|
||||
);
|
||||
this.throughput.valueHasMutated(); // force component re-render
|
||||
},
|
||||
(error: any) => {
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.UpdateSettings,
|
||||
{
|
||||
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.tabTitle(),
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
}
|
||||
);
|
||||
promises.push(Q(updateOfferBeyondLimitPromise));
|
||||
} else {
|
||||
const updateOfferPromise = updateOffer(this.collection.offer(), newOffer, headerOptions).then(
|
||||
(updatedOffer: DataModels.Offer) => {
|
||||
this.collection.offer(updatedOffer);
|
||||
this.collection.offer.valueHasMutated();
|
||||
}
|
||||
);
|
||||
const newThroughput = this.throughput();
|
||||
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
|
||||
const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
|
||||
|
||||
promises.push(updateOfferPromise);
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length === 0) {
|
||||
this.isExecuting(false);
|
||||
}
|
||||
|
||||
return Q.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
this.container.isRefreshingExplorer(false);
|
||||
this._setBaseline();
|
||||
this.collection.readSettings();
|
||||
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.UpdateSettings,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle()
|
||||
},
|
||||
startKey
|
||||
);
|
||||
},
|
||||
(reason: any) => {
|
||||
this.container.isRefreshingExplorer(false);
|
||||
this.isExecutionError(true);
|
||||
console.error(reason);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.UpdateSettings,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle()
|
||||
},
|
||||
startKey
|
||||
);
|
||||
if (newOffer.content) {
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
|
||||
} else {
|
||||
newOffer = _.extend({}, newOffer, {
|
||||
content: {
|
||||
offerThroughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
.finally(() => this.isExecuting(false));
|
||||
|
||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
maxThroughput: this.autoPilotThroughput()
|
||||
};
|
||||
} else {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
tier: this.selectedAutoPilotTier()
|
||||
};
|
||||
}
|
||||
|
||||
// user has changed from provisioned --> autoscale
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
} else {
|
||||
delete newOffer.content.offerThroughput;
|
||||
}
|
||||
} else {
|
||||
this.isAutoPilotSelected(false);
|
||||
this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
// user has changed from autoscale --> provisioned
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||
} else {
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
||||
this.container != null
|
||||
) {
|
||||
const requestPayload = {
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
databaseAccountName: userContext.databaseAccount.name,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
};
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||
this.throughput(originalThroughputValue);
|
||||
this.notificationStatusInfo(
|
||||
throughputApplyDelayedMessage(
|
||||
this.isAutoPilotSelected(),
|
||||
originalThroughputValue,
|
||||
this._getThroughputUnit(),
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newThroughput
|
||||
)
|
||||
);
|
||||
this.throughput.valueHasMutated(); // force component re-render
|
||||
} else {
|
||||
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions);
|
||||
this.collection.offer(updatedOffer);
|
||||
this.collection.offer.valueHasMutated();
|
||||
}
|
||||
}
|
||||
|
||||
this.container.isRefreshingExplorer(false);
|
||||
this._setBaseline();
|
||||
this.collection.readSettings();
|
||||
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.UpdateSettings,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle()
|
||||
},
|
||||
startKey
|
||||
);
|
||||
} catch (error) {
|
||||
this.container.isRefreshingExplorer(false);
|
||||
this.isExecutionError(true);
|
||||
console.error(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.UpdateSettings,
|
||||
{
|
||||
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.tabTitle(),
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
}
|
||||
|
||||
this.isExecuting(false);
|
||||
};
|
||||
|
||||
public onRevertClick = (): Q.Promise<any> => {
|
||||
|
@ -648,7 +648,9 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
// TODO: Use the collection entity cache to get quota info
|
||||
const quotaInfoPromise: Q.Promise<DataModels.CollectionQuotaInfo> = readCollectionQuotaInfo(this);
|
||||
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
||||
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers({
|
||||
isServerless: this.container.isServerlessEnabled()
|
||||
});
|
||||
Q.all([quotaInfoPromise, offerInfoPromise]).then(
|
||||
() => {
|
||||
this.container.isRefreshingExplorer(false);
|
||||
@ -657,9 +659,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy");
|
||||
|
||||
const collectionOffer = this._getOfferForCollection(offerInfoPromise.valueOf(), collectionDataModel);
|
||||
const isDatabaseShared = this.getDatabase() && this.getDatabase().isDatabaseShared();
|
||||
const isServerless = this.container.isServerlessEnabled();
|
||||
if ((isDatabaseShared || isServerless) && !collectionOffer) {
|
||||
if (!collectionOffer) {
|
||||
this.quotaInfo(quotaInfo);
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.LoadOffers,
|
||||
|
@ -123,10 +123,6 @@ export default class Database implements ViewModels.Database {
|
||||
|
||||
public readSettings(): Q.Promise<void> {
|
||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||
if (this.container.isServerlessEnabled()) {
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
this.container.isRefreshingExplorer(true);
|
||||
const databaseDataModel: DataModels.Database = <DataModels.Database>{
|
||||
id: this.id(),
|
||||
@ -138,7 +134,9 @@ export default class Database implements ViewModels.Database {
|
||||
defaultExperience: this.container.defaultExperience()
|
||||
});
|
||||
|
||||
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers();
|
||||
const offerInfoPromise: Q.Promise<DataModels.Offer[]> = readOffers({
|
||||
isServerless: this.container.isServerlessEnabled()
|
||||
});
|
||||
Q.all([offerInfoPromise]).then(
|
||||
() => {
|
||||
this.container.isRefreshingExplorer(false);
|
||||
@ -147,6 +145,11 @@ export default class Database implements ViewModels.Database {
|
||||
offerInfoPromise.valueOf(),
|
||||
databaseDataModel
|
||||
);
|
||||
|
||||
if (!databaseOffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
readOffer(databaseOffer).then((offerDetail: DataModels.OfferWithHeaders) => {
|
||||
const offerThroughputInfo: DataModels.OfferThroughputInfo = {
|
||||
minimumRUForCollection:
|
||||
|
@ -546,43 +546,52 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
(activeTab as any).notebookPath() === item.path
|
||||
);
|
||||
},
|
||||
contextMenu: createFileContextMenu
|
||||
? [
|
||||
{
|
||||
label: "Rename",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.renameNotebook(item)
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
iconSrc: DeleteIcon,
|
||||
onClick: () => {
|
||||
this.container.showOkCancelModalDialog(
|
||||
"Confirm delete",
|
||||
`Are you sure you want to delete "${item.name}"`,
|
||||
"Delete",
|
||||
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Copy to ...",
|
||||
iconSrc: CopyIcon,
|
||||
onClick: () => this.copyNotebook(item)
|
||||
},
|
||||
{
|
||||
label: "Download",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.downloadFile(item)
|
||||
}
|
||||
]
|
||||
: undefined,
|
||||
contextMenu: createFileContextMenu && this.createFileContextMenu(item),
|
||||
data: item
|
||||
};
|
||||
}
|
||||
|
||||
private createFileContextMenu(item: NotebookContentItem): TreeNodeMenuItem[] {
|
||||
let items: TreeNodeMenuItem[] = [
|
||||
{
|
||||
label: "Rename",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.renameNotebook(item)
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
iconSrc: DeleteIcon,
|
||||
onClick: () => {
|
||||
this.container.showOkCancelModalDialog(
|
||||
"Confirm delete",
|
||||
`Are you sure you want to delete "${item.name}"`,
|
||||
"Delete",
|
||||
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Copy to ...",
|
||||
iconSrc: CopyIcon,
|
||||
onClick: () => this.copyNotebook(item)
|
||||
},
|
||||
{
|
||||
label: "Download",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.downloadFile(item)
|
||||
}
|
||||
];
|
||||
|
||||
// "Copy to ..." isn't needed if github locations are not available
|
||||
if (!this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
items = items.filter(item => item.label !== "Copy to ...");
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private copyNotebook = async (item: NotebookContentItem) => {
|
||||
const content = await this.container.readFile(item);
|
||||
if (content) {
|
||||
|
@ -8,6 +8,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
|
||||
import { userContext } from "../UserContext";
|
||||
import { createUpdateCassandraKeyspace } from "../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||
|
||||
export class AddDbUtilities {
|
||||
// todo - remove any
|
||||
@ -47,7 +48,6 @@ export class AddDbUtilities {
|
||||
|
||||
// todo - remove any
|
||||
public static async createCassandraKeyspace(
|
||||
armEndpoint: string,
|
||||
params: DataModels.RpParameters,
|
||||
rpOptions: DataModels.RpOptions
|
||||
): Promise<any> {
|
||||
@ -70,9 +70,11 @@ export class AddDbUtilities {
|
||||
}
|
||||
|
||||
try {
|
||||
await AddDbUtilities.getRpClient<DataModels.CreateDatabaseWithRpResponse>(armEndpoint).putAsync(
|
||||
AddDbUtilities._getCassandraKeyspaceUri(params),
|
||||
DataExplorerConstants.ArmApiVersions.publicVersion,
|
||||
await createUpdateCassandraKeyspace(
|
||||
userContext.subscriptionId,
|
||||
userContext.resourceGroup,
|
||||
userContext.databaseAccount?.name,
|
||||
params.db,
|
||||
rpPayloadToCreateKeyspace
|
||||
);
|
||||
} catch (reason) {
|
||||
@ -159,10 +161,7 @@ export class AddDbUtilities {
|
||||
}
|
||||
|
||||
private static _handleCreationError(reason: any, params: DataModels.RpParameters, dbType: string = "database") {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleError(`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`);
|
||||
if (reason.status === HttpStatusCodes.Forbidden) {
|
||||
sendMessage({ type: MessageTypes.ForbiddenError });
|
||||
return;
|
||||
|
@ -114,13 +114,17 @@ export default class TelemetryProcessor {
|
||||
return validTimestamp;
|
||||
}
|
||||
|
||||
private static getData(data?: any): any {
|
||||
private static getData(data: any = {}): any {
|
||||
if (typeof data === "string") {
|
||||
data = { message: data };
|
||||
}
|
||||
return {
|
||||
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
|
||||
authType: (window as any).authType,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
platform: configContext.platform,
|
||||
...(data ? data : [])
|
||||
env: process.env.NODE_ENV,
|
||||
...data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ interface UserContext {
|
||||
authorizationToken?: string;
|
||||
resourceToken?: string;
|
||||
defaultExperience?: DefaultAccountExperienceType;
|
||||
useSDKOperations?: boolean;
|
||||
}
|
||||
|
||||
const userContext: Readonly<UserContext> = {} as const;
|
||||
|
@ -39,7 +39,7 @@ export async function createUpdateCassandraKeyspace(
|
||||
body: Types.CassandraKeyspaceCreateUpdateParameters
|
||||
): Promise<Types.CassandraKeyspaceGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB Cassandra keyspace. */
|
||||
@ -73,7 +73,7 @@ export async function updateCassandraKeyspaceThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB Cassandra Keyspace from manual throughput to autoscale */
|
||||
@ -131,7 +131,7 @@ export async function createUpdateCassandraTable(
|
||||
body: Types.CassandraTableCreateUpdateParameters
|
||||
): Promise<Types.CassandraTableGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/tables/${tableName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB Cassandra table. */
|
||||
@ -168,7 +168,7 @@ export async function updateCassandraTableThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/tables/${tableName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB Cassandra table from manual throughput to autoscale */
|
||||
|
@ -27,13 +27,7 @@ export async function update(
|
||||
body: Types.DatabaseAccountUpdateParameters
|
||||
): Promise<Types.DatabaseAccountGetResults> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`;
|
||||
return armRequest({
|
||||
host: configContext.ARM_ENDPOINT,
|
||||
path,
|
||||
method: "PATCH",
|
||||
apiVersion,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PATCH", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Creates or updates an Azure Cosmos DB database account. The "Update" method is preferred when performing updates on an account. */
|
||||
@ -44,7 +38,7 @@ export async function createOrUpdate(
|
||||
body: Types.DatabaseAccountCreateUpdateParameters
|
||||
): Promise<Types.DatabaseAccountGetResults> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB database account. */
|
||||
@ -61,7 +55,7 @@ export async function failoverPriorityChange(
|
||||
body: Types.FailoverPolicies
|
||||
): Promise<void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/failoverPriorityChange`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Lists all the Azure Cosmos DB database accounts available under the subscription. */
|
||||
@ -107,7 +101,7 @@ export async function offlineRegion(
|
||||
body: Types.RegionForOnlineOffline
|
||||
): Promise<void | Types.ErrorResponse> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/offlineRegion`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Online the specified region for the specified Azure Cosmos DB database account. */
|
||||
@ -118,7 +112,7 @@ export async function onlineRegion(
|
||||
body: Types.RegionForOnlineOffline
|
||||
): Promise<void | Types.ErrorResponse> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/onlineRegion`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Lists the read-only access keys for the specified Azure Cosmos DB database account. */
|
||||
@ -149,7 +143,7 @@ export async function regenerateKey(
|
||||
body: Types.DatabaseAccountRegenerateKeyParameters
|
||||
): Promise<void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/regenerateKey`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Checks that the Azure Cosmos DB account name already exists. A valid account name may contain only lowercase letters, numbers, and the '-' character, and must be between 3 and 50 characters. */
|
||||
|
@ -39,7 +39,7 @@ export async function createUpdateGremlinDatabase(
|
||||
body: Types.GremlinDatabaseCreateUpdateParameters
|
||||
): Promise<Types.GremlinDatabaseGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB Gremlin database. */
|
||||
@ -73,7 +73,7 @@ export async function updateGremlinDatabaseThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB Gremlin database from manual throughput to autoscale */
|
||||
@ -131,7 +131,7 @@ export async function createUpdateGremlinGraph(
|
||||
body: Types.GremlinGraphCreateUpdateParameters
|
||||
): Promise<Types.GremlinGraphGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/graphs/${graphName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB Gremlin graph. */
|
||||
@ -168,7 +168,7 @@ export async function updateGremlinGraphThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/graphs/${graphName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB Gremlin graph from manual throughput to autoscale */
|
||||
|
@ -39,7 +39,7 @@ export async function createUpdateMongoDBDatabase(
|
||||
body: Types.MongoDBDatabaseCreateUpdateParameters
|
||||
): Promise<Types.MongoDBDatabaseGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB MongoDB database. */
|
||||
@ -73,7 +73,7 @@ export async function updateMongoDBDatabaseThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB MongoDB database from manual throughput to autoscale */
|
||||
@ -131,7 +131,7 @@ export async function createUpdateMongoDBCollection(
|
||||
body: Types.MongoDBCollectionCreateUpdateParameters
|
||||
): Promise<Types.MongoDBCollectionGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/collections/${collectionName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB MongoDB Collection. */
|
||||
@ -168,7 +168,7 @@ export async function updateMongoDBCollectionThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/collections/${collectionName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB MongoDB collection from manual throughput to autoscale */
|
||||
|
@ -39,7 +39,7 @@ export async function createUpdateSqlDatabase(
|
||||
body: Types.SqlDatabaseCreateUpdateParameters
|
||||
): Promise<Types.SqlDatabaseGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB SQL database. */
|
||||
@ -73,7 +73,7 @@ export async function updateSqlDatabaseThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB SQL database from manual throughput to autoscale */
|
||||
@ -131,7 +131,7 @@ export async function createUpdateSqlContainer(
|
||||
body: Types.SqlContainerCreateUpdateParameters
|
||||
): Promise<Types.SqlContainerGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB SQL container. */
|
||||
@ -168,7 +168,7 @@ export async function updateSqlContainerThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB SQL container from manual throughput to autoscale */
|
||||
@ -231,7 +231,7 @@ export async function createUpdateSqlStoredProcedure(
|
||||
body: Types.SqlStoredProcedureCreateUpdateParameters
|
||||
): Promise<Types.SqlStoredProcedureGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/storedProcedures/${storedProcedureName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB SQL storedProcedure. */
|
||||
@ -283,7 +283,7 @@ export async function createUpdateSqlUserDefinedFunction(
|
||||
body: Types.SqlUserDefinedFunctionCreateUpdateParameters
|
||||
): Promise<Types.SqlUserDefinedFunctionGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/userDefinedFunctions/${userDefinedFunctionName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB SQL userDefinedFunction. */
|
||||
@ -335,7 +335,7 @@ export async function createUpdateSqlTrigger(
|
||||
body: Types.SqlTriggerCreateUpdateParameters
|
||||
): Promise<Types.SqlTriggerGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/triggers/${triggerName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB SQL trigger. */
|
||||
|
@ -39,7 +39,7 @@ export async function createUpdateTable(
|
||||
body: Types.TableCreateUpdateParameters
|
||||
): Promise<Types.TableGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/tables/${tableName}`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Deletes an existing Azure Cosmos DB Table. */
|
||||
@ -73,7 +73,7 @@ export async function updateTableThroughput(
|
||||
body: Types.ThroughputSettingsUpdateParameters
|
||||
): Promise<Types.ThroughputSettingsGetResults | void> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/tables/${tableName}/throughputSettings/default`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) });
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
|
||||
}
|
||||
|
||||
/* Migrate an Azure Cosmos DB Table from manual throughput to autoscale */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,15 +6,9 @@ Instead, generate ARM clients that consume this function with stricter typing.
|
||||
*/
|
||||
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import { ErrorResponse } from "./generatedClients/2020-04-01/types";
|
||||
import { userContext } from "../../UserContext";
|
||||
|
||||
interface ErrorResponse {
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ARMError extends Error {
|
||||
code: string;
|
||||
}
|
||||
@ -44,8 +38,8 @@ export async function armRequest<T>({ host, path, apiVersion, method, body: requ
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorResponse = (await response.json()) as ErrorResponse;
|
||||
const error = new Error(errorResponse.error?.message) as ARMError;
|
||||
error.code = errorResponse.error.code;
|
||||
const error = new Error(errorResponse.message) as ARMError;
|
||||
error.code = errorResponse.code;
|
||||
throw error;
|
||||
}
|
||||
|
||||
@ -92,8 +86,8 @@ async function getOperationStatus(operationStatusUrl: string) {
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorResponse = (await response.json()) as ErrorResponse;
|
||||
const error = new Error(errorResponse.error?.message) as ARMError;
|
||||
error.code = errorResponse.error.code;
|
||||
const error = new Error(errorResponse.message) as ARMError;
|
||||
error.code = errorResponse.code;
|
||||
throw new AbortError(error);
|
||||
}
|
||||
const body = (await response.json()) as OperationResponse;
|
||||
|
@ -106,6 +106,7 @@
|
||||
click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
|
||||
tabindex="0"
|
||||
aria-label="Refresh tree"
|
||||
title="Refresh tree"
|
||||
>
|
||||
<img
|
||||
class="refreshcol"
|
||||
@ -121,6 +122,7 @@
|
||||
click: toggleLeftPaneExpanded, event: { keypress: toggleLeftPaneExpandedKeyPress }"
|
||||
tabindex="0"
|
||||
aria-label="Collapse Tree"
|
||||
title="Collapse Tree"
|
||||
>
|
||||
<img class="refreshcol1" src="/imgarrowlefticon.svg" alt="Hide" />
|
||||
</span>
|
||||
@ -259,7 +261,7 @@
|
||||
<div class="splashLoaderContentContainer">
|
||||
<p class="connectExplorerContent"><img src="/HdeConnectCosmosDB.svg" alt="Azure Cosmos DB" /></p>
|
||||
<p class="splashLoaderTitle" id="explorerLoadingStatusTitle">Welcome to Azure Cosmos DB</p>
|
||||
<p class="splashLoaderText" id="explorerLoadingStatusText">Connecting...</p>
|
||||
<p class="splashLoaderText" id="explorerLoadingStatusText" role="alert">Connecting...</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Global loader - End -->
|
||||
|
@ -102,31 +102,31 @@ interface Property {
|
||||
}[];
|
||||
}
|
||||
|
||||
const propertyToType = (property: Property, prop: string) => {
|
||||
const propertyToType = (property: Property, prop: string, required: boolean) => {
|
||||
if (property) {
|
||||
if (property.allOf) {
|
||||
outputTypes.push(`
|
||||
/* ${property.description || "undocumented"} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${property.allOf
|
||||
.map((allof: { $ref: string }) => refToType(allof.$ref))
|
||||
.join(" & ")}`);
|
||||
${property.readOnly ? "readonly " : ""}${prop}${
|
||||
required ? "" : "?"
|
||||
}: ${property.allOf.map((allof: { $ref: string }) => refToType(allof.$ref)).join(" & ")}`);
|
||||
} else if (property.$ref) {
|
||||
const type = refToType(property.$ref);
|
||||
outputTypes.push(`
|
||||
/* ${property.description || "undocumented"} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${type}
|
||||
${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}
|
||||
`);
|
||||
} else if (property.type === "array") {
|
||||
const type = refToType(property.items.$ref);
|
||||
outputTypes.push(`
|
||||
/* ${property.description || "undocumented"} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${type}[]
|
||||
${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}[]
|
||||
`);
|
||||
} else if (property.type === "object") {
|
||||
const type = refToType(property.$ref);
|
||||
outputTypes.push(`
|
||||
/* ${property.description || "undocumented"} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${type}
|
||||
${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}
|
||||
`);
|
||||
} else {
|
||||
if (property.type === undefined) {
|
||||
@ -135,7 +135,7 @@ const propertyToType = (property: Property, prop: string) => {
|
||||
}
|
||||
outputTypes.push(`
|
||||
/* ${property.description || "undocumented"} */
|
||||
${property.readOnly ? "readonly " : ""}${prop}: ${
|
||||
${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${
|
||||
propertyMap[property.type] ? propertyMap[property.type] : property.type
|
||||
}`);
|
||||
}
|
||||
@ -166,7 +166,7 @@ async function main() {
|
||||
}
|
||||
for (const prop in schema.definitions[definition].properties) {
|
||||
const property = schema.definitions[definition].properties[prop];
|
||||
propertyToType(property, prop);
|
||||
propertyToType(property, prop, schema.definitions[definition].required?.includes(prop));
|
||||
}
|
||||
outputTypes.push(`}`);
|
||||
outputTypes.push("\n\n");
|
||||
@ -245,7 +245,7 @@ async function main() {
|
||||
) : Promise<${responseType(operation, "Types")}> {
|
||||
const path = \`${path.replace(/{/g, "${")}\`
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "${method.toLocaleUpperCase()}", apiVersion, ${
|
||||
bodyParameter ? "body: JSON.stringify(body)" : ""
|
||||
bodyParameter ? "body" : ""
|
||||
} })
|
||||
}
|
||||
`);
|
||||
|
@ -219,7 +219,7 @@ module.exports = function(env = {}, argv = {}) {
|
||||
// Hack since it is hard to disable watch entirely with webpack dev server https://github.com/webpack/webpack-dev-server/issues/1251#issuecomment-654240734
|
||||
watchOptions: isCI ? { poll: 24 * 60 * 60 * 1000 } : {},
|
||||
devServer: {
|
||||
hot: !isCI,
|
||||
hot: false,
|
||||
inline: !isCI,
|
||||
liveReload: !isCI,
|
||||
https: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user