Clean up unused utility functions for creating databases and collections (#181)

This commit is contained in:
victor-meng 2020-09-03 13:05:22 -07:00 committed by GitHub
parent efff26dbe7
commit 6bc506b81f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 88 additions and 970 deletions

View File

@ -266,10 +266,6 @@ src/ResourceProvider/ResourceProviderClientFactory.ts
src/RouteHandlers/RouteHandler.ts src/RouteHandlers/RouteHandler.ts
src/RouteHandlers/TabRouteHandler.test.ts src/RouteHandlers/TabRouteHandler.test.ts
src/RouteHandlers/TabRouteHandler.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/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.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/dataexplorer/TABLE/addCollection.spec.ts
cypress/integration/notebook/newNotebook.spec.ts cypress/integration/notebook/newNotebook.spec.ts
cypress/integration/notebook/resourceTree.spec.ts cypress/integration/notebook/resourceTree.spec.ts
__mocks__/AddDatabaseUtility.ts
__mocks__/monaco-editor.ts __mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx

View File

@ -1,5 +0,0 @@
export class AddDbUtilities {
createGremlinDatabase(params: any) {
return Promise.resolve(1)
}
}

View File

@ -5,14 +5,7 @@ import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
deleteDocument,
getEndpoint,
queryDocuments,
readDocument,
updateDocument,
_createMongoCollectionWithARM
} from "./MongoProxyClient";
jest.mock("../ResourceProvider/ResourceProviderClient.ts"); jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB"; const databaseId = "testDB";
@ -260,58 +253,4 @@ describe("MongoProxyClient", () => {
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer"); 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" }
}
}
);
});
});
}); });

View File

