Merge branch 'master' into generated-clients-strict-compile

This commit is contained in:
Steve Faulkner 2020-09-02 15:49:38 -05:00
commit c4fd56ecb0
55 changed files with 1918 additions and 1338 deletions

View File

@ -41,6 +41,7 @@ module.exports = {
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error", "no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }] "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error"
} }
}; };

View File

@ -7,7 +7,7 @@
"test": "cypress run", "test": "cypress run",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "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: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" "test:debug": "cypress open"
}, },
"devDependencies": { "devDependencies": {

42
package-lock.json generated
View File

@ -5,13 +5,14 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@azure/cosmos": { "@azure/cosmos": {
"version": "3.7.4", "version": "3.9.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.7.4.tgz", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.9.0.tgz",
"integrity": "sha512-IbSEadapQDajSCXj7gUc8OklkOd/oAY4w7XBLHouWc4iKQTtntb2DmGjhrbh2W5Ku+pmBSr1GTApCjQ55iIjlQ==", "integrity": "sha512-SA+QB54I8Dvg/ZolHpsEDLK/sbSB9sFmSU1ElnMTFw88TVik+LYHq4o/srU2TY6Gr1BketjPmgLVEqrmnRvjkw==",
"requires": { "requires": {
"@types/debug": "^4.1.4", "@types/debug": "^4.1.4",
"debug": "^4.1.1", "debug": "^4.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
"jsbi": "^3.1.3",
"node-abort-controller": "^1.0.4", "node-abort-controller": "^1.0.4",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"os-name": "^3.1.0", "os-name": "^3.1.0",
@ -22,14 +23,14 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
}, },
"uuid": { "uuid": {
"version": "8.2.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==" "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
} }
} }
}, },
@ -10132,9 +10133,9 @@
"dev": true "dev": true
}, },
"canvas": { "canvas": {
"version": "2.6.0", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.0.tgz", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
"integrity": "sha512-bEO9f1ThmbknLPxCa8Es7obPlN9W3stB1bo7njlhOFKIdUTldeTqXCh9YclCPAi2pSQs84XA0jq/QEZXSzgyMw==", "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
"requires": { "requires": {
"nan": "^2.14.0", "nan": "^2.14.0",
"node-pre-gyp": "^0.11.0", "node-pre-gyp": "^0.11.0",
@ -20204,6 +20205,11 @@
"esprima": "^4.0.0" "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": { "jsbn": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@ -21534,9 +21540,9 @@
} }
}, },
"needle": { "needle": {
"version": "2.4.1", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
"integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
"requires": { "requires": {
"debug": "^3.2.6", "debug": "^3.2.6",
"iconv-lite": "^0.4.4", "iconv-lite": "^0.4.4",
@ -24630,9 +24636,9 @@
"integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk=" "integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk="
}, },
"simple-concat": { "simple-concat": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
}, },
"simple-get": { "simple-get": {
"version": "3.1.0", "version": "3.1.0",

View File

@ -4,7 +4,7 @@
"description": "Cosmos Explorer", "description": "Cosmos Explorer",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/cosmos": "3.7.4", "@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.4", "@azure/cosmos-language-service": "0.0.4",
"@jupyterlab/services": "4.2.0", "@jupyterlab/services": "4.2.0",
"@jupyterlab/terminal": "1.2.1", "@jupyterlab/terminal": "1.2.1",
@ -42,7 +42,7 @@
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "2.6.0", "canvas": "2.6.1",
"clean-webpack-plugin": "0.1.19", "clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2", "copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2", "crossroads": "0.12.2",

View File

@ -3,8 +3,8 @@
"offerThroughput": 400, "offerThroughput": 400,
"databaseLevelThroughput": false, "databaseLevelThroughput": false,
"collectionId": "Persons", "collectionId": "Persons",
"rupmEnabled": false, "createNewDatabase": true,
"partitionKey": { "kind": "Hash", "paths": ["/firstname"] }, "partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
"data": [ "data": [
{ {
"firstname": "Eva", "firstname": "Eva",
@ -23,4 +23,4 @@
"age": 23 "age": 23
} }
] ]
} }

View File

@ -134,6 +134,7 @@ export class Features {
public static readonly enableAutoPilotV2 = "enableautopilotv2"; public static readonly enableAutoPilotV2 = "enableautopilotv2";
public static readonly ttl90Days = "ttl90days"; public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2"; public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSDKoperations = "enablesdkoperations";
} }
export class AfecFeatures { export class AfecFeatures {

View File

@ -6,19 +6,14 @@ import * as ViewModels from "../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { import {
ConflictDefinition, ConflictDefinition,
ContainerDefinition,
ContainerResponse,
DatabaseResponse,
FeedOptions, FeedOptions,
ItemDefinition, ItemDefinition,
PartitionKeyDefinition,
QueryIterator, QueryIterator,
Resource, Resource,
TriggerDefinition TriggerDefinition,
OfferDefinition
} from "@azure/cosmos"; } from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { client } from "./CosmosClient"; import { client } from "./CosmosClient";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import { sendCachedDataMessage } from "./MessageHandler"; import { sendCachedDataMessage } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
@ -202,23 +197,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
return [partitionKeyValue]; 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( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,
@ -244,7 +222,8 @@ export function updateOffer(
return Q( return Q(
client() client()
.offer(offer.id) .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 => { .then(response => {
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource); return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
}) })
@ -454,6 +433,10 @@ export function readCollectionQuotaInfo(
} }
export function readOffers(options: any): Q.Promise<DataModels.Offer[]> { export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
if (options.isServerless) {
return Q([]); // Reading offers is not supported for serverless accounts
}
try { try {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [ return sendCachedDataMessage<DataModels.Offer[]>(MessageTypes.AllOffers, [
@ -469,6 +452,13 @@ export function readOffers(options: any): Q.Promise<DataModels.Offer[]> {
.offers.readAll() .offers.readAll()
.fetchAll() .fetchAll()
.then(response => response.resources) .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> { export function refreshCachedOffers(): Q.Promise<void> {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
return sendCachedDataMessage(MessageTypes.RefreshOffers, []); return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
@ -598,33 +505,3 @@ export function queryConflicts(
.conflicts.query(query, options); .conflicts.query(query, options);
return Q(documentsIterator); 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);
})
);
}

View File

@ -1,6 +1,5 @@
import * as Constants from "./Constants"; import * as Constants from "./Constants";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ErrorParserUtility from "./ErrorParserUtility";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
@ -266,42 +265,6 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
return deferred.promise; 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( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,
@ -892,70 +855,3 @@ export function readOffer(
return deferred.promise; 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;
}

View File

@ -1,7 +1,6 @@
import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import queryString from "querystring"; import queryString from "querystring";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants";
import * as DataExplorerConstants from "../Common/Constants"; import * as DataExplorerConstants from "../Common/Constants";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
@ -285,43 +284,35 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
} }
export function createMongoCollectionWithProxy( export function createMongoCollectionWithProxy(
databaseId: string, params: DataModels.CreateCollectionParams
collectionId: string,
offerThroughput: number,
shardKey: string,
createDatabase: boolean,
sharedThroughput: boolean,
isSharded: boolean,
autopilotOptions?: DataModels.RpOptions
): Promise<DataModels.Collection> { ): Promise<DataModels.Collection> {
const databaseAccount = userContext.databaseAccount; 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, resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
db: databaseId, db: params.databaseId,
coll: collectionId, coll: params.collectionId,
pk: shardKey, pk: shardKey,
offerThroughput, offerThroughput: params.offerThroughput,
cd: createDatabase, cd: params.createNewDatabase,
st: sharedThroughput, st: params.databaseLevelThroughput,
is: isSharded, is: !!shardKey,
rid: "", rid: "",
rtype: "colls", rtype: "colls",
sid: userContext.subscriptionId, sid: userContext.subscriptionId,
rg: userContext.resourceGroup, rg: userContext.resourceGroup,
dba: databaseAccount.name, 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); const endpoint = getEndpoint(databaseAccount);
return window return window
.fetch( .fetch(
`${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`, `${endpoint}/createCollection?${queryString.stringify(
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
)}`,
{ {
method: "POST", method: "POST",
headers: { headers: {
@ -335,7 +326,7 @@ export function createMongoCollectionWithProxy(
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "creating collection", params); return errorHandling(response, "creating collection", mongoParams);
}); });
} }

View File

@ -10,13 +10,8 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils"; import { QueryUtils } from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
createDocument, import { createCollection } from "./dataAccess/createCollection";
deleteDocument,
getOrCreateDatabaseAndCollection,
queryDocuments,
queryDocumentsPage
} from "./DocumentClientUtilityBase";
import * as ErrorParserUtility from "./ErrorParserUtility"; import * as ErrorParserUtility from "./ErrorParserUtility";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
@ -41,12 +36,13 @@ export class QueriesClient {
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
"Setting up account for saving queries" "Setting up account for saving queries"
); );
return getOrCreateDatabaseAndCollection({ return createCollection({
collectionId: SavedQueries.CollectionName, collectionId: SavedQueries.CollectionName,
createNewDatabase: true,
databaseId: SavedQueries.DatabaseName, databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput, offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: undefined databaseLevelThroughput: false
}) })
.then( .then(
(collection: DataModels.Collection) => { (collection: DataModels.Collection) => {

View 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
}
});
});
});

View 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;
};

View 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
};
}

