Move updateCollection to ARM (#152)

This commit is contained in:
victor-meng 2020-08-21 11:24:01 -07:00 committed by GitHub
parent 1c80ced259
commit 47a5c315b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 407 additions and 251 deletions

View File

@ -6,8 +6,6 @@ import * as ViewModels from "../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { import {
ConflictDefinition, ConflictDefinition,
ContainerDefinition,
ContainerResponse,
DatabaseResponse, DatabaseResponse,
FeedOptions, FeedOptions,
ItemDefinition, ItemDefinition,
@ -203,23 +201,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
return [partitionKeyValue]; return [partitionKeyValue];
} }
export function updateCollection(
databaseId: string,
collectionId: string,
newCollection: DataModels.Collection,
options: any = {}
): Q.Promise<DataModels.Collection> {
return Q(
client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options)
.then(async (response: ContainerResponse) => {
return refreshCachedResources().then(() => response.resource as DataModels.Collection);
})
);
}
export function updateDocument( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,

View File

@ -266,42 +266,6 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
return deferred.promise; return deferred.promise;
} }
export function updateCollection(
databaseId: string,
collection: ViewModels.Collection,
newCollection: DataModels.Collection
): Q.Promise<DataModels.Collection> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Updating container ${collection.id()}`
);
DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection)
.then(
(replacedCollection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully updated container ${collection.id()}`
);
deferred.resolve(replacedCollection);
},
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to update container ${collection.id()}: ${JSON.stringify(error)}`
);
Logger.logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
}
export function updateDocument( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,

View File

@ -0,0 +1,225 @@
import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels";
import { ContainerDefinition } from "@azure/cosmos";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
ExtendedResourceProperties,
SqlContainerCreateUpdateParameters,
SqlContainerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraTable,
getCassandraTable
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinGraph,
getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function updateCollection(
databaseId: string,
collectionId: string,
newCollection: Collection,
options: RequestOptions = {}
): Promise<Collection> {
let collection: Collection;
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try {
if (
window.authType === AuthType.AAD &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options);
collection = sdkResponse.resource as Collection;
}
} catch (error) {
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
throw error;
}
logConsoleInfo(`Successfully updated container ${collectionId}`);
clearMessage();
await refreshCachedResources();
return collection;
}
async function updateCollectionWithARM(
databaseId: string,
collectionId: string,
newCollection: Collection
): Promise<Collection> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.MongoDB:
return updateMongoDBCollection(
databaseId,
collectionId,
subscriptionId,
resourceGroup,
accountName,
newCollection
);
case DefaultAccountExperienceType.Cassandra:
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Graph:
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Table:
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}
async function updateSqlContainer(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateSqlContainer(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
async function updateMongoDBCollection(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateMongoDBCollection(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(
`MongoDB collection to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
);
}
async function updateCassandraTable(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateCassandraTable(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(
`Cassandra table to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
);
}
async function updateGremlinGraph(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateGremlinGraph(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Gremlin graph to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
async function updateTable(
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateTable(
subscriptionId,
resourceGroup,
accountName,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Table to update does not exist. Table id: ${collectionId}`);
}

View File

@ -153,7 +153,14 @@ export interface KeyResource {
Token: string; Token: string;
} }
export interface IndexingPolicy {} export interface IndexingPolicy {
automatic: boolean;
indexingMode: string;
includedPaths: any;
excludedPaths: any;
compositeIndexes?: any;
spatialIndexes?: any;
}
export interface PartitionKey { export interface PartitionKey {
paths: string[]; paths: string[];

View File

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

View File

@ -17,7 +17,8 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { updateOffer, updateCollection } from "../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../Common/DocumentClientUtilityBase";
import { updateCollection } from "../../Common/dataAccess/updateCollection";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit"; import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
@ -1009,8 +1010,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
} }
public onSaveClick = (): Q.Promise<any> => { public onSaveClick = async (): Promise<any> => {
let promises: Q.Promise<void>[] = [];
this.isExecutionError(false); this.isExecutionError(false);
this.isExecuting(true); this.isExecuting(true);
@ -1023,6 +1023,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
const newCollectionAttributes: any = {}; const newCollectionAttributes: any = {};
try {
if (this.shouldUpdateCollection()) { if (this.shouldUpdateCollection()) {
let defaultTtl: number; let defaultTtl: number;
switch (this.timeToLive()) { switch (this.timeToLive()) {
@ -1064,9 +1065,18 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges; newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges;
} }
const newCollection: DataModels.Collection = _.extend({}, this.collection.rawDataModel, newCollectionAttributes); const newCollection: DataModels.Collection = _.extend(
const updateCollectionPromise = updateCollection(this.collection.databaseId, this.collection, newCollection).then( {},
(updatedCollection: DataModels.Collection) => { this.collection.rawDataModel,
newCollectionAttributes
);
const updatedCollection: DataModels.Collection = await updateCollection(
this.collection.databaseId,
this.collection.id(),
newCollection
);
if (updatedCollection) {
this.collection.rawDataModel = updatedCollection; this.collection.rawDataModel = updatedCollection;
this.collection.defaultTtl(updatedCollection.defaultTtl); this.collection.defaultTtl(updatedCollection.defaultTtl);
this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl); this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl);
@ -1076,9 +1086,6 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy); this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
this.collection.geospatialConfig(updatedCollection.geospatialConfig); this.collection.geospatialConfig(updatedCollection.geospatialConfig);
} }
);
promises.push(updateCollectionPromise);
} }
if ( if (
@ -1150,8 +1157,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
throughput: newThroughput, throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
}; };
const updateOfferBeyondLimitPromise = updateOfferThroughputBeyondLimit(requestPayload).then(
() => { await updateOfferThroughputBeyondLimit(requestPayload);
this.collection.offer().content.offerThroughput = originalThroughputValue; this.collection.offer().content.offerThroughput = originalThroughputValue;
this.throughput(originalThroughputValue); this.throughput(originalThroughputValue);
this.notificationStatusInfo( this.notificationStatusInfo(
@ -1165,43 +1172,13 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
) )
); );
this.throughput.valueHasMutated(); // force component re-render 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 { } else {
const updateOfferPromise = updateOffer(this.collection.offer(), newOffer, headerOptions).then( const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions);
(updatedOffer: DataModels.Offer) => {
this.collection.offer(updatedOffer); this.collection.offer(updatedOffer);
this.collection.offer.valueHasMutated(); this.collection.offer.valueHasMutated();
} }
);
promises.push(updateOfferPromise);
}
} }
if (promises.length === 0) {
this.isExecuting(false);
}
return Q.all(promises)
.then(
() => {
this.container.isRefreshingExplorer(false); this.container.isRefreshingExplorer(false);
this._setBaseline(); this._setBaseline();
this.collection.readSettings(); this.collection.readSettings();
@ -1216,24 +1193,26 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
}, },
startKey startKey
); );
}, } catch (error) {
(reason: any) => {
this.container.isRefreshingExplorer(false); this.container.isRefreshingExplorer(false);
this.isExecutionError(true); this.isExecutionError(true);
console.error(reason); console.error(error);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.UpdateSettings, Action.UpdateSettings,
{ {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.collection && this.collection.databaseId,
collectionName: this.collection && this.collection.id(),
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle() tabTitle: this.tabTitle(),
error: error
}, },
startKey startKey
); );
} }
)
.finally(() => this.isExecuting(false)); this.isExecuting(false);
}; };
public onRevertClick = (): Q.Promise<any> => { public onRevertClick = (): Q.Promise<any> => {