diff --git a/.eslintrc.js b/.eslintrc.js index bbc9fcc46..cb7e34f01 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,6 +41,7 @@ module.exports = { "@typescript-eslint/no-extraneous-class": "error", "no-null/no-null": "error", "@typescript-eslint/no-explicit-any": "error", - "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }] + "prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }], + eqeqeq: "error" } }; diff --git a/cypress/package.json b/cypress/package.json index 8b01790ba..cfc442439 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -7,7 +7,7 @@ "test": "cypress run", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"", - "test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless", + "test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless", "test:debug": "cypress open" }, "devDependencies": { diff --git a/package-lock.json b/package-lock.json index 1eadc9cc2..f1f0283ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,14 @@ "requires": true, "dependencies": { "@azure/cosmos": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.7.4.tgz", - "integrity": "sha512-IbSEadapQDajSCXj7gUc8OklkOd/oAY4w7XBLHouWc4iKQTtntb2DmGjhrbh2W5Ku+pmBSr1GTApCjQ55iIjlQ==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.9.0.tgz", + "integrity": "sha512-SA+QB54I8Dvg/ZolHpsEDLK/sbSB9sFmSU1ElnMTFw88TVik+LYHq4o/srU2TY6Gr1BketjPmgLVEqrmnRvjkw==", "requires": { "@types/debug": "^4.1.4", "debug": "^4.1.1", "fast-json-stable-stringify": "^2.0.0", + "jsbi": "^3.1.3", "node-abort-controller": "^1.0.4", "node-fetch": "^2.6.0", "os-name": "^3.1.0", @@ -22,14 +23,14 @@ }, "dependencies": { "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" }, "uuid": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", - "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" } } }, @@ -10132,9 +10133,9 @@ "dev": true }, "canvas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.0.tgz", - "integrity": "sha512-bEO9f1ThmbknLPxCa8Es7obPlN9W3stB1bo7njlhOFKIdUTldeTqXCh9YclCPAi2pSQs84XA0jq/QEZXSzgyMw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz", + "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==", "requires": { "nan": "^2.14.0", "node-pre-gyp": "^0.11.0", @@ -20204,6 +20205,11 @@ "esprima": "^4.0.0" } }, + "jsbi": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.3.tgz", + "integrity": "sha512-nBJqA0C6Qns+ZxurbEoIR56wyjiUszpNy70FHvxO5ervMoCbZVE3z3kxr5nKGhlxr/9MhKTSUBs7cAwwuf3g9w==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -21534,9 +21540,9 @@ } }, "needle": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", - "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", + "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -24630,9 +24636,9 @@ "integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk=" }, "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "simple-get": { "version": "3.1.0", diff --git a/package.json b/package.json index a8cf78f2a..3a222621a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Cosmos Explorer", "main": "index.js", "dependencies": { - "@azure/cosmos": "3.7.4", + "@azure/cosmos": "3.9.0", "@azure/cosmos-language-service": "0.0.4", "@jupyterlab/services": "4.2.0", "@jupyterlab/terminal": "1.2.1", @@ -42,7 +42,7 @@ "applicationinsights": "1.8.0", "babel-polyfill": "6.26.0", "bootstrap": "3.4.1", - "canvas": "2.6.0", + "canvas": "2.6.1", "clean-webpack-plugin": "0.1.19", "copy-webpack-plugin": "6.0.2", "crossroads": "0.12.2", diff --git a/sampleData/sqlSampleData.json b/sampleData/sqlSampleData.json index 62f3b6521..46b607a50 100644 --- a/sampleData/sqlSampleData.json +++ b/sampleData/sqlSampleData.json @@ -3,8 +3,8 @@ "offerThroughput": 400, "databaseLevelThroughput": false, "collectionId": "Persons", - "rupmEnabled": false, - "partitionKey": { "kind": "Hash", "paths": ["/firstname"] }, + "createNewDatabase": true, + "partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 }, "data": [ { "firstname": "Eva", @@ -23,4 +23,4 @@ "age": 23 } ] -} \ No newline at end of file +} diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 4d56c53d9..8458bc0c0 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -134,6 +134,7 @@ export class Features { public static readonly enableAutoPilotV2 = "enableautopilotv2"; public static readonly ttl90Days = "ttl90days"; public static readonly enableRightPanelV2 = "enablerightpanelv2"; + public static readonly enableSDKoperations = "enablesdkoperations"; } export class AfecFeatures { diff --git a/src/Common/DataAccessUtilityBase.ts b/src/Common/DataAccessUtilityBase.ts index 7b6f1633e..51b32e2ec 100644 --- a/src/Common/DataAccessUtilityBase.ts +++ b/src/Common/DataAccessUtilityBase.ts @@ -6,19 +6,14 @@ import * as ViewModels from "../Contracts/ViewModels"; import Q from "q"; import { ConflictDefinition, - ContainerDefinition, - ContainerResponse, - DatabaseResponse, FeedOptions, ItemDefinition, - PartitionKeyDefinition, QueryIterator, Resource, - TriggerDefinition + TriggerDefinition, + OfferDefinition } from "@azure/cosmos"; -import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest"; import { client } from "./CosmosClient"; -import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { sendCachedDataMessage } from "./MessageHandler"; import { MessageTypes } from "../Contracts/ExplorerContracts"; @@ -202,23 +197,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti return [partitionKeyValue]; } -export function updateCollection( - databaseId: string, - collectionId: string, - newCollection: DataModels.Collection, - options: any = {} -): Q.Promise { - return Q( - client() - .database(databaseId) - .container(collectionId) - .replace(newCollection as ContainerDefinition, options) - .then(async (response: ContainerResponse) => { - return refreshCachedResources().then(() => response.resource as DataModels.Collection); - }) - ); -} - export function updateDocument( collection: ViewModels.CollectionBase, documentId: DocumentId, @@ -244,7 +222,8 @@ export function updateOffer( return Q( client() .offer(offer.id) - .replace(newOffer, options) + // TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660) + .replace((newOffer as unknown) as OfferDefinition, options) .then(response => { return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource); }) @@ -454,6 +433,10 @@ export function readCollectionQuotaInfo( } export function readOffers(options: any): Q.Promise { + if (options.isServerless) { + return Q([]); // Reading offers is not supported for serverless accounts + } + try { if (configContext.platform === Platform.Portal) { return sendCachedDataMessage(MessageTypes.AllOffers, [ @@ -469,6 +452,13 @@ export function readOffers(options: any): Q.Promise { .offers.readAll() .fetchAll() .then(response => response.resources) + .catch(error => { + // This should be removed when we can correctly identify if an account is serverless when connected using connection string too. + if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) { + return []; + } + throw error; + }) ); } @@ -487,89 +477,6 @@ export function readOffer(requestedResource: DataModels.Offer, options: any): Q. ); } -export function getOrCreateDatabaseAndCollection( - request: DataModels.CreateDatabaseAndCollectionRequest, - options: any -): Q.Promise { - 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 { - var deferred = Q.defer(); - - _createDatabase(request, options).then( - (createdDatabase: DataModels.Database) => { - refreshCachedOffers().then(() => { - deferred.resolve(createdDatabase); - }); - }, - _createDatabaseError => { - deferred.reject(_createDatabaseError); - } - ); - - return deferred.promise; -} - export function refreshCachedOffers(): Q.Promise { if (configContext.platform === Platform.Portal) { return sendCachedDataMessage(MessageTypes.RefreshOffers, []); @@ -598,33 +505,3 @@ export function queryConflicts( .conflicts.query(query, options); return Q(documentsIterator); } - -function _createDatabase(request: DataModels.CreateDatabaseRequest, options: any = {}): Q.Promise { - 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); - }) - ); -} diff --git a/src/Common/DocumentClientUtilityBase.ts b/src/Common/DocumentClientUtilityBase.ts index 172e4ae9c..2ae5a4d39 100644 --- a/src/Common/DocumentClientUtilityBase.ts +++ b/src/Common/DocumentClientUtilityBase.ts @@ -1,6 +1,5 @@ import * as Constants from "./Constants"; import * as DataModels from "../Contracts/DataModels"; -import * as ErrorParserUtility from "./ErrorParserUtility"; import * as ViewModels from "../Contracts/ViewModels"; import Q from "q"; import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; @@ -266,42 +265,6 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId: return deferred.promise; } -export function updateCollection( - databaseId: string, - collection: ViewModels.Collection, - newCollection: DataModels.Collection -): Q.Promise { - var deferred = Q.defer(); - const id = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Updating container ${collection.id()}` - ); - DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection) - .then( - (replacedCollection: DataModels.Collection) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, - `Successfully updated container ${collection.id()}` - ); - deferred.resolve(replacedCollection); - }, - (error: any) => { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Failed to update container ${collection.id()}: ${JSON.stringify(error)}` - ); - Logger.logError(JSON.stringify(error), "UpdateCollection", error.code); - sendNotificationForError(error); - deferred.reject(error); - } - ) - .finally(() => { - NotificationConsoleUtils.clearInProgressMessageWithId(id); - }); - - return deferred.promise; -} - export function updateDocument( collection: ViewModels.CollectionBase, documentId: DocumentId, @@ -892,70 +855,3 @@ export function readOffer( return deferred.promise; } - -export function getOrCreateDatabaseAndCollection( - request: DataModels.CreateDatabaseAndCollectionRequest, - options: any = {} -): Q.Promise { - const deferred: Q.Deferred = Q.defer(); - 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 { - const deferred: Q.Deferred = Q.defer(); - 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; -} diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index bd4bd864b..bef3c3208 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -1,7 +1,6 @@ import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import queryString from "querystring"; import { AuthType } from "../AuthType"; -import * as Constants from "../Common/Constants"; import * as DataExplorerConstants from "../Common/Constants"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; @@ -285,43 +284,35 @@ export function deleteDocument(databaseId: string, collection: Collection, docum } export function createMongoCollectionWithProxy( - databaseId: string, - collectionId: string, - offerThroughput: number, - shardKey: string, - createDatabase: boolean, - sharedThroughput: boolean, - isSharded: boolean, - autopilotOptions?: DataModels.RpOptions + params: DataModels.CreateCollectionParams ): Promise { const databaseAccount = userContext.databaseAccount; - const params: DataModels.MongoParameters = { + const shardKey: string = params.partitionKey?.paths[0]; + const mongoParams: DataModels.MongoParameters = { resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, - db: databaseId, - coll: collectionId, + db: params.databaseId, + coll: params.collectionId, pk: shardKey, - offerThroughput, - cd: createDatabase, - st: sharedThroughput, - is: isSharded, + offerThroughput: params.offerThroughput, + cd: params.createNewDatabase, + st: params.databaseLevelThroughput, + is: !!shardKey, rid: "", rtype: "colls", sid: userContext.subscriptionId, rg: userContext.resourceGroup, dba: databaseAccount.name, - isAutoPilot: false + isAutoPilot: !!params.autoPilotMaxThroughput, + autoPilotThroughput: params.autoPilotMaxThroughput?.toString() }; - if (autopilotOptions) { - params.isAutoPilot = true; - params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string; - } - const endpoint = getEndpoint(databaseAccount); return window .fetch( - `${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`, + `${endpoint}/createCollection?${queryString.stringify( + (mongoParams as unknown) as queryString.ParsedUrlQueryInput + )}`, { method: "POST", headers: { @@ -335,7 +326,7 @@ export function createMongoCollectionWithProxy( if (response.ok) { return response.json(); } - return errorHandling(response, "creating collection", params); + return errorHandling(response, "creating collection", mongoParams); }); } diff --git a/src/Common/QueriesClient.ts b/src/Common/QueriesClient.ts index 27b97be8e..e15fff5ce 100644 --- a/src/Common/QueriesClient.ts +++ b/src/Common/QueriesClient.ts @@ -10,13 +10,8 @@ import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { QueryUtils } from "../Utils/QueryUtils"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { userContext } from "../UserContext"; -import { - createDocument, - deleteDocument, - getOrCreateDatabaseAndCollection, - queryDocuments, - queryDocumentsPage -} from "./DocumentClientUtilityBase"; +import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase"; +import { createCollection } from "./dataAccess/createCollection"; import * as ErrorParserUtility from "./ErrorParserUtility"; import * as Logger from "./Logger"; @@ -41,12 +36,13 @@ export class QueriesClient { ConsoleDataType.InProgress, "Setting up account for saving queries" ); - return getOrCreateDatabaseAndCollection({ + return createCollection({ collectionId: SavedQueries.CollectionName, + createNewDatabase: true, databaseId: SavedQueries.DatabaseName, partitionKey: QueriesClient.PartitionKey, offerThroughput: SavedQueries.OfferThroughput, - databaseLevelThroughput: undefined + databaseLevelThroughput: false }) .then( (collection: DataModels.Collection) => { diff --git a/src/Common/dataAccess/createCollection.test.ts b/src/Common/dataAccess/createCollection.test.ts new file mode 100644 index 000000000..79902e72f --- /dev/null +++ b/src/Common/dataAccess/createCollection.test.ts @@ -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 + } + }); + }); +}); diff --git a/src/Common/dataAccess/createCollection.ts b/src/Common/dataAccess/createCollection.ts new file mode 100644 index 000000000..204caba25 --- /dev/null +++ b/src/Common/dataAccess/createCollection.ts @@ -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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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; +}; diff --git a/src/Common/dataAccess/createDatabase.ts b/src/Common/dataAccess/createDatabase.ts new file mode 100644 index 000000000..785e69681 --- /dev/null +++ b/src/Common/dataAccess/createDatabase.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 + }; +} diff --git a/src/Common/dataAccess/deleteCollection.ts b/src/Common/dataAccess/deleteCollection.ts index 9be2fcce0..75d282fce 100644 --- a/src/Common/dataAccess/deleteCollection.ts +++ b/src/Common/dataAccess/deleteCollection.ts @@ -15,7 +15,7 @@ import { refreshCachedResources } from "../DataAccessUtilityBase"; export async function deleteCollection(databaseId: string, collectionId: string): Promise { const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`); try { - if (window.authType === AuthType.AAD) { + if (window.authType === AuthType.AAD && !userContext.useSDKOperations) { await deleteCollectionWithARM(databaseId, collectionId); } else { await client() diff --git a/src/Common/dataAccess/deleteDatabase.ts b/src/Common/dataAccess/deleteDatabase.ts index a44a2d5bb..4a2767d0f 100644 --- a/src/Common/dataAccess/deleteDatabase.ts +++ b/src/Common/dataAccess/deleteDatabase.ts @@ -15,7 +15,11 @@ export async function deleteDatabase(databaseId: string): Promise { const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`); try { - if (window.authType === AuthType.AAD) { + if ( + window.authType === AuthType.AAD && + userContext.defaultExperience !== DefaultAccountExperienceType.Table && + !userContext.useSDKOperations + ) { await deleteDatabaseWithARM(databaseId); } else { await client() diff --git a/src/Common/dataAccess/readCollections.ts b/src/Common/dataAccess/readCollections.ts index 2bc51599f..6ebca7b13 100644 --- a/src/Common/dataAccess/readCollections.ts +++ b/src/Common/dataAccess/readCollections.ts @@ -16,7 +16,12 @@ export async function readCollections(databaseId: string): Promise { let databases: DataModels.Database[]; const clearMessage = logConsoleProgress(`Querying databases`); try { - if (window.authType === AuthType.AAD) { + if ( + window.authType === AuthType.AAD && + !userContext.useSDKOperations && + userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB && + userContext.defaultExperience !== DefaultAccountExperienceType.Table && + userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra + ) { databases = await readDatabasesWithARM(); } else { const sdkResponse = await client() diff --git a/src/Common/dataAccess/updateCollection.ts b/src/Common/dataAccess/updateCollection.ts new file mode 100644 index 000000000..794c10003 --- /dev/null +++ b/src/Common/dataAccess/updateCollection.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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}`); +} diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index da4abb0aa..afa40f310 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -80,12 +80,20 @@ export async function initializeConfiguration(): Promise { console.error(error); } } - // Allow override of any config value with URL query parameters + // Allow override of platform value with URL query parameter const params = new URLSearchParams(window.location.search); - params.forEach((value, key) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (configContext as any)[key] = value; - }); + if (params.has("platform")) { + const platform = params.get("platform"); + switch (platform) { + default: + console.log("Invalid platform query parameter given, ignoring"); + break; + case Platform.Portal: + case Platform.Hosted: + case Platform.Emulator: + updateConfigContext({ platform }); + } + } } catch (error) { console.log("No configuration file found using defaults"); } diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index fa8a77a69..0a6d13970 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -153,7 +153,14 @@ export interface KeyResource { Token: string; } -export interface IndexingPolicy {} +export interface IndexingPolicy { + automatic: boolean; + indexingMode: string; + includedPaths: any; + excludedPaths: any; + compositeIndexes?: any; + spatialIndexes?: any; +} export interface PartitionKey { paths: string[]; @@ -320,12 +327,24 @@ export interface AutoPilotOfferSettings { targetMaxThroughput?: number; } -export interface CreateDatabaseRequest { +export interface CreateDatabaseParams { + autoPilotMaxThroughput?: number; databaseId: string; databaseLevelThroughput?: boolean; offerThroughput?: number; - autoPilot?: AutoPilotCreationSettings; - hasAutoPilotV2FeatureFlag?: boolean; +} + +export interface CreateCollectionParams { + createNewDatabase: boolean; + collectionId: string; + databaseId: string; + databaseLevelThroughput: boolean; + offerThroughput: number; + analyticalStorageTtl?: number; + autoPilotMaxThroughput?: number; + indexingPolicy?: IndexingPolicy; + partitionKey?: PartitionKey; + uniqueKeyPolicy?: UniqueKeyPolicy; } export interface SharedThroughputRange { diff --git a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts index cd9044a09..47141224a 100644 --- a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts +++ b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts @@ -1,4 +1,5 @@ jest.mock("../../Common/DocumentClientUtilityBase"); +jest.mock("../../Common/dataAccess/createCollection"); import * as ko from "knockout"; import * as sinon from "sinon"; import * as ViewModels from "../../Contracts/ViewModels"; @@ -33,8 +34,8 @@ describe("ContainerSampleGenerator", () => { databaseId: sampleDatabaseId, offerThroughput: 400, databaseLevelThroughput: false, + createNewDatabase: true, collectionId: sampleCollectionId, - rupmEnabled: false, data: [ { firstname: "Eva", @@ -99,8 +100,8 @@ describe("ContainerSampleGenerator", () => { databaseId: sampleDatabaseId, offerThroughput: 400, databaseLevelThroughput: false, + createNewDatabase: true, collectionId: sampleCollectionId, - rupmEnabled: false, data: [ "g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)" ] diff --git a/src/Explorer/DataSamples/ContainerSampleGenerator.ts b/src/Explorer/DataSamples/ContainerSampleGenerator.ts index 4a4d35008..aad91e6a1 100644 --- a/src/Explorer/DataSamples/ContainerSampleGenerator.ts +++ b/src/Explorer/DataSamples/ContainerSampleGenerator.ts @@ -1,4 +1,3 @@ -import * as Constants from "../../Common/Constants"; import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; import GraphTab from ".././Tabs/GraphTab"; @@ -6,10 +5,11 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import Explorer from "../Explorer"; -import { createDocument, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase"; +import { createDocument } from "../../Common/DocumentClientUtilityBase"; +import { createCollection } from "../../Common/dataAccess/createCollection"; import { userContext } from "../../UserContext"; -interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest { +interface SampleDataFile extends DataModels.CreateCollectionParams { data: any[]; } @@ -54,18 +54,11 @@ export class ContainerSampleGenerator { } private async createContainerAsync(): Promise { - const createRequest: DataModels.CreateDatabaseAndCollectionRequest = { + const createRequest: DataModels.CreateCollectionParams = { ...this.sampleDataFile }; - const options: any = {}; - if (this.container.isPreferredApiMongoDB()) { - options.initialHeaders = options.initialHeaders || {}; - options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true; - options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true; - } - - await getOrCreateDatabaseAndCollection(createRequest, options); + await createCollection(createRequest); await this.container.refreshAllDatabases(); const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId); if (!database) { diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 377d31cb7..3b7810e79 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; -import { configContext } from "../ConfigContext"; +import { configContext, updateConfigContext } from "../ConfigContext"; import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; @@ -975,6 +975,10 @@ export default class Explorer { this.sparkClusterConnectionInfo.valueHasMutated(); } + if (this.isFeatureEnabled(Constants.Features.enableSDKoperations)) { + updateUserContext({ useSDKOperations: true }); + } + featureSubcription.dispose(); }); @@ -1475,38 +1479,33 @@ export default class Explorer { ); }; - if (this.isServerlessEnabled()) { - // Serverless accounts don't support offers call - refreshDatabases(); - } else { - const offerPromise: Q.Promise = readOffers(); - this._setLoadingStatusText("Fetching offers..."); - offerPromise.then( - (offers: DataModels.Offer[]) => { - this._setLoadingStatusText("Successfully fetched offers."); - refreshDatabases(offers); - }, - error => { - this._setLoadingStatusText("Failed to fetch offers."); - this.isRefreshingExplorer(false); - deferred.reject(error); - TelemetryProcessor.traceFailure( - Action.LoadDatabases, - { - databaseAccountName: this.databaseAccount().name, - defaultExperience: this.defaultExperience(), - dataExplorerArea: Constants.Areas.ResourceTree, - error: JSON.stringify(error) - }, - startKey - ); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error while refreshing databases: ${JSON.stringify(error)}` - ); - } - ); - } + const offerPromise: Q.Promise = readOffers({ isServerless: this.isServerlessEnabled() }); + this._setLoadingStatusText("Fetching offers..."); + offerPromise.then( + (offers: DataModels.Offer[]) => { + this._setLoadingStatusText("Successfully fetched offers."); + refreshDatabases(offers); + }, + error => { + this._setLoadingStatusText("Failed to fetch offers."); + this.isRefreshingExplorer(false); + deferred.reject(error); + TelemetryProcessor.traceFailure( + Action.LoadDatabases, + { + databaseAccountName: this.databaseAccount().name, + defaultExperience: this.defaultExperience(), + dataExplorerArea: Constants.Areas.ResourceTree, + error: JSON.stringify(error) + }, + startKey + ); + NotificationConsoleUtils.logConsoleMessage( + ConsoleDataType.Error, + `Error while refreshing databases: ${JSON.stringify(error)}` + ); + } + ); return deferred.promise.then( () => { @@ -1954,12 +1953,17 @@ export default class Explorer { this._importExplorerConfigComplete = true; + updateConfigContext({ + ARM_ENDPOINT: this.armEndpoint() + }); + updateUserContext({ authorizationToken, masterKey, - databaseAccount + databaseAccount, + resourceGroup: inputs.resourceGroup, + subscriptionId: inputs.subscriptionId }); - updateUserContext({ resourceGroup: inputs.resourceGroup, subscriptionId: inputs.subscriptionId }); TelemetryProcessor.traceSuccess( Action.LoadDatabaseAccount, { diff --git a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx index c0c1fcf2b..7f98595f8 100644 --- a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx +++ b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx @@ -87,13 +87,31 @@ describe("getPkIdFromDocumentId", () => { expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']"); }); + it("should create pkid pair from partitioned graph (pk as number)", () => { + const doc = createFakeDoc({ id: "id", mypk: 234 }); + expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[234, 'id']"); + }); + + it("should create pkid pair from partitioned graph (pk as boolean)", () => { + const doc = createFakeDoc({ id: "id", mypk: true }); + expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("[true, 'id']"); + }); + it("should create pkid pair from partitioned graph (pk as valid array value)", () => { const doc = createFakeDoc({ id: "id", mypk: [{ id: "someid", _value: "pkvalue" }] }); expect(GraphExplorer.getPkIdFromDocumentId(doc, "mypk")).toEqual("['pkvalue', 'id']"); }); - it("should error if id is not a string", () => { - const doc = createFakeDoc({ id: { foo: 1 } }); + it("should error if id is not a string or number", () => { + let doc = createFakeDoc({ id: { foo: 1 } }); + try { + GraphExplorer.getPkIdFromDocumentId(doc, undefined); + expect(true).toBe(false); + } catch (e) { + expect(true).toBe(true); + } + + doc = createFakeDoc({ id: true }); try { GraphExplorer.getPkIdFromDocumentId(doc, undefined); expect(true).toBe(false); @@ -102,16 +120,8 @@ describe("getPkIdFromDocumentId", () => { } }); - it("should error if pk not string nor non-empty array", () => { - let doc = createFakeDoc({ mypk: { foo: 1 } }); - - try { - GraphExplorer.getPkIdFromDocumentId(doc, "mypk"); - } catch (e) { - expect(true).toBe(true); - } - - doc = createFakeDoc({ mypk: [] }); + it("should error if pk is empty array", () => { + let doc = createFakeDoc({ mypk: [] }); try { GraphExplorer.getPkIdFromDocumentId(doc, "mypk"); expect(true).toBe(false); diff --git a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx index cd4a3e58d..aa939ef5e 100644 --- a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx +++ b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx @@ -1371,7 +1371,7 @@ export class GraphExplorer extends React.Component 0) { // pk is [{ id: 'id', _value: 'value' }] pk = pk[0]["_value"]; diff --git a/src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx b/src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx index dfc64b161..f3933e8e4 100644 --- a/src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx +++ b/src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx @@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper { actions.fetchContentFulfilled({ filepath: undefined, model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content), - kernelRef: createKernelRef(), + kernelRef: undefined, // must be undefined or it will be auto-started by the epic contentRef: this.contentRef }) ); diff --git a/src/Explorer/Notebook/NotebookComponent/epics.test.ts b/src/Explorer/Notebook/NotebookComponent/epics.test.ts index ec6e95a47..5c95e563d 100644 --- a/src/Explorer/Notebook/NotebookComponent/epics.test.ts +++ b/src/Explorer/Notebook/NotebookComponent/epics.test.ts @@ -1,13 +1,13 @@ import * as Immutable from "immutable"; import { ActionsObservable, StateObservable } from "redux-observable"; -import { Subject } from "rxjs"; +import { Subject, empty } from "rxjs"; import { toArray } from "rxjs/operators"; import { makeNotebookRecord } from "@nteract/commutable"; import { actions, state } from "@nteract/core"; import * as sinon from "sinon"; import { CdbAppState, makeCdbRecord } from "./types"; -import { launchWebSocketKernelEpic } from "./epics"; +import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics"; import { NotebookUtil } from "../NotebookUtil"; import { sessions } from "rx-jupyter"; @@ -74,46 +74,47 @@ describe("Extract kernel from notebook", () => { }); }); +const initialState = { + app: state.makeAppRecord({ + host: state.makeJupyterHostRecord({ + type: "jupyter", + token: "eh", + basePath: "/" + }) + }), + comms: state.makeCommsRecord(), + config: Immutable.Map({}), + core: state.makeStateRecord({ + kernelRef: "fake", + entities: state.makeEntitiesRecord({ + contents: state.makeContentsRecord({ + byRef: Immutable.Map({ + fakeContentRef: state.makeNotebookContentRecord() + }) + }), + kernels: state.makeKernelsRecord({ + byRef: Immutable.Map({ + fake: state.makeRemoteKernelRecord({ + type: "websocket", + channels: new Subject(), + kernelSpecName: "fancy", + id: "0" + }) + }) + }) + }) + }), + cdb: makeCdbRecord({ + databaseAccountName: "dbAccountName", + defaultExperience: "defaultExperience" + }) +}; + describe("launchWebSocketKernelEpic", () => { const createSpy = sinon.spy(sessions, "create"); const contentRef = "fakeContentRef"; const kernelRef = "fake"; - const initialState = { - app: state.makeAppRecord({ - host: state.makeJupyterHostRecord({ - type: "jupyter", - token: "eh", - basePath: "/" - }) - }), - comms: state.makeCommsRecord(), - config: Immutable.Map({}), - core: state.makeStateRecord({ - kernelRef: "fake", - entities: state.makeEntitiesRecord({ - contents: state.makeContentsRecord({ - byRef: Immutable.Map({ - fakeContentRef: state.makeNotebookContentRecord() - }) - }), - kernels: state.makeKernelsRecord({ - byRef: Immutable.Map({ - fake: state.makeRemoteKernelRecord({ - type: "websocket", - channels: new Subject(), - kernelSpecName: "fancy", - id: "0" - }) - }) - }) - }) - }), - cdb: makeCdbRecord({ - databaseAccountName: "dbAccountName", - defaultExperience: "defaultExperience" - }) - }; it("launches remote kernels", async () => { const state$ = new StateObservable(new Subject(), 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(), 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(), 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([]); + }); +}); diff --git a/src/Explorer/Notebook/NotebookComponent/epics.ts b/src/Explorer/Notebook/NotebookComponent/epics.ts index 84b5a78dc..f65677591 100644 --- a/src/Explorer/Notebook/NotebookComponent/epics.ts +++ b/src/Explorer/Notebook/NotebookComponent/epics.ts @@ -1,4 +1,4 @@ -import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs"; +import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs"; import { webSocket } from "rxjs/webSocket"; import { ActionsObservable, StateObservable } from "redux-observable"; import { ofType } from "redux-observable"; @@ -77,7 +77,7 @@ const addInitialCodeCellEpic = ( // If it's not a notebook, we shouldn't be here if (!model || model.type !== "notebook") { - return empty(); + return EMPTY; } const cellOrder = selectors.notebook.cellOrder(model); @@ -90,7 +90,40 @@ const addInitialCodeCellEpic = ( ); } - return empty(); + return EMPTY; + }) + ); +}; + +/** + * Automatically start kernel if kernelRef is present. + * The kernel is normally lazy-started when a cell is being executed, but a running kernel is + * required for code completion to work. + * For notebook viewer, there is no kernel + * @param action$ + * @param state$ + */ +export const autoStartKernelEpic = ( + action$: ActionsObservable, + state$: StateObservable +): Observable<{} | actions.CreateCellBelow> => { + return action$.pipe( + ofType(actions.FETCH_CONTENT_FULFILLED), + mergeMap(action => { + const state = state$.value; + const { contentRef, kernelRef } = action.payload; + + if (!kernelRef) { + return EMPTY; + } + + return of( + actions.restartKernel({ + contentRef, + kernelRef, + outputHandling: "None" + }) + ); }) ); }; @@ -288,7 +321,7 @@ export const launchWebSocketKernelEpic = ( const state = state$.value; const host = selectors.currentHost(state); if (host.type !== "jupyter") { - return empty(); + return EMPTY; } const serverConfig: NotebookServiceConfig = selectors.serverConfig(host); serverConfig.userPuid = getUserPuid(); @@ -299,7 +332,7 @@ export const launchWebSocketKernelEpic = ( const content = selectors.content(state, { contentRef }); if (!content || content.type !== "notebook") { - return empty(); + return EMPTY; } let kernelSpecToLaunch = kernelSpecName; @@ -513,26 +546,26 @@ const changeWebSocketKernelEpic = ( const state = state$.value; const host = selectors.currentHost(state); if (host.type !== "jupyter") { - return empty(); + return EMPTY; } const serverConfig: NotebookServiceConfig = selectors.serverConfig(host); if (!oldKernelRef) { - return empty(); + return EMPTY; } const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef }); if (!oldKernel || oldKernel.type !== "websocket") { - return empty(); + return EMPTY; } const { sessionId } = oldKernel; if (!sessionId) { - return empty(); + return EMPTY; } const content = selectors.content(state, { contentRef }); if (!content || content.type !== "notebook") { - return empty(); + return EMPTY; } const { filepath, @@ -593,7 +626,7 @@ const focusInitialCodeCellEpic = ( // If it's not a notebook, we shouldn't be here if (!model || model.type !== "notebook") { - return empty(); + return EMPTY; } const cellOrder = selectors.notebook.cellOrder(model); @@ -608,7 +641,7 @@ const focusInitialCodeCellEpic = ( ); } - return empty(); + return EMPTY; }) ); }; @@ -661,7 +694,7 @@ const notificationsToUserEpic = ( break; } } - return empty(); + return EMPTY; }) ); }; @@ -701,7 +734,7 @@ const handleKernelConnectionLostEpic = ( if (explorer) { explorer.showOkModalDialog("kernel restarts", msg); } - return of(empty()); + return of(EMPTY); } return concat( @@ -814,7 +847,7 @@ const closeUnsupportedMimetypesEpic = ( explorer.showOkModalDialog("File cannot be rendered", msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); } - return empty(); + return EMPTY; }) ); }; @@ -842,13 +875,14 @@ const closeContentFailedToFetchEpic = ( explorer.showOkModalDialog("Failure to load", msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); } - return empty(); + return EMPTY; }) ); }; export const allEpics = [ addInitialCodeCellEpic, + autoStartKernelEpic, focusInitialCodeCellEpic, notificationsToUserEpic, launchWebSocketKernelEpic, diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index 458831769..dd520949c 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -9,18 +9,15 @@ import * as PricingUtils from "../../Utils/PricingUtils"; import * as SharedConstants from "../../Shared/Constants"; import * as ViewModels from "../../Contracts/ViewModels"; import editable from "../../Common/EditableUtility"; -import EnvironmentUtility from "../../Common/EnvironmentUtility"; -import Q from "q"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { configContext, Platform } from "../../ConfigContext"; import { ContextualPaneBase } from "./ContextualPaneBase"; -import { createMongoCollectionWithARM, createMongoCollectionWithProxy } from "../../Common/MongoProxyClient"; import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent"; import { HashMap } from "../../Common/HashMap"; import { PlatformType } from "../../PlatformType"; -import { refreshCachedResources, getOrCreateDatabaseAndCollection } from "../../Common/DocumentClientUtilityBase"; -import { userContext } from "../../UserContext"; +import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase"; +import { createCollection } from "../../Common/dataAccess/createCollection"; export interface AddCollectionPaneOptions extends ViewModels.PaneOptions { isPreferredApiTable: ko.Computed; @@ -811,7 +808,6 @@ export default class AddCollectionPane extends ContextualPaneBase { let databaseId: string = this.databaseCreateNew() ? this.databaseId().trim() : this.databaseId(); let collectionId: string = this.collectionId().trim(); - let rupm: boolean = this.rupm() === Constants.RUPMStates.on; let indexingPolicy: DataModels.IndexingPolicy; // todo - remove mongo indexing policy ticket # 616274 @@ -828,130 +824,28 @@ export default class AddCollectionPane extends ContextualPaneBase { } this.formErrors(""); - this.isExecuting(true); - const createRequest: DataModels.CreateDatabaseAndCollectionRequest = { + const databaseLevelThroughput: boolean = this.databaseCreateNew() + ? this.databaseCreateNewShared() + : this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(); + const autoPilotMaxThroughput: number = databaseLevelThroughput + ? this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput() + : this.isAutoPilotSelected() && this.autoPilotThroughput(); + const createCollectionParams: DataModels.CreateCollectionParams = { + createNewDatabase: this.databaseCreateNew(), collectionId, databaseId, + databaseLevelThroughput, offerThroughput, - databaseLevelThroughput: this.databaseHasSharedOffer() && !this.collectionWithThroughputInShared(), - rupmEnabled: rupm, - partitionKey, - indexingPolicy, - uniqueKeyPolicy, - autoPilot, analyticalStorageTtl: this._getAnalyticalStorageTtl(), - hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag() + autoPilotMaxThroughput, + indexingPolicy, + partitionKey, + uniqueKeyPolicy }; - const options: any = {}; - if (this.container.isPreferredApiMongoDB()) { - options.initialHeaders = options.initialHeaders || {}; - options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true; - options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true; - } - - const databaseCreateNew = this.databaseCreateNew(); - const useDatabaseSharedOffer = this.shouldUseDatabaseThroughput(); - const isSharded: boolean = !!partitionKeyPath; - const autopilotSettings: DataModels.RpOptions = this._getAutopilotSettings(); - - let createCollectionFunc: () => Q.Promise; - - if (this.container.isPreferredApiMongoDB()) { - const isFixedCollectionWithSharedThroughputBeingCreated = - this.container.isFixedCollectionWithSharedThroughputSupported() && - !this.isUnlimitedStorageSelected() && - this.databaseHasSharedOffer(); - const isAadUser = EnvironmentUtility.isAadUser(); - - // note: v3 autopilot not supported yet for Mongo fixed collections (only tier supported) - if (!isAadUser || isFixedCollectionWithSharedThroughputBeingCreated) { - createCollectionFunc = () => - Q( - createMongoCollectionWithProxy( - databaseId, - collectionId, - offerThroughput, - partitionKeyPath, - databaseCreateNew, - useDatabaseSharedOffer, - isSharded, - autopilotSettings - ) - ); - } else { - createCollectionFunc = () => - Q( - createMongoCollectionWithARM( - this.container.armEndpoint(), - databaseId, - this._getAnalyticalStorageTtl(), - collectionId, - offerThroughput, - partitionKeyPath, - databaseCreateNew, - useDatabaseSharedOffer, - isSharded, - autopilotSettings - ) - ); - } - } else if (this.container.isPreferredApiTable() && EnvironmentUtility.isAadUser()) { - createCollectionFunc = () => - Q( - AddCollectionUtility.Utilities.createAzureTableWithARM( - this.container.armEndpoint(), - createRequest, - autopilotSettings - ) - ); - } else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) { - createCollectionFunc = () => - Q( - AddCollectionUtility.CreateCollectionUtilities.createGremlinGraph( - this.container.armEndpoint(), - databaseId, - collectionId, - indexingPolicy, - offerThroughput, - partitionKeyPath, - partitionKey.version, - databaseCreateNew, - useDatabaseSharedOffer, - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount.name, - autopilotSettings - ) - ); - } else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) { - createCollectionFunc = () => - Q( - AddCollectionUtility.CreateSqlCollectionUtilities.createSqlCollection( - this.container.armEndpoint(), - databaseId, - this._getAnalyticalStorageTtl(), - collectionId, - indexingPolicy, - offerThroughput, - partitionKeyPath, - partitionKey.version, - databaseCreateNew, - useDatabaseSharedOffer, - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount.name, - uniqueKeyPolicy, - autopilotSettings - ) - ); - } else { - createCollectionFunc = () => getOrCreateDatabaseAndCollection(createRequest, options); - } - - createCollectionFunc().then( + createCollection(createCollectionParams).then( () => { this.isExecuting(false); this.close(); @@ -1234,35 +1128,6 @@ export default class AddCollectionPane extends ContextualPaneBase { return undefined; } - private _getAutopilotSettings(): DataModels.RpOptions { - if ( - (!this.hasAutoPilotV2FeatureFlag() && - this.databaseCreateNewShared() && - this.isSharedAutoPilotSelected() && - this.sharedAutoPilotThroughput()) || - (this.hasAutoPilotV2FeatureFlag() && - this.databaseCreateNewShared() && - this.isSharedAutoPilotSelected() && - this.selectedSharedAutoPilotTier()) - ) { - return !this.hasAutoPilotV2FeatureFlag() - ? { - [Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.sharedAutoPilotThroughput() * 1 } - } - : { [Constants.HttpHeaders.autoPilotTier]: this.selectedSharedAutoPilotTier().toString() }; - } - if ( - (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) || - (this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier()) - ) { - return !this.hasAutoPilotV2FeatureFlag() - ? { - [Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.autoPilotThroughput() * 1 } - } - : { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() }; - } - return undefined; - } private _calculateNumberOfPartitions(): number { // Note: this will not validate properly on accounts that have been set up for custom partitioning, diff --git a/src/Explorer/Panes/AddDatabasePane.ts b/src/Explorer/Panes/AddDatabasePane.ts index c489f19c0..e5e831180 100644 --- a/src/Explorer/Panes/AddDatabasePane.ts +++ b/src/Explorer/Panes/AddDatabasePane.ts @@ -14,8 +14,8 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan import { AddDbUtilities } from "../../Shared/AddDatabaseUtility"; import { CassandraAPIDataClient } from "../Tables/TableDataClient"; import { ContextualPaneBase } from "./ContextualPaneBase"; +import { createDatabase } from "../../Common/dataAccess/createDatabase"; import { PlatformType } from "../../PlatformType"; -import { refreshCachedOffers, refreshCachedResources, createDatabase } from "../../Common/DocumentClientUtilityBase"; import { userContext } from "../../UserContext"; export default class AddDatabasePane extends ContextualPaneBase { @@ -304,76 +304,23 @@ export default class AddDatabasePane extends ContextualPaneBase { this.formErrors(""); this.isExecuting(true); - const createDatabaseParameters: DataModels.RpParameters = { - db: addDatabasePaneStartMessage.database.id, - st: addDatabasePaneStartMessage.database.shared, - offerThroughput: addDatabasePaneStartMessage.offerThroughput, - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: addDatabasePaneStartMessage.databaseAccountName + const createDatabaseParams: DataModels.CreateDatabaseParams = { + autoPilotMaxThroughput: this.maxAutoPilotThroughputSet(), + databaseId: addDatabasePaneStartMessage.database.id, + databaseLevelThroughput: addDatabasePaneStartMessage.database.shared, + offerThroughput: addDatabasePaneStartMessage.offerThroughput }; - const autopilotSettings = this._getAutopilotSettings(); - - if (this.container.isPreferredApiCassandra()) { - this._createKeyspace(createDatabaseParameters, autopilotSettings, startKey); - } else if (this.container.isPreferredApiMongoDB() && EnvironmentUtility.isAadUser()) { - this._createMongoDatabase(createDatabaseParameters, autopilotSettings, startKey); - } else if (this.container.isPreferredApiGraph() && EnvironmentUtility.isAadUser()) { - this._createGremlinDatabase(createDatabaseParameters, autopilotSettings, startKey); - } else if (this.container.isPreferredApiDocumentDB() && EnvironmentUtility.isAadUser()) { - this._createSqlDatabase(createDatabaseParameters, autopilotSettings, startKey); - } else { - this._createDatabase(offerThroughput, startKey); - } - } - - private _createSqlDatabase( - createDatabaseParameters: DataModels.RpParameters, - autoPilotSettings: DataModels.RpOptions, - startKey: number - ) { - AddDbUtilities.createSqlDatabase(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings).then( - () => { - Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => { - this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey); - }); + createDatabase(createDatabaseParams).then( + (database: DataModels.Database) => { + this._onCreateDatabaseSuccess(offerThroughput, startKey); + }, + (reason: any) => { + this._onCreateDatabaseFailure(reason, offerThroughput, reason); } ); } - private _createMongoDatabase( - createDatabaseParameters: DataModels.RpParameters, - autoPilotSettings: DataModels.RpOptions, - startKey: number - ) { - AddDbUtilities.createMongoDatabaseWithARM( - this.container.armEndpoint(), - createDatabaseParameters, - autoPilotSettings - ).then(() => { - Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => { - this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey); - }); - }); - } - - private _createGremlinDatabase( - createDatabaseParameters: DataModels.RpParameters, - autoPilotSettings: DataModels.RpOptions, - startKey: number - ) { - AddDbUtilities.createGremlinDatabase( - this.container.armEndpoint(), - createDatabaseParameters, - autoPilotSettings - ).then(() => { - Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => { - this._onCreateDatabaseSuccess(createDatabaseParameters.offerThroughput, startKey); - }); - }); - } - public resetData() { this.databaseId(""); this.databaseCreateNewShared(this.getSharedThroughputDefault()); @@ -396,72 +343,6 @@ export default class AddDatabasePane extends ContextualPaneBase { return true; } - private _createDatabase(offerThroughput: number, telemetryStartKey: number): void { - const autoPilot: DataModels.AutoPilotCreationSettings = this._isAutoPilotSelectedAndWhatTier(); - const createRequest: DataModels.CreateDatabaseRequest = { - databaseId: this.databaseId().trim(), - offerThroughput, - databaseLevelThroughput: this.databaseCreateNewShared(), - autoPilot, - hasAutoPilotV2FeatureFlag: this.hasAutoPilotV2FeatureFlag() - }; - createDatabase(createRequest).then( - (database: DataModels.Database) => { - this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey); - }, - (reason: any) => { - this._onCreateDatabaseFailure(reason, offerThroughput, reason); - } - ); - } - - private _createKeyspace( - createDatabaseParameters: DataModels.RpParameters, - autoPilotSettings: DataModels.RpOptions, - startKey: number - ): void { - if (EnvironmentUtility.isAadUser()) { - this._createKeyspaceUsingRP(this.container.armEndpoint(), createDatabaseParameters, autoPilotSettings, startKey); - } else { - this._createKeyspaceUsingProxy(createDatabaseParameters.offerThroughput, startKey); - } - } - - private _createKeyspaceUsingProxy(offerThroughput: number, telemetryStartKey: number): void { - const provisionThroughputQueryPart: string = this.databaseCreateNewShared() - ? `AND cosmosdb_provisioned_throughput=${offerThroughput}` - : ""; - const createKeyspaceQuery: string = `CREATE KEYSPACE ${this.databaseId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 } ${provisionThroughputQueryPart};`; - (this.container.tableDataClient as CassandraAPIDataClient) - .createKeyspace( - this.container.databaseAccount().properties.cassandraEndpoint, - this.container.databaseAccount().id, - this.container, - createKeyspaceQuery - ) - .then( - () => { - this._onCreateDatabaseSuccess(offerThroughput, telemetryStartKey); - }, - (reason: any) => { - this._onCreateDatabaseFailure(reason, offerThroughput, telemetryStartKey); - } - ); - } - - private _createKeyspaceUsingRP( - armEndpoint: string, - createKeyspaceParameters: DataModels.RpParameters, - autoPilotSettings: DataModels.RpOptions, - startKey: number - ): void { - AddDbUtilities.createCassandraKeyspace(armEndpoint, createKeyspaceParameters, autoPilotSettings).then(() => { - Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => { - this._onCreateDatabaseSuccess(createKeyspaceParameters.offerThroughput, startKey); - }); - }); - } - private _onCreateDatabaseSuccess(offerThroughput: number, startKey: number): void { this.isExecuting(false); this.close(); @@ -582,20 +463,6 @@ export default class AddDatabasePane extends ContextualPaneBase { return undefined; } - private _getAutopilotSettings(): DataModels.RpOptions { - if ( - (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) || - (this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier()) - ) { - return !this.hasAutoPilotV2FeatureFlag() - ? { - [Constants.HttpHeaders.autoPilotThroughput]: { maxThroughput: this.maxAutoPilotThroughputSet() * 1 } - } - : { [Constants.HttpHeaders.autoPilotTier]: this.selectedAutoPilotTier().toString() }; - } - return undefined; - } - private _updateThroughputLimitByDatabase() { const throughputDefaults = this.container.collectionCreationDefaults.throughput; this.throughput(throughputDefaults.shared); diff --git a/src/Explorer/Panes/GenericRightPaneComponent.tsx b/src/Explorer/Panes/GenericRightPaneComponent.tsx index 30f278349..b3db7c358 100644 --- a/src/Explorer/Panes/GenericRightPaneComponent.tsx +++ b/src/Explorer/Panes/GenericRightPaneComponent.tsx @@ -16,7 +16,7 @@ export interface GenericRightPaneProps { onSubmit: () => void; submitButtonText: string; title: string; - isSubmitButtonVisible?: boolean; + isSubmitButtonHidden?: boolean; } export interface GenericRightPaneState { @@ -108,7 +108,7 @@ export class GenericRightPaneComponent extends React.Component
this.close(), onSubmit: () => this.submit(), - isSubmitButtonVisible: this.isCodeOfConductAccepted + isSubmitButtonHidden: !this.isCodeOfConductAccepted }; const publishNotebookPaneProps: PublishNotebookPaneProps = { diff --git a/src/Explorer/Panes/PublishNotebookPaneComponent.tsx b/src/Explorer/Panes/PublishNotebookPaneComponent.tsx index 986b42e2e..1980e7a77 100644 --- a/src/Explorer/Panes/PublishNotebookPaneComponent.tsx +++ b/src/Explorer/Panes/PublishNotebookPaneComponent.tsx @@ -285,7 +285,7 @@ export class PublishNotebookPaneComponent extends React.Component this.close(), diff --git a/src/Explorer/SplashScreen/SplashScreenComponent.tsx b/src/Explorer/SplashScreen/SplashScreenComponent.tsx index 8a452ed7d..5da93c65a 100644 --- a/src/Explorer/SplashScreen/SplashScreenComponent.tsx +++ b/src/Explorer/SplashScreen/SplashScreenComponent.tsx @@ -45,7 +45,7 @@ export class SplashScreenComponent extends React.Component - {item.title} +
{item.title}
{item.description}
@@ -66,7 +66,7 @@ export class SplashScreenComponent extends React.Component - {item.title} + {item.title} @@ -79,7 +79,7 @@ export class SplashScreenComponent extends React.Component {this.props.recentItems.map((item: SplashScreenItem, index: number) => (
  • - {item.title} + {item.title} diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index 5f3de274b..e369bc7be 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -147,6 +147,30 @@ export default class NotebookTabV2 extends TabsBase { const cellCodeType = "code"; const cellMarkdownType = "markdown"; const cellRawType = "raw"; + + const saveButtonChildren = []; + if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) { + saveButtonChildren.push({ + iconName: "Copy", + onCommandClick: () => this.copyNotebook(), + commandButtonLabel: copyToLabel, + hasPopup: false, + disabled: false, + ariaLabel: copyToLabel + }); + } + + if (this.container.isGalleryPublishEnabled()) { + saveButtonChildren.push({ + iconName: "PublishContent", + onCommandClick: async () => await this.publishToGallery(), + commandButtonLabel: publishLabel, + hasPopup: false, + disabled: false, + ariaLabel: publishLabel + }); + } + let buttons: CommandButtonComponentProps[] = [ { iconSrc: SaveIcon, @@ -156,34 +180,17 @@ export default class NotebookTabV2 extends TabsBase { hasPopup: false, disabled: false, ariaLabel: saveLabel, - children: this.container.isGalleryPublishEnabled() - ? [ - { - iconName: "Save", - onCommandClick: () => this.notebookComponentAdapter.notebookSave(), - commandButtonLabel: saveLabel, - hasPopup: false, - disabled: false, - ariaLabel: saveLabel - }, - { - iconName: "Copy", - onCommandClick: () => this.copyNotebook(), - commandButtonLabel: copyToLabel, - hasPopup: false, - disabled: false, - ariaLabel: copyToLabel - }, - { - iconName: "PublishContent", - onCommandClick: async () => await this.publishToGallery(), - commandButtonLabel: publishLabel, - hasPopup: false, - disabled: false, - ariaLabel: publishLabel - } - ] - : undefined + children: saveButtonChildren.length && [ + { + iconName: "Save", + onCommandClick: () => this.notebookComponentAdapter.notebookSave(), + commandButtonLabel: saveLabel, + hasPopup: false, + disabled: false, + ariaLabel: saveLabel + }, + ...saveButtonChildren + ] }, { iconSrc: null, diff --git a/src/Explorer/Tabs/SettingsTab.test.ts b/src/Explorer/Tabs/SettingsTab.test.ts index 67242446c..3e3e13b00 100644 --- a/src/Explorer/Tabs/SettingsTab.test.ts +++ b/src/Explorer/Tabs/SettingsTab.test.ts @@ -7,6 +7,7 @@ import Database from "../Tree/Database"; import Explorer from "../Explorer"; import SettingsTab from "../Tabs/SettingsTab"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; +import { IndexingPolicies } from "../../Shared/Constants"; describe("Settings tab", () => { const baseCollection: DataModels.Collection = { @@ -16,7 +17,7 @@ describe("Settings tab", () => { mode: DataModels.ConflictResolutionMode.LastWriterWins, conflictResolutionPath: "/_ts" }, - indexingPolicy: {}, + indexingPolicy: IndexingPolicies.SharedDatabaseDefault, _rid: "", _self: "", _etag: "", @@ -51,7 +52,7 @@ describe("Settings tab", () => { defaultTtl: 200, partitionKey: null, conflictResolutionPolicy: null, - indexingPolicy: {}, + indexingPolicy: IndexingPolicies.SharedDatabaseDefault, _rid: "", _self: "", _etag: "", @@ -345,7 +346,6 @@ describe("Settings tab", () => { const offer: DataModels.Offer = null; const defaultTtl = 200; - const indexingPolicy = {}; const database = new Database(explorer, baseDatabase, null); const conflictResolutionPolicy = { mode: DataModels.ConflictResolutionMode.LastWriterWins, @@ -367,7 +367,7 @@ describe("Settings tab", () => { } : null, conflictResolutionPolicy: conflictResolutionPolicy, - indexingPolicy: indexingPolicy, + indexingPolicy: IndexingPolicies.SharedDatabaseDefault, _rid: "", _self: "", _etag: "", diff --git a/src/Explorer/Tabs/SettingsTab.ts b/src/Explorer/Tabs/SettingsTab.ts index f3f24fd0a..844a26222 100644 --- a/src/Explorer/Tabs/SettingsTab.ts +++ b/src/Explorer/Tabs/SettingsTab.ts @@ -17,7 +17,8 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { PlatformType } from "../../PlatformType"; import { RequestOptions } from "@azure/cosmos/dist-esm"; import Explorer from "../Explorer"; -import { updateOffer, updateCollection } from "../../Common/DocumentClientUtilityBase"; +import { updateOffer } from "../../Common/DocumentClientUtilityBase"; +import { updateCollection } from "../../Common/dataAccess/updateCollection"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { userContext } from "../../UserContext"; import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit"; @@ -1009,8 +1010,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor ); } - public onSaveClick = (): Q.Promise => { - let promises: Q.Promise[] = []; + public onSaveClick = async (): Promise => { this.isExecutionError(false); this.isExecuting(true); @@ -1023,50 +1023,60 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor const newCollectionAttributes: any = {}; - if (this.shouldUpdateCollection()) { - let defaultTtl: number; - switch (this.timeToLive()) { - case "on": - defaultTtl = Number(this.timeToLiveSeconds()); - break; - case "on-nodefault": - defaultTtl = -1; - break; - case "off": - default: - defaultTtl = undefined; - break; - } + try { + if (this.shouldUpdateCollection()) { + let defaultTtl: number; + switch (this.timeToLive()) { + case "on": + defaultTtl = Number(this.timeToLiveSeconds()); + break; + case "on-nodefault": + defaultTtl = -1; + break; + case "off": + default: + defaultTtl = undefined; + break; + } - newCollectionAttributes.defaultTtl = defaultTtl; + newCollectionAttributes.defaultTtl = defaultTtl; - newCollectionAttributes.indexingPolicy = this.indexingPolicyContent(); + newCollectionAttributes.indexingPolicy = this.indexingPolicyContent(); - newCollectionAttributes.changeFeedPolicy = - this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On - ? ({ - retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration - } as DataModels.ChangeFeedPolicy) + newCollectionAttributes.changeFeedPolicy = + this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On + ? ({ + retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration + } as DataModels.ChangeFeedPolicy) + : undefined; + + newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled + ? this.analyticalStorageTtlSelection() === "on" + ? Number(this.analyticalStorageTtlSeconds()) + : Constants.AnalyticalStorageTtl.Infinite : undefined; - newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled - ? this.analyticalStorageTtlSelection() === "on" - ? Number(this.analyticalStorageTtlSeconds()) - : Constants.AnalyticalStorageTtl.Infinite - : undefined; + newCollectionAttributes.geospatialConfig = { + type: this.geospatialConfigType() + }; - newCollectionAttributes.geospatialConfig = { - type: this.geospatialConfigType() - }; + const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy(); + if (!!conflictResolutionChanges) { + newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges; + } - const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy(); - if (!!conflictResolutionChanges) { - newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges; - } + const newCollection: DataModels.Collection = _.extend( + {}, + this.collection.rawDataModel, + newCollectionAttributes + ); + const updatedCollection: DataModels.Collection = await updateCollection( + this.collection.databaseId, + this.collection.id(), + newCollection + ); - const newCollection: DataModels.Collection = _.extend({}, this.collection.rawDataModel, newCollectionAttributes); - const updateCollectionPromise = updateCollection(this.collection.databaseId, this.collection, newCollection).then( - (updatedCollection: DataModels.Collection) => { + if (updatedCollection) { this.collection.rawDataModel = updatedCollection; this.collection.defaultTtl(updatedCollection.defaultTtl); this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl); @@ -1076,164 +1086,133 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy); this.collection.geospatialConfig(updatedCollection.geospatialConfig); } - ); - - promises.push(updateCollectionPromise); - } - - if ( - this.throughput.editableIsDirty() || - this.rupm.editableIsDirty() || - this._isAutoPilotDirty() || - this._hasProvisioningTypeChanged() - ) { - const newThroughput = this.throughput(); - const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on; - let newOffer: DataModels.Offer = _.extend({}, this.collection.offer()); - const originalThroughputValue: number = this.throughput.getEditableOriginalValue(); - - if (newOffer.content) { - newOffer.content.offerThroughput = newThroughput; - newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled; - } else { - newOffer = _.extend({}, newOffer, { - content: { - offerThroughput: newThroughput, - offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled - } - }); - } - - const headerOptions: RequestOptions = { initialHeaders: {} }; - - if (this.isAutoPilotSelected()) { - if (!this.hasAutoPilotV2FeatureFlag()) { - newOffer.content.offerAutopilotSettings = { - maxThroughput: this.autoPilotThroughput() - }; - } else { - newOffer.content.offerAutopilotSettings = { - tier: this.selectedAutoPilotTier() - }; - } - - // user has changed from provisioned --> autoscale - if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) { - headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true"; - delete newOffer.content.offerAutopilotSettings; - } else { - delete newOffer.content.offerThroughput; - } - } else { - this.isAutoPilotSelected(false); - this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag()); - - // user has changed from autoscale --> provisioned - if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) { - headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true"; - } else { - delete newOffer.content.offerAutopilotSettings; - } } if ( - this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && - newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && - this.container != null + this.throughput.editableIsDirty() || + this.rupm.editableIsDirty() || + this._isAutoPilotDirty() || + this._hasProvisioningTypeChanged() ) { - const requestPayload = { - subscriptionId: userContext.subscriptionId, - databaseAccountName: userContext.databaseAccount.name, - resourceGroup: userContext.resourceGroup, - databaseName: this.collection.databaseId, - collectionName: this.collection.id(), - throughput: newThroughput, - offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled - }; - const updateOfferBeyondLimitPromise = updateOfferThroughputBeyondLimit(requestPayload).then( - () => { - this.collection.offer().content.offerThroughput = originalThroughputValue; - this.throughput(originalThroughputValue); - this.notificationStatusInfo( - throughputApplyDelayedMessage( - this.isAutoPilotSelected(), - originalThroughputValue, - this._getThroughputUnit(), - this.collection.databaseId, - this.collection.id(), - newThroughput - ) - ); - this.throughput.valueHasMutated(); // force component re-render - }, - (error: any) => { - TelemetryProcessor.traceFailure( - Action.UpdateSettings, - { - databaseAccountName: this.container.databaseAccount().name, - databaseName: this.collection && this.collection.databaseId, - collectionName: this.collection && this.collection.id(), - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle(), - error: error - }, - startKey - ); - } - ); - promises.push(Q(updateOfferBeyondLimitPromise)); - } else { - const updateOfferPromise = updateOffer(this.collection.offer(), newOffer, headerOptions).then( - (updatedOffer: DataModels.Offer) => { - this.collection.offer(updatedOffer); - this.collection.offer.valueHasMutated(); - } - ); + const newThroughput = this.throughput(); + const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on; + let newOffer: DataModels.Offer = _.extend({}, this.collection.offer()); + const originalThroughputValue: number = this.throughput.getEditableOriginalValue(); - promises.push(updateOfferPromise); - } - } - - if (promises.length === 0) { - this.isExecuting(false); - } - - return Q.all(promises) - .then( - () => { - this.container.isRefreshingExplorer(false); - this._setBaseline(); - this.collection.readSettings(); - this._wasAutopilotOriginallySet(this.isAutoPilotSelected()); - TelemetryProcessor.traceSuccess( - Action.UpdateSettings, - { - databaseAccountName: this.container.databaseAccount().name, - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle() - }, - startKey - ); - }, - (reason: any) => { - this.container.isRefreshingExplorer(false); - this.isExecutionError(true); - console.error(reason); - TelemetryProcessor.traceFailure( - Action.UpdateSettings, - { - databaseAccountName: this.container.databaseAccount().name, - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle() - }, - startKey - ); + if (newOffer.content) { + newOffer.content.offerThroughput = newThroughput; + newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled; + } else { + newOffer = _.extend({}, newOffer, { + content: { + offerThroughput: newThroughput, + offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled + } + }); } - ) - .finally(() => this.isExecuting(false)); + + const headerOptions: RequestOptions = { initialHeaders: {} }; + + if (this.isAutoPilotSelected()) { + if (!this.hasAutoPilotV2FeatureFlag()) { + newOffer.content.offerAutopilotSettings = { + maxThroughput: this.autoPilotThroughput() + }; + } else { + newOffer.content.offerAutopilotSettings = { + tier: this.selectedAutoPilotTier() + }; + } + + // user has changed from provisioned --> autoscale + if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) { + headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true"; + delete newOffer.content.offerAutopilotSettings; + } else { + delete newOffer.content.offerThroughput; + } + } else { + this.isAutoPilotSelected(false); + this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag()); + + // user has changed from autoscale --> provisioned + if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) { + headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true"; + } else { + delete newOffer.content.offerAutopilotSettings; + } + } + + if ( + this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && + newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && + this.container != null + ) { + const requestPayload = { + subscriptionId: userContext.subscriptionId, + databaseAccountName: userContext.databaseAccount.name, + resourceGroup: userContext.resourceGroup, + databaseName: this.collection.databaseId, + collectionName: this.collection.id(), + throughput: newThroughput, + offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled + }; + + await updateOfferThroughputBeyondLimit(requestPayload); + this.collection.offer().content.offerThroughput = originalThroughputValue; + this.throughput(originalThroughputValue); + this.notificationStatusInfo( + throughputApplyDelayedMessage( + this.isAutoPilotSelected(), + originalThroughputValue, + this._getThroughputUnit(), + this.collection.databaseId, + this.collection.id(), + newThroughput + ) + ); + this.throughput.valueHasMutated(); // force component re-render + } else { + const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions); + this.collection.offer(updatedOffer); + this.collection.offer.valueHasMutated(); + } + } + + this.container.isRefreshingExplorer(false); + this._setBaseline(); + this.collection.readSettings(); + this._wasAutopilotOriginallySet(this.isAutoPilotSelected()); + TelemetryProcessor.traceSuccess( + Action.UpdateSettings, + { + databaseAccountName: this.container.databaseAccount().name, + defaultExperience: this.container.defaultExperience(), + dataExplorerArea: Constants.Areas.Tab, + tabTitle: this.tabTitle() + }, + startKey + ); + } catch (error) { + this.container.isRefreshingExplorer(false); + this.isExecutionError(true); + console.error(error); + TelemetryProcessor.traceFailure( + Action.UpdateSettings, + { + databaseAccountName: this.container.databaseAccount().name, + databaseName: this.collection && this.collection.databaseId, + collectionName: this.collection && this.collection.id(), + defaultExperience: this.container.defaultExperience(), + dataExplorerArea: Constants.Areas.Tab, + tabTitle: this.tabTitle(), + error: error + }, + startKey + ); + } + + this.isExecuting(false); }; public onRevertClick = (): Q.Promise => { diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 121bc686f..f88df007b 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -648,7 +648,9 @@ export default class Collection implements ViewModels.Collection { }); // TODO: Use the collection entity cache to get quota info const quotaInfoPromise: Q.Promise = readCollectionQuotaInfo(this); - const offerInfoPromise: Q.Promise = readOffers(); + const offerInfoPromise: Q.Promise = readOffers({ + isServerless: this.container.isServerlessEnabled() + }); Q.all([quotaInfoPromise, offerInfoPromise]).then( () => { this.container.isRefreshingExplorer(false); @@ -657,9 +659,7 @@ export default class Collection implements ViewModels.Collection { const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy"); const collectionOffer = this._getOfferForCollection(offerInfoPromise.valueOf(), collectionDataModel); - const isDatabaseShared = this.getDatabase() && this.getDatabase().isDatabaseShared(); - const isServerless = this.container.isServerlessEnabled(); - if ((isDatabaseShared || isServerless) && !collectionOffer) { + if (!collectionOffer) { this.quotaInfo(quotaInfo); TelemetryProcessor.traceSuccess( Action.LoadOffers, diff --git a/src/Explorer/Tree/Database.ts b/src/Explorer/Tree/Database.ts index d92e0c9bc..cb007248e 100644 --- a/src/Explorer/Tree/Database.ts +++ b/src/Explorer/Tree/Database.ts @@ -123,10 +123,6 @@ export default class Database implements ViewModels.Database { public readSettings(): Q.Promise { const deferred: Q.Deferred = Q.defer(); - if (this.container.isServerlessEnabled()) { - deferred.resolve(); - } - this.container.isRefreshingExplorer(true); const databaseDataModel: DataModels.Database = { id: this.id(), @@ -138,7 +134,9 @@ export default class Database implements ViewModels.Database { defaultExperience: this.container.defaultExperience() }); - const offerInfoPromise: Q.Promise = readOffers(); + const offerInfoPromise: Q.Promise = readOffers({ + isServerless: this.container.isServerlessEnabled() + }); Q.all([offerInfoPromise]).then( () => { this.container.isRefreshingExplorer(false); @@ -147,6 +145,11 @@ export default class Database implements ViewModels.Database { offerInfoPromise.valueOf(), databaseDataModel ); + + if (!databaseOffer) { + return; + } + readOffer(databaseOffer).then((offerDetail: DataModels.OfferWithHeaders) => { const offerThroughputInfo: DataModels.OfferThroughputInfo = { minimumRUForCollection: diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index 2564a4930..7f9d8d6d5 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -546,43 +546,52 @@ export class ResourceTreeAdapter implements ReactAdapter { (activeTab as any).notebookPath() === item.path ); }, - contextMenu: createFileContextMenu - ? [ - { - label: "Rename", - iconSrc: NotebookIcon, - onClick: () => this.container.renameNotebook(item) - }, - { - label: "Delete", - iconSrc: DeleteIcon, - onClick: () => { - this.container.showOkCancelModalDialog( - "Confirm delete", - `Are you sure you want to delete "${item.name}"`, - "Delete", - () => this.container.deleteNotebookFile(item).then(() => this.triggerRender()), - "Cancel", - undefined - ); - } - }, - { - label: "Copy to ...", - iconSrc: CopyIcon, - onClick: () => this.copyNotebook(item) - }, - { - label: "Download", - iconSrc: NotebookIcon, - onClick: () => this.container.downloadFile(item) - } - ] - : undefined, + contextMenu: createFileContextMenu && this.createFileContextMenu(item), data: item }; } + private createFileContextMenu(item: NotebookContentItem): TreeNodeMenuItem[] { + let items: TreeNodeMenuItem[] = [ + { + label: "Rename", + iconSrc: NotebookIcon, + onClick: () => this.container.renameNotebook(item) + }, + { + label: "Delete", + iconSrc: DeleteIcon, + onClick: () => { + this.container.showOkCancelModalDialog( + "Confirm delete", + `Are you sure you want to delete "${item.name}"`, + "Delete", + () => this.container.deleteNotebookFile(item).then(() => this.triggerRender()), + "Cancel", + undefined + ); + } + }, + { + label: "Copy to ...", + iconSrc: CopyIcon, + onClick: () => this.copyNotebook(item) + }, + { + label: "Download", + iconSrc: NotebookIcon, + onClick: () => this.container.downloadFile(item) + } + ]; + + // "Copy to ..." isn't needed if github locations are not available + if (!this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) { + items = items.filter(item => item.label !== "Copy to ..."); + } + + return items; + } + private copyNotebook = async (item: NotebookContentItem) => { const content = await this.container.readFile(item); if (content) { diff --git a/src/Shared/AddDatabaseUtility.ts b/src/Shared/AddDatabaseUtility.ts index b73462837..a1197c732 100644 --- a/src/Shared/AddDatabaseUtility.ts +++ b/src/Shared/AddDatabaseUtility.ts @@ -8,6 +8,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; import { userContext } from "../UserContext"; +import { createUpdateCassandraKeyspace } from "../Utils/arm/generatedClients/2020-04-01/cassandraResources"; export class AddDbUtilities { // todo - remove any @@ -47,7 +48,6 @@ export class AddDbUtilities { // todo - remove any public static async createCassandraKeyspace( - armEndpoint: string, params: DataModels.RpParameters, rpOptions: DataModels.RpOptions ): Promise { @@ -70,9 +70,11 @@ export class AddDbUtilities { } try { - await AddDbUtilities.getRpClient(armEndpoint).putAsync( - AddDbUtilities._getCassandraKeyspaceUri(params), - DataExplorerConstants.ArmApiVersions.publicVersion, + await createUpdateCassandraKeyspace( + userContext.subscriptionId, + userContext.resourceGroup, + userContext.databaseAccount?.name, + params.db, rpPayloadToCreateKeyspace ); } catch (reason) { @@ -159,10 +161,7 @@ export class AddDbUtilities { } private static _handleCreationError(reason: any, params: DataModels.RpParameters, dbType: string = "database") { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}` - ); + NotificationConsoleUtils.logConsoleError(`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`); if (reason.status === HttpStatusCodes.Forbidden) { sendMessage({ type: MessageTypes.ForbiddenError }); return; diff --git a/src/Shared/Telemetry/TelemetryProcessor.ts b/src/Shared/Telemetry/TelemetryProcessor.ts index 8d8d16d95..bc6910a57 100644 --- a/src/Shared/Telemetry/TelemetryProcessor.ts +++ b/src/Shared/Telemetry/TelemetryProcessor.ts @@ -114,13 +114,17 @@ export default class TelemetryProcessor { return validTimestamp; } - private static getData(data?: any): any { + private static getData(data: any = {}): any { + if (typeof data === "string") { + data = { message: data }; + } return { // TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet authType: (window as any).authType, subscriptionId: userContext.subscriptionId, platform: configContext.platform, - ...(data ? data : []) + env: process.env.NODE_ENV, + ...data }; } } diff --git a/src/UserContext.ts b/src/UserContext.ts index e9b4f6e62..2cba6a417 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -11,6 +11,7 @@ interface UserContext { authorizationToken?: string; resourceToken?: string; defaultExperience?: DefaultAccountExperienceType; + useSDKOperations?: boolean; } const userContext: Readonly = {} as const; diff --git a/src/Utils/arm/generatedClients/2020-04-01/cassandraResources.ts b/src/Utils/arm/generatedClients/2020-04-01/cassandraResources.ts index 7477e79e5..924f30bb6 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/cassandraResources.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/cassandraResources.ts @@ -39,7 +39,7 @@ export async function createUpdateCassandraKeyspace( body: Types.CassandraKeyspaceCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB Cassandra keyspace. */ @@ -73,7 +73,7 @@ export async function updateCassandraKeyspaceThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/throughputSettings/default`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Migrate an Azure Cosmos DB Cassandra Keyspace from manual throughput to autoscale */ @@ -131,7 +131,7 @@ export async function createUpdateCassandraTable( body: Types.CassandraTableCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/cassandraKeyspaces/${keyspaceName}/tables/${tableName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB Cassandra table. */ @@ -168,7 +168,7 @@ export async function updateCassandraTableThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { 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 */ diff --git a/src/Utils/arm/generatedClients/2020-04-01/databaseAccounts.ts b/src/Utils/arm/generatedClients/2020-04-01/databaseAccounts.ts index d98fb51e4..499213664 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/databaseAccounts.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/databaseAccounts.ts @@ -27,13 +27,7 @@ export async function update( body: Types.DatabaseAccountUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; - return armRequest({ - host: configContext.ARM_ENDPOINT, - path, - method: "PATCH", - apiVersion, - body: JSON.stringify(body) - }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PATCH", apiVersion, body }); } /* Creates or updates an Azure Cosmos DB database account. The "Update" method is preferred when performing updates on an account. */ @@ -44,7 +38,7 @@ export async function createOrUpdate( body: Types.DatabaseAccountCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB database account. */ @@ -61,7 +55,7 @@ export async function failoverPriorityChange( body: Types.FailoverPolicies ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/failoverPriorityChange`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body }); } /* Lists all the Azure Cosmos DB database accounts available under the subscription. */ @@ -107,7 +101,7 @@ export async function offlineRegion( body: Types.RegionForOnlineOffline ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/offlineRegion`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body }); } /* Online the specified region for the specified Azure Cosmos DB database account. */ @@ -118,7 +112,7 @@ export async function onlineRegion( body: Types.RegionForOnlineOffline ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/onlineRegion`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion, body }); } /* Lists the read-only access keys for the specified Azure Cosmos DB database account. */ @@ -149,7 +143,7 @@ export async function regenerateKey( body: Types.DatabaseAccountRegenerateKeyParameters ): Promise { 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. */ diff --git a/src/Utils/arm/generatedClients/2020-04-01/gremlinResources.ts b/src/Utils/arm/generatedClients/2020-04-01/gremlinResources.ts index 831e8cc6c..26a1fa5b2 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/gremlinResources.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/gremlinResources.ts @@ -39,7 +39,7 @@ export async function createUpdateGremlinDatabase( body: Types.GremlinDatabaseCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB Gremlin database. */ @@ -73,7 +73,7 @@ export async function updateGremlinDatabaseThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/throughputSettings/default`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Migrate an Azure Cosmos DB Gremlin database from manual throughput to autoscale */ @@ -131,7 +131,7 @@ export async function createUpdateGremlinGraph( body: Types.GremlinGraphCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/gremlinDatabases/${databaseName}/graphs/${graphName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB Gremlin graph. */ @@ -168,7 +168,7 @@ export async function updateGremlinGraphThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { 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 */ diff --git a/src/Utils/arm/generatedClients/2020-04-01/mongoDBResources.ts b/src/Utils/arm/generatedClients/2020-04-01/mongoDBResources.ts index 7592effbb..e3a3e9439 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/mongoDBResources.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/mongoDBResources.ts @@ -39,7 +39,7 @@ export async function createUpdateMongoDBDatabase( body: Types.MongoDBDatabaseCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB MongoDB database. */ @@ -73,7 +73,7 @@ export async function updateMongoDBDatabaseThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/throughputSettings/default`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Migrate an Azure Cosmos DB MongoDB database from manual throughput to autoscale */ @@ -131,7 +131,7 @@ export async function createUpdateMongoDBCollection( body: Types.MongoDBCollectionCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/mongodbDatabases/${databaseName}/collections/${collectionName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB MongoDB Collection. */ @@ -168,7 +168,7 @@ export async function updateMongoDBCollectionThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { 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 */ diff --git a/src/Utils/arm/generatedClients/2020-04-01/sqlResources.ts b/src/Utils/arm/generatedClients/2020-04-01/sqlResources.ts index b12187bc6..7755731c2 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/sqlResources.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/sqlResources.ts @@ -39,7 +39,7 @@ export async function createUpdateSqlDatabase( body: Types.SqlDatabaseCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB SQL database. */ @@ -73,7 +73,7 @@ export async function updateSqlDatabaseThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/throughputSettings/default`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Migrate an Azure Cosmos DB SQL database from manual throughput to autoscale */ @@ -131,7 +131,7 @@ export async function createUpdateSqlContainer( body: Types.SqlContainerCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB SQL container. */ @@ -168,7 +168,7 @@ export async function updateSqlContainerThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/throughputSettings/default`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Migrate an Azure Cosmos DB SQL container from manual throughput to autoscale */ @@ -231,7 +231,7 @@ export async function createUpdateSqlStoredProcedure( body: Types.SqlStoredProcedureCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/storedProcedures/${storedProcedureName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB SQL storedProcedure. */ @@ -283,7 +283,7 @@ export async function createUpdateSqlUserDefinedFunction( body: Types.SqlUserDefinedFunctionCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlDatabases/${databaseName}/containers/${containerName}/userDefinedFunctions/${userDefinedFunctionName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB SQL userDefinedFunction. */ @@ -335,7 +335,7 @@ export async function createUpdateSqlTrigger( body: Types.SqlTriggerCreateUpdateParameters ): Promise { 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. */ diff --git a/src/Utils/arm/generatedClients/2020-04-01/tableResources.ts b/src/Utils/arm/generatedClients/2020-04-01/tableResources.ts index 526fae4ad..ede7fbcb4 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/tableResources.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/tableResources.ts @@ -39,7 +39,7 @@ export async function createUpdateTable( body: Types.TableCreateUpdateParameters ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/tables/${tableName}`; - return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: JSON.stringify(body) }); + return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body }); } /* Deletes an existing Azure Cosmos DB Table. */ @@ -73,7 +73,7 @@ export async function updateTableThroughput( body: Types.ThroughputSettingsUpdateParameters ): Promise { 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 */ diff --git a/src/Utils/arm/generatedClients/2020-04-01/types.ts b/src/Utils/arm/generatedClients/2020-04-01/types.ts index bf23c75b4..97eebbf13 100644 --- a/src/Utils/arm/generatedClients/2020-04-01/types.ts +++ b/src/Utils/arm/generatedClients/2020-04-01/types.ts @@ -7,87 +7,87 @@ /* The List operation response, that contains the database accounts and their properties. */ export interface DatabaseAccountsListResult { /* List of database account and their properties. */ - readonly value: DatabaseAccountGetResults[]; + readonly value?: DatabaseAccountGetResults[]; } /* The List operation response, that contains the SQL databases and their properties. */ export interface SqlDatabaseListResult { /* List of SQL databases and their properties. */ - readonly value: SqlDatabaseGetResults[]; + readonly value?: SqlDatabaseGetResults[]; } /* The List operation response, that contains the containers and their properties. */ export interface SqlContainerListResult { /* List of containers and their properties. */ - readonly value: SqlContainerGetResults[]; + readonly value?: SqlContainerGetResults[]; } /* The List operation response, that contains the storedProcedures and their properties. */ export interface SqlStoredProcedureListResult { /* List of storedProcedures and their properties. */ - readonly value: SqlStoredProcedureGetResults[]; + readonly value?: SqlStoredProcedureGetResults[]; } /* The List operation response, that contains the userDefinedFunctions and their properties. */ export interface SqlUserDefinedFunctionListResult { /* List of userDefinedFunctions and their properties. */ - readonly value: SqlUserDefinedFunctionGetResults[]; + readonly value?: SqlUserDefinedFunctionGetResults[]; } /* The List operation response, that contains the triggers and their properties. */ export interface SqlTriggerListResult { /* List of triggers and their properties. */ - readonly value: SqlTriggerGetResults[]; + readonly value?: SqlTriggerGetResults[]; } /* The List operation response, that contains the MongoDB databases and their properties. */ export interface MongoDBDatabaseListResult { /* List of MongoDB databases and their properties. */ - readonly value: MongoDBDatabaseGetResults[]; + readonly value?: MongoDBDatabaseGetResults[]; } /* The List operation response, that contains the MongoDB collections and their properties. */ export interface MongoDBCollectionListResult { /* List of MongoDB collections and their properties. */ - readonly value: MongoDBCollectionGetResults[]; + readonly value?: MongoDBCollectionGetResults[]; } /* The List operation response, that contains the Table and their properties. */ export interface TableListResult { /* List of Table and their properties. */ - readonly value: TableGetResults[]; + readonly value?: TableGetResults[]; } /* The List operation response, that contains the Cassandra keyspaces and their properties. */ export interface CassandraKeyspaceListResult { /* List of Cassandra keyspaces and their properties. */ - readonly value: CassandraKeyspaceGetResults[]; + readonly value?: CassandraKeyspaceGetResults[]; } /* The List operation response, that contains the Cassandra tables and their properties. */ export interface CassandraTableListResult { /* List of Cassandra tables and their properties. */ - readonly value: CassandraTableGetResults[]; + readonly value?: CassandraTableGetResults[]; } /* The List operation response, that contains the Gremlin databases and their properties. */ export interface GremlinDatabaseListResult { /* List of Gremlin databases and their properties. */ - readonly value: GremlinDatabaseGetResults[]; + readonly value?: GremlinDatabaseGetResults[]; } /* The List operation response, that contains the graphs and their properties. */ export interface GremlinGraphListResult { /* List of graphs and their properties. */ - readonly value: GremlinGraphGetResults[]; + readonly value?: GremlinGraphGetResults[]; } /* Error Response. */ export interface ErrorResponse { /* Error code. */ - code: string; + code?: string; /* Error message indicating why the operation failed. */ - message: string; + message?: string; } /* The list of new failover policies for the failover priority change. */ @@ -99,11 +99,11 @@ export interface FailoverPolicies { /* The failover policy for a given region of a database account. */ export interface FailoverPolicy { /* The unique identifier of the region in which the database account replicates to. Example: <accountName>-<locationName>. */ - readonly id: string; + readonly id?: string; /* The name of the region in which the database account exists. */ - locationName: string; + locationName?: string; /* The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists. */ - failoverPriority: number; + failoverPriority?: number; } /* Cosmos DB region to online or offline. */ @@ -115,59 +115,59 @@ export interface RegionForOnlineOffline { /* A region in which the Azure Cosmos DB database account is deployed. */ export interface Location { /* The unique identifier of the region within the database account. Example: <accountName>-<locationName>. */ - readonly id: string; + readonly id?: string; /* The name of the region. */ - locationName: string; + locationName?: string; /* The connection endpoint for the specific region. Example: https://<accountName>-<locationName>.documents.azure.com:443/ */ - readonly documentEndpoint: string; + readonly documentEndpoint?: string; /* undocumented */ - provisioningState: ProvisioningState; + provisioningState?: ProvisioningState; /* The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists. */ - failoverPriority: number; + failoverPriority?: number; /* Flag to indicate whether or not this region is an AvailabilityZone region */ - isZoneRedundant: boolean; + isZoneRedundant?: boolean; } /* The core properties of ARM resources. */ export interface ARMResourceProperties { /* The unique resource identifier of the ARM resource. */ - readonly id: string; + readonly id?: string; /* The name of the ARM resource. */ - readonly name: string; + readonly name?: string; /* The type of Azure resource. */ - readonly type: string; + readonly type?: string; /* The location of the resource group to which the resource belongs. */ - location: string; + location?: string; /* undocumented */ - tags: Tags; + tags?: Tags; } /* The resource model definition for a ARM proxy resource. It will have everything other than required location and tags */ export interface ARMProxyResource { /* The unique resource identifier of the database account. */ - readonly id: string; + readonly id?: string; /* The name of the database account. */ - readonly name: string; + readonly name?: string; /* The type of Azure resource. */ - readonly type: string; + readonly type?: string; } /* An Azure Cosmos DB database account. */ export type DatabaseAccountGetResults = ARMResourceProperties & { /* Indicates the type of database account. This can only be set at database account creation. */ - kind: string; + kind?: string; /* undocumented */ - properties: DatabaseAccountGetProperties; + properties?: DatabaseAccountGetProperties; }; /* The system generated resource properties associated with SQL databases, SQL containers, Gremlin databases and Gremlin graphs. */ // TODO: ExtendedResourceProperties was missing some properties such as _self which was manually added. Need to fix this in the RP spec. export interface ExtendedResourceProperties { /* A system generated property. A unique identifier. */ - readonly _rid: string; + readonly _rid?: string; /* A system generated property that denotes the last updated timestamp of the resource. */ - readonly _ts: unknown; + readonly _ts?: unknown; /* A system generated property representing the resource etag required for optimistic concurrency control. */ readonly _etag: string; // TODO: This property was manually added. It should be auto-generated like the other properties. @@ -177,175 +177,175 @@ export interface ExtendedResourceProperties { /* An Azure Cosmos DB resource throughput. */ export type ThroughputSettingsGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB resource throughput */ - properties: ThroughputSettingsGetProperties; + properties?: ThroughputSettingsGetProperties; }; /* The properties of an Azure Cosmos DB resource throughput */ export interface ThroughputSettingsGetProperties { /* undocumented */ - resource: ThroughputSettingsResource & ExtendedResourceProperties; + resource?: ThroughputSettingsResource & ExtendedResourceProperties; } /* An Azure Cosmos DB SQL database. */ export type SqlDatabaseGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB SQL database */ - properties: SqlDatabaseGetProperties; + properties?: SqlDatabaseGetProperties; }; /* The properties of an Azure Cosmos DB SQL database */ export interface SqlDatabaseGetProperties { /* undocumented */ - resource: SqlDatabaseResource & ExtendedResourceProperties; + resource?: SqlDatabaseResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB container. */ export type SqlContainerGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB container */ - properties: SqlContainerGetProperties; + properties?: SqlContainerGetProperties; }; /* The properties of an Azure Cosmos DB container */ export interface SqlContainerGetProperties { /* undocumented */ - resource: SqlContainerResource & ExtendedResourceProperties; + resource?: SqlContainerResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB storedProcedure. */ export type SqlStoredProcedureGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB storedProcedure */ - properties: SqlStoredProcedureGetProperties; + properties?: SqlStoredProcedureGetProperties; }; /* The properties of an Azure Cosmos DB StoredProcedure */ export interface SqlStoredProcedureGetProperties { /* undocumented */ - resource: SqlStoredProcedureResource & ExtendedResourceProperties; + resource?: SqlStoredProcedureResource & ExtendedResourceProperties; } /* An Azure Cosmos DB userDefinedFunction. */ export type SqlUserDefinedFunctionGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB userDefinedFunction */ - properties: SqlUserDefinedFunctionGetProperties; + properties?: SqlUserDefinedFunctionGetProperties; }; /* The properties of an Azure Cosmos DB userDefinedFunction */ export interface SqlUserDefinedFunctionGetProperties { /* undocumented */ - resource: SqlUserDefinedFunctionResource & ExtendedResourceProperties; + resource?: SqlUserDefinedFunctionResource & ExtendedResourceProperties; } /* An Azure Cosmos DB trigger. */ export type SqlTriggerGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB trigger */ - properties: SqlTriggerGetProperties; + properties?: SqlTriggerGetProperties; }; /* The properties of an Azure Cosmos DB trigger */ export interface SqlTriggerGetProperties { /* undocumented */ - resource: SqlTriggerResource & ExtendedResourceProperties; + resource?: SqlTriggerResource & ExtendedResourceProperties; } /* An Azure Cosmos DB MongoDB database. */ export type MongoDBDatabaseGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB MongoDB database */ - properties: MongoDBDatabaseGetProperties; + properties?: MongoDBDatabaseGetProperties; }; /* The properties of an Azure Cosmos DB MongoDB database */ export interface MongoDBDatabaseGetProperties { /* undocumented */ - resource: MongoDBDatabaseResource & ExtendedResourceProperties; + resource?: MongoDBDatabaseResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB MongoDB collection. */ export type MongoDBCollectionGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB MongoDB collection */ - properties: MongoDBCollectionGetProperties; + properties?: MongoDBCollectionGetProperties; }; /* The properties of an Azure Cosmos DB MongoDB collection */ export interface MongoDBCollectionGetProperties { /* undocumented */ - resource: MongoDBCollectionResource & ExtendedResourceProperties; + resource?: MongoDBCollectionResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB Table. */ export type TableGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB Table */ - properties: TableGetProperties; + properties?: TableGetProperties; }; /* The properties of an Azure Cosmos Table */ export interface TableGetProperties { /* undocumented */ - resource: TableResource & ExtendedResourceProperties; + resource?: TableResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB Cassandra keyspace. */ export type CassandraKeyspaceGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB Cassandra keyspace */ - properties: CassandraKeyspaceGetProperties; + properties?: CassandraKeyspaceGetProperties; }; /* The properties of an Azure Cosmos DB Cassandra keyspace */ export interface CassandraKeyspaceGetProperties { /* undocumented */ - resource: CassandraKeyspaceResource & ExtendedResourceProperties; + resource?: CassandraKeyspaceResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB Cassandra table. */ export type CassandraTableGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB Cassandra table */ - properties: CassandraTableGetProperties; + properties?: CassandraTableGetProperties; }; /* The properties of an Azure Cosmos DB Cassandra table */ export interface CassandraTableGetProperties { /* undocumented */ - resource: CassandraTableResource & ExtendedResourceProperties; + resource?: CassandraTableResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB Gremlin database. */ export type GremlinDatabaseGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB SQL database */ - properties: GremlinDatabaseGetProperties; + properties?: GremlinDatabaseGetProperties; }; /* The properties of an Azure Cosmos DB SQL database */ export interface GremlinDatabaseGetProperties { /* undocumented */ - resource: GremlinDatabaseResource & ExtendedResourceProperties; + resource?: GremlinDatabaseResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* An Azure Cosmos DB Gremlin graph. */ export type GremlinGraphGetResults = ARMResourceProperties & { /* The properties of an Azure Cosmos DB Gremlin graph */ - properties: GremlinGraphGetProperties; + properties?: GremlinGraphGetProperties; }; /* The properties of an Azure Cosmos DB Gremlin graph */ export interface GremlinGraphGetProperties { /* undocumented */ - resource: GremlinGraphResource & ExtendedResourceProperties; + resource?: GremlinGraphResource & ExtendedResourceProperties; /* undocumented */ - options: OptionsResource; + options?: OptionsResource; } /* The consistency policy for the Cosmos DB database account. */ @@ -353,79 +353,95 @@ export interface ConsistencyPolicy { /* The default consistency level and configuration settings of the Cosmos DB account. */ defaultConsistencyLevel: string; /* When used with the Bounded Staleness consistency level, this value represents the number of stale requests tolerated. Accepted range for this value is 1 – 2,147,483,647. Required when defaultConsistencyPolicy is set to 'BoundedStaleness'. */ - maxStalenessPrefix: number; + maxStalenessPrefix?: number; /* When used with the Bounded Staleness consistency level, this value represents the time amount of staleness (in seconds) tolerated. Accepted range for this value is 5 - 86400. Required when defaultConsistencyPolicy is set to 'BoundedStaleness'. */ - maxIntervalInSeconds: number; + maxIntervalInSeconds?: number; +} + +/* The CORS policy for the Cosmos DB database account. */ +export interface CorsPolicy { + /* The origin domains that are permitted to make a request against the service via CORS. */ + allowedOrigins: string; + /* The methods (HTTP request verbs) that the origin domain may use for a CORS request. */ + allowedMethods?: string; + /* The request headers that the origin domain may specify on the CORS request. */ + allowedHeaders?: string; + /* The response headers that may be sent in the response to the CORS request and exposed by the browser to the request issuer. */ + exposedHeaders?: string; + /* The maximum amount time that a browser should cache the preflight OPTIONS request. */ + maxAgeInSeconds?: number; } /* Properties for the database account. */ export interface DatabaseAccountGetProperties { /* undocumented */ - provisioningState: ProvisioningState; + provisioningState?: ProvisioningState; /* The connection endpoint for the Cosmos DB database account. */ - readonly documentEndpoint: string; + readonly documentEndpoint?: string; /* The offer type for the Cosmos DB database account. Default value: Standard. */ - readonly databaseAccountOfferType: DatabaseAccountOfferType; + readonly databaseAccountOfferType?: DatabaseAccountOfferType; /* List of IpRules. */ - ipRules: IPRules; + ipRules?: IPRules; /* Flag to indicate whether to enable/disable Virtual Network ACL rules. */ - isVirtualNetworkFilterEnabled: boolean; + isVirtualNetworkFilterEnabled?: boolean; /* Enables automatic failover of the write region in the rare event that the region is unavailable due to an outage. Automatic failover will result in a new write region for the account and is chosen based on the failover priorities configured for the account. */ - enableAutomaticFailover: boolean; + enableAutomaticFailover?: boolean; /* The consistency policy for the Cosmos DB database account. */ - consistencyPolicy: ConsistencyPolicy; + consistencyPolicy?: ConsistencyPolicy; /* List of Cosmos DB capabilities for the account */ - capabilities: Capability[]; + capabilities?: Capability[]; /* An array that contains the write location for the Cosmos DB account. */ - readonly writeLocations: Location[]; + readonly writeLocations?: Location[]; /* An array that contains of the read locations enabled for the Cosmos DB account. */ - readonly readLocations: Location[]; + readonly readLocations?: Location[]; /* An array that contains all of the locations enabled for the Cosmos DB account. */ - readonly locations: Location[]; + readonly locations?: Location[]; /* An array that contains the regions ordered by their failover priorities. */ - readonly failoverPolicies: FailoverPolicy[]; + readonly failoverPolicies?: FailoverPolicy[]; /* List of Virtual Network ACL rules configured for the Cosmos DB account. */ - virtualNetworkRules: VirtualNetworkRule[]; + virtualNetworkRules?: VirtualNetworkRule[]; /* List of Private Endpoint Connections configured for the Cosmos DB account. */ - readonly privateEndpointConnections: PrivateEndpointConnection[]; + readonly privateEndpointConnections?: PrivateEndpointConnection[]; /* Enables the account to write in multiple locations */ - enableMultipleWriteLocations: boolean; + enableMultipleWriteLocations?: boolean; /* Enables the cassandra connector on the Cosmos DB C* account */ - enableCassandraConnector: boolean; + enableCassandraConnector?: boolean; /* The cassandra connector offer type for the Cosmos DB database C* account. */ - connectorOffer: ConnectorOffer; + connectorOffer?: ConnectorOffer; /* Disable write operations on metadata resources (databases, containers, throughput) via account keys */ - disableKeyBasedMetadataWriteAccess: boolean; + disableKeyBasedMetadataWriteAccess?: boolean; /* The URI of the key vault */ - keyVaultKeyUri: string; + keyVaultKeyUri?: string; /* Whether requests from Public Network are allowed */ - publicNetworkAccess: PublicNetworkAccess; + publicNetworkAccess?: PublicNetworkAccess; /* Flag to indicate whether Free Tier is enabled. */ - enableFreeTier: boolean; + enableFreeTier?: boolean; /* API specific properties. */ - apiProperties: ApiProperties; + apiProperties?: ApiProperties; /* Flag to indicate whether to enable storage analytics. */ - enableAnalyticalStorage: boolean; + enableAnalyticalStorage?: boolean; + /* The CORS policy for the Cosmos DB database account. */ + cors?: CorsPolicy[]; } /* Properties to create and update Azure Cosmos DB database accounts. */ export interface DatabaseAccountCreateUpdateProperties { /* The consistency policy for the Cosmos DB account. */ - consistencyPolicy: ConsistencyPolicy; + consistencyPolicy?: ConsistencyPolicy; /* An array that contains the georeplication locations enabled for the Cosmos DB account. */ locations: Location[]; @@ -434,45 +450,47 @@ export interface DatabaseAccountCreateUpdateProperties { databaseAccountOfferType: DatabaseAccountOfferType; /* List of IpRules. */ - ipRules: IPRules; + ipRules?: IPRules; /* Flag to indicate whether to enable/disable Virtual Network ACL rules. */ - isVirtualNetworkFilterEnabled: boolean; + isVirtualNetworkFilterEnabled?: boolean; /* Enables automatic failover of the write region in the rare event that the region is unavailable due to an outage. Automatic failover will result in a new write region for the account and is chosen based on the failover priorities configured for the account. */ - enableAutomaticFailover: boolean; + enableAutomaticFailover?: boolean; /* List of Cosmos DB capabilities for the account */ - capabilities: Capability[]; + capabilities?: Capability[]; /* List of Virtual Network ACL rules configured for the Cosmos DB account. */ - virtualNetworkRules: VirtualNetworkRule[]; + virtualNetworkRules?: VirtualNetworkRule[]; /* Enables the account to write in multiple locations */ - enableMultipleWriteLocations: boolean; + enableMultipleWriteLocations?: boolean; /* Enables the cassandra connector on the Cosmos DB C* account */ - enableCassandraConnector: boolean; + enableCassandraConnector?: boolean; /* The cassandra connector offer type for the Cosmos DB database C* account. */ - connectorOffer: ConnectorOffer; + connectorOffer?: ConnectorOffer; /* Disable write operations on metadata resources (databases, containers, throughput) via account keys */ - disableKeyBasedMetadataWriteAccess: boolean; + disableKeyBasedMetadataWriteAccess?: boolean; /* The URI of the key vault */ - keyVaultKeyUri: string; + keyVaultKeyUri?: string; /* Whether requests from Public Network are allowed */ - publicNetworkAccess: PublicNetworkAccess; + publicNetworkAccess?: PublicNetworkAccess; /* Flag to indicate whether Free Tier is enabled. */ - enableFreeTier: boolean; + enableFreeTier?: boolean; /* API specific properties. Currently, supported only for MongoDB API. */ - apiProperties: ApiProperties; + apiProperties?: ApiProperties; /* Flag to indicate whether to enable storage analytics. */ - enableAnalyticalStorage: boolean; + enableAnalyticalStorage?: boolean; + /* The CORS policy for the Cosmos DB database account. */ + cors?: CorsPolicy[]; } /* Parameters to create and update Cosmos DB database accounts. */ export type DatabaseAccountCreateUpdateParameters = ARMResourceProperties & { /* Indicates the type of database account. This can only be set at database account creation. */ - kind: string; + kind?: string; /* undocumented */ properties: DatabaseAccountCreateUpdateProperties; }; @@ -480,86 +498,88 @@ export type DatabaseAccountCreateUpdateParameters = ARMResourceProperties & { /* Properties to update Azure Cosmos DB database accounts. */ export interface DatabaseAccountUpdateProperties { /* The consistency policy for the Cosmos DB account. */ - consistencyPolicy: ConsistencyPolicy; + consistencyPolicy?: ConsistencyPolicy; /* An array that contains the georeplication locations enabled for the Cosmos DB account. */ - locations: Location[]; + locations?: Location[]; /* List of IpRules. */ - ipRules: IPRules; + ipRules?: IPRules; /* Flag to indicate whether to enable/disable Virtual Network ACL rules. */ - isVirtualNetworkFilterEnabled: boolean; + isVirtualNetworkFilterEnabled?: boolean; /* Enables automatic failover of the write region in the rare event that the region is unavailable due to an outage. Automatic failover will result in a new write region for the account and is chosen based on the failover priorities configured for the account. */ - enableAutomaticFailover: boolean; + enableAutomaticFailover?: boolean; /* List of Cosmos DB capabilities for the account */ - capabilities: Capability[]; + capabilities?: Capability[]; /* List of Virtual Network ACL rules configured for the Cosmos DB account. */ - virtualNetworkRules: VirtualNetworkRule[]; + virtualNetworkRules?: VirtualNetworkRule[]; /* Enables the account to write in multiple locations */ - enableMultipleWriteLocations: boolean; + enableMultipleWriteLocations?: boolean; /* Enables the cassandra connector on the Cosmos DB C* account */ - enableCassandraConnector: boolean; + enableCassandraConnector?: boolean; /* The cassandra connector offer type for the Cosmos DB database C* account. */ - connectorOffer: ConnectorOffer; + connectorOffer?: ConnectorOffer; /* Disable write operations on metadata resources (databases, containers, throughput) via account keys */ - disableKeyBasedMetadataWriteAccess: boolean; + disableKeyBasedMetadataWriteAccess?: boolean; /* The URI of the key vault */ - keyVaultKeyUri: string; + keyVaultKeyUri?: string; /* Whether requests from Public Network are allowed */ - publicNetworkAccess: PublicNetworkAccess; + publicNetworkAccess?: PublicNetworkAccess; /* Flag to indicate whether Free Tier is enabled. */ - enableFreeTier: boolean; + enableFreeTier?: boolean; /* API specific properties. Currently, supported only for MongoDB API. */ - apiProperties: ApiProperties; + apiProperties?: ApiProperties; /* Flag to indicate whether to enable storage analytics. */ - enableAnalyticalStorage: boolean; + enableAnalyticalStorage?: boolean; + /* The CORS policy for the Cosmos DB database account. */ + cors?: CorsPolicy[]; } /* Parameters for patching Azure Cosmos DB database account properties. */ export interface DatabaseAccountUpdateParameters { /* undocumented */ - tags: Tags; + tags?: Tags; /* The location of the resource group to which the resource belongs. */ - location: string; + location?: string; /* undocumented */ - properties: DatabaseAccountUpdateProperties; + properties?: DatabaseAccountUpdateProperties; } /* The read-only access keys for the given database account. */ export interface DatabaseAccountListReadOnlyKeysResult { /* Base 64 encoded value of the primary read-only key. */ - readonly primaryReadonlyMasterKey: string; + readonly primaryReadonlyMasterKey?: string; /* Base 64 encoded value of the secondary read-only key. */ - readonly secondaryReadonlyMasterKey: string; + readonly secondaryReadonlyMasterKey?: string; } /* The access keys for the given database account. */ export type DatabaseAccountListKeysResult = DatabaseAccountListReadOnlyKeysResult & { /* Base 64 encoded value of the primary read-write key. */ - readonly primaryMasterKey: string; + readonly primaryMasterKey?: string; /* Base 64 encoded value of the secondary read-write key. */ - readonly secondaryMasterKey: string; + readonly secondaryMasterKey?: string; }; /* Connection string for the Cosmos DB account */ export interface DatabaseAccountConnectionString { /* Value of the connection string */ - readonly connectionString: string; + readonly connectionString?: string; /* Description of the connection string */ - readonly description: string; + readonly description?: string; } /* The connection strings for the given database account. */ export interface DatabaseAccountListConnectionStringsResult { /* An array that contains the connection strings for the Cosmos DB account. */ - connectionStrings: DatabaseAccountConnectionString[]; + connectionStrings?: DatabaseAccountConnectionString[]; } /* Parameters to regenerate the keys within the database account. */ @@ -766,14 +786,14 @@ export interface GremlinGraphCreateUpdateProperties { /* Cosmos DB resource throughput object. Either throughput is required or autoscaleSettings is required, but not both. */ export interface ThroughputSettingsResource { /* Value of the Cosmos DB resource throughput. Either throughput is required or autoscaleSettings is required, but not both. */ - throughput: number; + throughput?: number; /* Cosmos DB resource for autoscale settings. Either throughput is required or autoscaleSettings is required, but not both. */ - autoscaleSettings: AutoscaleSettingsResource; + autoscaleSettings?: AutoscaleSettingsResource; /* The minimum throughput of the resource */ - readonly minimumThroughput: string; + readonly minimumThroughput?: string; /* The throughput replace is pending */ - readonly offerReplacePending: string; + readonly offerReplacePending?: string; } /* Cosmos DB provisioned throughput settings object */ @@ -781,32 +801,32 @@ export interface AutoscaleSettingsResource { /* Represents maximum throughput container can scale up to. */ maxThroughput: number; /* Cosmos DB resource auto-upgrade policy */ - autoUpgradePolicy: AutoUpgradePolicyResource; + autoUpgradePolicy?: AutoUpgradePolicyResource; /* Represents target maximum throughput container can scale up to once offer is no longer in pending state. */ - readonly targetMaxThroughput: number; + readonly targetMaxThroughput?: number; } /* Cosmos DB resource auto-upgrade policy */ export interface AutoUpgradePolicyResource { /* Represents throughput policy which service must adhere to for auto-upgrade */ - throughputPolicy: ThroughputPolicyResource; + throughputPolicy?: ThroughputPolicyResource; } /* Cosmos DB resource throughput policy */ export interface ThroughputPolicyResource { /* Determines whether the ThroughputPolicy is active or not */ - isEnabled: boolean; + isEnabled?: boolean; /* Represents the percentage by which throughput can increase every time throughput policy kicks in. */ - incrementPercent: number; + incrementPercent?: number; } /* Cosmos DB options resource object */ export interface OptionsResource { /* Value of the Cosmos DB resource throughput or autoscaleSettings. Use the ThroughputSetting resource when retrieving offer details. */ - throughput: number; + throughput?: number; /* Specifies the Autoscale settings. */ - autoscaleSettings: AutoscaleSettings; + autoscaleSettings?: AutoscaleSettings; } /* Cosmos DB SQL database resource object */ @@ -820,61 +840,65 @@ export interface SqlContainerResource { /* Name of the Cosmos DB SQL container */ id: string; /* The configuration of the indexing policy. By default, the indexing is automatic for all document paths within the container */ - indexingPolicy: IndexingPolicy; + indexingPolicy?: IndexingPolicy; /* The configuration of the partition key to be used for partitioning data into multiple partitions */ - partitionKey: ContainerPartitionKey; + partitionKey?: ContainerPartitionKey; /* Default time to live */ - defaultTtl: number; + defaultTtl?: number; /* The unique key policy configuration for specifying uniqueness constraints on documents in the collection in the Azure Cosmos DB service. */ - uniqueKeyPolicy: UniqueKeyPolicy; + uniqueKeyPolicy?: UniqueKeyPolicy; /* The conflict resolution policy for the container. */ - conflictResolutionPolicy: ConflictResolutionPolicy; + conflictResolutionPolicy?: ConflictResolutionPolicy; + + //TODO: this property is manually added. It should be auto-generated instead. Need to be fixed in the API spec. + /* Analytical storage time to live */ + analyticalStorageTtl?: number; } /* Cosmos DB indexing policy */ export interface IndexingPolicy { /* Indicates if the indexing policy is automatic */ - automatic: boolean; + automatic?: boolean; /* Indicates the indexing mode. */ - indexingMode: string; + indexingMode?: string; /* List of paths to include in the indexing */ - includedPaths: IncludedPath[]; + includedPaths?: IncludedPath[]; /* List of paths to exclude from indexing */ - excludedPaths: ExcludedPath[]; + excludedPaths?: ExcludedPath[]; /* List of composite path list */ - compositeIndexes: CompositePathList[]; + compositeIndexes?: CompositePathList[]; /* List of spatial specifics */ - spatialIndexes: SpatialSpec[]; + spatialIndexes?: SpatialSpec[]; } /* undocumented */ export interface ExcludedPath { /* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */ - path: string; + path?: string; } /* The paths that are included in indexing */ export interface IncludedPath { /* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */ - path: string; + path?: string; /* List of indexes for this path */ - indexes: Indexes[]; + indexes?: Indexes[]; } /* The indexes for the path. */ export interface Indexes { /* The datatype for which the indexing behavior is applied to. */ - dataType: string; + dataType?: string; /* The precision of the index. -1 is maximum precision. */ - precision: number; + precision?: number; /* Indicates the type of index. */ - kind: string; + kind?: string; } /* List of composite path */ @@ -883,17 +907,17 @@ export type CompositePathList = CompositePath[]; /* undocumented */ export interface CompositePath { /* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */ - path: string; + path?: string; /* Sort order for composite paths. */ - order: string; + order?: string; } /* undocumented */ export interface SpatialSpec { /* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */ - path: string; + path?: string; /* List of path's spatial type */ - types: SpatialType[]; + types?: SpatialType[]; } /* Indicates the spatial type of index. */ @@ -902,12 +926,12 @@ export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon"; /* The configuration of the partition key to be used for partitioning data into multiple partitions */ export interface ContainerPartitionKey { /* List of paths using which data within the container can be partitioned */ - paths: Path[]; + paths?: Path[]; /* Indicates the kind of algorithm used for partitioning */ - kind: string; + kind?: string; /* Indicates the version of the partition key definition */ - version: number; + version?: number; } /* A path. These typically start with root (/path) */ @@ -916,23 +940,23 @@ export type Path = string; /* The unique key policy configuration for specifying uniqueness constraints on documents in the collection in the Azure Cosmos DB service. */ export interface UniqueKeyPolicy { /* List of unique keys on that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service. */ - uniqueKeys: UniqueKey[]; + uniqueKeys?: UniqueKey[]; } /* The unique key on that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service. */ export interface UniqueKey { /* List of paths must be unique for each document in the Azure Cosmos DB service */ - paths: Path[]; + paths?: Path[]; } /* The conflict resolution policy for the container. */ export interface ConflictResolutionPolicy { /* Indicates the conflict resolution mode. */ - mode: string; + mode?: string; /* The conflict resolution path in the case of LastWriterWins mode. */ - conflictResolutionPath: string; + conflictResolutionPath?: string; /* The procedure to resolve conflicts in the case of custom mode. */ - conflictResolutionProcedure: string; + conflictResolutionProcedure?: string; } /* Cosmos DB SQL storedProcedure resource object */ @@ -940,7 +964,7 @@ export interface SqlStoredProcedureResource { /* Name of the Cosmos DB SQL storedProcedure */ id: string; /* Body of the Stored Procedure */ - body: string; + body?: string; } /* Cosmos DB SQL userDefinedFunction resource object */ @@ -948,7 +972,7 @@ export interface SqlUserDefinedFunctionResource { /* Name of the Cosmos DB SQL userDefinedFunction */ id: string; /* Body of the User Defined Function */ - body: string; + body?: string; } /* Cosmos DB SQL trigger resource object */ @@ -956,11 +980,11 @@ export interface SqlTriggerResource { /* Name of the Cosmos DB SQL trigger */ id: string; /* Body of the Trigger */ - body: string; + body?: string; /* Type of the Trigger */ - triggerType: string; + triggerType?: string; /* The operation the trigger is associated with */ - triggerOperation: string; + triggerOperation?: string; } /* Cosmos DB MongoDB database resource object */ @@ -974,13 +998,13 @@ export interface MongoDBCollectionResource { /* Name of the Cosmos DB MongoDB collection */ id: string; /* A key-value pair of shard keys to be applied for the request. */ - shardKey: ShardKeys; + shardKey?: ShardKeys; /* List of index keys */ - indexes: MongoIndex[]; + indexes?: MongoIndex[]; /* Analytical TTL. */ - analyticalStorageTtl: number; + analyticalStorageTtl?: number; } /* The shard key and partition kind pair, only support "Hash" partition kind */ @@ -989,16 +1013,16 @@ export type ShardKeys = { [key: string]: string }; /* Cosmos DB MongoDB collection index key */ export interface MongoIndex { /* Cosmos DB MongoDB collection index keys */ - key: MongoIndexKeys; + key?: MongoIndexKeys; /* Cosmos DB MongoDB collection index key options */ - options: MongoIndexOptions; + options?: MongoIndexOptions; } /* Cosmos DB MongoDB collection resource object */ export interface MongoIndexKeys { /* List of keys for each MongoDB collection in the Azure Cosmos DB service */ - keys: Key[]; + keys?: Key[]; } /* A Key. */ @@ -1007,9 +1031,9 @@ export type Key = string; /* Cosmos DB MongoDB collection index options */ export interface MongoIndexOptions { /* Expire after seconds */ - expireAfterSeconds: number; + expireAfterSeconds?: number; /* Is unique or not */ - unique: boolean; + unique?: boolean; } /* Cosmos DB table resource object */ @@ -1029,46 +1053,46 @@ export interface CassandraTableResource { /* Name of the Cosmos DB Cassandra table */ id: string; /* Time to live of the Cosmos DB Cassandra table */ - defaultTtl: number; + defaultTtl?: number; /* Schema of the Cosmos DB Cassandra table */ - schema: CassandraSchema; + schema?: CassandraSchema; /* Analytical TTL. */ - analyticalStorageTtl: number; + analyticalStorageTtl?: number; } /* Cosmos DB Cassandra table schema */ export interface CassandraSchema { /* List of Cassandra table columns. */ - columns: Column[]; + columns?: Column[]; /* List of partition key. */ - partitionKeys: CassandraPartitionKey[]; + partitionKeys?: CassandraPartitionKey[]; /* List of cluster key. */ - clusterKeys: ClusterKey[]; + clusterKeys?: ClusterKey[]; } /* Cosmos DB Cassandra table column */ export interface Column { /* Name of the Cosmos DB Cassandra table column */ - name: string; + name?: string; /* Type of the Cosmos DB Cassandra table column */ - type: string; + type?: string; } /* Cosmos DB Cassandra table partition key */ export interface CassandraPartitionKey { /* Name of the Cosmos DB Cassandra table partition key */ - name: string; + name?: string; } /* Cosmos DB Cassandra table cluster key */ export interface ClusterKey { /* Name of the Cosmos DB Cassandra table cluster key */ - name: string; + name?: string; /* Order of the Cosmos DB Cassandra table cluster key, only support "Asc" and "Desc" */ - orderBy: string; + orderBy?: string; } /* Cosmos DB Gremlin database resource object */ @@ -1082,38 +1106,38 @@ export interface GremlinGraphResource { /* Name of the Cosmos DB Gremlin graph */ id: string; /* The configuration of the indexing policy. By default, the indexing is automatic for all document paths within the graph */ - indexingPolicy: IndexingPolicy; + indexingPolicy?: IndexingPolicy; /* The configuration of the partition key to be used for partitioning data into multiple partitions */ - partitionKey: ContainerPartitionKey; + partitionKey?: ContainerPartitionKey; /* Default time to live */ - defaultTtl: number; + defaultTtl?: number; /* The unique key policy configuration for specifying uniqueness constraints on documents in the collection in the Azure Cosmos DB service. */ - uniqueKeyPolicy: UniqueKeyPolicy; + uniqueKeyPolicy?: UniqueKeyPolicy; /* The conflict resolution policy for the graph. */ - conflictResolutionPolicy: ConflictResolutionPolicy; + conflictResolutionPolicy?: ConflictResolutionPolicy; } /* CreateUpdateOptions are a list of key-value pairs that describe the resource. Supported keys are "If-Match", "If-None-Match", "Session-Token" and "Throughput" */ export interface CreateUpdateOptions { /* Request Units per second. For example, "throughput": 10000. */ - throughput: number; + throughput?: number; /* Specifies the Autoscale settings. */ - autoscaleSettings: AutoscaleSettings; + autoscaleSettings?: AutoscaleSettings; } /* undocumented */ export interface AutoscaleSettings { /* Represents maximum throughput, the resource can scale up to. */ - maxThroughput: number; + maxThroughput?: number; } /* Cosmos DB capability object */ export interface Capability { /* Name of the Cosmos DB capability. For example, "name": "EnableCassandra". Current values also include "EnableTable" and "EnableGremlin". */ - name: string; + name?: string; } /* Tags are a list of key-value pairs that describe the resource. These tags can be used in viewing and grouping this resource (across resource groups). A maximum of 15 tags can be provided for a resource. Each tag must have a key no greater than 128 characters and value no greater than 256 characters. For example, the default experience for a template type is set with "defaultExperience": "Cassandra". Current "defaultExperience" values also include "Table", "Graph", "DocumentDB", and "MongoDB". */ @@ -1128,231 +1152,231 @@ export type IPRules = IpAddressOrRange[]; /* IpAddressOrRange object */ export interface IpAddressOrRange { /* A single IPv4 address or a single IPv4 address range in CIDR format. Provided IPs must be well-formatted and cannot be contained in one of the following ranges: 10.0.0.0/8, 100.64.0.0/10, 172.16.0.0/12, 192.168.0.0/16, since these are not enforceable by the IP address filter. Example of valid inputs: “23.40.210.245” or “23.40.210.0/8”. */ - ipAddressOrRange: string; + ipAddressOrRange?: string; } /* Virtual Network ACL Rule object */ export interface VirtualNetworkRule { /* Resource ID of a subnet, for example: /subscriptions/{subscriptionId}/resourceGroups/{groupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}/subnets/{subnetName}. */ - id: string; + id?: string; /* Create firewall rule before the virtual network has vnet service endpoint enabled. */ - ignoreMissingVNetServiceEndpoint: boolean; + ignoreMissingVNetServiceEndpoint?: boolean; } /* A private endpoint connection */ export type PrivateEndpointConnection = unknown & { /* Resource properties. */ - properties: PrivateEndpointConnectionProperties; + properties?: PrivateEndpointConnectionProperties; }; /* Properties of a private endpoint connection. */ export interface PrivateEndpointConnectionProperties { /* Private endpoint which the connection belongs to. */ - privateEndpoint: PrivateEndpointProperty; + privateEndpoint?: PrivateEndpointProperty; /* Connection State of the Private Endpoint Connection. */ - privateLinkServiceConnectionState: PrivateLinkServiceConnectionStateProperty; + privateLinkServiceConnectionState?: PrivateLinkServiceConnectionStateProperty; } /* Private endpoint which the connection belongs to. */ export interface PrivateEndpointProperty { /* Resource id of the private endpoint. */ - id: string; + id?: string; } /* Connection State of the Private Endpoint Connection. */ export interface PrivateLinkServiceConnectionStateProperty { /* The private link service connection status. */ - status: string; + status?: string; /* Any action that is required beyond basic workflow (approve/ reject/ disconnect) */ - readonly actionsRequired: string; + readonly actionsRequired?: string; } /* REST API operation */ export interface Operation { /* Operation name: {provider}/{resource}/{operation} */ - name: string; + name?: string; /* The object that represents the operation. */ - display: unknown; + display?: unknown; } /* Result of the request to list Resource Provider operations. It contains a list of operations and a URL link to get the next set of results. */ export interface OperationListResult { /* List of operations supported by the Resource Provider. */ - value: Operation[]; + value?: Operation[]; /* URL to get the next set of operation list results if there are any. */ - nextLink: string; + nextLink?: string; } /* The response to a list usage request. */ export interface UsagesResult { /* The list of usages for the database. A usage is a point in time metric */ - readonly value: Usage[]; + readonly value?: Usage[]; } /* The usage data for a usage request. */ export interface Usage { /* The unit of the metric. */ - unit: UnitType; + unit?: UnitType; /* The name information for the metric. */ - readonly name: MetricName; + readonly name?: MetricName; /* The quota period used to summarize the usage values. */ - readonly quotaPeriod: string; + readonly quotaPeriod?: string; /* Maximum value for this metric */ - readonly limit: number; + readonly limit?: number; /* Current value for this metric */ - readonly currentValue: number; + readonly currentValue?: number; } /* The response to a list partition level usage request. */ export interface PartitionUsagesResult { /* The list of partition-level usages for the database. A usage is a point in time metric */ - readonly value: PartitionUsage[]; + readonly value?: PartitionUsage[]; } /* The partition level usage data for a usage request. */ export type PartitionUsage = Usage & { /* The partition id (GUID identifier) of the usages. */ - readonly partitionId: string; + readonly partitionId?: string; /* The partition key range id (integer identifier) of the usages. */ - readonly partitionKeyRangeId: string; + readonly partitionKeyRangeId?: string; }; /* The response to a list metric definitions request. */ export interface MetricDefinitionsListResult { /* The list of metric definitions for the account. */ - readonly value: MetricDefinition[]; + readonly value?: MetricDefinition[]; } /* The definition of a metric. */ export interface MetricDefinition { /* The list of metric availabilities for the account. */ - readonly metricAvailabilities: MetricAvailability[]; + readonly metricAvailabilities?: MetricAvailability[]; /* The primary aggregation type of the metric. */ - readonly primaryAggregationType: string; + readonly primaryAggregationType?: string; /* The unit of the metric. */ - unit: UnitType; + unit?: UnitType; /* The resource uri of the database. */ - readonly resourceUri: string; + readonly resourceUri?: string; /* The name information for the metric. */ - readonly name: MetricName; + readonly name?: MetricName; } /* The availability of the metric. */ export interface MetricAvailability { /* The time grain to be used to summarize the metric values. */ - readonly timeGrain: string; + readonly timeGrain?: string; /* The retention for the metric values. */ - readonly retention: string; + readonly retention?: string; } /* The response to a list metrics request. */ export interface MetricListResult { /* The list of metrics for the account. */ - readonly value: Metric[]; + readonly value?: Metric[]; } /* Metric data */ export interface Metric { /* The start time for the metric (ISO-8601 format). */ - readonly startTime: string; + readonly startTime?: string; /* The end time for the metric (ISO-8601 format). */ - readonly endTime: string; + readonly endTime?: string; /* The time grain to be used to summarize the metric values. */ - readonly timeGrain: string; + readonly timeGrain?: string; /* The unit of the metric. */ - unit: UnitType; + unit?: UnitType; /* The name information for the metric. */ - readonly name: MetricName; + readonly name?: MetricName; /* The metric values for the specified time window and timestep. */ - readonly metricValues: MetricValue[]; + readonly metricValues?: MetricValue[]; } /* A metric name. */ export interface MetricName { /* The name of the metric. */ - readonly value: string; + readonly value?: string; /* The friendly name of the metric. */ - readonly localizedValue: string; + readonly localizedValue?: string; } /* Represents metrics values. */ export interface MetricValue { /* The number of values for the metric. */ - readonly _count: number; + readonly _count?: number; /* The average value of the metric. */ - readonly average: number; + readonly average?: number; /* The max value of the metric. */ - readonly maximum: number; + readonly maximum?: number; /* The min value of the metric. */ - readonly minimum: number; + readonly minimum?: number; /* The metric timestamp (ISO-8601 format). */ - readonly timestamp: string; + readonly timestamp?: string; /* The total value of the metric. */ - readonly total: number; + readonly total?: number; } /* The response to a list percentile metrics request. */ export interface PercentileMetricListResult { /* The list of percentile metrics for the account. */ - readonly value: PercentileMetric[]; + readonly value?: PercentileMetric[]; } /* Percentile Metric data */ export interface PercentileMetric { /* The start time for the metric (ISO-8601 format). */ - readonly startTime: string; + readonly startTime?: string; /* The end time for the metric (ISO-8601 format). */ - readonly endTime: string; + readonly endTime?: string; /* The time grain to be used to summarize the metric values. */ - readonly timeGrain: string; + readonly timeGrain?: string; /* The unit of the metric. */ - unit: UnitType; + unit?: UnitType; /* The name information for the metric. */ - readonly name: MetricName; + readonly name?: MetricName; /* The percentile metric values for the specified time window and timestep. */ - readonly metricValues: PercentileMetricValue[]; + readonly metricValues?: PercentileMetricValue[]; } /* Represents percentile metrics values. */ export type PercentileMetricValue = MetricValue & { /* The 10th percentile value for the metric. */ - readonly P10: number; + readonly P10?: number; /* The 25th percentile value for the metric. */ - readonly P25: number; + readonly P25?: number; /* The 50th percentile value for the metric. */ - readonly P50: number; + readonly P50?: number; /* The 75th percentile value for the metric. */ - readonly P75: number; + readonly P75?: number; /* The 90th percentile value for the metric. */ - readonly P90: number; + readonly P90?: number; /* The 95th percentile value for the metric. */ - readonly P95: number; + readonly P95?: number; /* The 99th percentile value for the metric. */ - readonly P99: number; + readonly P99?: number; }; /* The response to a list partition metrics request. */ export interface PartitionMetricListResult { /* The list of partition-level metrics for the account. */ - readonly value: PartitionMetric[]; + readonly value?: PartitionMetric[]; } /* The metric values for a single partition. */ export type PartitionMetric = Metric & { /* The partition id (GUID identifier) of the metric values. */ - readonly partitionId: string; + readonly partitionId?: string; /* The partition key range id (integer identifier) of the metric values. */ - readonly partitionKeyRangeId: string; + readonly partitionKeyRangeId?: string; }; /* The unit of the metric. */ @@ -1367,5 +1391,5 @@ export type PublicNetworkAccess = "Enabled" | "Disabled"; /* undocumented */ export interface ApiProperties { /* Describes the ServerVersion of an a MongoDB account. */ - serverVersion: string; + serverVersion?: string; } diff --git a/src/Utils/arm/request.ts b/src/Utils/arm/request.ts index 8992397f5..02dc92749 100644 --- a/src/Utils/arm/request.ts +++ b/src/Utils/arm/request.ts @@ -6,15 +6,9 @@ Instead, generate ARM clients that consume this function with stricter typing. */ import promiseRetry, { AbortError } from "p-retry"; +import { ErrorResponse } from "./generatedClients/2020-04-01/types"; import { userContext } from "../../UserContext"; -interface ErrorResponse { - error: { - code: string; - message: string; - }; -} - interface ARMError extends Error { code: string; } @@ -44,8 +38,8 @@ export async function armRequest({ host, path, apiVersion, method, body: requ }); if (!response.ok) { const errorResponse = (await response.json()) as ErrorResponse; - const error = new Error(errorResponse.error?.message) as ARMError; - error.code = errorResponse.error.code; + const error = new Error(errorResponse.message) as ARMError; + error.code = errorResponse.code; throw error; } @@ -92,8 +86,8 @@ async function getOperationStatus(operationStatusUrl: string) { }); if (!response.ok) { const errorResponse = (await response.json()) as ErrorResponse; - const error = new Error(errorResponse.error?.message) as ARMError; - error.code = errorResponse.error.code; + const error = new Error(errorResponse.message) as ARMError; + error.code = errorResponse.code; throw new AbortError(error); } const body = (await response.json()) as OperationResponse; diff --git a/src/explorer.html b/src/explorer.html index 263e6ab98..79e6bc738 100644 --- a/src/explorer.html +++ b/src/explorer.html @@ -106,6 +106,7 @@ click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }" tabindex="0" aria-label="Refresh tree" + title="Refresh tree" > Hide @@ -259,7 +261,7 @@

    Azure Cosmos DB

    Welcome to Azure Cosmos DB

    -

    Connecting...

    +
  • diff --git a/utils/armClientGenerator/generator.ts b/utils/armClientGenerator/generator.ts index b0b506f98..b4bd7023f 100644 --- a/utils/armClientGenerator/generator.ts +++ b/utils/armClientGenerator/generator.ts @@ -102,31 +102,31 @@ interface Property { }[]; } -const propertyToType = (property: Property, prop: string) => { +const propertyToType = (property: Property, prop: string, required: boolean) => { if (property) { if (property.allOf) { outputTypes.push(` /* ${property.description || "undocumented"} */ - ${property.readOnly ? "readonly " : ""}${prop}: ${property.allOf - .map((allof: { $ref: string }) => refToType(allof.$ref)) - .join(" & ")}`); + ${property.readOnly ? "readonly " : ""}${prop}${ + required ? "" : "?" + }: ${property.allOf.map((allof: { $ref: string }) => refToType(allof.$ref)).join(" & ")}`); } else if (property.$ref) { const type = refToType(property.$ref); outputTypes.push(` /* ${property.description || "undocumented"} */ - ${property.readOnly ? "readonly " : ""}${prop}: ${type} + ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type} `); } else if (property.type === "array") { const type = refToType(property.items.$ref); outputTypes.push(` /* ${property.description || "undocumented"} */ - ${property.readOnly ? "readonly " : ""}${prop}: ${type}[] + ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type}[] `); } else if (property.type === "object") { const type = refToType(property.$ref); outputTypes.push(` /* ${property.description || "undocumented"} */ - ${property.readOnly ? "readonly " : ""}${prop}: ${type} + ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${type} `); } else { if (property.type === undefined) { @@ -135,7 +135,7 @@ const propertyToType = (property: Property, prop: string) => { } outputTypes.push(` /* ${property.description || "undocumented"} */ - ${property.readOnly ? "readonly " : ""}${prop}: ${ + ${property.readOnly ? "readonly " : ""}${prop}${required ? "" : "?"}: ${ propertyMap[property.type] ? propertyMap[property.type] : property.type }`); } @@ -166,7 +166,7 @@ async function main() { } for (const prop in schema.definitions[definition].properties) { const property = schema.definitions[definition].properties[prop]; - propertyToType(property, prop); + propertyToType(property, prop, schema.definitions[definition].required?.includes(prop)); } outputTypes.push(`}`); outputTypes.push("\n\n"); @@ -245,7 +245,7 @@ async function main() { ) : Promise<${responseType(operation, "Types")}> { const path = \`${path.replace(/{/g, "${")}\` return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "${method.toLocaleUpperCase()}", apiVersion, ${ - bodyParameter ? "body: JSON.stringify(body)" : "" + bodyParameter ? "body" : "" } }) } `); diff --git a/webpack.config.js b/webpack.config.js index e8a57219c..f2c5cef4d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -219,7 +219,7 @@ module.exports = function(env = {}, argv = {}) { // Hack since it is hard to disable watch entirely with webpack dev server https://github.com/webpack/webpack-dev-server/issues/1251#issuecomment-654240734 watchOptions: isCI ? { poll: 24 * 60 * 60 * 1000 } : {}, devServer: { - hot: !isCI, + hot: false, inline: !isCI, liveReload: !isCI, https: true,