@ -1,15 +1,12 @@
import { Constants as CosmosSDKConstants } from "@azure/cosmos"; import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import queryString from "querystring"; import queryString from "querystring";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import * as DataExplorerConstants from "../Common/Constants";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { Collection } from "../Contracts/ViewModels"; import { Collection } from "../Contracts/ViewModels";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { userContext } from "../UserContext"; 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<DataModels.CreateCollectionWithRpResponse> {
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 { export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
const serverId = window.dataExplorer.serverId(); const serverId = window.dataExplorer.serverId();
const extensionEndpoint = window.dataExplorer.extensionEndpoint(); 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 { 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}`; 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<DataModels.CreateCollectionWithRpResponse> {
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<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
getARMCreateCollectionEndpoint(params),
DataExplorerConstants.ArmApiVersions.publicVersion,
rpPayloadToCreateCollection
);
} catch (response) {
errorHandling(response, "creating collection", undefined);
return undefined;
}
}

View File

@ -943,7 +943,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
const defaultThroughput = this.container.collectionCreationDefaults.throughput; const defaultThroughput = this.container.collectionCreationDefaults.throughput;
this.throughputSinglePartition(defaultThroughput.fixed); this.throughputSinglePartition(defaultThroughput.fixed);
this.throughputMultiPartition( this.throughputMultiPartition(
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container) AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
); );
this.throughputDatabase(defaultThroughput.shared); this.throughputDatabase(defaultThroughput.shared);
@ -1167,17 +1167,19 @@ export default class AddCollectionPane extends ContextualPaneBase {
private _updateThroughputLimitByCollectionStorage() { private _updateThroughputLimitByCollectionStorage() {
const storage = this.storage(); const storage = this.storage();
const minThroughputRU = AddCollectionUtility.Utilities.getMinRUForStorageOption( const minThroughputRU =
this.container.collectionCreationDefaults, storage === SharedConstants.CollectionCreation.storage10Gb
storage ? SharedConstants.CollectionCreation.DefaultCollectionRUs400
); : this.container.collectionCreationDefaults.throughput.unlimitedmin;
let maxThroughputRU = AddCollectionUtility.Utilities.getMaxRUForStorageOption( let maxThroughputRU;
this.container.collectionCreationDefaults,
storage
);
if (this.isTryCosmosDBSubscription()) { if (this.isTryCosmosDBSubscription()) {
maxThroughputRU = Constants.TryCosmosExperience.maxRU; maxThroughputRU = Constants.TryCosmosExperience.maxRU;
} else {
maxThroughputRU =
storage === SharedConstants.CollectionCreation.storage10Gb
? SharedConstants.CollectionCreation.DefaultCollectionRUs10K
: this.container.collectionCreationDefaults.throughput.unlimitedmax;
} }
this.minThroughputRU(minThroughputRU); this.minThroughputRU(minThroughputRU);

View File

@ -1,4 +1,3 @@
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
@ -8,15 +7,11 @@ import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants"; import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import editable from "../../Common/EditableUtility"; import editable from "../../Common/EditableUtility";
import EnvironmentUtility from "../../Common/EnvironmentUtility";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { AddDbUtilities } from "../../Shared/AddDatabaseUtility";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { createDatabase } from "../../Common/dataAccess/createDatabase"; import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { userContext } from "../../UserContext";
export default class AddDatabasePane extends ContextualPaneBase { export default class AddDatabasePane extends ContextualPaneBase {
public defaultExperience: ko.Computed<string>; public defaultExperience: ko.Computed<string>;

View File

@ -494,9 +494,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.selectedSharedAutoPilotTier(null); this.selectedSharedAutoPilotTier(null);
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.throughput( this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
AddCollectionUtility.Utilities.getMaxThroughput(this.container.collectionCreationDefaults, this.container)
);
this.keyspaceThroughput(throughputDefaults.shared); this.keyspaceThroughput(throughputDefaults.shared);
this.maxThroughputRU(throughputDefaults.unlimitedmax); this.maxThroughputRU(throughputDefaults.unlimitedmax);
this.minThroughputRU(throughputDefaults.unlimitedmin); this.minThroughputRU(throughputDefaults.unlimitedmin);

View File

@ -1,162 +1,64 @@
import * as SharedConstants from "../Shared/Constants"; import * as ko from "knockout";
import { AddDbUtilities } from "../Shared/AddDatabaseUtility"; import { Collection, Database } from "../Contracts/ViewModels";
import { CreateCollectionUtilities, CreateSqlCollectionUtilities, Utilities } from "./AddCollectionUtility"; import { getMaxThroughput } from "./AddCollectionUtility";
jest.mock("AddDatabaseUtility"); import Explorer from "../Explorer/Explorer";
const armEndpoint = "https://management.azure.com"; describe("getMaxThroughput", () => {
it("default unlimited throughput setting", () => {
describe("Add Collection Utitlity", () => { const defaults = {
describe("createSqlCollection", () => { storage: "100",
it("should invoke createSqlCollectionWithARM if create database is false", () => { throughput: {
const properties = { fixed: 400,
uniqueKeyPolicy: { uniqueKeys: [{ paths: [""] }] }, unlimited: 400,
cd: false, unlimitedmax: 1000000,
coll: "abc-collection", unlimitedmin: 400,
db: "a1-db", shared: 400
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"); expect(getMaxThroughput(defaults, {} as Explorer)).toEqual(defaults.throughput.unlimited);
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(createSqlCollectionWithARMSpy).toHaveBeenCalled();
}); });
it("should invoke createSqlDatabase + createSqlCollectionWithARM if create database is true", () => { describe("no unlimited throughput setting", () => {
const properties = { const defaults = {
uniqueKeyPolicy: { uniqueKeys: [{ paths: [""] }] }, storage: "100",
cd: true, throughput: {
coll: "abc-collection", fixed: 400,
db: "a1-db", unlimited: {
dba: "main", collectionThreshold: 3,
offerThroughput: 50000, lessThanOrEqualToThreshold: 400,
pk: "state", greatThanThreshold: 500
sid: "a1", },
rg: "b1", unlimitedmax: 1000000,
st: true, unlimitedmin: 400,
analyticalStorageTtl: -1, shared: 400
indexingPolicy: SharedConstants.IndexingPolicies.AllPropertiesIndexed, }
partitionKeyVersion: 2
}; };
const additionalOptions = {};
const createSqlCollectionWithARMSpy = jest.spyOn(CreateSqlCollectionUtilities, "createSqlCollectionWithARM"); const mockCollection1 = { id: ko.observable("collection1") } as Collection;
const createSqlDatabaseSpy = jest.spyOn(AddDbUtilities, "createSqlDatabase"); const mockCollection2 = { id: ko.observable("collection2") } as Collection;
CreateSqlCollectionUtilities.createSqlCollection( const mockCollection3 = { id: ko.observable("collection3") } as Collection;
armEndpoint, const mockCollection4 = { id: ko.observable("collection4") } as Collection;
properties.db, const mockDatabase = {} as Database;
properties.analyticalStorageTtl, const mockContainer = {
properties.coll, databases: ko.observableArray([mockDatabase])
properties.indexingPolicy, } as Explorer;
properties.offerThroughput,
properties.pk, it("less than or equal to collection threshold", () => {
properties.partitionKeyVersion, mockDatabase.collections = ko.observableArray([mockCollection1, mockCollection2]);
properties.cd, expect(getMaxThroughput(defaults, mockContainer)).toEqual(
properties.st, defaults.throughput.unlimited.lessThanOrEqualToThreshold
properties.sid,
properties.rg,
properties.dba,
properties.uniqueKeyPolicy,
additionalOptions
); );
expect(createSqlCollectionWithARMSpy).toHaveBeenCalled();
expect(createSqlDatabaseSpy).toHaveBeenCalled();
});
});
}); });
describe("Add Collection Utitlity", () => { it("exceeds collection threshold", () => {
describe("createGremlinGraph", () => { mockDatabase.collections = ko.observableArray([
it("should invoke createGremlinGraphWithARM if create database is false", () => { mockCollection1,
const properties = { mockCollection2,
cd: false, mockCollection3,
coll: "abc-collection", mockCollection4
db: "a1-db", ]);
dba: "main", expect(getMaxThroughput(defaults, mockContainer)).toEqual(defaults.throughput.unlimited.greatThanThreshold);
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();
}); });
}); });
}); });

View File

@ -1,303 +1,23 @@
import * as _ from "underscore"; import { any } from "underscore";
import * as DataExplorerConstants from "../Common/Constants"; import { CollectionCreationDefaults } from "../Contracts/ViewModels";
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 Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { userContext } from "../UserContext";
export class CreateSqlCollectionUtilities { export const getMaxThroughput = (defaults: CollectionCreationDefaults, container: Explorer): number => {
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<DataModels.CreateCollectionWithRpResponse> {
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);
}
public static async createSqlCollectionWithARM(
armEndpoint: string,
params: DataModels.SqlCollectionParameters,
rpOptions: DataModels.RpOptions
): Promise<DataModels.CreateCollectionWithRpResponse> {
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<DataModels.CreateCollectionWithRpResponse>(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<DataModels.CreateCollectionWithRpResponse> {
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<DataModels.CreateCollectionWithRpResponse> {
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<DataModels.CreateCollectionWithRpResponse>(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<any> {
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; const throughput = defaults.throughput.unlimited;
if (typeof throughput === "number") { if (typeof throughput === "number") {
return throughput; return throughput;
} else { } else {
return this._exceedsThreshold(throughput.collectionThreshold, container) return _exceedsThreshold(throughput.collectionThreshold, container)
? throughput.greatThanThreshold ? throughput.greatThanThreshold
: throughput.lessThanOrEqualToThreshold; : throughput.lessThanOrEqualToThreshold;
} }
} };
private static _exceedsThreshold(unlimitedThreshold: number, container: Explorer): boolean { const _exceedsThreshold = (unlimitedThreshold: number, container: Explorer): boolean => {
const databases = (container && container.databases && container.databases()) || []; const databases = (container && container.databases && container.databases()) || [];
return _.any( return any(
databases, databases,
database => database =>
database && database.collections && database.collections() && database.collections().length > unlimitedThreshold 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}`;
}
}

