Move update offers call to RP

This commit is contained in:
victor-meng 2020-10-07 15:25:21 -07:00 committed by GitHub
parent d346ebe054
commit 0382628249
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 580 additions and 250 deletions

View File

@ -72,22 +72,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
return [partitionKeyValue]; return [partitionKeyValue];
} }
export function updateOffer(
offer: DataModels.Offer,
newOffer: DataModels.Offer,
options?: RequestOptions
): Q.Promise<DataModels.Offer> {
return Q(
client()
.offer(offer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options)
.then(response => {
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
})
);
}
export function updateDocument( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,

View File

@ -157,41 +157,6 @@ export function updateDocument(
return deferred.promise; return deferred.promise;
} }
export function updateOffer(
offer: DataModels.Offer,
newOffer: DataModels.Offer,
options: RequestOptions
): Q.Promise<DataModels.Offer> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Updating offer for resource ${offer.resource}`);
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
.then(
(replacedOffer: DataModels.Offer) => {
logConsoleInfo(`Successfully updated offer for resource ${offer.resource}`);
deferred.resolve(replacedOffer);
},
(error: any) => {
logConsoleError(`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`);
Logger.logError(
JSON.stringify({
oldOffer: offer,
newOffer: newOffer,
error: error
}),
"UpdateOffer",
error.code
);
sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> { export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();

View File

@ -17,31 +17,21 @@ import { userContext } from "../../UserContext";
export const readDatabaseOffer = async ( export const readDatabaseOffer = async (
params: DataModels.ReadDatabaseOfferParams params: DataModels.ReadDatabaseOfferParams
): Promise<DataModels.OfferWithHeaders> => { ): Promise<DataModels.OfferWithHeaders> => {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Reading database offer is not allowed for tables accounts");
}
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
let offerId = params.offerId; let offerId = params.offerId;
if (!offerId) { if (!offerId) {
if ( offerId = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
window.authType === AuthType.AAD && ? getDatabaseOfferIdWithARM(params.databaseId)
!userContext.useSDKOperations && : getDatabaseOfferIdWithSDK(params.databaseResourceId));
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
try {
offerId = await getDatabaseOfferIdWithARM(params.databaseId);
} catch (error) {
clearMessage();
if (error.code !== "NotFound") {
throw new error();
}
return undefined;
}
} else {
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
if (!offerId) { if (!offerId) {
clearMessage(); clearMessage();
return undefined; return undefined;
} }
} }
}
const options: RequestOptions = { const options: RequestOptions = {
initialHeaders: { initialHeaders: {
@ -75,6 +65,8 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.defaultExperience;
try {
switch (defaultExperience) { switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB: case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId); rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
@ -93,6 +85,12 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
} }
return rpResponse?.name; return rpResponse?.name;
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
return undefined;
}
}; };
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => { const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {

View File

@ -41,6 +41,7 @@ export async function updateCollection(
try { try {
if ( if (
window.authType === AuthType.AAD && window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB && userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table
) { ) {
@ -52,16 +53,18 @@ export async function updateCollection(
.replace(newCollection as ContainerDefinition, options); .replace(newCollection as ContainerDefinition, options);
collection = sdkResponse.resource as Collection; collection = sdkResponse.resource as Collection;
} }
logConsoleInfo(`Successfully updated container ${collectionId}`);
await refreshCachedResources();
return collection;
} catch (error) { } catch (error) {
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`); logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code); logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error); sendNotificationForError(error);
throw error; throw error;
} } finally {
logConsoleInfo(`Successfully updated container ${collectionId}`);
clearMessage(); clearMessage();
await refreshCachedResources(); }
return collection;
} }
async function updateCollectionWithARM( async function updateCollectionWithARM(

View File

@ -0,0 +1,420 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { Offer, UpdateOfferParams } from "../../Contracts/DataModels";
import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import {
updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
updateCassandraKeyspaceThroughput,
migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput,
updateCassandraTableThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
updateMongoDBDatabaseThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
updateGremlinDatabaseThroughput,
migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput,
updateGremlinGraphThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { userContext } from "../../UserContext";
import {
migrateTableToAutoscale,
migrateTableToManualThroughput,
updateTableThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
let updatedOffer: Offer;
const offerResourceText: string = params.collectionId
? `collection ${params.collectionId}`
: `database ${params.databaseId}`;
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
updatedOffer = await (params.collectionId
? updateCollectionOfferWithARM(params)
: updateDatabaseOfferWithARM(params));
} else {
updatedOffer = await updateOfferWithSDK(params);
}
await refreshCachedOffers();
await refreshCachedResources();
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
return updatedOffer;
} catch (error) {
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
};
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlContainerOffer(params);
break;
case DefaultAccountExperienceType.MongoDB:
await updateMongoCollectionOffer(params);
break;
case DefaultAccountExperienceType.Cassandra:
await updateCassandraTableOffer(params);
break;
case DefaultAccountExperienceType.Graph:
await updateGremlinGraphOffer(params);
break;
case DefaultAccountExperienceType.Table:
await updateTableOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
throw error;
}
}
return await readCollectionOffer({
collectionId: params.collectionId,
databaseId: params.databaseId,
offerId: params.currentOffer.id
});
};
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Updating database offer is not allowed for tables accounts");
}
try {
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlDatabaseOffer(params);
break;
case DefaultAccountExperienceType.MongoDB:
await updateMongoDatabaseOffer(params);
break;
case DefaultAccountExperienceType.Cassandra:
await updateCassandraKeyspaceOffer(params);
break;
case DefaultAccountExperienceType.Graph:
await updateGremlinDatabaseOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
throw error;
}
}
return await readDatabaseOffer({
databaseId: params.databaseId,
offerId: params.currentOffer.id
});
};
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlContainerToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateSqlContainerToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateSqlContainerThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBCollectionToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateMongoDBCollectionToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateMongoDBCollectionThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraTableToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateCassandraTableToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateCassandraTableThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinGraphToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateGremlinGraphToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateGremlinGraphThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
} else if (params.migrateToManual) {
await migrateTableToManualThroughput(subscriptionId, resourceGroup, accountName, params.collectionId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateTableThroughput(subscriptionId, resourceGroup, accountName, params.collectionId, body);
}
};
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateSqlDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateMongoDBDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateCassandraKeyspaceToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateGremlinDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpdateParameters => {
const body: ThroughputSettingsUpdateParameters = {
properties: {
resource: {}
}
};
if (params.autopilotThroughput) {
body.properties.resource.autoscaleSettings = {
maxThroughput: params.autopilotThroughput
};
} else {
body.properties.resource.throughput = params.manualThroughput;
}
return body;
};
const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> => {
const currentOffer = params.currentOffer;
const newOffer: Offer = {
content: {
offerThroughput: undefined,
offerIsRUPerMinuteThroughputEnabled: false
},
_etag: undefined,
_ts: undefined,
_rid: currentOffer._rid,
_self: currentOffer._self,
id: currentOffer.id,
offerResourceId: currentOffer.offerResourceId,
offerVersion: currentOffer.offerVersion,
offerType: currentOffer.offerType,
resource: currentOffer.resource
};
if (params.autopilotThroughput) {
newOffer.content.offerAutopilotSettings = {
maxThroughput: params.autopilotThroughput
};
} else {
newOffer.content.offerThroughput = params.manualThroughput;
}
const options: RequestOptions = {};
if (params.migrateToAutoPilot) {
options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
delete newOffer.content.offerAutopilotSettings;
} else if (params.migrateToManual) {
options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
}
const sdkResponse = await client()
.offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options);
return sdkResponse?.resource;
};

