From 6bc506b81fd7389ae65f8fc6001855e9534a8baf Mon Sep 17 00:00:00 2001 From: victor-meng <56978073+victor-meng@users.noreply.github.com> Date: Thu, 3 Sep 2020 13:05:22 -0700 Subject: [PATCH] Clean up unused utility functions for creating databases and collections (#181) --- .eslintignore | 5 - __mocks__/AddDatabaseUtility.ts | 5 - src/Common/MongoProxyClient.test.ts | 63 +--- src/Common/MongoProxyClient.ts | 88 ----- src/Explorer/Panes/AddCollectionPane.ts | 20 +- src/Explorer/Panes/AddDatabasePane.ts | 5 - .../Panes/CassandraAddCollectionPane.ts | 4 +- src/Shared/AddCollectionUtility.test.ts | 210 +++--------- src/Shared/AddCollectionUtility.ts | 318 ++---------------- src/Shared/AddDatabaseUtility.test.ts | 153 --------- src/Shared/AddDatabaseUtility.ts | 187 ---------- 11 files changed, 88 insertions(+), 970 deletions(-) delete mode 100644 __mocks__/AddDatabaseUtility.ts delete mode 100644 src/Shared/AddDatabaseUtility.test.ts delete mode 100644 src/Shared/AddDatabaseUtility.ts diff --git a/.eslintignore b/.eslintignore index 94dd6b723..42351f99a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -266,10 +266,6 @@ src/ResourceProvider/ResourceProviderClientFactory.ts src/RouteHandlers/RouteHandler.ts src/RouteHandlers/TabRouteHandler.test.ts src/RouteHandlers/TabRouteHandler.ts -src/Shared/AddCollectionUtility.test.ts -src/Shared/AddCollectionUtility.ts -src/Shared/AddDatabaseUtility.test.ts -src/Shared/AddDatabaseUtility.ts src/Shared/Constants.ts src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.ts @@ -418,6 +414,5 @@ cypress/integration/dataexplorer/SQL/addCollection.spec.ts cypress/integration/dataexplorer/TABLE/addCollection.spec.ts cypress/integration/notebook/newNotebook.spec.ts cypress/integration/notebook/resourceTree.spec.ts -__mocks__/AddDatabaseUtility.ts __mocks__/monaco-editor.ts src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx \ No newline at end of file diff --git a/__mocks__/AddDatabaseUtility.ts b/__mocks__/AddDatabaseUtility.ts deleted file mode 100644 index 11561a643..000000000 --- a/__mocks__/AddDatabaseUtility.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class AddDbUtilities { - createGremlinDatabase(params: any) { - return Promise.resolve(1) - } -} \ No newline at end of file diff --git a/src/Common/MongoProxyClient.test.ts b/src/Common/MongoProxyClient.test.ts index 9fa04be0f..aa8c612ce 100644 --- a/src/Common/MongoProxyClient.test.ts +++ b/src/Common/MongoProxyClient.test.ts @@ -5,14 +5,7 @@ import { Collection } from "../Contracts/ViewModels"; import DocumentId from "../Explorer/Tree/DocumentId"; import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; import { updateUserContext } from "../UserContext"; -import { - deleteDocument, - getEndpoint, - queryDocuments, - readDocument, - updateDocument, - _createMongoCollectionWithARM -} from "./MongoProxyClient"; +import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient"; jest.mock("../ResourceProvider/ResourceProviderClient.ts"); const databaseId = "testDB"; @@ -260,58 +253,4 @@ describe("MongoProxyClient", () => { expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); }); }); - - describe("createMongoCollectionWithARM", () => { - it("should create a collection with autopilot when autopilot is selected + shared throughput is false", () => { - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - const properties = { - pk: "state", - coll: "abc-collection", - cd: true, - db: "a1-db", - st: false, - sid: "a2", - rg: "c1", - dba: "main", - is: false - }; - _createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" }); - expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith( - "subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection", - "2020-04-01", - { - properties: { - options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, - resource: { id: "abc-collection" } - } - } - ); - }); - it("should create a collection with provisioned throughput when provisioned throughput is selected + shared throughput is false", () => { - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - const properties = { - pk: "state", - coll: "abc-collection", - cd: true, - db: "a1-db", - st: false, - sid: "a2", - rg: "c1", - dba: "main", - is: false, - offerThroughput: 400 - }; - _createMongoCollectionWithARM("management.azure.com", properties, undefined); - expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith( - "subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection", - "2020-04-01", - { - properties: { - options: { throughput: "400" }, - resource: { id: "abc-collection" } - } - } - ); - }); - }); }); diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index bef3c3208..285e3343a 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -1,15 +1,12 @@ import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import queryString from "querystring"; import { AuthType } from "../AuthType"; -import * as DataExplorerConstants from "../Common/Constants"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; import { MessageTypes } from "../Contracts/ExplorerContracts"; import { Collection } from "../Contracts/ViewModels"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import DocumentId from "../Explorer/Tree/DocumentId"; -import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; -import { AddDbUtilities } from "../Shared/AddDatabaseUtility"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; import { userContext } from "../UserContext"; @@ -330,48 +327,6 @@ export function createMongoCollectionWithProxy( }); } -export function createMongoCollectionWithARM( - armEndpoint: string, - databaseId: string, - analyticalStorageTtl: number, - collectionId: string, - offerThroughput: number, - shardKey: string, - createDatabase: boolean, - sharedThroughput: boolean, - isSharded: boolean, - additionalOptions?: DataModels.RpOptions -): Promise { - const databaseAccount = userContext.databaseAccount; - const params: DataModels.MongoParameters = { - resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, - db: databaseId, - coll: collectionId, - pk: shardKey, - offerThroughput, - cd: createDatabase, - st: sharedThroughput, - is: isSharded, - rid: "", - rtype: "colls", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - analyticalStorageTtl - }; - - if (createDatabase) { - return AddDbUtilities.createMongoDatabaseWithARM( - armEndpoint, - params, - sharedThroughput ? additionalOptions : {} - ).then(() => { - return _createMongoCollectionWithARM(armEndpoint, params, sharedThroughput ? {} : additionalOptions); - }); - } - return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions); -} - export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string { const serverId = window.dataExplorer.serverId(); const extensionEndpoint = window.dataExplorer.extensionEndpoint(); @@ -404,46 +359,3 @@ async function errorHandling(response: Response, action: string, params: unknown export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string { return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`; } - -export async function _createMongoCollectionWithARM( - armEndpoint: string, - params: DataModels.MongoParameters, - rpOptions: DataModels.RpOptions -): Promise { - const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = { - properties: { - resource: { - id: params.coll - }, - options: {} - } - }; - - if (params.is) { - rpPayloadToCreateCollection.properties.resource["shardKey"] = { [params.pk]: "Hash" }; - } - - if (!params.st) { - if (rpOptions) { - rpPayloadToCreateCollection.properties.options = rpOptions; - } else { - rpPayloadToCreateCollection.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - if (params.analyticalStorageTtl) { - rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl; - } - - try { - return new ResourceProviderClient(armEndpoint).putAsync( - getARMCreateCollectionEndpoint(params), - DataExplorerConstants.ArmApiVersions.publicVersion, - rpPayloadToCreateCollection - ); - } catch (response) { - errorHandling(response, "creating collection", undefined); - return undefined; - } -} diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index dd520949c..b3aeda75d 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -943,7 +943,7 @@ export default class AddCollectionPane extends ContextualPaneBase { const defaultThroughput = this.container.collectionCreationDefaults.throughput; this.throughputSinglePartition(defaultThroughput.fixed); this.throughputMultiPartition( - AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container) + AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container) ); this.throughputDatabase(defaultThroughput.shared); @@ -1167,17 +1167,19 @@ export default class AddCollectionPane extends ContextualPaneBase { private _updateThroughputLimitByCollectionStorage() { const storage = this.storage(); - const minThroughputRU = AddCollectionUtility.Utilities.getMinRUForStorageOption( - this.container.collectionCreationDefaults, - storage - ); + const minThroughputRU = + storage === SharedConstants.CollectionCreation.storage10Gb + ? SharedConstants.CollectionCreation.DefaultCollectionRUs400 + : this.container.collectionCreationDefaults.throughput.unlimitedmin; - let maxThroughputRU = AddCollectionUtility.Utilities.getMaxRUForStorageOption( - this.container.collectionCreationDefaults, - storage - ); + let maxThroughputRU; if (this.isTryCosmosDBSubscription()) { maxThroughputRU = Constants.TryCosmosExperience.maxRU; + } else { + maxThroughputRU = + storage === SharedConstants.CollectionCreation.storage10Gb + ? SharedConstants.CollectionCreation.DefaultCollectionRUs10K + : this.container.collectionCreationDefaults.throughput.unlimitedmax; } this.minThroughputRU(minThroughputRU); diff --git a/src/Explorer/Panes/AddDatabasePane.ts b/src/Explorer/Panes/AddDatabasePane.ts index e5e831180..e395a839f 100644 --- a/src/Explorer/Panes/AddDatabasePane.ts +++ b/src/Explorer/Panes/AddDatabasePane.ts @@ -1,4 +1,3 @@ -import * as AddCollectionUtility from "../../Shared/AddCollectionUtility"; import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; import * as Constants from "../../Common/Constants"; import * as DataModels from "../../Contracts/DataModels"; @@ -8,15 +7,11 @@ 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 TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -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 { userContext } from "../../UserContext"; export default class AddDatabasePane extends ContextualPaneBase { public defaultExperience: ko.Computed; diff --git a/src/Explorer/Panes/CassandraAddCollectionPane.ts b/src/Explorer/Panes/CassandraAddCollectionPane.ts index 00097e86f..66fa4af24 100644 --- a/src/Explorer/Panes/CassandraAddCollectionPane.ts +++ b/src/Explorer/Panes/CassandraAddCollectionPane.ts @@ -494,9 +494,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase { this.selectedSharedAutoPilotTier(null); this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); - this.throughput( - AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container) - ); + this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)); this.keyspaceThroughput(throughputDefaults.shared); this.maxThroughputRU(throughputDefaults.unlimitedmax); this.minThroughputRU(throughputDefaults.unlimitedmin); diff --git a/src/Shared/AddCollectionUtility.test.ts b/src/Shared/AddCollectionUtility.test.ts index f459e3a41..e539e4313 100644 --- a/src/Shared/AddCollectionUtility.test.ts +++ b/src/Shared/AddCollectionUtility.test.ts @@ -1,162 +1,64 @@ -import * as SharedConstants from "../Shared/Constants"; -import { AddDbUtilities } from "../Shared/AddDatabaseUtility"; -import { CreateCollectionUtilities, CreateSqlCollectionUtilities, Utilities } from "./AddCollectionUtility"; -jest.mock("AddDatabaseUtility"); +import * as ko from "knockout"; +import { Collection, Database } from "../Contracts/ViewModels"; +import { getMaxThroughput } from "./AddCollectionUtility"; +import Explorer from "../Explorer/Explorer"; -const armEndpoint = "https://management.azure.com"; +describe("getMaxThroughput", () => { + it("default unlimited throughput setting", () => { + const defaults = { + storage: "100", + throughput: { + fixed: 400, + unlimited: 400, + unlimitedmax: 1000000, + unlimitedmin: 400, + shared: 400 + } + }; -describe("Add Collection Utitlity", () => { - describe("createSqlCollection", () => { - it("should invoke createSqlCollectionWithARM if create database is false", () => { - const properties = { - uniqueKeyPolicy: { uniqueKeys: [{ paths: [""] }] }, - cd: false, - coll: "abc-collection", - db: "a1-db", - dba: "main", - offerThroughput: 50000, - pk: "state", - sid: "a1", - rg: "b1", - st: true, - defaultTtl: -1, - indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed, - partitionKeyVersion: 2 - }; - const additionalOptions = {}; - const createSqlCollectionWithARMSpy = jest.spyOn(CreateSqlCollectionUtilities, "createSqlCollectionWithARM"); - CreateSqlCollectionUtilities.createSqlCollection( - armEndpoint, - properties.db, - properties.defaultTtl, - properties.coll, - properties.indexingPolicy, - properties.offerThroughput, - properties.pk, - properties.partitionKeyVersion, - properties.cd, - properties.st, - properties.sid, - properties.rg, - properties.dba, - properties.uniqueKeyPolicy, - additionalOptions + expect(getMaxThroughput(defaults, {} as Explorer)).toEqual(defaults.throughput.unlimited); + }); + + describe("no unlimited throughput setting", () => { + const defaults = { + storage: "100", + throughput: { + fixed: 400, + unlimited: { + collectionThreshold: 3, + lessThanOrEqualToThreshold: 400, + greatThanThreshold: 500 + }, + unlimitedmax: 1000000, + unlimitedmin: 400, + shared: 400 + } + }; + + const mockCollection1 = { id: ko.observable("collection1") } as Collection; + const mockCollection2 = { id: ko.observable("collection2") } as Collection; + const mockCollection3 = { id: ko.observable("collection3") } as Collection; + const mockCollection4 = { id: ko.observable("collection4") } as Collection; + const mockDatabase = {} as Database; + const mockContainer = { + databases: ko.observableArray([mockDatabase]) + } as Explorer; + + it("less than or equal to collection threshold", () => { + mockDatabase.collections = ko.observableArray([mockCollection1, mockCollection2]); + expect(getMaxThroughput(defaults, mockContainer)).toEqual( + defaults.throughput.unlimited.lessThanOrEqualToThreshold ); - expect(createSqlCollectionWithARMSpy).toHaveBeenCalled(); }); - it("should invoke createSqlDatabase + createSqlCollectionWithARM if create database is true", () => { - const properties = { - uniqueKeyPolicy: { uniqueKeys: [{ paths: [""] }] }, - cd: true, - coll: "abc-collection", - db: "a1-db", - dba: "main", - offerThroughput: 50000, - pk: "state", - sid: "a1", - rg: "b1", - st: true, - analyticalStorageTtl: -1, - indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed, - partitionKeyVersion: 2 - }; - const additionalOptions = {}; - const createSqlCollectionWithARMSpy = jest.spyOn(CreateSqlCollectionUtilities, "createSqlCollectionWithARM"); - const createSqlDatabaseSpy = jest.spyOn(AddDbUtilities, "createSqlDatabase"); - CreateSqlCollectionUtilities.createSqlCollection( - armEndpoint, - properties.db, - properties.analyticalStorageTtl, - properties.coll, - properties.indexingPolicy, - properties.offerThroughput, - properties.pk, - properties.partitionKeyVersion, - properties.cd, - properties.st, - properties.sid, - properties.rg, - properties.dba, - properties.uniqueKeyPolicy, - additionalOptions - ); - expect(createSqlCollectionWithARMSpy).toHaveBeenCalled(); - expect(createSqlDatabaseSpy).toHaveBeenCalled(); - }); - }); -}); - -describe("Add Collection Utitlity", () => { - describe("createGremlinGraph", () => { - it("should invoke createGremlinGraphWithARM if create database is false", () => { - const properties = { - cd: false, - coll: "abc-collection", - db: "a1-db", - dba: "main", - offerThroughput: 50000, - pk: "state", - sid: "a1", - rg: "b1", - st: true, - indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed, - partitionKeyVersion: 2 - }; - const additionalOptions = {}; - const createGremlinGraphWithARMSpy = jest.spyOn(CreateCollectionUtilities, "createGremlinGraphWithARM"); - CreateCollectionUtilities.createGremlinGraph( - armEndpoint, - properties.db, - properties.coll, - properties.indexingPolicy, - properties.offerThroughput, - properties.pk, - properties.partitionKeyVersion, - properties.cd, - properties.st, - properties.sid, - properties.rg, - properties.dba, - additionalOptions - ); - expect(createGremlinGraphWithARMSpy).toHaveBeenCalled(); - }); - - it("should invoke createGremlinDatabase + createGremlinGraphWithARM if create database is true", () => { - const properties = { - cd: true, - coll: "abc-collection", - db: "a1-db", - dba: "main", - offerThroughput: 50000, - pk: "state", - sid: "a1", - rg: "b1", - st: true, - indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed, - partitionKeyVersion: 2 - }; - const additionalOptions = {}; - const createGremlinGraphWithARMSpy = jest.spyOn(CreateCollectionUtilities, "createGremlinGraphWithARM"); - const createGremlinDatabaseSpy = jest.spyOn(AddDbUtilities, "createGremlinDatabase"); - CreateCollectionUtilities.createGremlinGraph( - armEndpoint, - properties.db, - properties.coll, - properties.indexingPolicy, - properties.offerThroughput, - properties.pk, - properties.partitionKeyVersion, - properties.cd, - properties.st, - properties.sid, - properties.rg, - properties.dba, - additionalOptions - ); - expect(createGremlinDatabaseSpy).toHaveBeenCalled(); - expect(createGremlinGraphWithARMSpy).toHaveBeenCalled(); + it("exceeds collection threshold", () => { + mockDatabase.collections = ko.observableArray([ + mockCollection1, + mockCollection2, + mockCollection3, + mockCollection4 + ]); + expect(getMaxThroughput(defaults, mockContainer)).toEqual(defaults.throughput.unlimited.greatThanThreshold); }); }); }); diff --git a/src/Shared/AddCollectionUtility.ts b/src/Shared/AddCollectionUtility.ts index d191063fe..e43a0b617 100644 --- a/src/Shared/AddCollectionUtility.ts +++ b/src/Shared/AddCollectionUtility.ts @@ -1,303 +1,23 @@ -import * as _ from "underscore"; -import * as DataExplorerConstants from "../Common/Constants"; -import * as DataModels from "../Contracts/DataModels"; -import * as SharedConstants from "./Constants"; -import * as ViewModels from "../Contracts/ViewModels"; -import { AddDbUtilities } from "../Shared/AddDatabaseUtility"; -import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; -import { HttpStatusCodes } from "../Common/Constants"; -import { sendMessage } from "../Common/MessageHandler"; -import { MessageTypes } from "../Contracts/ExplorerContracts"; -import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; -import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; +import { any } from "underscore"; +import { CollectionCreationDefaults } from "../Contracts/ViewModels"; import Explorer from "../Explorer/Explorer"; -import { userContext } from "../UserContext"; -export class CreateSqlCollectionUtilities { - public static createSqlCollection( - armEndpoint: string, - databaseId: string, - analyticalStorageTtl: number, - collectionId: string, - indexingPolicy: DataModels.IndexingPolicy, - offerThroughput: number, - partitionKey: string, - partitionKeyVersion: number, - createDatabase: boolean, - useDatabaseSharedOffer: boolean, - sid: string, - rg: string, - dba: string, - uniqueKeyPolicy: DataModels.UniqueKeyPolicy, - additionalOptions: DataModels.RpOptions - ): Promise { - const params: DataModels.SqlCollectionParameters = { - uniqueKeyPolicy, - db: databaseId, - coll: collectionId, - pk: partitionKey, - offerThroughput, - cd: createDatabase, - st: useDatabaseSharedOffer, - sid, - rg, - dba, - analyticalStorageTtl, - indexingPolicy, - partitionKeyVersion - }; - - if (params.cd) { - return AddDbUtilities.createSqlDatabase(armEndpoint, params, additionalOptions).then(() => { - return CreateSqlCollectionUtilities.createSqlCollectionWithARM(armEndpoint, params, additionalOptions); - }); - } - return CreateSqlCollectionUtilities.createSqlCollectionWithARM(armEndpoint, params, additionalOptions); +export const getMaxThroughput = (defaults: CollectionCreationDefaults, container: Explorer): number => { + const throughput = defaults.throughput.unlimited; + if (typeof throughput === "number") { + return throughput; + } else { + return _exceedsThreshold(throughput.collectionThreshold, container) + ? throughput.greatThanThreshold + : throughput.lessThanOrEqualToThreshold; } +}; - public static async createSqlCollectionWithARM( - armEndpoint: string, - params: DataModels.SqlCollectionParameters, - rpOptions: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateCollection: DataModels.SqlCollectionCreationRequest = { - properties: { - resource: { - id: params.coll, - partitionKey: { - paths: [params.pk], - kind: "Hash", - version: params.partitionKeyVersion - } - }, - options: {} - } - }; - - if (params.analyticalStorageTtl) { - rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl; - } - - if (params.indexingPolicy) { - rpPayloadToCreateCollection.properties.resource.indexingPolicy = params.indexingPolicy; - } - - if (!params.st) { - if (rpOptions) { - rpPayloadToCreateCollection.properties.options = rpOptions; - } else { - rpPayloadToCreateCollection.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - if (params.uniqueKeyPolicy) { - rpPayloadToCreateCollection.properties.resource.uniqueKeyPolicy = params.uniqueKeyPolicy; - } - - try { - let response = await new ResourceProviderClient(armEndpoint).putAsync( - CreateSqlCollectionUtilities.getSqlCollectionUri(params), - DataExplorerConstants.ArmApiVersions.publicVersion, - rpPayloadToCreateCollection - ); - return response; - } catch (response) { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error creating collection: ${JSON.stringify(response)}` - ); - if (response.status === HttpStatusCodes.Forbidden) { - sendMessage({ type: MessageTypes.ForbiddenError }); - } - throw new Error(`Error creating collection`); - } - } - - public static getSqlCollectionUri(params: DataModels.SqlCollectionParameters): string { - return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/sqlDatabases/${params.db}/containers/${params.coll}`; - } -} - -export class CreateCollectionUtilities { - public static createGremlinGraph( - armEndpoint: string, - databaseId: string, - collectionId: string, - indexingPolicy: DataModels.IndexingPolicy, - offerThroughput: number, - partitionKey: string, - partitionKeyVersion: number, - createDatabase: boolean, - useDatabaseSharedOffer: boolean, - sid: string, - rg: string, - dba: string, - additionalOptions: DataModels.RpOptions - ): Promise { - const params: DataModels.GraphParameters = { - db: databaseId, - coll: collectionId, - pk: partitionKey, - offerThroughput, - cd: createDatabase, - st: useDatabaseSharedOffer, - sid, - rg, - dba, - indexingPolicy, - partitionKeyVersion - }; - - if (params.cd) { - return AddDbUtilities.createGremlinDatabase(armEndpoint, params, additionalOptions).then(() => { - return CreateCollectionUtilities.createGremlinGraphWithARM(armEndpoint, params, additionalOptions); - }); - } - return CreateCollectionUtilities.createGremlinGraphWithARM(armEndpoint, params, additionalOptions); - } - - public static async createGremlinGraphWithARM( - armEndpoint: string, - params: DataModels.GraphParameters, - rpOptions: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateCollection: DataModels.GraphCreationRequest = { - properties: { - resource: { - id: params.coll, - partitionKey: { - paths: [params.pk], - kind: "Hash", - version: params.partitionKeyVersion - } - }, - options: {} - } - }; - - if (params.indexingPolicy) { - rpPayloadToCreateCollection.properties.resource.indexingPolicy = params.indexingPolicy; - } - - if (!params.st) { - if (rpOptions) { - rpPayloadToCreateCollection.properties.options = rpOptions; - } else { - rpPayloadToCreateCollection.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - try { - let response = await new ResourceProviderClient(armEndpoint).putAsync( - CreateCollectionUtilities.getGremlinGraphUri(params), - DataExplorerConstants.ArmApiVersions.publicVersion, - rpPayloadToCreateCollection - ); - return response; - } catch (response) { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error creating graph: ${JSON.stringify(response)}` - ); - if (response.status === HttpStatusCodes.Forbidden) { - sendMessage({ type: MessageTypes.ForbiddenError }); - } - throw new Error(`Error creating graph`); - } - } - - public static getGremlinGraphUri(params: DataModels.GraphParameters): string { - return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/gremlinDatabases/${params.db}/graphs/${params.coll}`; - } -} -export class Utilities { - public static async createAzureTableWithARM( - armEndpoint: string, - params: DataModels.CreateDatabaseAndCollectionRequest, - rpOptions: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateDatabase: DataModels.CreationRequest = { - properties: { - resource: { - id: params.collectionId - }, - options: {} - } - }; - - if (!params.databaseLevelThroughput) { - if (rpOptions) { - rpPayloadToCreateDatabase.properties.options = rpOptions; - } else { - rpPayloadToCreateDatabase.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - try { - await new ResourceProviderClient(armEndpoint).putAsync( - Utilities._getAzureTableUri(params), - DataExplorerConstants.ArmApiVersions.publicVersion, - rpPayloadToCreateDatabase - ); - } catch (reason) { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error creating table: ${JSON.stringify(reason)}, Payload: ${params}` - ); - if (reason.status === HttpStatusCodes.Forbidden) { - sendMessage({ type: MessageTypes.ForbiddenError }); - return; - } - throw new Error(`Error creating table`); - } - } - - public static getMaxRUForStorageOption( - defaults: ViewModels.CollectionCreationDefaults, - storageOption: string - ): number { - if (storageOption === SharedConstants.CollectionCreation.storage10Gb) { - return SharedConstants.CollectionCreation.DefaultCollectionRUs10K; - } - - return defaults.throughput.unlimitedmax; - } - - public static getMinRUForStorageOption( - defaults: ViewModels.CollectionCreationDefaults, - storageOption: string - ): number { - if (storageOption === SharedConstants.CollectionCreation.storage10Gb) { - return SharedConstants.CollectionCreation.DefaultCollectionRUs400; - } - - return defaults.throughput.unlimitedmin; - } - - public static getMaxThroughput(defaults: ViewModels.CollectionCreationDefaults, container: Explorer): number { - const throughput = defaults.throughput.unlimited; - if (typeof throughput === "number") { - return throughput; - } else { - return this._exceedsThreshold(throughput.collectionThreshold, container) - ? throughput.greatThanThreshold - : throughput.lessThanOrEqualToThreshold; - } - } - - private static _exceedsThreshold(unlimitedThreshold: number, container: Explorer): boolean { - const databases = (container && container.databases && container.databases()) || []; - return _.any( - databases, - database => - database && database.collections && database.collections() && database.collections().length > unlimitedThreshold - ); - } - - private static _getAzureTableUri(params: DataModels.CreateDatabaseAndCollectionRequest): string { - return `subscriptions/${userContext.subscriptionId}/resourceGroups/${userContext.resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/tables/${params.collectionId}`; - } -} +const _exceedsThreshold = (unlimitedThreshold: number, container: Explorer): boolean => { + const databases = (container && container.databases && container.databases()) || []; + return any( + databases, + database => + database && database.collections && database.collections() && database.collections().length > unlimitedThreshold + ); +}; diff --git a/src/Shared/AddDatabaseUtility.test.ts b/src/Shared/AddDatabaseUtility.test.ts deleted file mode 100644 index 1fa4607af..000000000 --- a/src/Shared/AddDatabaseUtility.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { AddDbUtilities } from "./AddDatabaseUtility"; -import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; -jest.mock("../ResourceProvider/ResourceProviderClient.ts"); - -describe("Add Database Utitlity", () => { - const armEndpoint = "https://management.azure.com"; - const properties = { - pk: "state", - coll: "abc-collection", - cd: true, - db: "a1-db", - offerThroughput: 50000, - st: true, - sid: "a1", - rg: "b1", - dba: "main" - }; - - describe("getRpClient", () => { - it("should return an instance of ResourceProviderClient", () => { - expect(AddDbUtilities.getRpClient()).not.toBeFalsy(); - expect(AddDbUtilities.getRpClient()).toBeInstanceOf(ResourceProviderClient); - }); - }); - - describe("getGremlinDatabaseUri", () => { - it("should return a uri in the correct format", () => { - expect(AddDbUtilities.getGremlinDatabaseUri(properties)).toEqual( - "subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a1-db" - ); - }); - }); - - describe("createGremlinDatabase", () => { - it("should utilize resource provider client", () => { - const resourceProviderClientSpy = spyOn(AddDbUtilities, "getRpClient"); - AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined); - expect(resourceProviderClientSpy).toHaveBeenCalled(); - }); - - it("should invoke getGremlinDatabaseUri", () => { - const getGremlinDatabaseUriSpy = spyOn(AddDbUtilities, "getGremlinDatabaseUri"); - AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined); - expect(getGremlinDatabaseUriSpy).toHaveBeenCalled(); - }); - - it("should invoke a put call via resource provider client to create a database and set throughput if shared throughtput is true", () => { - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined); - expect( - resourceProviderClientPutAsyncSpy - ).toHaveBeenCalledWith( - "subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a1-db", - "2020-04-01", - { properties: { options: { throughput: "50000" }, resource: { id: "a1-db" } } } - ); - }); - - it("should invoke a put call via resource provider client to create a database and set autopilot if shared throughtput is true and autopilot settings are passed", () => { - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - AddDbUtilities.createGremlinDatabase(armEndpoint, properties, { "x-ms-cosmos-offer-autopilot-tier": "1" }); - expect( - resourceProviderClientPutAsyncSpy - ).toHaveBeenCalledWith( - "subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a1-db", - "2020-04-01", - { properties: { options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, resource: { id: "a1-db" } } } - ); - }); - - it("should invoke a put call via resource provider client to create a database and not set throughput if shared throughtput is false", () => { - const properties = { - pk: "state", - coll: "abc-collection", - cd: true, - db: "a2-db", - st: false, - sid: "a2", - rg: "c1", - dba: "main" - }; - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined); - expect( - resourceProviderClientPutAsyncSpy - ).toHaveBeenCalledWith( - "subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/main/gremlinDatabases/a2-db", - "2020-04-01", - { properties: { options: {}, resource: { id: "a2-db" } } } - ); - }); - }); - - describe("createSqlDatabase", () => { - it("should utilize resource provider client", () => { - const resourceProviderClientSpy = spyOn(AddDbUtilities, "getRpClient"); - AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined); - expect(resourceProviderClientSpy).toHaveBeenCalled(); - }); - - it("should invoke getSqlDatabaseUri", () => { - const getSqlDatabaseUriSpy = spyOn(AddDbUtilities, "getSqlDatabaseUri"); - AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined); - expect(getSqlDatabaseUriSpy).toHaveBeenCalled(); - }); - - it("should invoke a put call via resource provider client to create a database and set throughput if shared throughtput is true", () => { - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined); - expect( - resourceProviderClientPutAsyncSpy - ).toHaveBeenCalledWith( - "subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/sqlDatabases/a1-db", - "2020-04-01", - { properties: { options: { throughput: "50000" }, resource: { id: "a1-db" } } } - ); - }); - - it("should invoke a put call via resource provider client to create a database and set autopilot if shared throughtput is true and autopilot settings are passed", () => { - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - AddDbUtilities.createSqlDatabase(armEndpoint, properties, { "x-ms-cosmos-offer-autopilot-tier": "1" }); - expect( - resourceProviderClientPutAsyncSpy - ).toHaveBeenCalledWith( - "subscriptions/a1/resourceGroups/b1/providers/Microsoft.DocumentDB/databaseAccounts/main/sqlDatabases/a1-db", - "2020-04-01", - { properties: { options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, resource: { id: "a1-db" } } } - ); - }); - - it("should invoke a put call via resource provider client to create a database and not set throughput if shared throughtput is false", () => { - const properties = { - pk: "state", - coll: "abc-collection", - cd: true, - db: "a2-db", - st: false, - sid: "a2", - rg: "c1", - dba: "main" - }; - const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync"); - AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined); - expect( - resourceProviderClientPutAsyncSpy - ).toHaveBeenCalledWith( - "subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/main/sqlDatabases/a2-db", - "2020-04-01", - { properties: { options: {}, resource: { id: "a2-db" } } } - ); - }); - }); -}); diff --git a/src/Shared/AddDatabaseUtility.ts b/src/Shared/AddDatabaseUtility.ts deleted file mode 100644 index a1197c732..000000000 --- a/src/Shared/AddDatabaseUtility.ts +++ /dev/null @@ -1,187 +0,0 @@ -import * as DataExplorerConstants from "../Common/Constants"; -import * as DataModels from "../Contracts/DataModels"; -import { configContext } from "../ConfigContext"; -import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; -import { HttpStatusCodes } from "../Common/Constants"; -import { sendMessage } from "../Common/MessageHandler"; -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 - public static async createMongoDatabaseWithARM( - armEndpoint: string, - params: DataModels.RpParameters, - rpOptions: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateDatabase: DataModels.MongoCreationRequest = { - properties: { - resource: { - id: params.db - }, - options: {} - } - }; - - if (params.st) { - if (rpOptions) { - rpPayloadToCreateDatabase.properties.options = rpOptions; - } else { - rpPayloadToCreateDatabase.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - try { - await AddDbUtilities.getRpClient(armEndpoint).putAsync( - AddDbUtilities._getMongoDatabaseUri(params), - DataExplorerConstants.ArmApiVersions.publicVersion, - rpPayloadToCreateDatabase - ); - } catch (reason) { - AddDbUtilities._handleCreationError(reason, params); - } - } - - // todo - remove any - public static async createCassandraKeyspace( - params: DataModels.RpParameters, - rpOptions: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateKeyspace: DataModels.CreationRequest = { - properties: { - resource: { - id: params.db - }, - options: {} - } - }; - - if (params.st) { - if (rpOptions) { - rpPayloadToCreateKeyspace.properties.options = rpOptions; - } else { - rpPayloadToCreateKeyspace.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - try { - await createUpdateCassandraKeyspace( - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount?.name, - params.db, - rpPayloadToCreateKeyspace - ); - } catch (reason) { - AddDbUtilities._handleCreationError(reason, params, "keyspace"); - } - } - - public static async createSqlDatabase( - armEndpoint: string, - params: DataModels.RpParameters, - rpOptions: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateSqlDatabase: DataModels.CreationRequest = { - properties: { - resource: { - id: params.db - }, - options: {} - } - }; - - if (params.st) { - if (rpOptions) { - rpPayloadToCreateSqlDatabase.properties.options = rpOptions; - } else { - rpPayloadToCreateSqlDatabase.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - try { - await AddDbUtilities.getRpClient(armEndpoint).putAsync( - AddDbUtilities.getSqlDatabaseUri(params), - DataExplorerConstants.ArmApiVersions.publicVersion, - rpPayloadToCreateSqlDatabase - ); - } catch (reason) { - AddDbUtilities._handleCreationError(reason, params, "database"); - } - } - - public static getRpClient(armEndpoint?: string): ResourceProviderClient { - return new ResourceProviderClient(armEndpoint || configContext.ARM_ENDPOINT); - } - - public static async createGremlinDatabase( - armEndpoint: string, - params: DataModels.RpParameters, - autoPilotSettings: DataModels.RpOptions - ): Promise { - const rpPayloadToCreateDatabase: DataModels.CreationRequest = { - properties: { - resource: { - id: params.db - }, - options: {} - } - }; - - const uri = AddDbUtilities.getGremlinDatabaseUri(params); - - if (params.st) { - if (autoPilotSettings) { - rpPayloadToCreateDatabase.properties.options = autoPilotSettings; - } else { - rpPayloadToCreateDatabase.properties.options["throughput"] = - params.offerThroughput && params.offerThroughput.toString(); - } - } - - return new Promise((resolve, reject) => { - AddDbUtilities.getRpClient(armEndpoint) - .putAsync(uri, DataExplorerConstants.ArmApiVersions.publicVersion, rpPayloadToCreateDatabase) - .then( - () => { - resolve(); - }, - reason => { - AddDbUtilities._handleCreationError(reason, params); - reject(); - } - ); - }); - } - - private static _handleCreationError(reason: any, params: DataModels.RpParameters, dbType: string = "database") { - NotificationConsoleUtils.logConsoleError(`Error creating ${dbType}: ${JSON.stringify(reason)}, Payload: ${params}`); - if (reason.status === HttpStatusCodes.Forbidden) { - sendMessage({ type: MessageTypes.ForbiddenError }); - return; - } - throw new Error(`Error creating ${dbType}`); - } - - private static _getMongoDatabaseUri(params: DataModels.RpParameters): string { - return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}`; - } - - private static _getCassandraKeyspaceUri(params: DataModels.RpParameters): string { - return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/cassandraKeyspaces/${params.db}`; - } - - public static getGremlinDatabaseUri(params: DataModels.RpParameters): string { - return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/gremlinDatabases/${params.db}`; - } - - public static getSqlDatabaseUri(params: DataModels.RpParameters): string { - return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${params.dba}/sqlDatabases/${params.db}`; - } -}