View File

@ -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<any>(AddDbUtilities, "getRpClient");
AddDbUtilities.createGremlinDatabase(armEndpoint, properties, undefined);
expect(resourceProviderClientSpy).toHaveBeenCalled();
});
it("should invoke getGremlinDatabaseUri", () => {
const getGremlinDatabaseUriSpy = spyOn<any>(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<any>(AddDbUtilities, "getRpClient");
AddDbUtilities.createSqlDatabase(armEndpoint, properties, undefined);
expect(resourceProviderClientSpy).toHaveBeenCalled();
});
it("should invoke getSqlDatabaseUri", () => {
const getSqlDatabaseUriSpy = spyOn<any>(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" } } }
);
});
});
});

View File

@ -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<any> {
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<DataModels.CreateDatabaseWithRpResponse>(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<any> {
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<any> {
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<DataModels.CreateDatabaseWithRpResponse>(armEndpoint).putAsync(
AddDbUtilities.getSqlDatabaseUri(params),
DataExplorerConstants.ArmApiVersions.publicVersion,
rpPayloadToCreateSqlDatabase
);
} catch (reason) {
AddDbUtilities._handleCreationError(reason, params, "database");
}
}
public static getRpClient<T>(armEndpoint?: string): ResourceProviderClient<T> {
return new ResourceProviderClient<T>(armEndpoint || configContext.ARM_ENDPOINT);
}
public static async createGremlinDatabase(
armEndpoint: string,
params: DataModels.RpParameters,
autoPilotSettings: DataModels.RpOptions
): Promise<DataModels.CreateDatabaseWithRpResponse> {
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<DataModels.CreateDatabaseWithRpResponse>((resolve, reject) => {
AddDbUtilities.getRpClient<DataModels.CreateDatabaseWithRpResponse>(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}`;
}
}