View File

@ -303,6 +303,16 @@ export interface ReadCollectionOfferParams {
offerId?: string; offerId?: string;
} }
export interface UpdateOfferParams {
currentOffer: Offer;
databaseId: string;
autopilotThroughput: number;
manualThroughput: number;
collectionId?: string;
migrateToAutoPilot?: boolean;
migrateToManual?: boolean;
}
export interface Notification { export interface Notification {
id: string; id: string;
kind: string; kind: string;

View File

@ -20,8 +20,8 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
geospatialConfig: undefined geospatialConfig: undefined
} as DataModels.Collection) } as DataModels.Collection)
})); }));
import { updateOffer } from "../../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
jest.mock("../../../Common/DocumentClientUtilityBase", () => ({ jest.mock("../../../Common/dataAccess/updateOffer", () => ({
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer) updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
})); }));

View File

@ -10,7 +10,7 @@ import { traceStart, traceFailure, traceSuccess } from "../../../Shared/Telemetr
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { updateOffer } from "../../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; 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";
@ -426,7 +426,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
throw error; throw error;
} }
} else { } else {
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions); const updateOfferParams: DataModels.UpdateOfferParams = {
databaseId: this.collection.databaseId,
collectionId: this.collection.id(),
currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
};
if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) {
updateOfferParams.migrateToAutoPilot = true;
} else {
updateOfferParams.migrateToManual = true;
}
}
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
this.collection.offer(updatedOffer); this.collection.offer(updatedOffer);
this.setState({ isScaleSaveable: false, isScaleDiscardable: false }); this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {

View File

@ -16,7 +16,7 @@ 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 } from "../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../Common/dataAccess/updateOffer";
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";
@ -455,8 +455,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this._buildCommandBarOptions(); this._buildCommandBarOptions();
} }
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);
@ -470,48 +469,22 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
const headerOptions: RequestOptions = { initialHeaders: {} }; const headerOptions: RequestOptions = { initialHeaders: {} };
try {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
const offer = this.database.offer(); const updateOfferParams: DataModels.UpdateOfferParams = {
let offerAutopilotSettings: any = {}; databaseId: this.database.id(),
if (!this.hasAutoPilotV2FeatureFlag()) { currentOffer: this.database.offer(),
offerAutopilotSettings.maxThroughput = this.autoPilotThroughput(); autopilotThroughput: this.autoPilotThroughput(),
} else { manualThroughput: undefined,
offerAutopilotSettings.tier = this.selectedAutoPilotTier(); migrateToAutoPilot: this._hasProvisioningTypeChanged()
}
const newOffer: DataModels.Offer = {
content: {
offerThroughput: undefined,
offerIsRUPerMinuteThroughputEnabled: false,
offerAutopilotSettings
},
_etag: undefined,
_ts: undefined,
_rid: offer._rid,
_self: offer._self,
id: offer.id,
offerResourceId: offer.offerResourceId,
offerVersion: offer.offerVersion,
offerType: offer.offerType,
resource: offer.resource
}; };
// user has changed from provisioned --> autoscale const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
delete newOffer.content.offerAutopilotSettings;
}
const updateOfferPromise = updateOffer(this.database.offer(), newOffer, headerOptions).then(
(updatedOffer: DataModels.Offer) => {
this.database.offer(updatedOffer); this.database.offer(updatedOffer);
this.database.offer.valueHasMutated(); this.database.offer.valueHasMutated();
this._wasAutopilotOriginallySet(this.isAutoPilotSelected()); this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
}
);
promises.push(updateOfferPromise);
} else { } else {
if (this.throughput.editableIsDirty() || this.isAutoPilotSelected.editableIsDirty()) { if (this.throughput.editableIsDirty() || this.isAutoPilotSelected.editableIsDirty()) {
const offer = this.database.offer();
const originalThroughputValue = this.throughput.getEditableOriginalValue(); const originalThroughputValue = this.throughput.getEditableOriginalValue();
const newThroughput = this.throughput(); const newThroughput = this.throughput();
@ -528,16 +501,34 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
throughput: newThroughput, throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false offerIsRUPerMinuteThroughputEnabled: false
}; };
const updateOfferBeyondLimitPromise = updateOfferThroughputBeyondLimit(requestPayload).then( await updateOfferThroughputBeyondLimit(requestPayload);
() => {
this.database.offer().content.offerThroughput = originalThroughputValue; this.database.offer().content.offerThroughput = originalThroughputValue;
this.throughput(originalThroughputValue); this.throughput(originalThroughputValue);
this.notificationStatusInfo( this.notificationStatusInfo(
throughputApplyDelayedMessage(this.isAutoPilotSelected(), newThroughput, this.database.id()) throughputApplyDelayedMessage(this.isAutoPilotSelected(), newThroughput, this.database.id())
); );
this.throughput.valueHasMutated(); // force component re-render this.throughput.valueHasMutated(); // force component re-render
}, } else {
(error: any) => { const updateOfferParams: DataModels.UpdateOfferParams = {
databaseId: this.database.id(),
currentOffer: this.database.offer(),
autopilotThroughput: undefined,
manualThroughput: newThroughput,
migrateToManual: this._hasProvisioningTypeChanged()
};
const updatedOffer = await updateOffer(updateOfferParams);
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
this.database.offer(updatedOffer);
this.database.offer.valueHasMutated();
}
}
}
} catch (error) {
this.container.isRefreshingExplorer(false);
this.isExecutionError(true);
console.error(error);
this.displayedError(ErrorParserUtility.parse(error)[0].message);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.UpdateSettings, Action.UpdateSettings,
{ {
@ -550,83 +541,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
}, },
startKey startKey
); );
} } finally {
);
promises.push(Q(updateOfferBeyondLimitPromise));
} else {
const newOffer: DataModels.Offer = {
content: {
offerThroughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
},
_etag: undefined,
_ts: undefined,
_rid: offer._rid,
_self: offer._self,
id: offer.id,
offerResourceId: offer.offerResourceId,
offerVersion: offer.offerVersion,
offerType: offer.offerType,
resource: offer.resource
};
// user has changed from autoscale --> provisioned
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
}
const updateOfferPromise = updateOffer(this.database.offer(), newOffer, headerOptions).then(
(updatedOffer: DataModels.Offer) => {
this._wasAutopilotOriginallySet(this.isAutoPilotSelected());
this.database.offer(updatedOffer);
this.database.offer.valueHasMutated();
}
);
promises.push(updateOfferPromise);
}
}
}
if (promises.length === 0) {
this.isExecuting(false); this.isExecuting(false);
} }
return Q.all(promises)
.then(
() => {
this.container.isRefreshingExplorer(false);
this._setBaseline();
TelemetryProcessor.traceSuccess(
Action.UpdateSettings,
{
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
},
startKey
);
},
(reason: any) => {
this.container.isRefreshingExplorer(false);
this.isExecutionError(true);
console.error(reason);
this.displayedError(ErrorParserUtility.parse(reason)[0].message);
TelemetryProcessor.traceFailure(
Action.UpdateSettings,
{
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
},
startKey
);
}
)
.finally(() => this.isExecuting(false));
}; };
public onRevertClick = (): Q.Promise<any> => { public onRevertClick = (): Q.Promise<any> => {

View File

@ -17,7 +17,7 @@ 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 } from "../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../Common/dataAccess/updateOffer";
import { updateCollection } from "../../Common/dataAccess/updateCollection"; 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";
@ -1175,7 +1175,21 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
this.throughput.valueHasMutated(); // force component re-render this.throughput.valueHasMutated(); // force component re-render
} else { } else {
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions); const updateOfferParams: DataModels.UpdateOfferParams = {
databaseId: this.collection.databaseId,
collectionId: this.collection.id(),
currentOffer: this.collection.offer(),
autopilotThroughput: this.isAutoPilotSelected() ? this.autoPilotThroughput() : undefined,
manualThroughput: this.isAutoPilotSelected() ? undefined : newThroughput
};
if (this._hasProvisioningTypeChanged()) {
if (this.isAutoPilotSelected()) {
updateOfferParams.migrateToAutoPilot = true;
} else {
updateOfferParams.migrateToManual = true;
}
}
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
this.collection.offer(updatedOffer); this.collection.offer(updatedOffer);
this.collection.offer.valueHasMutated(); this.collection.offer.valueHasMutated();
} }

View File

@ -14,6 +14,7 @@ import * as Logger from "../../Common/Logger";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { readCollections } from "../../Common/dataAccess/readCollections"; import { readCollections } from "../../Common/dataAccess/readCollections";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer"; import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
export default class Database implements ViewModels.Database { export default class Database implements ViewModels.Database {
public nodeKind: string; public nodeKind: string;
@ -200,7 +201,11 @@ export default class Database implements ViewModels.Database {
} }
public async loadOffer(): Promise<void> { public async loadOffer(): Promise<void> {
if (!this.container.isServerlessEnabled() && !this.offer()) { if (
!this.container.isServerlessEnabled() &&
this.container.defaultExperience() !== DefaultAccountExperienceType.Table &&
!this.offer()
) {
const params: DataModels.ReadDatabaseOfferParams = { const params: DataModels.ReadDatabaseOfferParams = {
databaseId: this.id(), databaseId: this.id(),
databaseResourceId: this.self databaseResourceId: this.self