View File

@ -15,7 +15,7 @@ import { refreshCachedResources } from "../DataAccessUtilityBase";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> { export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`); const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try { try {
if (window.authType === AuthType.AAD) { if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteCollectionWithARM(databaseId, collectionId); await deleteCollectionWithARM(databaseId, collectionId);
} else { } else {
await client() await client()

View File

@ -15,7 +15,11 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`); const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try { try {
if (window.authType === AuthType.AAD) { if (
window.authType === AuthType.AAD &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
!userContext.useSDKOperations
) {
await deleteDatabaseWithARM(databaseId); await deleteDatabaseWithARM(databaseId);
} else { } else {
await client() await client()

View File

@ -16,7 +16,12 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
let collections: DataModels.Collection[]; let collections: DataModels.Collection[];
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`); const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try { try {
if (window.authType === AuthType.AAD) { if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collections = await readCollectionsWithARM(databaseId); collections = await readCollectionsWithARM(databaseId);
} else { } else {
const sdkResponse = await client() const sdkResponse = await client()

View File

@ -15,7 +15,13 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[]; let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
try { 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(); databases = await readDatabasesWithARM();
} else { } else {
const sdkResponse = await client() const sdkResponse = await client()

View 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}`);
}

View File

@ -80,12 +80,20 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
console.error(error); 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); const params = new URLSearchParams(window.location.search);
params.forEach((value, key) => { if (params.has("platform")) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const platform = params.get("platform");
(configContext as any)[key] = value; 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) { } catch (error) {
console.log("No configuration file found using defaults"); console.log("No configuration file found using defaults");
} }

View File

@ -153,7 +153,14 @@ export interface KeyResource {
Token: string; Token: string;
} }
export interface IndexingPolicy {} export interface IndexingPolicy {
automatic: boolean;
indexingMode: string;
includedPaths: any;
excludedPaths: any;
compositeIndexes?: any;
spatialIndexes?: any;
}
export interface PartitionKey { export interface PartitionKey {
paths: string[]; paths: string[];
@ -320,12 +327,24 @@ export interface AutoPilotOfferSettings {
targetMaxThroughput?: number; targetMaxThroughput?: number;
} }
export interface CreateDatabaseRequest { export interface CreateDatabaseParams {
autoPilotMaxThroughput?: number;
databaseId: string; databaseId: string;
databaseLevelThroughput?: boolean; databaseLevelThroughput?: boolean;
offerThroughput?: number; 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 { export interface SharedThroughputRange {

View File

@ -1,4 +1,5 @@
jest.mock("../../Common/DocumentClientUtilityBase"); jest.mock("../../Common/DocumentClientUtilityBase");
jest.mock("../../Common/dataAccess/createCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as sinon from "sinon"; import * as sinon from "sinon";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@ -33,8 +34,8 @@ describe("ContainerSampleGenerator", () => {
databaseId: sampleDatabaseId, databaseId: sampleDatabaseId,
offerThroughput: 400, offerThroughput: 400,
databaseLevelThroughput: false, databaseLevelThroughput: false,
createNewDatabase: true,
collectionId: sampleCollectionId, collectionId: sampleCollectionId,
rupmEnabled: false,
data: [ data: [
{ {
firstname: "Eva", firstname: "Eva",
@ -99,8 +100,8 @@ describe("ContainerSampleGenerator", () => {
databaseId: sampleDatabaseId, databaseId: sampleDatabaseId,
offerThroughput: 400, offerThroughput: 400,
databaseLevelThroughput: false, databaseLevelThroughput: false,
createNewDatabase: true,
collectionId: sampleCollectionId, collectionId: sampleCollectionId,
rupmEnabled: false,
data: [ data: [
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)" "g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
] ]

View File

@ -1,4 +1,3 @@
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import GraphTab from ".././Tabs/GraphTab"; import GraphTab from ".././Tabs/GraphTab";
@ -6,10 +5,11 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer"; 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"; import { userContext } from "../../UserContext";
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest { interface SampleDataFile extends DataModels.CreateCollectionParams {
data: any[]; data: any[];
} }
@ -54,18 +54,11 @@ export class ContainerSampleGenerator {
} }
private async createContainerAsync(): Promise<ViewModels.Collection> { private async createContainerAsync(): Promise<ViewModels.Collection> {
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = { const createRequest: DataModels.CreateCollectionParams = {
...this.sampleDataFile ...this.sampleDataFile
}; };
const options: any = {}; await createCollection(createRequest);
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 this.container.refreshAllDatabases(); await this.container.refreshAllDatabases();
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId); const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
if (!database) { if (!database) {

View File

@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { configContext } from "../ConfigContext"; import { configContext, updateConfigContext } from "../ConfigContext";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
@ -975,6 +975,10 @@ export default class Explorer {
this.sparkClusterConnectionInfo.valueHasMutated(); this.sparkClusterConnectionInfo.valueHasMutated();
} }
if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) {
updateUserContext({ useSDKOperations: true });
}
featureSubcription.dispose(); featureSubcription.dispose();
}); });
@ -1475,38 +1479,33 @@ export default class Explorer {
); );
}; };
if (this.isServerlessEnabled()) { const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers({ isServerless: this.isServerlessEnabled() });
// Serverless accounts don't support offers call this._setLoadingStatusText("Fetching offers...");
refreshDatabases(); offerPromise.then(
} else { (offers: DataModels.Offer[]) => {
const offerPromise: Q.Promise<DataModels.Offer[]> = readOffers(); this._setLoadingStatusText("Successfully fetched offers.");
this._setLoadingStatusText("Fetching offers..."); refreshDatabases(offers);
offerPromise.then( },
(offers: DataModels.Offer[]) => { error => {
this._setLoadingStatusText("Successfully fetched offers."); this._setLoadingStatusText("Failed to fetch offers.");
refreshDatabases(offers); this.isRefreshingExplorer(false);
}, deferred.reject(error);
error => { TelemetryProcessor.traceFailure(
this._setLoadingStatusText("Failed to fetch offers."); Action.LoadDatabases,
this.isRefreshingExplorer(false); {
deferred.reject(error); databaseAccountName: this.databaseAccount().name,
TelemetryProcessor.traceFailure( defaultExperience: this.defaultExperience(),
Action.LoadDatabases, dataExplorerArea: Constants.Areas.ResourceTree,
{ error: JSON.stringify(error)
databaseAccountName: this.databaseAccount().name, },
defaultExperience: this.defaultExperience(), startKey
dataExplorerArea: Constants.Areas.ResourceTree, );
error: JSON.stringify(error) NotificationConsoleUtils.logConsoleMessage(
}, ConsoleDataType.Error,
startKey `Error while refreshing databases: ${JSON.stringify(error)}`
); );
NotificationConsoleUtils.logConsoleMessage( }
ConsoleDataType.Error, );
`Error while refreshing databases: ${JSON.stringify(error)}`
);
}
);
}
return deferred.promise.then( return deferred.promise.then(
() => { () => {
@ -1954,12 +1953,17 @@ export default class Explorer {
this._importExplorerConfigComplete = true; this._importExplorerConfigComplete = true;
updateConfigContext({
ARM_ENDPOINT: this.armEndpoint()
});
updateUserContext({ updateUserContext({
authorizationToken, authorizationToken,
masterKey, masterKey,
databaseAccount databaseAccount,
resourceGroup: inputs.resourceGroup,
subscriptionId: inputs.subscriptionId
}); });
updateUserContext({ resourceGroup: inputs.resourceGroup, subscriptionId: inputs.subscriptionId });
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadDatabaseAccount, Action.LoadDatabaseAccount,
{ {

View File

@ -87,13 +87,31 @@ describe("getPkIdFromDocumentId", () => {
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']"); 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)", () => { it("should create pkid pair from partitioned graph (pk as valid array value)", () => {
const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] }); const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] });
expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']"); expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']");
}); });
it("should error if id is not a string", () => { it("should error if id is not a string or number", () => {
const doc = createFakeDoc({ id: { foo: 1 } }); 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 { try {
GraphExplorer.getPkIdFromDocumentId(doc, undefined); GraphExplorer.getPkIdFromDocumentId(doc, undefined);
expect(true).toBe(false); expect(true).toBe(false);
@ -102,16 +120,8 @@ describe("getPkIdFromDocumentId", () => {
} }
}); });
it("should error if pk not string nor non-empty array", () => { it("should error if pk is empty array", () => {
let doc = createFakeDoc({ mypk: { foo: 1 } }); let doc = createFakeDoc({ mypk: [] });
try {
GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
} catch (e) {
expect(true).toBe(true);
}
doc = createFakeDoc({ mypk: [] });
try { try {
GraphExplorer.getPkIdFromDocumentId(doc, "mypk"); GraphExplorer.getPkIdFromDocumentId(doc, "mypk");
expect(true).toBe(false); expect(true).toBe(false);

View File

@ -1371,7 +1371,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) { if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
let pk = (d as any)[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) { if (Array.isArray(pk) && pk.length > 0) {
// pk is [{ id: 'id', _value: 'value' }] // pk is [{ id: 'id', _value: 'value' }]
pk = pk[0]["_value"]; pk = pk[0]["_value"];

View File

@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
actions.fetchContentFulfilled({ actions.fetchContentFulfilled({
filepath: undefined, filepath: undefined,
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content), 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 contentRef: this.contentRef
}) })
); );

View File

@ -1,13 +1,13 @@
import * as Immutable from "immutable"; import * as Immutable from "immutable";
import { ActionsObservable, StateObservable } from "redux-observable"; import { ActionsObservable, StateObservable } from "redux-observable";
import { Subject } from "rxjs"; import { Subject, empty } from "rxjs";
import { toArray } from "rxjs/operators"; import { toArray } from "rxjs/operators";
import { makeNotebookRecord } from "@nteract/commutable"; import { makeNotebookRecord } from "@nteract/commutable";
import { actions, state } from "@nteract/core"; import { actions, state } from "@nteract/core";
import * as sinon from "sinon"; import * as sinon from "sinon";
import { CdbAppState, makeCdbRecord } from "./types"; import { CdbAppState, makeCdbRecord } from "./types";
import { launchWebSocketKernelEpic } from "./epics"; import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import { sessions } from "rx-jupyter"; 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", () => { describe("launchWebSocketKernelEpic", () => {
const createSpy = sinon.spy(sessions, "create"); const createSpy = sinon.spy(sessions, "create");
const contentRef = "fakeContentRef"; const contentRef = "fakeContentRef";
const kernelRef = "fake"; 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 () => { it("launches remote kernels", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); 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([]);
});
});

View File

@ -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 { webSocket } from "rxjs/webSocket";
import { ActionsObservable, StateObservable } from "redux-observable"; import { ActionsObservable, StateObservable } from "redux-observable";
import { ofType } 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 it's not a notebook, we shouldn't be here
if (!model || model.type !== "notebook") { if (!model || model.type !== "notebook") {
return empty(); return EMPTY;
} }
const cellOrder = selectors.notebook.cellOrder(model); 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 state = state$.value;
const host = selectors.currentHost(state); const host = selectors.currentHost(state);
if (host.type !== "jupyter") { if (host.type !== "jupyter") {
return empty(); return EMPTY;
} }
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host); const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
serverConfig.userPuid = getUserPuid(); serverConfig.userPuid = getUserPuid();
@ -299,7 +332,7 @@ export const launchWebSocketKernelEpic = (
const content = selectors.content(state, { contentRef }); const content = selectors.content(state, { contentRef });
if (!content || content.type !== "notebook") { if (!content || content.type !== "notebook") {
return empty(); return EMPTY;
} }
let kernelSpecToLaunch = kernelSpecName; let kernelSpecToLaunch = kernelSpecName;
@ -513,26 +546,26 @@ const changeWebSocketKernelEpic = (
const state = state$.value; const state = state$.value;
const host = selectors.currentHost(state); const host = selectors.currentHost(state);
if (host.type !== "jupyter") { if (host.type !== "jupyter") {
return empty(); return EMPTY;
} }
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host); const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
if (!oldKernelRef) { if (!oldKernelRef) {
return empty(); return EMPTY;
} }
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef }); const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
if (!oldKernel || oldKernel.type !== "websocket") { if (!oldKernel || oldKernel.type !== "websocket") {
return empty(); return EMPTY;
} }
const { sessionId } = oldKernel; const { sessionId } = oldKernel;
if (!sessionId) { if (!sessionId) {
return empty(); return EMPTY;
} }
const content = selectors.content(state, { contentRef }); const content = selectors.content(state, { contentRef });
if (!content || content.type !== "notebook") { if (!content || content.type !== "notebook") {
return empty(); return EMPTY;
} }
const { const {
filepath, filepath,
@ -593,7 +626,7 @@ const focusInitialCodeCellEpic = (
// If it's not a notebook, we shouldn't be here // If it's not a notebook, we shouldn't be here
if (!model || model.type !== "notebook") { if (!model || model.type !== "notebook") {
return empty(); return EMPTY;
} }
const cellOrder = selectors.notebook.cellOrder(model); const cellOrder = selectors.notebook.cellOrder(model);
@ -608,7 +641,7 @@ const focusInitialCodeCellEpic = (
); );
} }
return empty(); return EMPTY;
}) })
); );
}; };
@ -661,7 +694,7 @@ const notificationsToUserEpic = (
break; break;
} }
} }
return empty(); return EMPTY;
}) })
); );
}; };
@ -701,7 +734,7 @@ const handleKernelConnectionLostEpic = (
if (explorer) { if (explorer) {
explorer.showOkModalDialog("kernel restarts", msg); explorer.showOkModalDialog("kernel restarts", msg);
} }
return of(empty()); return of(EMPTY);
} }
return concat( return concat(
@ -814,7 +847,7 @@ const closeUnsupportedMimetypesEpic = (
explorer.showOkModalDialog("File cannot be rendered", msg); explorer.showOkModalDialog("File cannot be rendered", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
} }
return empty(); return EMPTY;
}) })
); );
}; };
@ -842,13 +875,14 @@ const closeContentFailedToFetchEpic = (
explorer.showOkModalDialog("Failure to load", msg); explorer.showOkModalDialog("Failure to load", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
} }
return empty(); return EMPTY;
}) })
); );
}; };
export const allEpics = [ export const allEpics = [
addInitialCodeCellEpic, addInitialCodeCellEpic,
autoStartKernelEpic,
focusInitialCodeCellEpic, focusInitialCodeCellEpic,
notificationsToUserEpic, notificationsToUserEpic,
launchWebSocketKernelEpic, launchWebSocketKernelEpic,

View File

@ -9,18 +9,15 @@ import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants"; import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import editable from "../../Common/EditableUtility"; import editable from "../../Common/EditableUtility";
import EnvironmentUtility from "../../Common/EnvironmentUtility";
import Q from "q";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { createMongoCollectionWithARM, createMongoCollectionWithProxy } from "../../Common/MongoProxyClient";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent"; import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { HashMap } from "../../Common/HashMap"; import { HashMap } from "../../Common/HashMap";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { refreshCachedResources, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase"; import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
import { userContext } from "../../UserContext"; import { createCollection } from "../../Common/dataAccess/createCollection";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions { export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
isPreferredApiTable: ko.Computed<boolean>; 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 databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId();
let collectionId: string = this.collectionId().trim(); let collectionId: string = this.collectionId().trim();
let rupm: boolean = this.rupm() === Constants.RUPMStates.on;
let indexingPolicy: DataModels.IndexingPolicy; let indexingPolicy: DataModels.IndexingPolicy;
// todo - remove mongo indexing policy ticket # 616274 // todo - remove mongo indexing policy ticket # 616274
@ -828,130 +824,28 @@ export default class AddCollectionPane extends ContextualPaneBase {
} }
this.formErrors(""); this.formErrors("");
this.isExecuting(true); 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, collectionId,
databaseId, databaseId,
databaseLevelThroughput,
offerThroughput, offerThroughput,
databaseLevelThroughput: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(),
rupmEnabled: rupm,
partitionKey,
indexingPolicy,
uniqueKeyPolicy,
autoPilot,
analyticalStorageTtl: this._getAnalyticalStorageTtl(), analyticalStorageTtl: this._getAnalyticalStorageTtl(),
hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag() autoPilotMaxThroughput,
indexingPolicy,
partitionKey,
uniqueKeyPolicy
}; };
const options: any = {}; createCollection(createCollectionParams).then(
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(
() => { () => {
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
@ -1234,35 +1128,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
return undefined; 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 { private _calculateNumberOfPartitions(): number {
// Note: this will not validate properly on accounts that have been set up for custom partitioning, // Note: this will not validate properly on accounts that have been set up for custom partitioning,

View File

@ -14,8 +14,8 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import { AddDbUtilities } from "../../Shared/AddDatabaseUtility"; import { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
import { CassandraAPIDataClient } from "../Tables/TableDataClient"; import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { refreshCachedOffers, refreshCachedResources, createDatabase } from "../../Common/DocumentClientUtilityBase";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export default class AddDatabasePane extends ContextualPaneBase { export default class AddDatabasePane extends ContextualPaneBase {
@ -304,76 +304,23 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.formErrors(""); this.formErrors("");
this.isExecuting(true); this.isExecuting(true);
const createDatabaseParameters: DataModels.RpParameters = { const createDatabaseParams: DataModels.CreateDatabaseParams = {
db: addDatabasePaneStartMessage.database.id, autoPilotMaxThroughput: this.maxAutoPilotThroughputSet(),
st: addDatabasePaneStartMessage.database.shared, databaseId: addDatabasePaneStartMessage.database.id,
offerThroughput: addDatabasePaneStartMessage.offerThroughput, databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
sid: userContext.subscriptionId, offerThroughput: addDatabasePaneStartMessage.offerThroughput
rg: userContext.resourceGroup,
dba: addDatabasePaneStartMessage.databaseAccountName
}; };
const autopilotSettings = this._getAutopilotSettings(); createDatabase(createDatabaseParams).then(
(database: DataModels.Database) => {
if (this.container.isPreferredApiCassandra()) { this._onCreateDatabaseSuccess(offerThroughput, startKey);
this._createKeyspace(createDatabaseParameters, autopilotSettings, startKey); },
} else if (this.container.isPreferredApiMongoDB() && EnvironmentUtility.isAadUser()) { (reason: any) => {
this._createMongoDatabase(createDatabaseParameters, autopilotSettings, startKey); this._onCreateDatabaseFailure(reason, offerThroughput, reason);
} 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);
});
} }
); );
} }
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() { public resetData() {
this.databaseId(""); this.databaseId("");
this.databaseCreateNewShared(this.getSharedThroughputDefault()); this.databaseCreateNewShared(this.getSharedThroughputDefault());
@ -396,72 +343,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
return true; 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 { private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void {
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
@ -582,20 +463,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
return undefined; 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() { private _updateThroughputLimitByDatabase() {
const throughputDefaults = this.container.collectionCreationDefaults.throughput; const throughputDefaults = this.container.collectionCreationDefaults.throughput;
this.throughput(throughputDefaults.shared); this.throughput(throughputDefaults.shared);

View File

@ -16,7 +16,7 @@ export interface GenericRightPaneProps {
onSubmit: () => void; onSubmit: () => void;
submitButtonText: string; submitButtonText: string;
title: string; title: string;
isSubmitButtonVisible?: boolean; isSubmitButtonHidden?: boolean;
} }
export interface GenericRightPaneState { export interface GenericRightPaneState {
@ -108,7 +108,7 @@ export class GenericRightPaneComponent extends React.Component<GenericRightPaneP
<div className="paneFooter"> <div className="paneFooter">
<div className="leftpanel-okbut"> <div className="leftpanel-okbut">
<PrimaryButton <PrimaryButton
style={{ visibility: this.props.isSubmitButtonVisible ? "visible" : "hidden" }} style={{ visibility: this.props.isSubmitButtonHidden ? "hidden" : "visible" }}
ariaLabel="Submit" ariaLabel="Submit"
title="Submit" title="Submit"
onClick={this.props.onSubmit} onClick={this.props.onSubmit}

View File

@ -52,7 +52,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
submitButtonText: "Publish", submitButtonText: "Publish",
onClose: () => this.close(), onClose: () => this.close(),
onSubmit: () => this.submit(), onSubmit: () => this.submit(),
isSubmitButtonVisible: this.isCodeOfConductAccepted isSubmitButtonHidden: !this.isCodeOfConductAccepted
}; };
const publishNotebookPaneProps: PublishNotebookPaneProps = { const publishNotebookPaneProps: PublishNotebookPaneProps = {

View File

@ -285,7 +285,7 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
<GalleryCardComponent <GalleryCardComponent
data={{ data={{
id: undefined, id: undefined,
name: this.props.notebookName, name: this.state.notebookName,
description: this.state.notebookDescription, description: this.state.notebookDescription,
gitSha: undefined, gitSha: undefined,
tags: this.state.notebookTags.split(","), tags: this.state.notebookTags.split(","),

View File

@ -39,7 +39,6 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
formErrorDetail: this.formErrorDetail, formErrorDetail: this.formErrorDetail,
id: "uploaditemspane", id: "uploaditemspane",
isExecuting: this.isExecuting, isExecuting: this.isExecuting,
isSubmitButtonVisible: true,
title: "Upload Items", title: "Upload Items",
submitButtonText: "Upload", submitButtonText: "Upload",
onClose: () => this.close(), onClose: () => this.close(),

View File

@ -45,7 +45,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
tabIndex={0} tabIndex={0}
role="button" role="button"
> >
<img src={item.iconSrc} alt={item.title} /> <img src={item.iconSrc} alt="" />
<div className="legendContainer"> <div className="legendContainer">
<div className="legend">{item.title}</div> <div className="legend">{item.title}</div>
<div className="description">{item.description}</div> <div className="description">{item.description}</div>
@ -66,7 +66,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
tabIndex={0} tabIndex={0}
role="button" role="button"
> >
<img src={item.iconSrc} alt={item.title} /> <img src={item.iconSrc} alt="" />
<span className="oneLineContent" title={item.info}> <span className="oneLineContent" title={item.info}>
{item.title} {item.title}
</span> </span>
@ -79,7 +79,7 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
<ul> <ul>
{this.props.recentItems.map((item: SplashScreenItem, index: number) => ( {this.props.recentItems.map((item: SplashScreenItem, index: number) => (
<li key={`${item.title}${item.description}${index}`}> <li key={`${item.title}${item.description}${index}`}>
<img src={item.iconSrc} alt={item.title} /> <img src={item.iconSrc} alt="" />
<span className="twoLineContent"> <span className="twoLineContent">
<Link onClick={item.onClick} title={item.info}> <Link onClick={item.onClick} title={item.info}>
{item.title} {item.title}

View File

@ -147,6 +147,30 @@ export default class NotebookTabV2 extends TabsBase {
const cellCodeType = "code"; const cellCodeType = "code";
const cellMarkdownType = "markdown"; const cellMarkdownType = "markdown";
const cellRawType = "raw"; 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[] = [ let buttons: CommandButtonComponentProps[] = [
{ {
iconSrc: SaveIcon, iconSrc: SaveIcon,
@ -156,34 +180,17 @@ export default class NotebookTabV2 extends TabsBase {
hasPopup: false, hasPopup: false,
disabled: false, disabled: false,
ariaLabel: saveLabel, ariaLabel: saveLabel,
children: this.container.isGalleryPublishEnabled() children: saveButtonChildren.length && [
? [ {
{ iconName: "Save",
iconName: "Save", onCommandClick: () => this.notebookComponentAdapter.notebookSave(),
onCommandClick: () => this.notebookComponentAdapter.notebookSave(), commandButtonLabel: saveLabel,
commandButtonLabel: saveLabel, hasPopup: false,
hasPopup: false, disabled: false,
disabled: false, ariaLabel: saveLabel
ariaLabel: saveLabel },
}, ...saveButtonChildren
{ ]
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
}, },
{ {
iconSrc: null, iconSrc: null,

View File

@ -7,6 +7,7 @@ import Database from "../Tree/Database";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import SettingsTab from "../Tabs/SettingsTab"; import SettingsTab from "../Tabs/SettingsTab";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { IndexingPolicies } from "../../Shared/Constants";
describe("Settings tab", () => { describe("Settings tab", () => {
const baseCollection: DataModels.Collection = { const baseCollection: DataModels.Collection = {
@ -16,7 +17,7 @@ describe("Settings tab", () => {
mode: DataModels.ConflictResolutionMode.LastWriterWins, mode: DataModels.ConflictResolutionMode.LastWriterWins,
conflictResolutionPath: "/_ts" conflictResolutionPath: "/_ts"
}, },
indexingPolicy: {}, indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
_rid: "", _rid: "",
_self: "", _self: "",
_etag: "", _etag: "",
@ -51,7 +52,7 @@ describe("Settings tab", () => {
defaultTtl: 200, defaultTtl: 200,
partitionKey: null, partitionKey: null,
conflictResolutionPolicy: null, conflictResolutionPolicy: null,
indexingPolicy: {}, indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
_rid: "", _rid: "",
_self: "", _self: "",
_etag: "", _etag: "",
@ -345,7 +346,6 @@ describe("Settings tab", () => {
const offer: DataModels.Offer = null; const offer: DataModels.Offer = null;
const defaultTtl = 200; const defaultTtl = 200;
const indexingPolicy = {};
const database = new Database(explorer, baseDatabase, null); const database = new Database(explorer, baseDatabase, null);
const conflictResolutionPolicy = { const conflictResolutionPolicy = {
mode: DataModels.ConflictResolutionMode.LastWriterWins, mode: DataModels.ConflictResolutionMode.LastWriterWins,
@ -367,7 +367,7 @@ describe("Settings tab", () => {
} }
: null, : null,
conflictResolutionPolicy: conflictResolutionPolicy, conflictResolutionPolicy: conflictResolutionPolicy,
indexingPolicy: indexingPolicy, indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
_rid: "", _rid: "",
_self: "", _self: "",
_etag: "", _etag: "",

View File

@ -17,7 +17,8 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer"; 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 { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit"; import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
@ -1009,8 +1010,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
} }
public onSaveClick = (): Q.Promise<any> => { public onSaveClick = async (): Promise<any> => {
let promises: Q.Promise<void>[] = [];
this.isExecutionError(false); this.isExecutionError(false);
this.isExecuting(true); this.isExecuting(true);
@ -1023,50 +1023,60 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
const newCollectionAttributes: any = {}; const newCollectionAttributes: any = {};
if (this.shouldUpdateCollection()) { try {
let defaultTtl: number; if (this.shouldUpdateCollection()) {
switch (this.timeToLive()) { let defaultTtl: number;
case "on": switch (this.timeToLive()) {
defaultTtl = Number(this.timeToLiveSeconds()); case "on":
break; defaultTtl = Number(this.timeToLiveSeconds());
case "on-nodefault": break;
defaultTtl = -1; case "on-nodefault":
break; defaultTtl = -1;
case "off": break;
default: case "off":
defaultTtl = undefined; default:
break; defaultTtl = undefined;
} break;
}
newCollectionAttributes.defaultTtl = defaultTtl; newCollectionAttributes.defaultTtl = defaultTtl;
newCollectionAttributes.indexingPolicy = this.indexingPolicyContent(); newCollectionAttributes.indexingPolicy = this.indexingPolicyContent();
newCollectionAttributes.changeFeedPolicy = newCollectionAttributes.changeFeedPolicy =
this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On
? ({ ? ({
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration
} as DataModels.ChangeFeedPolicy) } as DataModels.ChangeFeedPolicy)
: undefined;
newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled
? this.analyticalStorageTtlSelection() === "on"
? Number(this.analyticalStorageTtlSeconds())
: Constants.AnalyticalStorageTtl.Infinite
: undefined; : undefined;
newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled newCollectionAttributes.geospatialConfig = {
? this.analyticalStorageTtlSelection() === "on" type: this.geospatialConfigType()
? Number(this.analyticalStorageTtlSeconds()) };
: Constants.AnalyticalStorageTtl.Infinite
: undefined;
newCollectionAttributes.geospatialConfig = { const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
type: this.geospatialConfigType() if (!!conflictResolutionChanges) {
}; newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges;
}
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy(); const newCollection: DataModels.Collection = _.extend(
if (!!conflictResolutionChanges) { {},
newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges; 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); if (updatedCollection) {
const updateCollectionPromise = updateCollection(this.collection.databaseId, this.collection, newCollection).then(
(updatedCollection: DataModels.Collection) => {
this.collection.rawDataModel = updatedCollection; this.collection.rawDataModel = updatedCollection;
this.collection.defaultTtl(updatedCollection.defaultTtl); this.collection.defaultTtl(updatedCollection.defaultTtl);
this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl); 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.changeFeedPolicy(updatedCollection.changeFeedPolicy);
this.collection.geospatialConfig(updatedCollection.geospatialConfig); 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 ( if (
this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && this.throughput.editableIsDirty() ||
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && this.rupm.editableIsDirty() ||
this.container != null this._isAutoPilotDirty() ||
this._hasProvisioningTypeChanged()
) { ) {
const requestPayload = { const newThroughput = this.throughput();
subscriptionId: userContext.subscriptionId, const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
databaseAccountName: userContext.databaseAccount.name, let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
resourceGroup: userContext.resourceGroup, const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
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();
}
);
promises.push(updateOfferPromise); if (newOffer.content) {
} newOffer.content.offerThroughput = newThroughput;
} newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
} else {
if (promises.length === 0) { newOffer = _.extend({}, newOffer, {
this.isExecuting(false); content: {
} offerThroughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
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
);
} }
)
.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> => { public onRevertClick = (): Q.Promise<any> => {

View File

@ -648,7 +648,9 @@ export default class Collection implements ViewModels.Collection {
}); });
// TODO: Use the collection entity cache to get quota info // TODO: Use the collection entity cache to get quota info
const quotaInfoPromise: Q.Promise<DataModels.CollectionQuotaInfo> = readCollectionQuotaInfo(this); 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( Q.all([quotaInfoPromise, offerInfoPromise]).then(
() => { () => {
this.container.isRefreshingExplorer(false); this.container.isRefreshingExplorer(false);
@ -657,9 +659,7 @@ export default class Collection implements ViewModels.Collection {
const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy"); const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy");
const collectionOffer = this._getOfferForCollection(offerInfoPromise.valueOf(), collectionDataModel); const collectionOffer = this._getOfferForCollection(offerInfoPromise.valueOf(), collectionDataModel);
const isDatabaseShared = this.getDatabase() && this.getDatabase().isDatabaseShared(); if (!collectionOffer) {
const isServerless = this.container.isServerlessEnabled();
if ((isDatabaseShared || isServerless) && !collectionOffer) {
this.quotaInfo(quotaInfo); this.quotaInfo(quotaInfo);
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadOffers, Action.LoadOffers,

View File

@ -123,10 +123,6 @@ export default class Database implements ViewModels.Database {
public readSettings(): Q.Promise<void> { public readSettings(): Q.Promise<void> {
const deferred: Q.Deferred<void> = Q.defer<void>(); const deferred: Q.Deferred<void> = Q.defer<void>();
if (this.container.isServerlessEnabled()) {
deferred.resolve();
}
this.container.isRefreshingExplorer(true); this.container.isRefreshingExplorer(true);
const databaseDataModel: DataModels.Database = <DataModels.Database>{ const databaseDataModel: DataModels.Database = <DataModels.Database>{
id: this.id(), id: this.id(),
@ -138,7 +134,9 @@ export default class Database implements ViewModels.Database {
defaultExperience: this.container.defaultExperience() 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( Q.all([offerInfoPromise]).then(
() => { () => {
this.container.isRefreshingExplorer(false); this.container.isRefreshingExplorer(false);
@ -147,6 +145,11 @@ export default class Database implements ViewModels.Database {
offerInfoPromise.valueOf(), offerInfoPromise.valueOf(),
databaseDataModel databaseDataModel
); );
if (!databaseOffer) {
return;
}
readOffer(databaseOffer).then((offerDetail: DataModels.OfferWithHeaders) => { readOffer(databaseOffer).then((offerDetail: DataModels.OfferWithHeaders) => {
const offerThroughputInfo: DataModels.OfferThroughputInfo = { const offerThroughputInfo: DataModels.OfferThroughputInfo = {
minimumRUForCollection: minimumRUForCollection:

View File

@ -546,43 +546,52 @@ export class ResourceTreeAdapter implements ReactAdapter {
(activeTab as any).notebookPath() === item.path (activeTab as any).notebookPath() === item.path
); );
}, },
contextMenu: createFileContextMenu contextMenu: createFileContextMenu && this.createFileContextMenu(item),
? [
{
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,
data: 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) => { private copyNotebook = async (item: NotebookContentItem) => {
const content = await this.container.readFile(item); const content = await this.container.readFile(item);
if (content) { if (content) {

View File

@ -8,6 +8,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { createUpdateCassandraKeyspace } from "../Utils/arm/generatedClients/2020-04-01/cassandraResources";
export class AddDbUtilities { export class AddDbUtilities {
// todo - remove any // todo - remove any
@ -47,7 +48,6 @@ export class AddDbUtilities {
// todo - remove any // todo - remove any
public static async createCassandraKeyspace( public static async createCassandraKeyspace(
armEndpoint: string,
params: DataModels.RpParameters, params: DataModels.RpParameters,
rpOptions: DataModels.RpOptions rpOptions: DataModels.RpOptions
): Promise<any> { ): Promise<any> {
@ -70,9 +70,11 @@ export class AddDbUtilities {
} }
try { try {
await AddDbUtilities.getRpClient<DataModels.CreateDatabaseWithRpResponse>(armEndpoint).putAsync( await createUpdateCassandraKeyspace(
AddDbUtilities._getCassandraKeyspaceUri(params), userContext.subscriptionId,
DataExplorerConstants.ArmApiVersions.publicVersion, userContext.resourceGroup,
userContext.databaseAccount?.name,
params.db,
rpPayloadToCreateKeyspace rpPayloadToCreateKeyspace
); );
} catch (reason) { } catch (reason) {
@ -159,10 +161,7 @@ export class AddDbUtilities {
} }
private static _handleCreationError(reason: any, params: DataModels.RpParameters, dbType: string = "database") { private static _handleCreationError(reason: any, params: DataModels.RpParameters, dbType: string = "database") {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleError(`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`);
ConsoleDataType.Error,
`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`
);
if (reason.status === HttpStatusCodes.Forbidden) { if (reason.status === HttpStatusCodes.Forbidden) {
sendMessage({ type: MessageTypes.ForbiddenError }); sendMessage({ type: MessageTypes.ForbiddenError });
return; return;

View File

@ -114,13 +114,17 @@ export default class TelemetryProcessor {
return validTimestamp; return validTimestamp;
} }
private static getData(data?: any): any { private static getData(data: any = {}): any {
if (typeof data === "string") {
data = { message: data };
}
return { return {
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet // TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
authType: (window as any).authType, authType: (window as any).authType,
subscriptionId: userContext.subscriptionId, subscriptionId: userContext.subscriptionId,
platform: configContext.platform, platform: configContext.platform,
...(data ? data : []) env: process.env.NODE_ENV,
...data
}; };
} }
} }

View File

@ -11,6 +11,7 @@ interface UserContext {
authorizationToken?: string; authorizationToken?: string;
resourceToken?: string; resourceToken?: string;
defaultExperience?: DefaultAccountExperienceType; defaultExperience?: DefaultAccountExperienceType;
useSDKOperations?: boolean;
} }
const userContext: Readonly<UserContext> = {} as const; const userContext: Readonly<UserContext> = {} as const;

View File

@ -39,7 +39,7 @@ export async function createUpdateCassandraKeyspace(
body: Types.CassandraKeyspaceCreateUpdateParameters body: Types.CassandraKeyspaceCreateUpdateParameters
): Promise<Types.CassandraKeyspaceGetResults | void> { ): Promise<Types.CassandraKeyspaceGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}`; 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. */ /* Deletes an existing Azure Cosmos DB Cassandra keyspace. */
@ -73,7 +73,7 @@ export async function updateCassandraKeyspaceThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB Cassandra Keyspace from manual throughput to autoscale */
@ -131,7 +131,7 @@ export async function createUpdateCassandraTable(
body: Types.CassandraTableCreateUpdateParameters body: Types.CassandraTableCreateUpdateParameters
): Promise<Types.CassandraTableGetResults | void> { ): Promise<Types.CassandraTableGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/tables/${tableName}`; 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. */ /* Deletes an existing Azure Cosmos DB Cassandra table. */
@ -168,7 +168,7 @@ export async function updateCassandraTableThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/tables/${tableName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB Cassandra table from manual throughput to autoscale */

View File

@ -27,13 +27,7 @@ export async function update(
body: Types.DatabaseAccountUpdateParameters body: Types.DatabaseAccountUpdateParameters
): Promise<Types.DatabaseAccountGetResults> { ): Promise<Types.DatabaseAccountGetResults> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`;
return armRequest({ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PATCH", apiVersion, body });
host: configContext.ARM_ENDPOINT,
path,
method: "PATCH",
apiVersion,
body: JSON.stringify(body)
});
} }
/* Creates or updates an Azure Cosmos DB database account. The "Update" method is preferred when performing updates on an account. */ /* 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 body: Types.DatabaseAccountCreateUpdateParameters
): Promise<Types.DatabaseAccountGetResults> { ): Promise<Types.DatabaseAccountGetResults> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; 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. */ /* Deletes an existing Azure Cosmos DB database account. */
@ -61,7 +55,7 @@ export async function failoverPriorityChange(
body: Types.FailoverPolicies body: Types.FailoverPolicies
): Promise<void> { ): Promise<void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/failoverPriorityChange`; 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. */ /* Lists all the Azure Cosmos DB database accounts available under the subscription. */
@ -107,7 +101,7 @@ export async function offlineRegion(
body: Types.RegionForOnlineOffline body: Types.RegionForOnlineOffline
): Promise<void | Types.ErrorResponse> { ): Promise<void | Types.ErrorResponse> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/offlineRegion`; 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. */ /* Online the specified region for the specified Azure Cosmos DB database account. */
@ -118,7 +112,7 @@ export async function onlineRegion(
body: Types.RegionForOnlineOffline body: Types.RegionForOnlineOffline
): Promise<void | Types.ErrorResponse> { ): Promise<void | Types.ErrorResponse> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/onlineRegion`; 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. */ /* 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 body: Types.DatabaseAccountRegenerateKeyParameters
): Promise<void> { ): Promise<void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/regenerateKey`; 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. */ /* 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. */

View File

@ -39,7 +39,7 @@ export async function createUpdateGremlinDatabase(
body: Types.GremlinDatabaseCreateUpdateParameters body: Types.GremlinDatabaseCreateUpdateParameters
): Promise<Types.GremlinDatabaseGetResults | void> { ): Promise<Types.GremlinDatabaseGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}`; 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. */ /* Deletes an existing Azure Cosmos DB Gremlin database. */
@ -73,7 +73,7 @@ export async function updateGremlinDatabaseThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB Gremlin database from manual throughput to autoscale */
@ -131,7 +131,7 @@ export async function createUpdateGremlinGraph(
body: Types.GremlinGraphCreateUpdateParameters body: Types.GremlinGraphCreateUpdateParameters
): Promise<Types.GremlinGraphGetResults | void> { ): Promise<Types.GremlinGraphGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/graphs/${graphName}`; 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. */ /* Deletes an existing Azure Cosmos DB Gremlin graph. */
@ -168,7 +168,7 @@ export async function updateGremlinGraphThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/graphs/${graphName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB Gremlin graph from manual throughput to autoscale */

View File

@ -39,7 +39,7 @@ export async function createUpdateMongoDBDatabase(
body: Types.MongoDBDatabaseCreateUpdateParameters body: Types.MongoDBDatabaseCreateUpdateParameters
): Promise<Types.MongoDBDatabaseGetResults | void> { ): Promise<Types.MongoDBDatabaseGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}`; 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. */ /* Deletes an existing Azure Cosmos DB MongoDB database. */
@ -73,7 +73,7 @@ export async function updateMongoDBDatabaseThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB MongoDB database from manual throughput to autoscale */
@ -131,7 +131,7 @@ export async function createUpdateMongoDBCollection(
body: Types.MongoDBCollectionCreateUpdateParameters body: Types.MongoDBCollectionCreateUpdateParameters
): Promise<Types.MongoDBCollectionGetResults | void> { ): Promise<Types.MongoDBCollectionGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/collections/${collectionName}`; 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. */ /* Deletes an existing Azure Cosmos DB MongoDB Collection. */
@ -168,7 +168,7 @@ export async function updateMongoDBCollectionThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/collections/${collectionName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB MongoDB collection from manual throughput to autoscale */

View File

@ -39,7 +39,7 @@ export async function createUpdateSqlDatabase(
body: Types.SqlDatabaseCreateUpdateParameters body: Types.SqlDatabaseCreateUpdateParameters
): Promise<Types.SqlDatabaseGetResults | void> { ): Promise<Types.SqlDatabaseGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}`; 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. */ /* Deletes an existing Azure Cosmos DB SQL database. */
@ -73,7 +73,7 @@ export async function updateSqlDatabaseThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB SQL database from manual throughput to autoscale */
@ -131,7 +131,7 @@ export async function createUpdateSqlContainer(
body: Types.SqlContainerCreateUpdateParameters body: Types.SqlContainerCreateUpdateParameters
): Promise<Types.SqlContainerGetResults | void> { ): Promise<Types.SqlContainerGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}`; 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. */ /* Deletes an existing Azure Cosmos DB SQL container. */
@ -168,7 +168,7 @@ export async function updateSqlContainerThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB SQL container from manual throughput to autoscale */
@ -231,7 +231,7 @@ export async function createUpdateSqlStoredProcedure(
body: Types.SqlStoredProcedureCreateUpdateParameters body: Types.SqlStoredProcedureCreateUpdateParameters
): Promise<Types.SqlStoredProcedureGetResults | void> { ): Promise<Types.SqlStoredProcedureGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/storedProcedures/${storedProcedureName}`; 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. */ /* Deletes an existing Azure Cosmos DB SQL storedProcedure. */
@ -283,7 +283,7 @@ export async function createUpdateSqlUserDefinedFunction(
body: Types.SqlUserDefinedFunctionCreateUpdateParameters body: Types.SqlUserDefinedFunctionCreateUpdateParameters
): Promise<Types.SqlUserDefinedFunctionGetResults | void> { ): Promise<Types.SqlUserDefinedFunctionGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/userDefinedFunctions/${userDefinedFunctionName}`; 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. */ /* Deletes an existing Azure Cosmos DB SQL userDefinedFunction. */
@ -335,7 +335,7 @@ export async function createUpdateSqlTrigger(
body: Types.SqlTriggerCreateUpdateParameters body: Types.SqlTriggerCreateUpdateParameters
): Promise<Types.SqlTriggerGetResults | void> { ): Promise<Types.SqlTriggerGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/triggers/${triggerName}`; 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. */ /* Deletes an existing Azure Cosmos DB SQL trigger. */

View File

@ -39,7 +39,7 @@ export async function createUpdateTable(
body: Types.TableCreateUpdateParameters body: Types.TableCreateUpdateParameters
): Promise<Types.TableGetResults | void> { ): Promise<Types.TableGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/tables/${tableName}`; 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. */ /* Deletes an existing Azure Cosmos DB Table. */
@ -73,7 +73,7 @@ export async function updateTableThroughput(
body: Types.ThroughputSettingsUpdateParameters body: Types.ThroughputSettingsUpdateParameters
): Promise<Types.ThroughputSettingsGetResults | void> { ): Promise<Types.ThroughputSettingsGetResults | void> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/tables/${tableName}/throughputSettings/default`; 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 */ /* Migrate an Azure Cosmos DB Table from manual throughput to autoscale */

File diff suppressed because it is too large Load Diff

View File

@ -6,15 +6,9 @@ Instead, generate ARM clients that consume this function with stricter typing.
*/ */
import promiseRetry, { AbortError } from "p-retry"; import promiseRetry, { AbortError } from "p-retry";
import { ErrorResponse } from "./generatedClients/2020-04-01/types";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
interface ErrorResponse {
error: {
code: string;
message: string;
};
}
interface ARMError extends Error { interface ARMError extends Error {
code: string; code: string;
} }
@ -44,8 +38,8 @@ export async function armRequest<T>({ host, path, apiVersion, method, body: requ
}); });
if (!response.ok) { if (!response.ok) {
const errorResponse = (await response.json()) as ErrorResponse; const errorResponse = (await response.json()) as ErrorResponse;
const error = new Error(errorResponse.error?.message) as ARMError; const error = new Error(errorResponse.message) as ARMError;
error.code = errorResponse.error.code; error.code = errorResponse.code;
throw error; throw error;
} }
@ -92,8 +86,8 @@ async function getOperationStatus(operationStatusUrl: string) {
}); });
if (!response.ok) { if (!response.ok) {
const errorResponse = (await response.json()) as ErrorResponse; const errorResponse = (await response.json()) as ErrorResponse;
const error = new Error(errorResponse.error?.message) as ARMError; const error = new Error(errorResponse.message) as ARMError;
error.code = errorResponse.error.code; error.code = errorResponse.code;
throw new AbortError(error); throw new AbortError(error);
} }
const body = (await response.json()) as OperationResponse; const body = (await response.json()) as OperationResponse;

View File

@ -106,6 +106,7 @@
click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }" click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
tabindex="0" tabindex="0"
aria-label="Refresh tree" aria-label="Refresh tree"
title="Refresh tree"
> >
<img <img
class="refreshcol" class="refreshcol"
@ -121,6 +122,7 @@
click: toggleLeftPaneExpanded, event: { keypress: toggleLeftPaneExpandedKeyPress }" click: toggleLeftPaneExpanded, event: { keypress: toggleLeftPaneExpandedKeyPress }"
tabindex="0" tabindex="0"
aria-label="Collapse Tree" aria-label="Collapse Tree"
title="Collapse Tree"
> >
<img class="refreshcol1" src="/imgarrowlefticon.svg" alt="Hide" /> <img class="refreshcol1" src="/imgarrowlefticon.svg" alt="Hide" />
</span> </span>
@ -259,7 +261,7 @@
<div class="splashLoaderContentContainer"> <div class="splashLoaderContentContainer">
<p class="connectExplorerContent"><img src="/HdeConnectCosmosDB.svg" alt="Azure Cosmos DB" /></p> <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="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>
</div> </div>
<!-- Global loader - End --> <!-- Global loader - End -->

View File

@ -102,31 +102,31 @@ interface Property {
}[]; }[];
} }
const propertyToType = (property: Property, prop: string) => { const propertyToType = (property: Property, prop: string, required: boolean) => {
if (property) { if (property) {
if (property.allOf) { if (property.allOf) {
outputTypes.push(` outputTypes.push(`
/* ${property.description || "undocumented"} */ /* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}: ${property.allOf ${property.readOnly ? "readonly " : ""}${prop}${
.map((allof: { $ref: string }) => refToType(allof.$ref)) required ? "" : "?"
.join(" & ")}`); }: ${property.allOf.map((allof: { $ref: string }) => refToType(allof.$ref)).join(" & ")}`);
} else if (property.$ref) { } else if (property.$ref) {
const type = refToType(property.$ref); const type = refToType(property.$ref);
outputTypes.push(` outputTypes.push(`
/* ${property.description || "undocumented"} */ /* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}: ${type} ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}
`); `);
} else if (property.type === "array") { } else if (property.type === "array") {
const type = refToType(property.items.$ref); const type = refToType(property.items.$ref);
outputTypes.push(` outputTypes.push(`
/* ${property.description || "undocumented"} */ /* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}: ${type}[] ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}[]
`); `);
} else if (property.type === "object") { } else if (property.type === "object") {
const type = refToType(property.$ref); const type = refToType(property.$ref);
outputTypes.push(` outputTypes.push(`
/* ${property.description || "undocumented"} */ /* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}: ${type} ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}
`); `);
} else { } else {
if (property.type === undefined) { if (property.type === undefined) {
@ -135,7 +135,7 @@ const propertyToType = (property: Property, prop: string) => {
} }
outputTypes.push(` outputTypes.push(`
/* ${property.description || "undocumented"} */ /* ${property.description || "undocumented"} */
${property.readOnly ? "readonly " : ""}${prop}: ${ ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${
propertyMap[property.type] ? propertyMap[property.type] : property.type propertyMap[property.type] ? propertyMap[property.type] : property.type
}`); }`);
} }
@ -166,7 +166,7 @@ async function main() {
} }
for (const prop in schema.definitions[definition].properties) { for (const prop in schema.definitions[definition].properties) {
const property = schema.definitions[definition].properties[prop]; const property = schema.definitions[definition].properties[prop];
propertyToType(property, prop); propertyToType(property, prop, schema.definitions[definition].required?.includes(prop));
} }
outputTypes.push(`}`); outputTypes.push(`}`);
outputTypes.push("\n\n"); outputTypes.push("\n\n");
@ -245,7 +245,7 @@ async function main() {
) : Promise<${responseType(operation, "Types")}> { ) : Promise<${responseType(operation, "Types")}> {
const path = \`${path.replace(/{/g, "${")}\` const path = \`${path.replace(/{/g, "${")}\`
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "${method.toLocaleUpperCase()}", apiVersion, ${ return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "${method.toLocaleUpperCase()}", apiVersion, ${
bodyParameter ? "body: JSON.stringify(body)" : "" bodyParameter ? "body" : ""
} }) } })
} }
`); `);

View File

@ -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 // 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 } : {}, watchOptions: isCI ? { poll: 24 * 60 * 60 * 1000 } : {},
devServer: { devServer: {
hot: !isCI, hot: false,
inline: !isCI, inline: !isCI,
liveReload: !isCI, liveReload: !isCI,
https: true, https: true,