Compare commits

..

1 Commits

Author SHA1 Message Date
Jordi Bunster
6adfea03ab Q removal: the easy parts 2020-11-16 03:26:08 -08:00
88 changed files with 2722 additions and 810 deletions

View File

@@ -3,6 +3,7 @@
"offerThroughput": 400, "offerThroughput": 400,
"databaseLevelThroughput": false, "databaseLevelThroughput": false,
"collectionId": "Persons", "collectionId": "Persons",
"rupmEnabled": false,
"partitionKey": { "kind": "Hash", "paths": ["/name"] }, "partitionKey": { "kind": "Hash", "paths": ["/name"] },
"data": [ "data": [
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)", "g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",

View File

@@ -1,3 +1,4 @@
import { AutopilotTier } from "../Contracts/DataModels";
import { HashMap } from "./HashMap"; import { HashMap } from "./HashMap";
export class AuthorizationEndpoints { export class AuthorizationEndpoints {
@@ -108,6 +109,7 @@ export class CapabilityNames {
export class Features { export class Features {
public static readonly cosmosdb = "cosmosdb"; public static readonly cosmosdb = "cosmosdb";
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy"; public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
public static readonly enableRupm = "enablerupm";
public static readonly executeSproc = "dataexplorerexecutesproc"; public static readonly executeSproc = "dataexplorerexecutesproc";
public static readonly hostedDataExplorer = "hosteddataexplorerenabled"; public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl"; public static readonly enableTtl = "enablettl";
@@ -122,6 +124,7 @@ export class Features {
public static readonly notebookBasePath = "notebookbasepath"; public static readonly notebookBasePath = "notebookbasepath";
public static readonly canExceedMaximumValue = "canexceedmaximumvalue"; public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput"; public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
public static readonly enableAutoPilotV2 = "enableautopilotv2";
public static readonly ttl90Days = "ttl90days"; public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2"; public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSDKoperations = "enablesdkoperations"; public static readonly enableSDKoperations = "enablesdkoperations";
@@ -177,6 +180,12 @@ export class CassandraBackend {
public static readonly schemaApi: string = "api/cassandra/schema"; public static readonly schemaApi: string = "api/cassandra/schema";
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
} }
export class RUPMStates {
public static on: string = "on";
public static off: string = "off";
}
export class Queries { export class Queries {
public static CustomPageOption: string = "custom"; public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited"; public static UnlimitedPageOption: string = "unlimited";
@@ -253,6 +262,7 @@ export class HttpHeaders {
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere"; public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
public static autoPilotThroughput = "autoscaleSettings"; public static autoPilotThroughput = "autoscaleSettings";
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings"; public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
public static partitionKey: string = "x-ms-documentdb-partitionkey"; public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput"; public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot"; public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
@@ -397,6 +407,54 @@ export enum ConflictOperationType {
Delete = "delete" Delete = "delete"
} }
export class AutoPilot {
public static tier1Text: string = "4,000 RU/s";
public static tier2Text: string = "20,000 RU/s";
public static tier3Text: string = "100,000 RU/s";
public static tier4Text: string = "500,000 RU/s";
public static tierText = {
[AutopilotTier.Tier1]: "Tier 1",
[AutopilotTier.Tier2]: "Tier 2",
[AutopilotTier.Tier3]: "Tier 3",
[AutopilotTier.Tier4]: "Tier 4"
};
public static tierMaxRus = {
[AutopilotTier.Tier1]: 2000,
[AutopilotTier.Tier2]: 20000,
[AutopilotTier.Tier3]: 100000,
[AutopilotTier.Tier4]: 500000
};
public static tierMinRus = {
[AutopilotTier.Tier1]: 0,
[AutopilotTier.Tier2]: 0,
[AutopilotTier.Tier3]: 0,
[AutopilotTier.Tier4]: 0
};
public static tierStorageInGB = {
[AutopilotTier.Tier1]: 50,
[AutopilotTier.Tier2]: 200,
[AutopilotTier.Tier3]: 1000,
[AutopilotTier.Tier4]: 5000
};
}
export class DataExplorerVersions {
public static readonly v_1_0_0: string = "1.0.0";
public static readonly v_1_0_1: string = "1.0.1";
}
export class DataExplorerFeatures {
public static offerCache: string = "OfferCache";
}
export const DataExplorerFeaturesVersions: any = {
OfferCache: DataExplorerVersions.v_1_0_1
};
export const EmulatorMasterKey = export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")] //[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";

View File

@@ -7,7 +7,6 @@ import {
Resource Resource
} from "@azure/cosmos"; } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Q from "q";
import { configContext, Platform } from "../ConfigContext"; import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
@@ -39,18 +38,17 @@ export function getCommonQueryOptions(options: FeedOptions): any {
return options; return options;
} }
export function queryDocuments( export async function queryDocuments(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> { ): Promise<QueryIterator<ItemDefinition & Resource>> {
options = getCommonQueryOptions(options); options = getCommonQueryOptions(options);
const documentsIterator = client() return client()
.database(databaseId) .database(databaseId)
.container(containerId) .container(containerId)
.items.query(query, options); .items.query(query, options);
return Q(documentsIterator);
} }
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object { export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
@@ -76,17 +74,15 @@ export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,
newDocument: any newDocument: any
): Q.Promise<any> { ): Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return Q( return client()
client() .database(collection.databaseId)
.database(collection.databaseId) .container(collection.id())
.container(collection.id()) .item(documentId.id(), partitionKey)
.item(documentId.id(), partitionKey) .replace(newDocument)
.replace(newDocument) .then(response => response.resource);
.then(response => response.resource)
);
} }
export function executeStoredProcedure( export function executeStoredProcedure(
@@ -94,89 +90,89 @@ export function executeStoredProcedure(
storedProcedure: StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
params: any[] params: any[]
): Q.Promise<any> { ): Promise<any> {
// TODO remove this deferred. Kept it because of timeout code at bottom of function return Promise.race([
const deferred = Q.defer<any>(); client()
.database(collection.databaseId)
client() .container(collection.id())
.database(collection.databaseId) .scripts.storedProcedure(storedProcedure.id())
.container(collection.id()) .execute(partitionKeyValue, params, { enableScriptLogging: true })
.scripts.storedProcedure(storedProcedure.id()) .then(response => ({
.execute(partitionKeyValue, params, { enableScriptLogging: true })
.then(response =>
deferred.resolve({
result: response.resource, result: response.resource,
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults] scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
}) })),
new Promise((_, reject) =>
setTimeout(
() => reject(`Request timed out while executing stored procedure ${storedProcedure.id()}`),
Constants.ClientDefaults.requestTimeoutMs
)
) )
.catch(error => deferred.reject(error)); ]);
return deferred.promise.timeout(
Constants.ClientDefaults.requestTimeoutMs,
`Request timed out while executing stored procedure ${storedProcedure.id()}`
);
} }
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> { export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> {
return Q( return client()
client() .database(collection.databaseId)
.database(collection.databaseId) .container(collection.id())
.container(collection.id()) .items.create(newDocument)
.items.create(newDocument) .then(response => response.resource);
.then(response => response.resource)
);
} }
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return Q( return client()
client() .database(collection.databaseId)
.database(collection.databaseId) .container(collection.id())
.container(collection.id()) .item(documentId.id(), partitionKey)
.item(documentId.id(), partitionKey) .read()
.read() .then(response => response.resource);
.then(response => response.resource)
);
} }
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
const partitionKey = documentId.partitionKeyValue; const partitionKey = documentId.partitionKeyValue;
return Q( return client()
client() .database(collection.databaseId)
.database(collection.databaseId) .container(collection.id())
.container(collection.id()) .item(documentId.id(), partitionKey)
.item(documentId.id(), partitionKey) .delete();
.delete()
);
} }
export function deleteConflict( export function deleteConflict(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
conflictId: ConflictId, conflictId: ConflictId,
options: any = {} options: any = {}
): Q.Promise<any> { ): Promise<any> {
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId); options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
return Q( return client()
client() .database(collection.databaseId)
.database(collection.databaseId) .container(collection.id())
.container(collection.id()) .conflict(conflictId.id())
.conflict(conflictId.id()) .delete(options);
.delete(options)
);
} }
export function queryConflicts( export async function refreshCachedOffers(): Promise<void> {
if (configContext.platform === Platform.Portal) {
sendCachedDataMessage(MessageTypes.RefreshOffers, []);
}
}
export async function refreshCachedResources(options?: any): Promise<void> {
if (configContext.platform === Platform.Portal) {
sendCachedDataMessage(MessageTypes.RefreshResources, []);
}
}
export async function queryConflicts(
databaseId: string, databaseId: string,
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> { ): Promise<QueryIterator<ConflictDefinition & Resource>> {
const documentsIterator = client() return client()
.database(databaseId) .database(databaseId)
.container(containerId) .container(containerId)
.conflicts.query(query, options); .conflicts.query(query, options);
return Q(documentsIterator);
} }

View File

@@ -1,5 +1,4 @@
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import Q from "q";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import ConflictId from "../Explorer/Tree/ConflictId"; import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
@@ -16,7 +15,7 @@ export function queryDocuments(
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> { ): Promise<QueryIterator<ItemDefinition & Resource>> {
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options); return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
} }
@@ -25,7 +24,7 @@ export function queryConflicts(
containerId: string, containerId: string,
query: string, query: string,
options: any options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> { ): Promise<QueryIterator<ConflictDefinition & Resource>> {
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options); return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
} }
@@ -43,17 +42,15 @@ export function executeStoredProcedure(
storedProcedure: StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
params: any[] params: any[]
): Q.Promise<any> { ): Promise<any> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`); const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params) return DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
.then( .then(
(response: any) => { (response: any) => {
deferred.resolve(response);
logConsoleInfo( logConsoleInfo(
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}` `Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
); );
return response;
}, },
(error: any) => { (error: any) => {
handleError( handleError(
@@ -61,14 +58,10 @@ export function executeStoredProcedure(
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`, `Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`,
"ExecuteStoredProcedure" "ExecuteStoredProcedure"
); );
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(clearMessage);
clearMessage();
});
return deferred.promise;
} }
export function queryDocumentsPage( export function queryDocumentsPage(
@@ -76,142 +69,114 @@ export function queryDocumentsPage(
documentsIterator: MinimalQueryIterator, documentsIterator: MinimalQueryIterator,
firstItemIndex: number, firstItemIndex: number,
options: any options: any
): Q.Promise<ViewModels.QueryResults> { ): Promise<ViewModels.QueryResults> {
var deferred = Q.defer<ViewModels.QueryResults>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`); const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
Q(nextPage(documentsIterator, firstItemIndex)) return nextPage(documentsIterator, firstItemIndex)
.then( .then(
(result: ViewModels.QueryResults) => { (result: ViewModels.QueryResults) => {
const itemCount = (result.documents && result.documents.length) || 0; const itemCount = (result.documents && result.documents.length) || 0;
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`); logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
deferred.resolve(result); return result;
}, },
(error: any) => { (error: any) => {
handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage"); handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage");
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(clearMessage);
clearMessage();
});
return deferred.promise;
} }
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`); const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
DataAccessUtilityBase.readDocument(collection, documentId) return DataAccessUtilityBase.readDocument(collection, documentId)
.then( .catch((error: any) => {
(document: any) => { handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
deferred.resolve(document); throw error;
}, })
(error: any) => { .finally(clearMessage);
handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
} }
export function updateDocument( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,
newDocument: any newDocument: any
): Q.Promise<any> { ): Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`); const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument) return DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
.then( .then(
(updatedDocument: any) => { (updatedDocument: any) => {
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`); logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
deferred.resolve(updatedDocument); return updatedDocument;
}, },
(error: any) => { (error: any) => {
handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument"); handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument");
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(clearMessage);
clearMessage();
});
return deferred.promise;
} }
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> { export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`); const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
DataAccessUtilityBase.createDocument(collection, newDocument) return DataAccessUtilityBase.createDocument(collection, newDocument)
.then( .then(
(savedDocument: any) => { (savedDocument: any) => {
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`); logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
deferred.resolve(savedDocument); return savedDocument;
}, },
(error: any) => { (error: any) => {
handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument"); handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument");
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(clearMessage);
clearMessage();
});
return deferred.promise;
} }
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> { export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Promise<any> {
var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`); const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
DataAccessUtilityBase.deleteDocument(collection, documentId) return DataAccessUtilityBase.deleteDocument(collection, documentId)
.then( .then(
(response: any) => { (response: any) => {
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`); logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
deferred.resolve(response); return response;
}, },
(error: any) => { (error: any) => {
handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument"); handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument");
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(clearMessage);
clearMessage();
});
return deferred.promise;
} }
export function deleteConflict( export function deleteConflict(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
conflictId: ConflictId, conflictId: ConflictId,
options?: any options?: any
): Q.Promise<any> { ): Promise<any> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`); const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
DataAccessUtilityBase.deleteConflict(collection, conflictId, options) return DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
.then( .then(
(response: any) => { (response: any) => {
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`); logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
deferred.resolve(response); return response;
}, },
(error: any) => { (error: any) => {
handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict"); handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict");
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(clearMessage);
clearMessage(); }
});
export function refreshCachedResources(options: any = {}): Promise<void> {
return deferred.promise; return DataAccessUtilityBase.refreshCachedResources(options);
}
export function refreshCachedOffers(): Promise<void> {
return DataAccessUtilityBase.refreshCachedOffers();
} }

View File

@@ -136,7 +136,7 @@ export class QueriesClient {
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options) return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
.then( .then(
(queryIterator: QueryIterator<ItemDefinition & Resource>) => { (queryIterator: QueryIterator<ItemDefinition & Resource>) => {
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> => const fetchQueries = (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options); queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
return QueryUtils.queryAllPages(fetchQueries).then( return QueryUtils.queryAllPages(fetchQueries).then(
(results: ViewModels.QueryResults) => { (results: ViewModels.QueryResults) => {

View File

@@ -23,6 +23,7 @@ import {
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { createDatabase } from "./createDatabase"; import { createDatabase } from "./createDatabase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -53,6 +54,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
} }
logConsoleInfo(`Successfully created container ${params.collectionId}`); logConsoleInfo(`Successfully created container ${params.collectionId}`);
await refreshCachedResources();
return collection; return collection;
} catch (error) { } catch (error) {
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection"); handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");

View File

@@ -26,6 +26,7 @@ import {
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources"; } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> { export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
@@ -38,6 +39,8 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
? createDatabaseWithARM(params) ? createDatabaseWithARM(params)
: createDatabaseWithSDK(params)); : createDatabaseWithSDK(params));
await refreshCachedResources();
await refreshCachedOffers();
logConsoleInfo(`Successfully created database ${params.databaseId}`); logConsoleInfo(`Successfully created database ${params.databaseId}`);
return database; return database;
} catch (error) { } catch (error) {

View File

@@ -7,6 +7,7 @@ import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { sendCachedDataMessage } from "../MessageHandler";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteCollection", () => { describe("deleteCollection", () => {
@@ -17,6 +18,7 @@ describe("deleteCollection", () => {
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB
}); });
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {

View File

@@ -9,6 +9,7 @@ import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> { export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`); const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
@@ -22,6 +23,7 @@ export async function deleteCollection(databaseId: string, collectionId: string)
.delete(); .delete();
} }
logConsoleInfo(`Successfully deleted container ${collectionId}`); logConsoleInfo(`Successfully deleted container ${collectionId}`);
await refreshCachedResources();
} catch (error) { } catch (error) {
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection"); handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
throw error; throw error;

View File

@@ -7,6 +7,7 @@ import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels"; import { DatabaseAccount } from "../../Contracts/DataModels";
import { sendCachedDataMessage } from "../MessageHandler";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType"; import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteDatabase", () => { describe("deleteDatabase", () => {
@@ -17,6 +18,7 @@ describe("deleteDatabase", () => {
} as DatabaseAccount, } as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB defaultExperience: DefaultAccountExperienceType.DocumentDB
}); });
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
}); });
it("should call ARM if logged in with AAD", async () => { it("should call ARM if logged in with AAD", async () => {

View File

@@ -8,6 +8,7 @@ import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase";
export async function deleteDatabase(databaseId: string): Promise<void> { export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`); const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
@@ -24,6 +25,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
.delete(); .delete();
} }
logConsoleInfo(`Successfully deleted database ${databaseId}`); logConsoleInfo(`Successfully deleted database ${databaseId}`);
await refreshCachedResources();
} catch (error) { } catch (error) {
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase"); handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
throw error; throw error;

View File

@@ -1,10 +1,28 @@
import { Offer } from "../../Contracts/DataModels"; import { Offer } from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { ClientDefaults } from "../Constants";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { Platform, configContext } from "../../ConfigContext";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { sendCachedDataMessage } from "../MessageHandler";
import { userContext } from "../../UserContext";
export const readOffers = async (): Promise<Offer[]> => { export const readOffers = async (): Promise<Offer[]> => {
const clearMessage = logConsoleProgress(`Querying offers`); const clearMessage = logConsoleProgress(`Querying offers`);
try {
if (configContext.platform === Platform.Portal) {
const offers = sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
userContext.databaseAccount.id,
ClientDefaults.portalCacheTimeoutMs
]);
clearMessage();
return offers;
}
} catch (error) {
// If error getting cached Offers, continue on and read via SDK
}
try { try {
const response = await client() const response = await client()

View File

@@ -28,6 +28,7 @@ import {
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources"; import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function updateCollection( export async function updateCollection(
@@ -57,6 +58,7 @@ export async function updateCollection(
} }
logConsoleInfo(`Successfully updated container ${collectionId}`); logConsoleInfo(`Successfully updated container ${collectionId}`);
await refreshCachedResources();
return collection; return collection;
} catch (error) { } catch (error) {
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection"); handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");

View File

@@ -10,6 +10,7 @@ import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { readCollectionOffer } from "./readCollectionOffer"; import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer"; import { readDatabaseOffer } from "./readDatabaseOffer";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { import {
updateSqlDatabaseThroughput, updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale, migrateSqlDatabaseToAutoscale,
@@ -69,6 +70,8 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
} else { } else {
updatedOffer = await updateOfferWithSDK(params); updatedOffer = await updateOfferWithSDK(params);
} }
await refreshCachedOffers();
await refreshCachedResources();
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`); logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
return updatedOffer; return updatedOffer;
} catch (error) { } catch (error) {
@@ -376,7 +379,8 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
const currentOffer = params.currentOffer; const currentOffer = params.currentOffer;
const newOffer: Offer = { const newOffer: Offer = {
content: { content: {
offerThroughput: undefined offerThroughput: undefined,
offerIsRUPerMinuteThroughputEnabled: false
}, },
_etag: undefined, _etag: undefined,
_ts: undefined, _ts: undefined,

View File

@@ -17,7 +17,8 @@ describe("updateOfferThroughputBeyondLimit", () => {
resourceGroup: "foo", resourceGroup: "foo",
databaseAccountName: "foo", databaseAccountName: "foo",
databaseName: "foo", databaseName: "foo",
throughput: 1000000000 throughput: 1000000000,
offerIsRUPerMinuteThroughputEnabled: false
}); });
expect(window.fetch).toHaveBeenCalled(); expect(window.fetch).toHaveBeenCalled();
}); });

View File

@@ -11,6 +11,7 @@ interface UpdateOfferThroughputRequest {
databaseName: string; databaseName: string;
collectionName?: string; collectionName?: string;
throughput: number; throughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
offerAutopilotSettings?: AutoPilotOfferSettings; offerAutopilotSettings?: AutoPilotOfferSettings;
} }

View File

@@ -179,6 +179,7 @@ export interface Offer extends Resource {
offerType?: string; offerType?: string;
content?: { content?: {
offerThroughput: number; offerThroughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
collectionThroughputInfo?: OfferThroughputInfo; collectionThroughputInfo?: OfferThroughputInfo;
offerAutopilotSettings?: AutoPilotOfferSettings; offerAutopilotSettings?: AutoPilotOfferSettings;
}; };
@@ -232,17 +233,27 @@ export interface CreateDatabaseAndCollectionRequest {
collectionId: string; collectionId: string;
offerThroughput: number; offerThroughput: number;
databaseLevelThroughput: boolean; databaseLevelThroughput: boolean;
rupmEnabled?: boolean;
partitionKey?: PartitionKey; partitionKey?: PartitionKey;
indexingPolicy?: IndexingPolicy; indexingPolicy?: IndexingPolicy;
uniqueKeyPolicy?: UniqueKeyPolicy; uniqueKeyPolicy?: UniqueKeyPolicy;
autoPilot?: AutoPilotCreationSettings; autoPilot?: AutoPilotCreationSettings;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
hasAutoPilotV2FeatureFlag?: boolean;
} }
export interface AutoPilotCreationSettings { export interface AutoPilotCreationSettings {
autopilotTier?: AutopilotTier;
maxThroughput?: number; maxThroughput?: number;
} }
export enum AutopilotTier {
Tier1 = 1,
Tier2 = 2,
Tier3 = 3,
Tier4 = 4
}
export interface Query { export interface Query {
id: string; id: string;
resourceId: string; resourceId: string;
@@ -251,7 +262,9 @@ export interface Query {
} }
export interface AutoPilotOfferSettings { export interface AutoPilotOfferSettings {
tier?: AutopilotTier;
maximumTierThroughput?: number; maximumTierThroughput?: number;
targetTier?: AutopilotTier;
maxThroughput?: number; maxThroughput?: number;
targetMaxThroughput?: number; targetMaxThroughput?: number;
} }
@@ -478,6 +491,7 @@ export interface MongoParameters extends RpParameters {
rid?: string; rid?: string;
rtype?: string; rtype?: string;
isAutoPilot?: Boolean; isAutoPilot?: Boolean;
autoPilotTier?: string;
autoPilotThroughput?: string; autoPilotThroughput?: string;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
} }

View File

@@ -5,7 +5,6 @@ import {
TriggerDefinition, TriggerDefinition,
UserDefinedFunctionDefinition UserDefinedFunctionDefinition
} from "@azure/cosmos"; } from "@azure/cosmos";
import Q from "q";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
@@ -107,7 +106,7 @@ export interface CollectionBase extends TreeNode {
onDocumentDBDocumentsClick(): void; onDocumentDBDocumentsClick(): void;
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void; onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
expandCollection(): Q.Promise<any>; expandCollection(): Promise<any>;
collapseCollection(): void; collapseCollection(): void;
getDatabase(): Database; getDatabase(): Database;
} }
@@ -172,7 +171,7 @@ export interface Collection extends CollectionBase {
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void; onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
onDrop(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Q.Promise<UploadDetails>; uploadFiles(fileList: FileList): Promise<UploadDetails>;
getLabel(): string; getLabel(): string;
} }
@@ -290,7 +289,7 @@ export interface DocumentsTabOptions extends TabOptions {
} }
export interface SettingsTabV2Options extends TabOptions { export interface SettingsTabV2Options extends TabOptions {
getPendingNotification: Q.Promise<DataModels.Notification>; getPendingNotification: Promise<DataModels.Notification>;
} }
export interface ConflictsTabOptions extends TabOptions { export interface ConflictsTabOptions extends TabOptions {

View File

@@ -123,4 +123,8 @@ describe("Component Registerer", () => {
it("should register dynamic-list component", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); expect(ko.components.isRegistered("dynamic-list")).toBe(true);
}); });
it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true);
});
}); });

View File

@@ -11,6 +11,7 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager"; import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("input-typeahead", new InputTypeaheadComponent());
@@ -22,6 +23,7 @@ ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent()); ko.components.register("tabs-manager", TabsManagerKOComponent());

View File

@@ -57,7 +57,7 @@ class EditorViewModel extends JsonEditorViewModel {
} }
} }
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected async getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
return ErrorMarkProvider.getErrorMark(input); return ErrorMarkProvider.getErrorMark(input);
} }
} }

View File

@@ -44,6 +44,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void; onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
}[] = [ }[] = [
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" }, { key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
{ key: "feature.enablerupm", label: "Enable RUPM", value: "true" },
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" }, { key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" }, { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
{ key: "feature.enablettl", label: "Enable TTL", value: "true" }, { key: "feature.enablettl", label: "Enable TTL", value: "true" },

View File

@@ -131,6 +131,12 @@ exports[`Feature panel renders all flags 1`] = `
label="Enable change feed policy" label="Enable change feed policy"
onChange={[Function]} onChange={[Function]}
/> />
<StyledCheckboxBase
checked={false}
key="feature.enablerupm"
label="Enable RUPM"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.dataexplorerexecutesproc" key="feature.dataexplorerexecutesproc"

View File

@@ -1,4 +1,3 @@
import Q from "q";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
@@ -107,8 +106,8 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected registerCompletionItemProvider() {} protected registerCompletionItemProvider() {}
// Interface. Will be implemented in children editor view model such as EditorViewModel. // Interface. Will be implemented in children editor view model such as EditorViewModel.
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected getErrorMarkers(input: string): Promise<monaco.editor.IMarkerData[]> {
return Q.Promise(() => {}); return new Promise(() => {});
} }
protected getEditorLanguage(): string { protected getEditorLanguage(): string {

View File

@@ -31,7 +31,6 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
})); }));
import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types"; import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import Q from "q";
jest.mock("../../../Common/dataAccess/updateOffer", () => ({ jest.mock("../../../Common/dataAccess/updateOffer", () => ({
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer) updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
})); }));
@@ -47,9 +46,7 @@ describe("SettingsComponent", () => {
hashLocation: "settings", hashLocation: "settings",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: undefined, onUpdateTabsButtons: undefined,
getPendingNotification: Q.Promise<DataModels.Notification>(() => { getPendingNotification: Promise.resolve(undefined)
return;
})
}) })
}; };

View File

@@ -125,6 +125,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private container: Explorer; private container: Explorer;
private changeFeedPolicyVisible: boolean; private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean; private isFixedContainer: boolean;
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
private shouldShowIndexingPolicyEditor: boolean; private shouldShowIndexingPolicyEditor: boolean;
public mongoDBCollectionResource: MongoDBCollectionResource; public mongoDBCollectionResource: MongoDBCollectionResource;
@@ -229,7 +230,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if ( if (
this.container.isMongoIndexEditorEnabled() && this.container.isMongoIndexEditorEnabled() &&
this.container.isPreferredApiMongoDB() && this.container.isPreferredApiMongoDB() &&
this.container.isEnableMongoCapabilityPresent() &&
this.container.databaseAccount() this.container.databaseAccount()
) { ) {
await this.refreshIndexTransformationProgress(); await this.refreshIndexTransformationProgress();
@@ -395,54 +395,24 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) { if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
try { const newMongoIndexes = this.getMongoIndexesToSave();
const newMongoIndexes = this.getMongoIndexesToSave(); const newMongoCollection: MongoDBCollectionResource = {
const newMongoCollection: MongoDBCollectionResource = { ...this.mongoDBCollectionResource,
...this.mongoDBCollectionResource, indexes: newMongoIndexes
indexes: newMongoIndexes };
}; this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
this.collection.databaseId,
this.collection.id(),
newMongoCollection
);
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP( await this.refreshIndexTransformationProgress();
this.collection.databaseId, this.setState({
this.collection.id(), isMongoIndexingPolicySaveable: false,
newMongoCollection indexesToDrop: [],
); indexesToAdd: [],
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
await this.refreshIndexTransformationProgress(); });
this.setState({
isMongoIndexingPolicySaveable: false,
indexesToDrop: [],
indexesToAdd: [],
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
});
traceSuccess(
Action.MongoIndexUpdated,
{
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
},
startKey
);
} catch (error) {
traceFailure(
Action.MongoIndexUpdated,
{
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
error: error.message
},
startKey
);
throw error;
}
} }
if (this.state.isScaleSaveable) { if (this.state.isScaleSaveable) {
@@ -454,7 +424,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
newOffer.content.offerThroughput = newThroughput; newOffer.content.offerThroughput = newThroughput;
} else { } else {
newOffer.content = { newOffer.content = {
offerThroughput: newThroughput offerThroughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
}; };
} }
@@ -497,7 +468,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseName: this.collection.databaseId, databaseName: this.collection.databaseId,
collectionName: this.collection.id(), collectionName: this.collection.id(),
throughput: newThroughput throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
}; };
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
@@ -570,7 +542,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
error: reason.message error: reason
}, },
startKey startKey
); );
@@ -895,6 +867,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
collection: this.collection, collection: this.collection,
container: this.container, container: this.container,
isFixedContainer: this.isFixedContainer, isFixedContainer: this.isFixedContainer,
autoPilotTiersList: this.autoPilotTiersList,
onThroughputChange: this.onThroughputChange, onThroughputChange: this.onThroughputChange,
throughput: this.state.throughput, throughput: this.state.throughput,
throughputBaseline: this.state.throughputBaseline, throughputBaseline: this.state.throughputBaseline,

View File

@@ -31,7 +31,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{getAutoPilotV3SpendElement(1000, true)} {getAutoPilotV3SpendElement(1000, true)}
{getAutoPilotV3SpendElement(undefined, true)} {getAutoPilotV3SpendElement(undefined, true)}
{getEstimatedSpendElement(1000, "mooncake", 2, false)} {getEstimatedSpendElement(1000, "mooncake", 2, false, true)}
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)} {getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}

View File

@@ -199,9 +199,10 @@ export const getEstimatedSpendElement = (
throughput: number, throughput: number,
serverId: string, serverId: string,
regions: number, regions: number,
multimaster: boolean multimaster: boolean,
rupmEnabled: boolean
): JSX.Element => { ): JSX.Element => {
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster); const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24; const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * hoursInAMonth; const monthlyPrice: number = hourlyPrice * hoursInAMonth;
const currency: string = getPriceCurrency(serverId); const currency: string = getPriceCurrency(serverId);

View File

@@ -20,6 +20,7 @@ describe("ScaleComponent", () => {
collection: collection, collection: collection,
container: container, container: container,
isFixedContainer: false, isFixedContainer: false,
autoPilotTiersList: [],
onThroughputChange: () => { onThroughputChange: () => {
return; return;
}, },

View File

@@ -24,6 +24,7 @@ export interface ScaleComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
container: Explorer; container: Explorer;
isFixedContainer: boolean; isFixedContainer: boolean;
autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
onThroughputChange: (newThroughput: number) => void; onThroughputChange: (newThroughput: number) => void;
throughput: number; throughput: number;
throughputBaseline: number; throughputBaseline: number;
@@ -85,7 +86,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
public getThroughputTitle = (): string => { public getThroughputTitle = (): string => {
if (this.props.isAutoPilotSelected) { if (this.props.isAutoPilotSelected) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(false);
} }
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString(); const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();

View File

@@ -174,7 +174,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput, this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
serverId, serverId,
regions, regions,
multimaster multimaster,
false
); );
} else { } else {
estimatedSpend = getEstimatedAutoscaleSpendElement( estimatedSpend = getEstimatedAutoscaleSpendElement(

View File

@@ -22,6 +22,7 @@ export const collection = ({
offer: ko.observable<DataModels.Offer>({ offer: ko.observable<DataModels.Offer>({
content: { content: {
offerThroughput: 10000, offerThroughput: 10000,
offerIsRUPerMinuteThroughputEnabled: false,
collectionThroughputInfo: { collectionThroughputInfo: {
minimumRUForCollection: 6000, minimumRUForCollection: 6000,
numPhysicalPartitions: 4 numPhysicalPartitions: 4

View File

@@ -40,6 +40,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -55,6 +56,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -67,6 +69,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -83,6 +86,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -104,6 +108,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -133,7 +138,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -344,6 +354,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -355,6 +366,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -375,7 +387,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -570,6 +585,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -591,6 +607,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -620,7 +637,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -650,6 +672,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -665,6 +688,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -677,6 +701,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -753,6 +778,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -764,6 +790,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -784,7 +811,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -939,6 +969,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
@@ -1316,6 +1347,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1331,6 +1363,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -1343,6 +1376,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -1359,6 +1393,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1380,6 +1415,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -1409,7 +1445,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -1620,6 +1661,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1631,6 +1673,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -1651,7 +1694,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -1846,6 +1892,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1867,6 +1914,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -1896,7 +1944,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -1926,6 +1979,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1941,6 +1995,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -1953,6 +2008,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -2029,6 +2085,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2040,6 +2097,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -2060,7 +2118,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -2215,6 +2276,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
@@ -2605,6 +2667,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2620,6 +2683,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -2632,6 +2696,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -2648,6 +2713,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2669,6 +2735,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -2698,7 +2765,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -2909,6 +2981,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2920,6 +2993,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -2940,7 +3014,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -3135,6 +3212,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3156,6 +3234,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -3185,7 +3264,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -3215,6 +3299,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3230,6 +3315,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -3242,6 +3328,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -3318,6 +3405,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3329,6 +3417,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -3349,7 +3438,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -3504,6 +3596,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
@@ -3881,6 +3974,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3896,6 +3990,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -3908,6 +4003,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -3924,6 +4020,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3945,6 +4042,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -3974,7 +4072,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -4185,6 +4288,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4196,6 +4300,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -4216,7 +4321,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -4411,6 +4519,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4432,6 +4541,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -4461,7 +4571,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -4491,6 +4606,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4506,6 +4622,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -4518,6 +4635,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -4594,6 +4712,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4605,6 +4724,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -4625,7 +4745,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -4780,6 +4903,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],

View File

@@ -69,15 +69,15 @@ exports[`SettingsUtils functions render 1`] = `
<b> <b>
¥ ¥
1.02 1.29
hourly hourly
/ /
¥ ¥
24.48 31.06
daily daily
/ /
¥ ¥
744.60 944.60
monthly monthly
</b> </b>

View File

@@ -0,0 +1,222 @@
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import editable from "../../../Common/EditableUtility";
import { ThroughputInputComponent, ThroughputInputParams, ThroughputInputViewModel } from "./ThroughputInputComponent";
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
describe.skip("Throughput Input Component", () => {
let component: any;
let vm: ThroughputInputViewModel;
const testId: string = "ThroughputValue";
const value: ViewModels.Editable<number> = editable.observable(500);
const minimum: ko.Observable<number> = ko.observable(400);
const maximum: ko.Observable<number> = ko.observable(2000);
function buildListOptions(
value: ViewModels.Editable<number>,
minimum: ko.Observable<number>,
maxium: ko.Observable<number>,
canExceedMaximumValue?: boolean
): ThroughputInputParams {
return {
testId,
value,
minimum,
maximum,
canExceedMaximumValue: ko.computed<boolean>(() => Boolean(canExceedMaximumValue)),
costsVisible: ko.observable(false),
isFixed: false,
label: ko.observable("Label"),
requestUnitsUsageCost: ko.observable("requestUnitsUsageCost"),
showAsMandatory: false,
autoPilotTiersList: null,
autoPilotUsageCost: null,
isAutoPilotSelected: null,
selectedAutoPilotTier: null,
throughputAutoPilotRadioId: null,
throughputProvisionedRadioId: null,
throughputModeRadioName: null
};
}
function simulateKeyPressSpace(target: HTMLElement): Promise<boolean> {
const event = new KeyboardEvent("keydown", {
key: "space"
});
const result = target.dispatchEvent(event);
return new Promise(resolve => {
setTimeout(() => {
resolve(result);
}, 1000);
});
}
beforeEach(() => {
component = ThroughputInputComponent;
document.body.innerHTML = component.template as any;
});
afterEach(async () => {
await ko.cleanNode(document);
});
describe("Rendering", () => {
it("should display value text", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
expect(($("input") as HTMLInputElement).value).toContain(value().toString());
});
});
describe("Behavior", () => {
it("should decrease value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(450);
$(".testhook-decreaseThroughput").click();
expect(value()).toBe(400);
$(".testhook-decreaseThroughput").click();
expect(value()).toBe(400);
});
it("should increase value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(1950);
$(".test-increaseThroughput").click();
expect(value()).toBe(2000);
$(".test-increaseThroughput").click();
expect(value()).toBe(2000);
});
it("should respect lower bound limits", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(minimum());
$(".testhook-decreaseThroughput").click();
expect(value()).toBe(minimum());
});
it("should respect upper bound limits", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(maximum());
$(".test-increaseThroughput").click();
expect(value()).toBe(maximum());
});
it("should allow throughput to exceed upper bound limit when canExceedMaximumValue is set", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
value(maximum());
$(".test-increaseThroughput").click();
expect(value()).toBe(maximum() + 100);
});
});
describe("Accessibility", () => {
it.skip("should decrease value with keypress", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
const target = $(".testhook-decreaseThroughput");
value(500);
expect(value()).toBe(500);
const result = await simulateKeyPressSpace(target);
expect(value()).toBe(400);
});
it.skip("should increase value with keypress", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
const target = $(".test-increaseThroughput");
value(400);
expect(value()).toBe(400);
const result = await simulateKeyPressSpace(target);
// expect(value()).toBe(500);
});
it("should set the decreaseButtonAriaLabel using the default step value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 100");
});
it("should set the increaseButtonAriaLabel using the default step value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 100");
});
it("should set the increaseButtonAriaLabel using the params step value", async () => {
const options = buildListOptions(value, minimum, maximum, true);
options.step = 10;
vm = new component.viewModel(options);
await ko.applyBindings(vm);
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 10");
});
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
const options = buildListOptions(value, minimum, maximum, true);
options.step = 10;
vm = new component.viewModel(options);
await ko.applyBindings(vm);
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 10");
});
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
const options = buildListOptions(value, minimum, maximum, true);
options.step = 10;
vm = new component.viewModel(options);
await ko.applyBindings(vm);
});
it("should have aria-label attribute on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const ariaLabel = $(".test-increaseThroughput").attributes.getNamedItem("aria-label").value;
expect(ariaLabel).toBe("Increase throughput by 100");
});
it("should have aria-label attribute on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const ariaLabel = $(".testhook-decreaseThroughput").attributes.getNamedItem("aria-label").value;
expect(ariaLabel).toBe("Decrease throughput by 100");
});
it("should have role on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".test-increaseThroughput").attributes.getNamedItem("role").value;
expect(role).toBe("button");
});
it("should have role on decrease button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("role").value;
expect(role).toBe("button");
});
it("should have tabindex 0 on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
expect(role).toBe("0");
});
it("should have tabindex 0 on decrease button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
expect(role).toBe("0");
});
});
});

View File

@@ -0,0 +1,145 @@
<div>
<div>
<p class="pkPadding">
<!-- ko if: showAsMandatory -->
<span class="mandatoryStar">*</span>
<!-- /ko -->
<span class="addCollectionLabel" data-bind="text: label"></span>
<!-- ko if: infoBubbleText -->
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="../../../../images/info-bubble.svg" alt="More information" />
<span data-bind="text: infoBubbleText" class="tooltiptext throughputRuInfo"></span>
</span>
<!-- /ko -->
</p>
</div>
<!-- ko if: !isFixed -->
<div data-bind="visible: showAutoPilot" class="throughputModeContainer">
<input
class="throughputModeRadio"
aria-label="Autopilot mode"
data-test="throughput-autoPilot"
type="radio"
role="radio"
tabindex="0"
data-bind="
checked: isAutoPilotSelected,
checkedValue: true,
attr: {
id: throughputAutoPilotRadioId,
name: throughputModeRadioName,
'aria-checked': isAutoPilotSelected() ? 'true' : 'false'
}"
/>
<span
class="throughputModeSpace"
data-bind="
attr: {
for: throughputAutoPilotRadioId
}"
>Autopilot (preview)
</span>
<input
class="throughputModeRadio nonFirstRadio"
aria-label="Manual mode"
type="radio"
role="radio"
tabindex="0"
data-bind="
checked: isAutoPilotSelected,
checkedValue: false,
attr: {
id: throughputProvisionedRadioId,
name: throughputModeRadioName,
'aria-checked': !isAutoPilotSelected() ? 'true' : 'false'
}"
/>
<span
class="throughputModeSpace"
data-bind="
attr: {
for: throughputProvisionedRadioId
}"
>Manual
</span>
</div>
<!-- /ko -->
<div data-bind="visible: isAutoPilotSelected">
<select
name="autoPilotTiers"
class="collid select-font-size"
aria-label="Autopilot Max RU/s"
data-bind="
options: autoPilotTiersList,
optionsText: 'text',
optionsValue: 'value',
value: selectedAutoPilotTier,
optionsCaption: 'Choose Max RU/s'"
>
</select>
<p>
<span
data-bind="
html: autoPilotUsageCost,
visible: selectedAutoPilotTier"
>
</span>
</p>
</div>
<div data-bind="visible: !isAutoPilotSelected()">
<div data-bind="setTemplateReady: true">
<p class="addContainerThroughputInput">
<input
type="number"
required
data-bind="
textInput: value,
css: {
dirty: value.editableIsDirty
},
enable: isEnabled,
attr:{
'data-test': testId,
'class': cssClass,
step: step,
min: minimum,
max: canExceedMaximumValue() ? null : maximum,
'aria-label': ariaLabel
}"
/>
</p>
</div>
<p data-bind="visible: costsVisible">
<span data-bind="html: requestUnitsUsageCost"></span>
</p>
<!-- ko if: spendAckVisible -->
<p class="pkPadding">
<input
type="checkbox"
aria-label="acknowledge spend throughput"
data-bind="
attr: {
title: spendAckText,
id: spendAckId
},
checked: spendAckChecked"
/>
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
</p>
<!-- /ko -->
<!-- ko if: isFixed -->
<p>
Choose unlimited storage capacity for more than 10,000 RU/s.
</p>
<!-- /ko -->
</div>
</div>

View File

@@ -0,0 +1,261 @@
import * as DataModels from "../../../Contracts/DataModels";
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import { KeyCodes } from "../../../Common/Constants";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import ThroughputInputComponentTemplate from "./ThroughputInputComponent.html";
/**
* Throughput Input:
*
* Creates a set of controls to input, sanitize and increase/decrease throughput
*
* How to use in your markup:
* <throughput-input params="{ value: anObservableToHoldTheValue, minimum: anObservableWithMinimum, maximum: anObservableWithMaximum }">
* </throughput-input>
*
*/
/**
* Parameters for this component
*/
export interface ThroughputInputParams {
/**
* Callback triggered when the template is bound to the component (for testing purposes)
*/
onTemplateReady?: () => void;
/**
* Observable to bind the Throughput value to
*/
value: ViewModels.Editable<number>;
/**
* Text to use as id for testing
*/
testId: string;
/**
* Text to use as aria-label
*/
ariaLabel?: ko.Observable<string>;
/**
* Minimum value in the range
*/
minimum: ko.Observable<number>;
/**
* Maximum value in the range
*/
maximum: ko.Observable<number>;
/**
* Step value for increase/decrease
*/
step?: number;
/**
* Observable to bind the Throughput enabled status
*/
isEnabled?: ko.Observable<boolean>;
/**
* Should show pricing controls
*/
costsVisible: ko.Observable<boolean>;
/**
* RU price
*/
requestUnitsUsageCost: ko.Subscribable<string>; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable
/**
* State of the spending acknowledge checkbox
*/
spendAckChecked?: ko.Observable<boolean>;
/**
* id of the spending acknowledge checkbox
*/
spendAckId?: ko.Observable<string>;
/**
* spending acknowledge text
*/
spendAckText?: ko.Observable<string>;
/**
* Show spending acknowledge controls
*/
spendAckVisible?: ko.Observable<boolean>;
/**
* Display * to the left of the label
*/
showAsMandatory: boolean;
/**
* If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed
*/
isFixed: boolean;
/**
* Label of the provisioned throughut control
*/
label: ko.Observable<string>;
/**
* Text of the info bubble for provisioned throughut control
*/
infoBubbleText?: ko.Observable<string>;
/**
* Computed value that decides if value can exceed maximum allowable value
*/
canExceedMaximumValue?: ko.Computed<boolean>;
/**
* CSS classes to apply on input element
*/
cssClass?: string;
isAutoPilotSelected: ko.Observable<boolean>;
throughputAutoPilotRadioId: string;
throughputProvisionedRadioId: string;
throughputModeRadioName: string;
autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
autoPilotUsageCost: ko.Computed<string>;
showAutoPilot?: ko.Observable<boolean>;
}
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
public ariaLabel: ko.Observable<string>;
public canExceedMaximumValue: ko.Computed<boolean>;
public step: number;
public testId: string;
public value: ViewModels.Editable<number>;
public minimum: ko.Observable<number>;
public maximum: ko.Observable<number>;
public isEnabled: ko.Observable<boolean>;
public cssClass: string;
public decreaseButtonAriaLabel: string;
public increaseButtonAriaLabel: string;
public costsVisible: ko.Observable<boolean>;
public requestUnitsUsageCost: ko.Subscribable<string>;
public spendAckChecked: ko.Observable<boolean>;
public spendAckId: ko.Observable<string>;
public spendAckText: ko.Observable<string>;
public spendAckVisible: ko.Observable<boolean>;
public showAsMandatory: boolean;
public infoBubbleText: string | ko.Observable<string>;
public label: ko.Observable<string>;
public isFixed: boolean;
public showAutoPilot: ko.Observable<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>;
public throughputAutoPilotRadioId: string;
public throughputProvisionedRadioId: string;
public throughputModeRadioName: string;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotUsageCost: ko.Computed<string>;
public constructor(options: ThroughputInputParams) {
super();
super.onTemplateReady((isTemplateReady: boolean) => {
if (isTemplateReady && options.onTemplateReady) {
options.onTemplateReady();
}
});
const params: ThroughputInputParams = options;
this.testId = params.testId || "ThroughputValue";
this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || "");
this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false);
this.step = params.step || ThroughputInputViewModel._defaultStep;
this.isEnabled = params.isEnabled || ko.observable(true);
this.cssClass = params.cssClass || "textfontclr collid";
this.minimum = params.minimum;
this.maximum = params.maximum;
this.value = params.value;
this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step.toString();
this.increaseButtonAriaLabel = "Increase throughput by " + this.step.toString();
this.costsVisible = options.costsVisible;
this.requestUnitsUsageCost = options.requestUnitsUsageCost;
this.spendAckChecked = options.spendAckChecked || ko.observable<boolean>(false);
this.spendAckId = options.spendAckId || ko.observable<string>();
this.spendAckText = options.spendAckText || ko.observable<string>();
this.spendAckVisible = options.spendAckVisible || ko.observable<boolean>(false);
this.showAsMandatory = !!options.showAsMandatory;
this.isFixed = !!options.isFixed;
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
this.label = options.label || ko.observable<string>();
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
this.throughputModeRadioName = options.throughputModeRadioName;
this.autoPilotTiersList = options.autoPilotTiersList;
this.selectedAutoPilotTier = options.selectedAutoPilotTier;
this.autoPilotUsageCost = options.autoPilotUsageCost;
}
public decreaseThroughput() {
let offerThroughput: number = this._getSanitizedValue();
if (offerThroughput > this.minimum()) {
offerThroughput -= this.step;
if (offerThroughput < this.minimum()) {
offerThroughput = this.minimum();
}
this.value(offerThroughput);
}
}
public increaseThroughput() {
let offerThroughput: number = this._getSanitizedValue();
if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) {
offerThroughput += this.step;
if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) {
offerThroughput = this.maximum();
}
this.value(offerThroughput);
}
}
public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.increaseThroughput();
event.stopPropagation();
return false;
}
return true;
};
public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.decreaseThroughput();
event.stopPropagation();
return false;
}
return true;
};
private _getSanitizedValue(): number {
const throughput = this.value();
return isNaN(throughput) ? 0 : Number(throughput);
}
private static _defaultStep: number = 100;
}
export const ThroughputInputComponent = {
viewModel: ThroughputInputViewModel,
template: ThroughputInputComponentTemplate
};

View File

@@ -3,7 +3,6 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
jest.mock("../../Common/dataAccess/createCollection"); jest.mock("../../Common/dataAccess/createCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Q from "q";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { createDocument } from "../../Common/DocumentClientUtilityBase"; import { createDocument } from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
@@ -19,8 +18,9 @@ describe("ContainerSampleGenerator", () => {
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false); explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false); explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false);
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false); explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorerStub.findDatabaseWithId = () => database; explorerStub.findDatabaseWithId = () => database;
explorerStub.refreshAllDatabases = () => Q.resolve(); explorerStub.refreshAllDatabases = () => Promise.resolve();
return explorerStub; return explorerStub;
}; };

View File

@@ -70,7 +70,7 @@ export class ContainerSampleGenerator {
if (!collection) { if (!collection) {
throw new Error("No container to populate"); throw new Error("No container to populate");
} }
const promises: Q.Promise<any>[] = []; const promises: Promise<any>[] = [];
if (this.container.isPreferredApiGraph()) { if (this.container.isPreferredApiGraph()) {
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries // For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries

View File

@@ -15,6 +15,7 @@ import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
import Database from "./Tree/Database"; import Database from "./Tree/Database";
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane"; import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane"; import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
import { refreshCachedResources } from "../Common/DocumentClientUtilityBase";
import { readCollection } from "../Common/dataAccess/readCollection"; import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases"; import { readDatabases } from "../Common/dataAccess/readDatabases";
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane"; import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
@@ -212,10 +213,11 @@ export default class Explorer {
public isHostedDataExplorerEnabled: ko.Computed<boolean>; public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>; public isRightPanelV2Enabled: ko.Computed<boolean>;
public canExceedMaximumValue: ko.Computed<boolean>; public canExceedMaximumValue: ko.Computed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
public shouldShowShareDialogContents: ko.Observable<boolean>; public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<AdHocAccessData>; public shareAccessData: ko.Observable<AdHocAccessData>;
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>; public renewExplorerShareAccess: (explorer: Explorer, token: string) => Promise<void>;
public renewTokenError: ko.Observable<string>; public renewTokenError: ko.Observable<string>;
public tokenForRenewal: ko.Observable<string>; public tokenForRenewal: ko.Observable<string>;
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>; public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
@@ -421,6 +423,13 @@ export default class Explorer {
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue) this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
); );
this.hasAutoPilotV2FeatureFlag = ko.computed(() => {
if (this.isFeatureEnabled(Constants.Features.enableAutoPilotV2)) {
return true;
}
return false;
});
this.isNotificationConsoleExpanded = ko.observable<boolean>(false); this.isNotificationConsoleExpanded = ko.observable<boolean>(false);
this.databases = ko.observableArray<ViewModels.Database>(); this.databases = ko.observableArray<ViewModels.Database>();
@@ -1111,7 +1120,7 @@ export default class Explorer {
"Initiating connection to account" "Initiating connection to account"
); );
this.renewExplorerShareAccess(this, this.tokenForRenewal()) this.renewExplorerShareAccess(this, this.tokenForRenewal())
.fail((error: any) => { .catch((error: any) => {
const stringifiedError: string = error.message; const stringifiedError: string = error.message;
this.renewTokenError("Invalid connection string specified"); this.renewTokenError("Invalid connection string specified");
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -1148,33 +1157,27 @@ export default class Explorer {
); );
} }
public renewShareAccess(token: string): Q.Promise<void> { public async renewShareAccess(token: string): Promise<void> {
if (!this.renewExplorerShareAccess) { if (!this.renewExplorerShareAccess) {
return Q.reject("Not implemented"); throw "Not implemented";
} }
const deferred: Q.Deferred<void> = Q.defer<void>();
const id: string = NotificationConsoleUtils.logConsoleMessage( const id: string = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
"Initiating connection to account" "Initiating connection to account"
); );
this.renewExplorerShareAccess(this, token) return this.renewExplorerShareAccess(this, token)
.then( .then(
() => { () => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful"); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Connection successful");
this.renewAdHocAccessPane && this.renewAdHocAccessPane.close(); this.renewAdHocAccessPane && this.renewAdHocAccessPane.close();
deferred.resolve();
}, },
(error: any) => { (error: any) => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
deferred.reject(error); throw error;
} }
) )
.finally(() => { .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
NotificationConsoleUtils.clearInProgressMessageWithId(id);
});
return deferred.promise;
} }
public displayGuestAccessTokenRenewalPrompt(): void { public displayGuestAccessTokenRenewalPrompt(): void {
@@ -1369,24 +1372,19 @@ export default class Explorer {
} }
} }
public refreshDatabaseForResourceToken(): Q.Promise<any> { public async refreshDatabaseForResourceToken(): Promise<any> {
const databaseId = this.resourceTokenDatabaseId(); const databaseId = this.resourceTokenDatabaseId();
const collectionId = this.resourceTokenCollectionId(); const collectionId = this.resourceTokenCollectionId();
if (!databaseId || !collectionId) { if (!databaseId || !collectionId) {
return Q.reject(); throw new Error();
} }
const deferred: Q.Deferred<void> = Q.defer(); const collection = await readCollection(databaseId, collectionId);
readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => { this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection));
this.resourceTokenCollection(new ResourceTokenCollection(this, databaseId, collection)); this.selectedNode(this.resourceTokenCollection());
this.selectedNode(this.resourceTokenCollection());
deferred.resolve();
});
return deferred.promise;
} }
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> { public refreshAllDatabases(isInitialLoad?: boolean): Promise<any> {
this.isRefreshingExplorer(true); this.isRefreshingExplorer(true);
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, { const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name, databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
@@ -1403,89 +1401,85 @@ export default class Explorer {
} }
// TODO: Refactor // TODO: Refactor
const deferred: Q.Deferred<any> = Q.defer();
this._setLoadingStatusText("Fetching databases..."); this._setLoadingStatusText("Fetching databases...");
readDatabases().then( return readDatabases()
(databases: DataModels.Database[]) => { .then(
this._setLoadingStatusText("Successfully fetched databases."); (databases: DataModels.Database[]) => {
TelemetryProcessor.traceSuccess( this._setLoadingStatusText("Successfully fetched databases.");
Action.LoadDatabases,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
},
startKey
);
const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
const deltaDatabases = this.getDeltaDatabases(databases);
this.addDatabasesToList(deltaDatabases.toAdd);
this.deleteDatabasesFromList(deltaDatabases.toDelete);
this.selectedNode(currentlySelectedNode);
this._setLoadingStatusText("Fetching containers...");
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
.then(
() => {
this._setLoadingStatusText("Successfully fetched containers.");
deferred.resolve();
},
reason => {
this._setLoadingStatusText("Failed to fetch containers.");
deferred.reject(reason);
}
)
.finally(() => this.isRefreshingExplorer(false));
},
error => {
this._setLoadingStatusText("Failed to fetch databases.");
this.isRefreshingExplorer(false);
deferred.reject(error);
TelemetryProcessor.traceFailure(
Action.LoadDatabases,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: error.message
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while refreshing databases: ${error.message}`
);
}
);
return deferred.promise.then(
() => {
if (resourceTreeStartKey != null) {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.LoadResourceTree, Action.LoadDatabases,
{ {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name, databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(), defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}, },
resourceTreeStartKey startKey
); );
} const currentlySelectedNode: ViewModels.TreeNode = this.selectedNode();
}, const deltaDatabases = this.getDeltaDatabases(databases);
reason => { this.addDatabasesToList(deltaDatabases.toAdd);
if (resourceTreeStartKey != null) { this.deleteDatabasesFromList(deltaDatabases.toDelete);
this.selectedNode(currentlySelectedNode);
this._setLoadingStatusText("Fetching containers...");
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd)
.then(
() => this._setLoadingStatusText("Successfully fetched containers."),
reason => {
this._setLoadingStatusText("Failed to fetch containers.");
throw reason;
}
)
.finally(() => this.isRefreshingExplorer(false));
},
error => {
this._setLoadingStatusText("Failed to fetch databases.");
this.isRefreshingExplorer(false);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.LoadResourceTree, Action.LoadDatabases,
{ {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name, databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(), defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree, dataExplorerArea: Constants.Areas.ResourceTree,
error: reason error: error.message
}, },
resourceTreeStartKey startKey
); );
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while refreshing databases: ${error.message}`
);
throw error;
} }
} )
); .then(
() => {
if (resourceTreeStartKey != null) {
TelemetryProcessor.traceSuccess(
Action.LoadResourceTree,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
},
resourceTreeStartKey
);
}
},
reason => {
if (resourceTreeStartKey != null) {
TelemetryProcessor.traceFailure(
Action.LoadResourceTree,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: reason
},
resourceTreeStartKey
);
}
}
);
} }
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => { public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
@@ -1504,7 +1498,41 @@ export default class Explorer {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
this.isRefreshingExplorer(true); this.isRefreshingExplorer(true);
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(); refreshCachedResources().then(
() => {
TelemetryProcessor.traceSuccess(
Action.LoadDatabases,
{
description: "Refresh successful",
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
},
startKey
);
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases();
},
(error: any) => {
this.isRefreshingExplorer(false);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while refreshing data: ${error.message}`
);
TelemetryProcessor.traceFailure(
Action.LoadDatabases,
{
description: "Unable to refresh cached resources",
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error: error
},
startKey
);
throw error;
}
);
this.refreshNotebookList(); this.refreshNotebookList();
}; };
@@ -1751,7 +1779,7 @@ export default class Explorer {
inputs.extensionEndpoint = configContext.PROXY_PATH; inputs.extensionEndpoint = configContext.PROXY_PATH;
} }
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q(); const initPromise: Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Promise.resolve();
initPromise.then(() => { initPromise.then(() => {
const openAction: ActionContracts.DataExplorerAction = message.openAction; const openAction: ActionContracts.DataExplorerAction = message.openAction;
@@ -1845,7 +1873,7 @@ export default class Explorer {
return false; return false;
} }
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> { public async initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Promise<void> {
if (inputs != null) { if (inputs != null) {
const authorizationToken = inputs.authorizationToken || ""; const authorizationToken = inputs.authorizationToken || "";
const masterKey = inputs.masterKey || ""; const masterKey = inputs.masterKey || "";
@@ -1895,7 +1923,6 @@ export default class Explorer {
this.isAccountReady(true); this.isAccountReady(true);
} }
return Q();
} }
public setFeatureFlagsFromFlights(flights: readonly string[]): void { public setFeatureFlagsFromFlights(flights: readonly string[]): void {
@@ -2013,7 +2040,7 @@ export default class Explorer {
// we reload collections for all databases so the resource tree reflects any collection-level changes // we reload collections for all databases so the resource tree reflects any collection-level changes
// i.e addition of stored procedures, etc. // i.e addition of stored procedures, etc.
const deferred: Q.Deferred<void> = Q.defer<void>(); const deferred: Q.Deferred<void> = Q.defer<void>();
let loadCollectionPromises: Q.Promise<void>[] = []; let loadCollectionPromises: Promise<void>[] = [];
// If the user has a lot of databases, only load expanded databases. // If the user has a lot of databases, only load expanded databases.
const databasesToLoad = const databasesToLoad =
@@ -2397,7 +2424,7 @@ export default class Explorer {
return true; return true;
} }
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> { public async renameNotebook(notebookFile: NotebookContentItem): Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to rename notebook, but notebook is not enabled"; const error = "Attempt to rename notebook, but notebook is not enabled";
Logger.logError(error, "Explorer/renameNotebook"); Logger.logError(error, "Explorer/renameNotebook");
@@ -2414,39 +2441,35 @@ export default class Explorer {
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
return Q.reject(); throw new Error();
} }
const originalPath = notebookFile.path; const originalPath = notebookFile.path;
const result = this.stringInputPane const newNotebookFile = await this.stringInputPane.openWithOptions<NotebookContentItem>({
.openWithOptions<NotebookContentItem>({ errorMessage: "Could not rename notebook",
errorMessage: "Could not rename notebook", inProgressMessage: "Renaming notebook to",
inProgressMessage: "Renaming notebook to", successMessage: "Renamed notebook to",
successMessage: "Renamed notebook to", inputLabel: "Enter new notebook name",
inputLabel: "Enter new notebook name", paneTitle: "Rename Notebook",
paneTitle: "Rename Notebook", submitButtonLabel: "Rename",
submitButtonLabel: "Rename", defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"),
defaultInput: FileSystemUtil.stripExtension(notebookFile.name, "ipynb"), onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input) });
}) const notebookTabs = this.tabsManager.getTabs(
.then(newNotebookFile => { ViewModels.CollectionTabKind.NotebookV2,
const notebookTabs = this.tabsManager.getTabs( (tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
ViewModels.CollectionTabKind.NotebookV2, );
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath) notebookTabs.forEach(tab => {
); tab.tabTitle(newNotebookFile.name);
notebookTabs.forEach(tab => { tab.tabPath(newNotebookFile.path);
tab.tabTitle(newNotebookFile.name); (tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
tab.tabPath(newNotebookFile.path); });
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
return newNotebookFile; this.resourceTree.triggerRender();
}); return newNotebookFile;
result.then(() => this.resourceTree.triggerRender());
return result;
} }
public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> { public async onCreateDirectory(parent: NotebookContentItem): Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create notebook directory, but notebook is not enabled"; const error = "Attempt to create notebook directory, but notebook is not enabled";
Logger.logError(error, "Explorer/onCreateDirectory"); Logger.logError(error, "Explorer/onCreateDirectory");
@@ -2454,7 +2477,7 @@ export default class Explorer {
throw new Error(error); throw new Error(error);
} }
const result = this.stringInputPane.openWithOptions<NotebookContentItem>({ const result = await this.stringInputPane.openWithOptions<NotebookContentItem>({
errorMessage: "Could not create directory ", errorMessage: "Could not create directory ",
inProgressMessage: "Creating directory ", inProgressMessage: "Creating directory ",
successMessage: "Created directory ", successMessage: "Created directory ",
@@ -2464,7 +2487,7 @@ export default class Explorer {
defaultInput: "", defaultInput: "",
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input) onSubmit: (input: string) => this.notebookManager?.notebookContentClient.createDirectory(parent, input)
}); });
result.then(() => this.resourceTree.triggerRender()); this.resourceTree.triggerRender();
return result; return result;
} }

View File

@@ -378,8 +378,7 @@ export class D3ForceGraph implements GraphRenderer {
* @param targetPosition * @param targetPosition
* @return promise with shift offset * @return promise with shift offset
*/ */
private shiftGraph(targetPosition: Point2D): Q.Promise<Point2D> { private async shiftGraph(targetPosition: Point2D): Promise<Point2D> {
const deferred: Q.Deferred<Point2D> = Q.defer<Point2D>();
const offset = { x: this.width / 2 - targetPosition.x, y: this.height / 2 - targetPosition.y }; const offset = { x: this.width / 2 - targetPosition.x, y: this.height / 2 - targetPosition.y };
this.viewCenter = targetPosition; this.viewCenter = targetPosition;
@@ -391,18 +390,15 @@ export class D3ForceGraph implements GraphRenderer {
.translate(-targetPosition.x, -targetPosition.y); .translate(-targetPosition.x, -targetPosition.y);
}; };
this.zoomBackground const transition = this.zoomBackground
.transition() .transition()
.duration(D3ForceGraph.TRANSITION_STEP1_MS) .duration(D3ForceGraph.TRANSITION_STEP1_MS)
.call(this.zoom.transform, transform) .call(this.zoom.transform, transform);
.on("end", () => {
deferred.resolve(offset);
});
} else {
deferred.resolve(null);
}
return deferred.promise; await new Promise(resolve => transition.on("end", resolve));
return offset;
}
return null;
} }
private onGraphDataUpdate(graph: GraphData<D3Node, D3Link>) { private onGraphDataUpdate(graph: GraphData<D3Node, D3Link>) {
@@ -435,7 +431,7 @@ export class D3ForceGraph implements GraphRenderer {
} }
} }
private animateRemoveExitSelections(): Q.Promise<void> { private animateRemoveExitSelections(): Promise<void> {
const deferred1 = Q.defer<void>(); const deferred1 = Q.defer<void>();
const deferred2 = Q.defer<void>(); const deferred2 = Q.defer<void>();
const linkExitSelection = this.linkSelection.exit(); const linkExitSelection = this.linkSelection.exit();
@@ -508,7 +504,7 @@ export class D3ForceGraph implements GraphRenderer {
deferred2.resolve(); deferred2.resolve();
} }
return Q.allSettled([deferred1.promise, deferred2.promise]).then(undefined); return Promise.all([deferred1.promise, deferred2.promise]).then(undefined);
} }
/** /**

View File

@@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import { mount, ReactWrapper } from "enzyme"; import { mount, ReactWrapper } from "enzyme";
import * as Q from "q";
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent"; import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer"; import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
@@ -41,11 +40,11 @@ describe("Property pane", () => {
node: highlightedNode, node: highlightedNode,
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null, getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
collectionPartitionKeyProperty: null, collectionPartitionKeyProperty: null,
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(), updateVertexProperties: (editedProperties: EditedProperties): Promise<void> => Promise.resolve(),
selectNode: (id: string): void => {}, selectNode: (id: string): void => {},
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null), updatePossibleVertices: async (): Promise<PossibleVertex[]> => null,
possibleEdgeLabels: null, possibleEdgeLabels: null,
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(), editGraphEdges: async (editedEdges: EditedEdges): Promise<any> => undefined,
deleteHighlightedNode: (): void => {}, deleteHighlightedNode: (): void => {},
onModeChanged: (newMode: Mode): void => {}, onModeChanged: (newMode: Mode): void => {},
viewMode: Mode.READONLY_PROP viewMode: Mode.READONLY_PROP

View File

@@ -36,11 +36,11 @@ export interface NodePropertiesComponentProps {
node: GraphHighlightedNodeData; node: GraphHighlightedNodeData;
getPkIdFromNodeData: (v: GraphHighlightedNodeData) => string; getPkIdFromNodeData: (v: GraphHighlightedNodeData) => string;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
updateVertexProperties: (editedProperties: EditedProperties) => Q.Promise<void>; updateVertexProperties: (editedProperties: EditedProperties) => Promise<void>;
selectNode: (id: string) => void; selectNode: (id: string) => void;
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>; updatePossibleVertices: () => Promise<PossibleVertex[]>;
possibleEdgeLabels: Item[]; possibleEdgeLabels: Item[];
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<any>; editGraphEdges: (editedEdges: EditedEdges) => Promise<any>;
deleteHighlightedNode: () => void; deleteHighlightedNode: () => void;
onModeChanged: (newMode: Mode) => void; onModeChanged: (newMode: Mode) => void;
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it

View File

@@ -20,7 +20,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
@@ -62,7 +62,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
@@ -126,7 +126,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
@@ -208,7 +208,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
@@ -289,7 +289,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -348,7 +348,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isAuthWithResourceToken = ko.observable(true); mockExplorer.isAuthWithResourceToken = ko.observable(true);
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true); mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true); mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });

View File

@@ -127,6 +127,7 @@
be shared across all containers within the database.</span> be shared across all containers within the database.</span>
</span> </span>
</div> </div>
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()"> <div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
<!-- 1 --> <!-- 1 -->
<throughput-input-autopilot-v3 params="{ <throughput-input-autopilot-v3 params="{
@@ -157,6 +158,38 @@
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
<!-- 1 -->
<throughput-input params="{
testId: 'databaseThroughputValue',
value: throughputDatabase,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: databaseCreateNewShared() && databaseCreateNew(),
label: sharedThroughputRangeText,
ariaLabel: sharedThroughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAck',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newContainer-databaseThroughput-autoPilotRadio',
throughputProvisionedRadioId: 'newContainer-databaseThroughput-manualRadio',
throughputModeRadioName: 'sharedThroughputModeRadio',
isAutoPilotSelected: isSharedAutoPilotSelected,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
autoPilotTiersList: sharedAutoPilotTiersList,
selectedAutoPilotTier: selectedSharedAutoPilotTier
}">
</throughput-input>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Database provisioned throughput - End --> <!-- Database provisioned throughput - End -->
</div> </div>
@@ -178,6 +211,19 @@
data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }"> data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }">
</div> </div>
<!-- <p class="seconddivpadding" data-bind="visible:(container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) && !databaseHasSharedOffer()">
Where did 'fixed' containers go?
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information">
<span class="tooltiptext noFixedCollectionsTooltipWidth">
We lowered the minimum throughput for partitioned containers to 400 RU/s, removing the only drawback partitioned containers had. <br/><br/>
We are planning to deprecate ability to create non-partitioned containers, as they do not allow you to scale elastically.
If for some reason you still need a container without partition key, you can use our SDKs to create one. <br/><br/>
Please <a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback">contact us</a> if you have questions or concerns.
</span>
</span>
</p> -->
<!-- Indexing For Shared Throughput - start --> <!-- Indexing For Shared Throughput - start -->
<div class="seconddivpadding" <div class="seconddivpadding"
data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()"> data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()">
@@ -241,8 +287,96 @@
</div> </div>
<!-- Unlimited option button - End --> <!-- Unlimited option button - End -->
</div> </div>
<!-- Fixed Button Content - Start -->
<div class="tabcontent" data-bind="visible: isFixedStorageSelected() && !databaseHasSharedOffer()">
<!-- 2 -->
<!-- note: this is used when creating a fixed collection without shared throughput. only manual throughput is available. -->
<throughput-input params="{
testId: 'fixedThroughputValue',
value: throughputSinglePartition,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: isFixedStorageSelected() && !databaseHasSharedOffer(),
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost
showAsMandatory: true,
isFixed: true,
infoBubbleText: ruToolTipText,
canExceedMaximumValue: canExceedMaximumValue
}">
</throughput-input>
<div data-bind="visible: rupmVisible">
<div class="tabs">
<p class="pkPadding">
<span class="mandatoryStar">*</span>
<span class="addCollectionLabel">RU/m</span>
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information">
<span class="tooltiptext throughputRuInfo">
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
per
minute
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
with
RU/m
enabled, the RU/m budget will be 50,000 RU/m.
</span>
</span>
</p>
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
<div class="tab">
<input type="radio" id="rupmOn" name="rupmcoll" value="on" class="radio"
data-bind="checked: rupm">
<label for="rupmOn">ON</label>
</div>
<div class="tab">
<input type="radio" id="rupmOff" name="rupmcoll" value="off" class="radio"
data-bind="checked: rupm">
<label for="rupmOff">OFF</label>
</div>
</div>
</div>
</div>
</div>
<!-- Fixed Button Content - End -->
<!-- Unlimited Button Content - Start --> <!-- Unlimited Button Content - Start -->
<div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()"> <div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()">
<div data-bind="visible: rupmVisible">
<div class="tabs">
<p>
<span class="mandatoryStar">*</span>
<span class="addCollectionLabel">RU/m</span>
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information">
<span class="tooltiptext throughputRuInfo">
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
per
minute
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
with
RU/m
enabled, the RU/m budget will be 50,000 RU/m.
</span>
</span>
</p>
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
<div class="tab">
<input type="radio" id="rupmOn2" name="rupmcoll2" value="on" class="radio"
data-bind="checked: rupm">
<label for="rupmOn2">ON</label>
</div>
<div class="tab">
<input type="radio" id="rupmOff2" name="rupmcoll2" value="off" class="radio"
data-bind="checked: rupm">
<label for="rupmOff2">OFF</label>
</div>
</div>
</div>
</div>
<div data-bind="visible: partitionKeyVisible"> <div data-bind="visible: partitionKeyVisible">
<p> <p>
<span class="mandatoryStar">*</span> <span class="mandatoryStar">*</span>
@@ -308,6 +442,7 @@
<!-- Provision collection throughput checkbox - end --> <!-- Provision collection throughput checkbox - end -->
<!-- Provision collection throughput spinner - start --> <!-- Provision collection throughput spinner - start -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput"> <div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
<!-- 3 --> <!-- 3 -->
<throughput-input-autopilot-v3 params="{ <throughput-input-autopilot-v3 params="{
@@ -337,6 +472,40 @@
}"> }">
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
<!-- 3 -->
<throughput-input params="{
testId: 'collectionThroughputValue',
value: throughputMultiPartition,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: displayCollectionThroughput,
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: dedicatedRequestUnitsUsageCost,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckCollection',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newContainer-containerThroughput-autoPilotRadio',
throughputProvisionedRadioId: 'newContainer-containerThroughput-manualRadio',
throughputModeRadioName: 'throughputModeRadioName',
isAutoPilotSelected: isAutoPilotSelected,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
showAutoPilot: !isFixedStorageSelected()
}">
</throughput-input>
</div>
<!-- /ko -->
<!-- Provision collection throughput spinner - end --> <!-- Provision collection throughput spinner - end -->
<!-- /ko --> <!-- /ko -->
<!-- Provision collection throughput - end --> <!-- Provision collection throughput - end -->

View File

@@ -1,7 +1,8 @@
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import AddCollectionPane from "./AddCollectionPane"; import AddCollectionPane from "./AddCollectionPane";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { DatabaseAccount } from "../../Contracts/DataModels"; import ko from "knockout";
import { AutopilotTier, DatabaseAccount } from "../../Contracts/DataModels";
describe("Add Collection Pane", () => { describe("Add Collection Pane", () => {
describe("isValid()", () => { describe("isValid()", () => {
@@ -40,6 +41,25 @@ describe("Add Collection Pane", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
});
it("should be true if autopilot enabled and select valid tier", () => {
explorer.databaseAccount(mockDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
addCollectionPane.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
addCollectionPane.isAutoPilotSelected(true);
addCollectionPane.selectedAutoPilotTier(AutopilotTier.Tier2);
expect(addCollectionPane.isValid()).toBe(true);
});
it("should be false if autopilot enabled and select invalid tier", () => {
explorer.databaseAccount(mockDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
addCollectionPane.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
addCollectionPane.isAutoPilotSelected(true);
addCollectionPane.selectedAutoPilotTier(0);
expect(addCollectionPane.isValid()).toBe(false);
}); });
it("should be true if graph API and partition key is not /id nor /label", () => { it("should be true if graph API and partition key is not /id nor /label", () => {

View File

@@ -14,6 +14,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent"; import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
import { createCollection } from "../../Common/dataAccess/createCollection"; import { createCollection } from "../../Common/dataAccess/createCollection";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions { export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
@@ -41,6 +42,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
public partitionKeyVisible: ko.Computed<boolean>; public partitionKeyVisible: ko.Computed<boolean>;
public partitionKeyPattern: ko.Computed<string>; public partitionKeyPattern: ko.Computed<string>;
public partitionKeyTitle: ko.Computed<string>; public partitionKeyTitle: ko.Computed<string>;
public rupm: ko.Observable<string>;
public rupmVisible: ko.Observable<boolean>;
public storage: ko.Observable<string>; public storage: ko.Observable<string>;
public throughputSinglePartition: ViewModels.Editable<number>; public throughputSinglePartition: ViewModels.Editable<number>;
public throughputMultiPartition: ViewModels.Editable<number>; public throughputMultiPartition: ViewModels.Editable<number>;
@@ -73,6 +76,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
public debugstring: ko.Computed<string>; public debugstring: ko.Computed<string>;
public displayCollectionThroughput: ko.Computed<boolean>; public displayCollectionThroughput: ko.Computed<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public selectedSharedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public sharedAutoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public isSharedAutoPilotSelected: ko.Observable<boolean>; public isSharedAutoPilotSelected: ko.Observable<boolean>;
public autoPilotThroughput: ko.Observable<number>; public autoPilotThroughput: ko.Observable<number>;
public sharedAutoPilotThroughput: ko.Observable<number>; public sharedAutoPilotThroughput: ko.Observable<number>;
@@ -86,6 +93,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
public isAnalyticalStorageOn: ko.Observable<boolean>; public isAnalyticalStorageOn: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Computed<boolean>; public isSynapseLinkUpdating: ko.Computed<boolean>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
public showUpsellMessage: ko.PureComputed<boolean>; public showUpsellMessage: ko.PureComputed<boolean>;
@@ -95,7 +103,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
constructor(options: AddCollectionPaneOptions) { constructor(options: AddCollectionPaneOptions) {
super(options); super(options);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.formWarnings = ko.observable<string>(); this.formWarnings = ko.observable<string>();
@@ -140,6 +149,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
} }
return ""; return "";
}); });
this.rupm = ko.observable<string>(Constants.RUPMStates.off);
this.rupmVisible = ko.observable<boolean>(false);
const featureSubcription = this.container.features.subscribe(() => {
this.rupmVisible(this.container.isFeatureEnabled(Constants.Features.enableRupm));
featureSubcription.dispose();
});
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
@@ -157,13 +172,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.databaseHasSharedOffer = ko.observable<boolean>(true); this.databaseHasSharedOffer = ko.observable<boolean>(true);
this.throughputRangeText = ko.pureComputed<string>(() => { this.throughputRangeText = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
this.sharedThroughputRangeText = ko.pureComputed<string>(() => { this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
if (this.isSharedAutoPilotSelected()) { if (this.isSharedAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
@@ -192,6 +207,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
account.properties.readLocations.length) || account.properties.readLocations.length) ||
1; 1;
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
let throughputSpendAckText: string; let throughputSpendAckText: string;
let estimatedSpend: string; let estimatedSpend: string;
@@ -201,15 +217,23 @@ export default class AddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
serverId,
regions,
multimaster,
rupmEnabled
);
} else { } else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(), this.sharedAutoPilotThroughput(),
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -246,6 +270,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
account.properties.readLocations.length) || account.properties.readLocations.length) ||
1; 1;
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
let throughputSpendAckText: string; let throughputSpendAckText: string;
let estimatedSpend: string; let estimatedSpend: string;
@@ -255,13 +280,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedSpendHtml( estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.throughputMultiPartition(), this.throughputMultiPartition(),
serverId, serverId,
regions, regions,
multimaster multimaster,
rupmEnabled
); );
} else { } else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
@@ -269,6 +296,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -420,7 +448,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => { this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.autoPilotThroughput() * 1; const autoscaleThroughput = this.autoPilotThroughput() * 1;
if (this.isAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
const selectedThroughput: number = this._getThroughput(); const selectedThroughput: number = this._getThroughput();
@@ -463,6 +491,14 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.isAutoPilotSelected = ko.observable<boolean>(false); this.isAutoPilotSelected = ko.observable<boolean>(false);
this.isSharedAutoPilotSelected = ko.observable<boolean>(false); this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.sharedAutoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
@@ -471,7 +507,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
return ""; return "";
} }
const isDatabaseThroughput: boolean = this.databaseCreateNewShared(); const isDatabaseThroughput: boolean = this.databaseCreateNewShared();
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, isDatabaseThroughput);
}); });
this.resetData(); this.resetData();
@@ -666,7 +704,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
storage: this.storage(), storage: this.storage(),
offerThroughput: this._getThroughput(), offerThroughput: this._getThroughput(),
partitionKey: this.partitionKey(), partitionKey: this.partitionKey(),
databaseId: this.databaseId() databaseId: this.databaseId(),
rupm: this.rupm()
}), }),
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()], subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: this.container.quotaId(),
@@ -767,7 +806,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
partitionKey, partitionKey,
rupm: this.rupm(),
uniqueKeyPolicy, uniqueKeyPolicy,
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}), }),
@@ -842,7 +881,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
partitionKey, partitionKey,
rupm: this.rupm(),
uniqueKeyPolicy, uniqueKeyPolicy,
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}), }),
@@ -857,7 +896,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
}; };
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey); TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey);
this.resetData(); this.resetData();
this.container.refreshAllDatabases(); return refreshCachedResources().then(() => {
this.container.refreshAllDatabases();
});
}, },
(reason: any) => { (reason: any) => {
this.isExecuting(false); this.isExecuting(false);
@@ -878,7 +919,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
partitionKey, partitionKey,
rupm: this.rupm(),
uniqueKeyPolicy, uniqueKeyPolicy,
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}, },
@@ -904,9 +945,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.throughputSpendAck(false); this.throughputSpendAck(false);
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.isSharedAutoPilotSelected(false); this.isSharedAutoPilotSelected(false);
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); if (!this.hasAutoPilotV2FeatureFlag()) {
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
} else {
this.selectedAutoPilotTier(undefined);
this.selectedSharedAutoPilotTier(undefined);
}
this.uniqueKeys([]); this.uniqueKeys([]);
this.useIndexingForSharedThroughput(true); this.useIndexingForSharedThroughput(true);
@@ -960,6 +1005,20 @@ export default class AddCollectionPane extends ContextualPaneBase {
return true; return true;
} }
public onRupmOptionsKeyDown(source: any, event: KeyboardEvent): boolean {
if (event.key === "ArrowRight") {
this.rupm("off");
return false;
}
if (event.key === "ArrowLeft") {
this.rupm("on");
return false;
}
return true;
}
public onEnableSynapseLinkButtonClicked() { public onEnableSynapseLinkButtonClicked() {
this.container.openEnableSynapseLinkDialog(); this.container.openEnableSynapseLinkDialog();
} }
@@ -971,18 +1030,32 @@ export default class AddCollectionPane extends ContextualPaneBase {
if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) { if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) {
const autoPilot = this._getAutoPilot(); const autoPilot = this._getAutoPilot();
if ( if (
!autoPilot || (!this.hasAutoPilotV2FeatureFlag() &&
!autoPilot.maxThroughput || (!autoPilot ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) !autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
(this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
) { ) {
this.formErrors( this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` !this.hasAutoPilotV2FeatureFlag()
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
: "Please select an Autopilot tier from the list."
); );
return false; return false;
} }
} }
const throughput = this._getThroughput(); const throughput = this._getThroughput();
const maxThroughputWithRUPM =
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this._calculateNumberOfPartitions();
if (this.rupm() === Constants.RUPMStates.on && throughput > maxThroughputWithRUPM) {
this.formErrors(
`The maximum supported provisioned throughput with RU/m enabled is ${maxThroughputWithRUPM} RU/s. Please turn off RU/m to incease thoughput above ${maxThroughputWithRUPM} RU/s.`
);
return false;
}
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) { if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
this.formErrors(`Please acknowledge the estimated daily spend.`); this.formErrors(`Please acknowledge the estimated daily spend.`);
@@ -997,6 +1070,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
const autoscaleThroughput = this.autoPilotThroughput() * 1; const autoscaleThroughput = this.autoPilotThroughput() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isAutoPilotSelected() && this.isAutoPilotSelected() &&
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck() !this.throughputSpendAck()
@@ -1043,15 +1117,31 @@ export default class AddCollectionPane extends ContextualPaneBase {
} }
private _getAutoPilot(): DataModels.AutoPilotCreationSettings { private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() &&
maxThroughput: this.sharedAutoPilotThroughput() * 1 this.databaseCreateNewShared() &&
}; this.isSharedAutoPilotSelected() &&
this.sharedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() &&
this.databaseCreateNewShared() &&
this.isSharedAutoPilotSelected() &&
this.selectedSharedAutoPilotTier())
) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.sharedAutoPilotThroughput() * 1
}
: { autopilotTier: this.selectedSharedAutoPilotTier() };
} }
if (this.isAutoPilotSelected() && this.autoPilotThroughput()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
maxThroughput: this.autoPilotThroughput() * 1 (this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
}; ) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.autoPilotThroughput() * 1
}
: { autopilotTier: this.selectedAutoPilotTier() };
} }
return undefined; return undefined;

View File

@@ -89,6 +89,7 @@
data-bind="text: databaseLevelThroughputTooltipText"></span> data-bind="text: databaseLevelThroughputTooltipText"></span>
</span> </span>
</div> </div>
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared"> <div data-bind="visible: databaseCreateNewShared">
<throughput-input-autopilot-v3 params="{ <throughput-input-autopilot-v3 params="{
step: 100, step: 100,
@@ -123,6 +124,42 @@
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p> support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared">
<throughput-input params="{
step: 100,
value: throughput,
testId: 'sharedThroughputValue',
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: databaseCreateNewShared,
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckDatabase',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newDatabase-databaseThroughput-autoPilotRadio',
throughputProvisionedRadioId: 'newDatabase-databaseThroughput-manualRadio',
throughputModeRadioName: 'throughputModeRadioName',
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}">
</throughput-input>
<p data-bind="visible: canRequestSupport">
<!-- TODO: Replace link with call to the Azure Support blade --><a
href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request">Contact
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Database provisioned throughput - End --> <!-- Database provisioned throughput - End -->
</div> </div>
</div> </div>

View File

@@ -38,9 +38,12 @@ export default class AddDatabasePane extends ContextualPaneBase {
public upsellAnchorUrl: ko.PureComputed<string>; public upsellAnchorUrl: ko.PureComputed<string>;
public upsellAnchorText: ko.PureComputed<string>; public upsellAnchorText: ko.PureComputed<string>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public maxAutoPilotThroughputSet: ko.Observable<number>; public maxAutoPilotThroughputSet: ko.Observable<number>;
public autoPilotUsageCost: ko.Computed<string>; public autoPilotUsageCost: ko.Computed<string>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public isFreeTierAccount: ko.Computed<boolean>; public isFreeTierAccount: ko.Computed<boolean>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
@@ -50,7 +53,8 @@ export default class AddDatabasePane extends ContextualPaneBase {
super(options); super(options);
this.title((this.container && this.container.addDatabaseText()) || "New Database"); this.title((this.container && this.container.addDatabaseText()) || "New Database");
this.databaseId = ko.observable<string>(); this.databaseId = ko.observable<string>();
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
@@ -90,6 +94,10 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.minThroughputRU = ko.observable<number>(); this.minThroughputRU = ko.observable<number>();
this.throughputSpendAckText = ko.observable<string>(); this.throughputSpendAckText = ko.observable<string>();
this.throughputSpendAck = ko.observable<boolean>(false); this.throughputSpendAck = ko.observable<boolean>(false);
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.isAutoPilotSelected = ko.observable<boolean>(false); this.isAutoPilotSelected = ko.observable<boolean>(false);
this.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
@@ -97,11 +105,13 @@ export default class AddDatabasePane extends ContextualPaneBase {
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, true /* isDatabaseThroughput */);
}); });
this.throughputRangeText = ko.pureComputed<string>(() => { this.throughputRangeText = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
@@ -132,12 +142,19 @@ export default class AddDatabasePane extends ContextualPaneBase {
let estimatedSpendAcknowledge: string; let estimatedSpendAcknowledge: string;
let estimatedSpend: string; let estimatedSpend: string;
if (!this.isAutoPilotSelected()) { if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
serverId,
regions,
multimaster,
false /*rupmEnabled*/
);
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput, offerThroughput,
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} else { } else {
@@ -152,6 +169,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} }
@@ -190,7 +208,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => { this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
if (this.isAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
@@ -307,6 +325,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
public resetData() { public resetData() {
this.databaseId(""); this.databaseId("");
this.databaseCreateNewShared(this.getSharedThroughputDefault()); this.databaseCreateNewShared(this.getSharedThroughputDefault());
this.selectedAutoPilotTier(undefined);
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput); this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput);
this._updateThroughputLimitByDatabase(); this._updateThroughputLimitByDatabase();
@@ -394,12 +413,17 @@ export default class AddDatabasePane extends ContextualPaneBase {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
const autoPilot = this._isAutoPilotSelectedAndWhatTier(); const autoPilot = this._isAutoPilotSelectedAndWhatTier();
if ( if (
!autoPilot || (!this.hasAutoPilotV2FeatureFlag() &&
!autoPilot.maxThroughput || (!autoPilot ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) !autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
(this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
) { ) {
this.formErrors( this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` !this.hasAutoPilotV2FeatureFlag()
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
: "Please select an Autopilot tier from the list."
); );
return false; return false;
} }
@@ -414,6 +438,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isAutoPilotSelected() && this.isAutoPilotSelected() &&
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck() !this.throughputSpendAck()
@@ -426,10 +451,15 @@ export default class AddDatabasePane extends ContextualPaneBase {
} }
private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings { private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings {
if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
maxThroughput: this.maxAutoPilotThroughputSet() * 1 (this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
}; ) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.maxAutoPilotThroughputSet() * 1
}
: { autopilotTier: this.selectedAutoPilotTier() };
} }
return undefined; return undefined;
} }

View File

@@ -142,6 +142,7 @@
</span> </span>
</div> </div>
<!-- 1 --> <!-- 1 -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()"> <div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
@@ -172,6 +173,38 @@
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
<throughput-input
params="{
testId: 'cassandraThroughputValue-v2-shared',
value: keyspaceThroughput,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: keyspaceCreateNew() && keyspaceHasSharedOffer(),
label: sharedThroughputRangeText,
ariaLabel: sharedThroughputRangeText,
requestUnitsUsageCost: requestUnitsUsageCostShared,
spendAckChecked: sharedThroughputSpendAck,
spendAckId: 'sharedThroughputSpendAck-v2-shared',
spendAckText: sharedThroughputSpendAckText,
spendAckVisible: sharedThroughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newKeyspace-databaseThroughput-autoPilotRadio-v2-shared',
throughputProvisionedRadioId: 'newKeyspace-databaseThroughput-manualRadio-v2-shared',
isAutoPilotSelected: isSharedAutoPilotSelected,
autoPilotTiersList: sharedAutoPilotTiersList,
costsVisible: costsVisible,
selectedAutoPilotTier: selectedSharedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Database provisioned throughput - End --> <!-- Database provisioned throughput - End -->
</div> </div>
<div class="seconddivpadding"> <div class="seconddivpadding">
@@ -224,6 +257,7 @@
</span> </span>
</div> </div>
<!-- 2 --> <!-- 2 -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()"> <div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
@@ -255,6 +289,40 @@
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
<throughput-input
params="{
testId: 'cassandraSharedThroughputValue-v2-dedicated',
value: throughput,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: !keyspaceHasSharedOffer() || dedicateTableThroughput(),
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCostDedicated,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckCassandra-v2-dedicated',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newKeyspace-containerThroughput-autoPilotRadio-v2-dedicated',
throughputProvisionedRadioId: 'newKeyspace-containerThroughput-manualRadio-v2-dedicated',
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
showAutoPilot: false,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Provision table throughput - end --> <!-- Provision table throughput - end -->
</div> </div>
<div class="paneFooter"> <div class="paneFooter">

View File

@@ -38,6 +38,10 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
public sharedThroughputSpendAck: ko.Observable<boolean>; public sharedThroughputSpendAck: ko.Observable<boolean>;
public sharedThroughputSpendAckText: ko.Observable<string>; public sharedThroughputSpendAckText: ko.Observable<string>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public selectedSharedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public sharedAutoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public isSharedAutoPilotSelected: ko.Observable<boolean>; public isSharedAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotThroughput: ko.Observable<number>; public selectedAutoPilotThroughput: ko.Observable<number>;
public sharedAutoPilotThroughput: ko.Observable<number>; public sharedAutoPilotThroughput: ko.Observable<number>;
@@ -45,6 +49,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
public sharedThroughputSpendAckVisible: ko.Computed<boolean>; public sharedThroughputSpendAckVisible: ko.Computed<boolean>;
public throughputSpendAckVisible: ko.Computed<boolean>; public throughputSpendAckVisible: ko.Computed<boolean>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public isFreeTierAccount: ko.Computed<boolean>; public isFreeTierAccount: ko.Computed<boolean>;
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
@@ -56,7 +61,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.title("Add Table"); this.title("Add Table");
this.createTableQuery = ko.observable<string>("CREATE TABLE "); this.createTableQuery = ko.observable<string>("CREATE TABLE ");
this.keyspaceCreateNew = ko.observable<boolean>(true); this.keyspaceCreateNew = ko.observable<boolean>(true);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.keyspaceOffers = new HashMap<DataModels.Offer>(); this.keyspaceOffers = new HashMap<DataModels.Offer>();
this.keyspaceIds = ko.observableArray<string>(); this.keyspaceIds = ko.observableArray<string>();
@@ -84,6 +90,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}); });
this.tableId = ko.observable<string>(""); this.tableId = ko.observable<string>("");
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.isAutoPilotSelected = ko.observable<boolean>(false); this.isAutoPilotSelected = ko.observable<boolean>(false);
this.isSharedAutoPilotSelected = ko.observable<boolean>(false); this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
this.selectedAutoPilotThroughput = ko.observable<number>(); this.selectedAutoPilotThroughput = ko.observable<number>();
@@ -94,11 +102,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
if (!enableAutoPilot) { if (!enableAutoPilot) {
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
} }
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
}); });
this.sharedThroughputRangeText = ko.pureComputed<string>(() => { this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
if (this.isSharedAutoPilotSelected()) { if (this.isSharedAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
@@ -136,12 +144,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string; let estimatedSpend: string;
let estimatedDedicatedSpendAcknowledge: string; let estimatedDedicatedSpendAcknowledge: string;
if (!this.isAutoPilotSelected()) { if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
serverId,
regions,
multimaster,
false /*rupmEnabled*/
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput, offerThroughput,
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} else { } else {
@@ -156,6 +171,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} }
@@ -180,12 +196,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string; let estimatedSpend: string;
let estimatedSharedSpendAcknowledge: string; let estimatedSharedSpendAcknowledge: string;
if (!this.isSharedAutoPilotSelected()) { if (!this.isSharedAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.keyspaceThroughput(),
serverId,
regions,
multimaster,
false /*rupmEnabled*/
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.keyspaceThroughput(), this.keyspaceThroughput(),
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
} else { } else {
@@ -200,6 +223,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
} }
@@ -222,7 +246,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => { this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1; const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
if (this.isSharedAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
@@ -231,7 +255,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => { this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1; const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
if (this.isAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
@@ -256,13 +280,22 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
updateKeyspaceIds(this.container.nonSystemDatabases()); updateKeyspaceIds(this.container.nonSystemDatabases());
} }
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.sharedAutoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this._getAutoPilot(); const autoPilot = this._getAutoPilot();
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
const isDatabaseThroughput: boolean = this.keyspaceCreateNew(); const isDatabaseThroughput: boolean = this.keyspaceCreateNew();
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, isDatabaseThroughput);
}); });
} }
@@ -294,7 +327,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId() databaseId: this.keyspaceId(),
rupm: false
}), }),
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()], subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: this.container.quotaId(),
@@ -316,13 +350,17 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
} }
this.isExecuting(true); this.isExecuting(true);
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
let createTableAndKeyspacePromise: Q.Promise<any>; let createTableAndKeyspacePromise: Promise<any>;
const toCreateKeyspace: boolean = this.keyspaceCreateNew(); const toCreateKeyspace: boolean = this.keyspaceCreateNew();
const useAutoPilotForKeyspace: boolean = this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput(); const useAutoPilotForKeyspace: boolean =
(!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.selectedSharedAutoPilotTier());
const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`; const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
const createKeyspaceQuery: string = this.keyspaceHasSharedOffer() const createKeyspaceQuery: string = this.keyspaceHasSharedOffer()
? useAutoPilotForKeyspace ? useAutoPilotForKeyspace
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};` ? !this.hasAutoPilotV2FeatureFlag()
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
: `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.selectedSharedAutoPilotTier()};`
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};` : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};`
: `${createKeyspaceQueryPrefix};`; : `${createKeyspaceQueryPrefix};`;
const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`; const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`;
@@ -347,6 +385,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId(), databaseId: this.keyspaceId(),
rupm: false,
hasDedicatedThroughput: this.dedicateTableThroughput() hasDedicatedThroughput: this.dedicateTableThroughput()
}), }),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
@@ -393,6 +432,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId(), databaseId: this.keyspaceId(),
rupm: false,
hasDedicatedThroughput: this.dedicateTableThroughput() hasDedicatedThroughput: this.dedicateTableThroughput()
}), }),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
@@ -422,6 +462,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId(), databaseId: this.keyspaceId(),
rupm: false,
hasDedicatedThroughput: this.dedicateTableThroughput() hasDedicatedThroughput: this.dedicateTableThroughput()
}, },
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
@@ -448,6 +489,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const throughputDefaults = this.container.collectionCreationDefaults.throughput; const throughputDefaults = this.container.collectionCreationDefaults.throughput;
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.isSharedAutoPilotSelected(false); this.isSharedAutoPilotSelected(false);
this.selectedAutoPilotTier(null);
this.selectedSharedAutoPilotTier(null);
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)); this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
@@ -469,6 +512,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1; const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isSharedAutoPilotSelected() && this.isSharedAutoPilotSelected() &&
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.sharedThroughputSpendAck() !this.sharedThroughputSpendAck()
@@ -479,6 +523,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1; const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isAutoPilotSelected() && this.isAutoPilotSelected() &&
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck() !this.throughputSpendAck()
@@ -493,12 +538,17 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
) { ) {
const autoPilot = this._getAutoPilot(); const autoPilot = this._getAutoPilot();
if ( if (
!autoPilot || (!this.hasAutoPilotV2FeatureFlag() &&
!autoPilot.maxThroughput || (!autoPilot ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) !autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
(this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
) { ) {
this.formErrors( this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` !this.hasAutoPilotV2FeatureFlag()
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
: "Please select an Autopilot tier from the list."
); );
return false; return false;
} }
@@ -525,20 +575,33 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
private _getAutoPilot(): DataModels.AutoPilotCreationSettings { private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
if ( if (
this.keyspaceCreateNew() && (!this.hasAutoPilotV2FeatureFlag() &&
this.keyspaceHasSharedOffer() && this.keyspaceCreateNew() &&
this.isSharedAutoPilotSelected() && this.keyspaceHasSharedOffer() &&
this.sharedAutoPilotThroughput() this.isSharedAutoPilotSelected() &&
this.sharedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() &&
this.keyspaceCreateNew() &&
this.keyspaceHasSharedOffer() &&
this.isSharedAutoPilotSelected() &&
this.selectedSharedAutoPilotTier())
) { ) {
return { return !this.hasAutoPilotV2FeatureFlag()
maxThroughput: this.sharedAutoPilotThroughput() * 1 ? {
}; maxThroughput: this.sharedAutoPilotThroughput() * 1
}
: { autopilotTier: this.selectedSharedAutoPilotTier() };
} }
if (this.selectedAutoPilotThroughput()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotThroughput()) ||
maxThroughput: this.selectedAutoPilotThroughput() * 1 (this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier())
}; ) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.selectedAutoPilotThroughput() * 1
}
: { autopilotTier: this.selectedAutoPilotTier() };
} }
return undefined; return undefined;

View File

@@ -1,7 +1,6 @@
jest.mock("../../Common/dataAccess/deleteCollection"); jest.mock("../../Common/dataAccess/deleteCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as sinon from "sinon"; import * as sinon from "sinon";
import Q from "q";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -58,7 +57,7 @@ describe("Delete Collection Confirmation Pane", () => {
it("should return true if last collection and database does not have shared throughput else false", () => { it("should return true if last collection and database does not have shared throughput else false", () => {
let fakeExplorer = new Explorer(); let fakeExplorer = new Explorer();
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false); fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.refreshAllDatabases = () => Q.resolve(); fakeExplorer.refreshAllDatabases = () => Promise.resolve();
let pane = new DeleteCollectionConfirmationPane({ let pane = new DeleteCollectionConfirmationPane({
id: "deletecollectionconfirmationpane", id: "deletecollectionconfirmationpane",
@@ -119,7 +118,7 @@ describe("Delete Collection Confirmation Pane", () => {
fakeExplorer.selectedNode = ko.observable<TreeNode>(); fakeExplorer.selectedNode = ko.observable<TreeNode>();
fakeExplorer.isLastCollection = () => true; fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared = () => false; fakeExplorer.isSelectedDatabaseShared = () => false;
fakeExplorer.refreshAllDatabases = () => Q.resolve(); fakeExplorer.refreshAllDatabases = () => Promise.resolve();
let pane = new DeleteCollectionConfirmationPane({ let pane = new DeleteCollectionConfirmationPane({
id: "deletecollectionconfirmationpane", id: "deletecollectionconfirmationpane",

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";

View File

@@ -1,7 +1,6 @@
jest.mock("../../Common/dataAccess/deleteDatabase"); jest.mock("../../Common/dataAccess/deleteDatabase");
jest.mock("../../Shared/Telemetry/TelemetryProcessor"); jest.mock("../../Shared/Telemetry/TelemetryProcessor");
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -91,7 +90,7 @@ describe("Delete Database Confirmation Pane", () => {
collections: ko.observableArray<ViewModels.Collection>() collections: ko.observableArray<ViewModels.Collection>()
} as ViewModels.Database; } as ViewModels.Database;
}; };
fakeExplorer.refreshAllDatabases = () => Q.resolve(); fakeExplorer.refreshAllDatabases = () => Promise.resolve();
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false); fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.selectedDatabaseId = ko.computed<string>(() => selectedDatabaseId); fakeExplorer.selectedDatabaseId = ko.computed<string>(() => selectedDatabaseId);
fakeExplorer.isSelectedDatabaseShared = () => false; fakeExplorer.isSelectedDatabaseShared = () => false;

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -31,7 +30,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
this.resetData(); this.resetData();
} }
public submit(): Q.Promise<any> { public async submit(): Promise<any> {
if (!this._isValid()) { if (!this._isValid()) {
const selectedDatabase: ViewModels.Database = this.container.findSelectedDatabase(); const selectedDatabase: ViewModels.Database = this.container.findSelectedDatabase();
this.formErrors("Input database name does not match the selected database"); this.formErrors("Input database name does not match the selected database");
@@ -39,7 +38,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
ConsoleDataType.Error, ConsoleDataType.Error,
`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}: ${this.formErrors()}` `Error while deleting collection ${selectedDatabase && selectedDatabase.id()}: ${this.formErrors()}`
); );
return Q.resolve(); return;
} }
this.formErrors(""); this.formErrors("");
@@ -53,7 +52,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
paneTitle: this.title() paneTitle: this.title()
}); });
// TODO: Should not be a Q promise anymore, but the Cassandra code requires it // TODO: Should not be a Q promise anymore, but the Cassandra code requires it
let promise: Q.Promise<any>; let promise: Promise<any>;
if (this.container.isPreferredApiCassandra()) { if (this.container.isPreferredApiCassandra()) {
promise = (<CassandraAPIDataClient>this.container.tableDataClient).deleteTableOrKeyspace( promise = (<CassandraAPIDataClient>this.container.tableDataClient).deleteTableOrKeyspace(
this.container.databaseAccount().properties.cassandraEndpoint, this.container.databaseAccount().properties.cassandraEndpoint,
@@ -62,7 +61,7 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
this.container this.container
); );
} else { } else {
promise = Q(deleteDatabase(selectedDatabase.id())); promise = deleteDatabase(selectedDatabase.id());
} }
return promise.then( return promise.then(
() => { () => {

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
@@ -97,33 +96,30 @@ export class LoadQueryPane extends ContextualPaneBase {
return true; return true;
}; };
public loadQueryFromFile(file: File): Q.Promise<void> { public async loadQueryFromFile(file: File): Promise<void> {
const selectedCollection: ViewModels.Collection = this.container && this.container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.container && this.container.findSelectedCollection();
if (!selectedCollection) { if (!selectedCollection) {
// should never get into this state // should never get into this state
Logger.logError("No collection was selected", "LoadQueryPane.loadQueryFromFile"); Logger.logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
return Q.reject("No collection was selected"); throw new Error("No collection was selected");
} else if (this.container.isPreferredApiMongoDB()) { } else if (this.container.isPreferredApiMongoDB()) {
selectedCollection.onNewMongoQueryClick(selectedCollection, null); selectedCollection.onNewMongoQueryClick(selectedCollection, null);
} else { } else {
selectedCollection.onNewQueryClick(selectedCollection, null); selectedCollection.onNewQueryClick(selectedCollection, null);
} }
const deferred: Q.Deferred<void> = Q.defer<void>();
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (evt: any): void => { reader.onload = (evt: any): void => {
const fileData: string = evt.target.result; const fileData: string = evt.target.result;
const queryTab = this.container.tabsManager.activeTab() as QueryTab; const queryTab = this.container.tabsManager.activeTab() as QueryTab;
queryTab.initialEditorContent(fileData); queryTab.initialEditorContent(fileData);
queryTab.sqlQueryEditorContent(fileData); queryTab.sqlQueryEditorContent(fileData);
deferred.resolve();
}; };
reader.onerror = (evt: ProgressEvent): void => { reader.onerror = (evt: ProgressEvent): void => {
deferred.reject((evt as any).error.message); throw new Error((evt as any).error.message);
}; };
reader.readAsText(file); reader.readAsText(file);
return deferred.promise;
} }
private updateSelectedFilesTitle(fileList: FileList) { private updateSelectedFilesTitle(fileList: FileList) {

View File

@@ -1,5 +1,4 @@
import _ from "underscore"; import _ from "underscore";
import Q from "q";
import * as Entities from "../Entities"; import * as Entities from "../Entities";
import * as QueryBuilderConstants from "../Constants"; import * as QueryBuilderConstants from "../Constants";
@@ -84,11 +83,11 @@ export function filterColumns(table: DataTables.DataTable, settings: boolean[]):
* Reorder columns based on current order. * Reorder columns based on current order.
* If no current order is specified, reorder the columns based on intial order. * If no current order is specified, reorder the columns based on intial order.
*/ */
export function reorderColumns( export async function reorderColumns(
table: DataTables.DataTable, table: DataTables.DataTable,
targetOrder: number[], targetOrder: number[],
currentOrder?: number[] currentOrder?: number[]
): Q.Promise<any> { ): Promise<any> {
var columnsCount: number = targetOrder.length; var columnsCount: number = targetOrder.length;
var isCurrentOrderPassedIn: boolean = !!currentOrder; var isCurrentOrderPassedIn: boolean = !!currentOrder;
if (!isCurrentOrderPassedIn) { if (!isCurrentOrderPassedIn) {
@@ -107,13 +106,9 @@ export function reorderColumns(
var transformationOrder: number[] = isCurrentOrderPassedIn var transformationOrder: number[] = isCurrentOrderPassedIn
? calculateTransformationOrder(currentOrder, targetOrder) ? calculateTransformationOrder(currentOrder, targetOrder)
: targetOrder; : targetOrder;
try { $.fn.dataTable.ColReorder(table).fnOrder(transformationOrder);
$.fn.dataTable.ColReorder(table).fnOrder(transformationOrder);
} catch (err) {
return Q.reject(err);
}
} }
return Q.resolve(null); return null;
} }
export function resetColumns(table: DataTables.DataTable): void { export function resetColumns(table: DataTables.DataTable): void {

View File

@@ -1,5 +1,4 @@
import _ from "underscore"; import _ from "underscore";
import Q from "q";
import * as DataTableUtilities from "./DataTableUtilities"; import * as DataTableUtilities from "./DataTableUtilities";
import * as DataTableOperations from "./DataTableOperations"; import * as DataTableOperations from "./DataTableOperations";
import TableEntityListViewModel from "./TableEntityListViewModel"; import TableEntityListViewModel from "./TableEntityListViewModel";
@@ -49,7 +48,7 @@ export default class TableCommands {
/** /**
* Edit entity * Edit entity
*/ */
public editEntityCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public editEntityCommand(viewModel: TableEntityListViewModel): Promise<any> {
if (!viewModel) { if (!viewModel) {
return null; // Error return null; // Error
} }
@@ -68,7 +67,7 @@ export default class TableCommands {
return null; return null;
} }
public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public deleteEntitiesCommand(viewModel: TableEntityListViewModel): Promise<any> {
if (!viewModel) { if (!viewModel) {
return null; // Error return null; // Error
} }
@@ -92,7 +91,7 @@ export default class TableCommands {
return null; return null;
} }
public customizeColumnsCommand(viewModel: TableEntityListViewModel): Q.Promise<any> { public customizeColumnsCommand(viewModel: TableEntityListViewModel): Promise<any> {
var table: DataTables.DataTable = viewModel.table; var table: DataTables.DataTable = viewModel.table;
var displayedColumnNames: string[] = DataTableOperations.getDataTableHeaders(table); var displayedColumnNames: string[] = DataTableOperations.getDataTableHeaders(table);
var columnsCount: number = displayedColumnNames.length; var columnsCount: number = displayedColumnNames.length;
@@ -120,7 +119,7 @@ export default class TableCommands {
return null; return null;
} }
public reorderColumnsBasedOnSelectedEntities(viewModel: TableEntityListViewModel): Q.Promise<boolean> { public reorderColumnsBasedOnSelectedEntities(viewModel: TableEntityListViewModel): Promise<boolean> {
var selected = viewModel.selected(); var selected = viewModel.selected();
if (!selected || !selected.length) { if (!selected || !selected.length) {
return null; return null;

View File

@@ -15,7 +15,13 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { MessageTypes } from "../../Contracts/ExplorerContracts"; import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { sendMessage } from "../../Common/MessageHandler"; import { sendMessage } from "../../Common/MessageHandler";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { queryDocuments, deleteDocument, updateDocument, createDocument } from "../../Common/DocumentClientUtilityBase"; import {
queryDocuments,
refreshCachedResources,
deleteDocument,
updateDocument,
createDocument
} from "../../Common/DocumentClientUtilityBase";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
export interface CassandraTableKeys { export interface CassandraTableKeys {
@@ -418,7 +424,7 @@ export class CassandraAPIDataClient extends TableDataClient {
ConsoleDataType.Info, ConsoleDataType.Info,
`Successfully created a keyspace with query ${createKeyspaceQuery}` `Successfully created a keyspace with query ${createKeyspaceQuery}`
); );
deferred.resolve(); refreshCachedResources().finally(() => deferred.resolve());
}, },
reason => { reason => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -465,7 +471,15 @@ export class CassandraAPIDataClient extends TableDataClient {
ConsoleDataType.Info, ConsoleDataType.Info,
`Successfully created a table with query ${createTableQuery}` `Successfully created a table with query ${createTableQuery}`
); );
deferred.resolve(); refreshCachedResources(null).then(
() => {
deferred.resolve();
},
reason => {
// Still resolve since the keyspace/table was successfully created at this point.
deferred.resolve();
}
);
}, },
reason => { reason => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -506,7 +520,15 @@ export class CassandraAPIDataClient extends TableDataClient {
ConsoleDataType.Info, ConsoleDataType.Info,
`Successfully deleted resource with query ${deleteQuery}` `Successfully deleted resource with query ${deleteQuery}`
); );
deferred.resolve(); refreshCachedResources(null).then(
() => {
deferred.resolve();
},
reason => {
// Still resolve since the keyspace/table was successfully deleted at this point.
deferred.resolve();
}
);
}, },
reason => { reason => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(

View File

@@ -1,5 +1,4 @@
import * as _ from "underscore"; import * as _ from "underscore";
import Q from "q";
import * as Entities from "./Entities"; import * as Entities from "./Entities";
import { CassandraTableKey } from "./TableDataClient"; import { CassandraTableKey } from "./TableDataClient";
import * as Constants from "./Constants"; import * as Constants from "./Constants";
@@ -19,8 +18,8 @@ export function guid() {
/** /**
* Returns a promise that resolves in the specified number of milliseconds. * Returns a promise that resolves in the specified number of milliseconds.
*/ */
export function delay(milliseconds: number): Q.Promise<any> { export function delay(milliseconds: number): Promise<any> {
return Q.delay(milliseconds); return new Promise(resolve => setTimeout(resolve, milliseconds));
} }
/** /**

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -225,7 +224,7 @@ export default class ConflictsTab extends TabsBase {
}); });
} }
public refreshDocumentsGrid(): Q.Promise<any> { public refreshDocumentsGrid(): Promise<any> {
// clear documents grid // clear documents grid
this.conflictIds([]); this.conflictIds([]);
return this.createIterator() return this.createIterator()
@@ -256,19 +255,17 @@ export default class ConflictsTab extends TabsBase {
return true; return true;
}; };
public onConflictIdClick(clickedDocumentId: ConflictId): Q.Promise<any> { public async onConflictIdClick(clickedDocumentId: ConflictId): Promise<any> {
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) { if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
return Q(); return;
} }
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
return Q();
} }
public onAcceptChangesClick = (): Q.Promise<any> => { public onAcceptChangesClick = async (): Promise<any> => {
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) { if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
return Q(); return;
} }
this.isExecutionError(false); this.isExecutionError(false);
@@ -286,7 +283,7 @@ export default class ConflictsTab extends TabsBase {
conflictResourceId: selectedConflict.resourceId conflictResourceId: selectedConflict.resourceId
}); });
let operationPromise: Q.Promise<any> = Q(); let operationPromise: Promise<any>;
if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) { if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) {
const documentContent = JSON.parse(this.selectedConflictContent()); const documentContent = JSON.parse(this.selectedConflictContent());
@@ -358,7 +355,7 @@ export default class ConflictsTab extends TabsBase {
.finally(() => this.isExecuting(false)); .finally(() => this.isExecuting(false));
}; };
public onDeleteClick = (): Q.Promise<any> => { public onDeleteClick = (): Promise<any> => {
this.isExecutionError(false); this.isExecutionError(false);
this.isExecuting(true); this.isExecuting(true);
@@ -418,40 +415,34 @@ export default class ConflictsTab extends TabsBase {
.finally(() => this.isExecuting(false)); .finally(() => this.isExecuting(false));
}; };
public onDiscardClick = (): Q.Promise<any> => { public onDiscardClick = async (): Promise<any> => {
this.selectedConflictContent(this.selectedConflictContent.getEditableOriginalValue()); this.selectedConflictContent(this.selectedConflictContent.getEditableOriginalValue());
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
return Q();
}; };
public onValidDocumentEdit(): Q.Promise<any> { public async onValidDocumentEdit(): Promise<any> {
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid);
return Q();
} }
public onInvalidDocumentEdit(): Q.Promise<any> { public async onInvalidDocumentEdit(): Promise<any> {
if ( if (
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentNoEdits || this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentNoEdits ||
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid
) { ) {
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid);
return Q();
} }
return Q();
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Promise<any> {
return super.onTabClick().then(() => { return super.onTabClick().then(() => {
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts); this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
}); });
} }
public onActivate(): Q.Promise<any> { public onActivate(): Promise<any> {
return super.onActivate().then(() => { return super.onActivate().then(() => {
if (this._documentsIterator) { if (this._documentsIterator) {
return Q.resolve(this._documentsIterator); return this._documentsIterator;
} }
return this.createIterator().then( return this.createIterator().then(
@@ -481,7 +472,7 @@ export default class ConflictsTab extends TabsBase {
}); });
} }
public onRefreshClick(): Q.Promise<any> { public onRefreshClick(): Promise<any> {
return this.refreshDocumentsGrid().then(() => { return this.refreshDocumentsGrid().then(() => {
this.selectedConflictContent(""); this.selectedConflictContent("");
this.selectedConflictId(null); this.selectedConflictId(null);
@@ -489,7 +480,7 @@ export default class ConflictsTab extends TabsBase {
}); });
} }
public createIterator(): Q.Promise<QueryIterator<ConflictDefinition & Resource>> { public createIterator(): Promise<QueryIterator<ConflictDefinition & Resource>> {
// TODO: Conflict Feed does not allow filtering atm // TODO: Conflict Feed does not allow filtering atm
const query: string = undefined; const query: string = undefined;
let options: any = {}; let options: any = {};
@@ -497,7 +488,7 @@ export default class ConflictsTab extends TabsBase {
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options); return queryConflicts(this.collection.databaseId, this.collection.id(), query, options);
} }
public loadNextPage(): Q.Promise<any> { public loadNextPage(): Promise<any> {
this.isExecuting(true); this.isExecuting(true);
this.isExecutionError(false); this.isExecutionError(false);
return this._loadNextPageInternal() return this._loadNextPageInternal()
@@ -564,8 +555,8 @@ export default class ConflictsTab extends TabsBase {
} }
}; };
protected _loadNextPageInternal(): Q.Promise<DataModels.ConflictId[]> { protected _loadNextPageInternal(): Promise<DataModels.ConflictId[]> {
return Q(this._documentsIterator.fetchNext().then(response => response.resources)); return this._documentsIterator.fetchNext().then(response => response.resources);
} }
protected _onEditorContentChange(newContent: string) { protected _onEditorContentChange(newContent: string) {
@@ -577,22 +568,20 @@ export default class ConflictsTab extends TabsBase {
} }
} }
public initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Q.Promise<any> { public async initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Promise<any> {
if (documentId) { if (documentId) {
let parsedConflictContent: any = JSON.parse(documentToInsert); let parsedConflictContent: any = JSON.parse(documentToInsert);
const renderedConflictContent: string = this.renderObjectForEditor(parsedConflictContent, null, 4); const renderedConflictContent: string = this.renderObjectForEditor(parsedConflictContent, null, 4);
this.selectedConflictContent.setBaseline(renderedConflictContent); this.selectedConflictContent.setBaseline(renderedConflictContent);
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
} }
return Q();
} }
public initDocumentEditorForReplace( public async initDocumentEditorForReplace(
documentId: ConflictId, documentId: ConflictId,
conflictContent: any, conflictContent: any,
currentContent: any currentContent: any
): Q.Promise<any> { ): Promise<any> {
if (documentId) { if (documentId) {
currentContent = ConflictsTab.removeSystemProperties(currentContent); currentContent = ConflictsTab.removeSystemProperties(currentContent);
const renderedCurrentContent: string = this.renderObjectForEditor(currentContent, null, 4); const renderedCurrentContent: string = this.renderObjectForEditor(currentContent, null, 4);
@@ -605,11 +594,9 @@ export default class ConflictsTab extends TabsBase {
this.selectedConflictContent.setBaseline(renderedConflictContent); this.selectedConflictContent.setBaseline(renderedConflictContent);
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
} }
return Q();
} }
public initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Q.Promise<any> { public async initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Promise<any> {
if (documentId) { if (documentId) {
let parsedDocumentToDelete: any = JSON.parse(documentToDelete); let parsedDocumentToDelete: any = JSON.parse(documentToDelete);
parsedDocumentToDelete = ConflictsTab.removeSystemProperties(parsedDocumentToDelete); parsedDocumentToDelete = ConflictsTab.removeSystemProperties(parsedDocumentToDelete);
@@ -617,15 +604,12 @@ export default class ConflictsTab extends TabsBase {
this.selectedConflictContent.setBaseline(renderedDocumentToDelete); this.selectedConflictContent.setBaseline(renderedDocumentToDelete);
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
} }
return Q();
} }
public initDocumentEditorForNoOp(documentId: ConflictId): Q.Promise<any> { public async initDocumentEditorForNoOp(documentId: ConflictId): Promise<any> {
this.selectedConflictContent(null); this.selectedConflictContent(null);
this.selectedConflictCurrent(null); this.selectedConflictCurrent(null);
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected); this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
return Q();
} }
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {

View File

@@ -24,6 +24,7 @@
<span class="scaleSettingTitle">Scale</span> <span class="scaleSettingTitle">Scale</span>
</div> </div>
<div class="ssTextAllignment" id="scaleRegion"> <div class="ssTextAllignment" id="scaleRegion">
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
testId: testId, testId: testId,
@@ -50,6 +51,34 @@
}" }"
> >
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
<!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<throughput-input
params="{
testId: testId,
class: 'scaleForm dirty',
value: throughput,
minimum: minRUs,
maximum: maxRUThroughputInputLimit,
canExceedMaximumValue: canThroughputExceedMaximumValue,
step: throughputIncreaseFactor,
label: throughputTitle,
ariaLabel: throughputAriaLabel,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
throughputProvisionedRadioId: throughputProvisionedRadioId,
throughputModeRadioName: throughputModeRadioName,
showAutoPilot: userCanChangeProvisioningTypes,
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
<!-- /ko -->
<div class="estimatedCost" data-bind="visible: costsVisible"> <div class="estimatedCost" data-bind="visible: costsVisible">
<p data-bind="visible: minRUAnotationVisible"> <p data-bind="visible: minRUAnotationVisible">

View File

@@ -8,7 +8,6 @@ import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
import editable from "../../Common/EditableUtility"; import editable from "../../Common/EditableUtility";
import Q from "q";
import SaveIcon from "../../../images/save-cosmos.svg"; import SaveIcon from "../../../images/save-cosmos.svg";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -22,12 +21,12 @@ import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/update
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
const updateThroughputBeyondLimitWarningMessage: string = ` const updateThroughputBeyondLimitWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity. You are about to request an increase in throughput beyond the pre-allocated capacity.
The service will scale out and increase throughput for the selected database. The service will scale out and increase throughput for the selected database.
This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`; This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`;
const updateThroughputDelayedApplyWarningMessage: string = ` const updateThroughputDelayedApplyWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity. You are about to request an increase in throughput beyond the pre-allocated capacity.
This operation will take some time to complete.`; This operation will take some time to complete.`;
const currentThroughput: (isAutoscale: boolean, throughput: number) => string = (isAutoscale, throughput) => const currentThroughput: (isAutoscale: boolean, throughput: number) => string = (isAutoscale, throughput) =>
@@ -36,17 +35,17 @@ const currentThroughput: (isAutoscale: boolean, throughput: number) => string =
: `Current manual throughput: ${throughput} RU/s`; : `Current manual throughput: ${throughput} RU/s`;
const throughputApplyDelayedMessage = (isAutoscale: boolean, throughput: number, databaseName: string) => const throughputApplyDelayedMessage = (isAutoscale: boolean, throughput: number, databaseName: string) =>
`The request to increase the throughput has successfully been submitted. `The request to increase the throughput has successfully been submitted.
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br /> This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`; Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
const throughputApplyShortDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) => const throughputApplyShortDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) =>
`A request to increase the throughput is currently in progress. `A request to increase the throughput is currently in progress.
This operation will take some time to complete.<br /> This operation will take some time to complete.<br />
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`; Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
const throughputApplyLongDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) => const throughputApplyLongDelayMessage = (isAutoscale: boolean, throughput: number, databaseName: string) =>
`A request to increase the throughput is currently in progress. `A request to increase the throughput is currently in progress.
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br /> This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`; Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
@@ -54,6 +53,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
// editables // editables
public isAutoPilotSelected: ViewModels.Editable<boolean>; public isAutoPilotSelected: ViewModels.Editable<boolean>;
public throughput: ViewModels.Editable<number>; public throughput: ViewModels.Editable<number>;
public selectedAutoPilotTier: ViewModels.Editable<DataModels.AutopilotTier>;
public autoPilotThroughput: ViewModels.Editable<number>; public autoPilotThroughput: ViewModels.Editable<number>;
public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor; public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor;
@@ -80,9 +80,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public throughputTitle: ko.PureComputed<string>; public throughputTitle: ko.PureComputed<string>;
public throughputAriaLabel: ko.PureComputed<string>; public throughputAriaLabel: ko.PureComputed<string>;
public userCanChangeProvisioningTypes: ko.Observable<boolean>; public userCanChangeProvisioningTypes: ko.Observable<boolean>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public autoPilotUsageCost: ko.PureComputed<string>; public autoPilotUsageCost: ko.PureComputed<string>;
public warningMessage: ko.Computed<string>; public warningMessage: ko.Computed<string>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public overrideWithAutoPilotSettings: ko.Computed<boolean>; public overrideWithAutoPilotSettings: ko.Computed<boolean>;
public overrideWithProvisionedThroughputSettings: ko.Computed<boolean>; public overrideWithProvisionedThroughputSettings: ko.Computed<boolean>;
public testId: string; public testId: string;
@@ -99,6 +101,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
super(options); super(options);
this.container = options.node && (options.node as ViewModels.Database).container; this.container = options.node && (options.node as ViewModels.Database).container;
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.selectedAutoPilotTier = editable.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>();
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
// html element ids // html element ids
@@ -113,13 +118,23 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.autoPilotThroughput = editable.observable<number>(); this.autoPilotThroughput = editable.observable<number>();
const offer = this.database && this.database.offer && this.database.offer(); const offer = this.database && this.database.offer && this.database.offer();
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings; const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
this.userCanChangeProvisioningTypes = ko.observable(true); this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
if (!this.hasAutoPilotV2FeatureFlag()) {
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) { if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) { if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
this._wasAutopilotOriginallySet(true); this._wasAutopilotOriginallySet(true);
this.isAutoPilotSelected(true); this.isAutoPilotSelected(true);
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput); this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
}
}
} else {
if (offerAutopilotSettings && offerAutopilotSettings.tier) {
if (AutoPilotUtils.isValidAutoPilotTier(offerAutopilotSettings.tier)) {
this._wasAutopilotOriginallySet(true);
this.isAutoPilotSelected(true);
this.selectedAutoPilotTier(offerAutopilotSettings.tier);
this.autoPilotTiersList(AutoPilotUtils.getAvailableAutoPilotTiersOptions(offerAutopilotSettings.tier));
}
} }
} }
@@ -134,11 +149,13 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
}); });
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this.autoPilotThroughput(); const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, true /* isDatabaseThroughput */);
}); });
this.requestUnitsUsageCost = ko.pureComputed(() => { this.requestUnitsUsageCost = ko.pureComputed(() => {
@@ -163,7 +180,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(), this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
serverId, serverId,
regions, regions,
multimaster multimaster,
false /*rupmEnabled*/
); );
} else { } else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -197,10 +215,16 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
}); });
this.overrideWithAutoPilotSettings = ko.pureComputed(() => { this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
}); });
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => { this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
}); });
@@ -258,7 +282,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.throughputTitle = ko.pureComputed<string>(() => { this.throughputTitle = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minRUs().toLocaleString()} - unlimited RU/s)`; return `Throughput (${this.minRUs().toLocaleString()} - unlimited RU/s)`;
@@ -281,7 +305,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.warningMessage = ko.computed<string>(() => { this.warningMessage = ko.computed<string>(() => {
const offer = this.database && this.database.offer && this.database.offer(); const offer = this.database && this.database.offer && this.database.offer();
if (this.overrideWithProvisionedThroughputSettings()) { if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
return AutoPilotUtils.manualToAutoscaleDisclaimer; return AutoPilotUtils.manualToAutoscaleDisclaimer;
} }
@@ -291,7 +315,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending] !!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
) { ) {
const throughput = offer.content.offerAutopilotSettings const throughput = offer.content.offerAutopilotSettings
? offer.content.offerAutopilotSettings.maxThroughput ? !this.hasAutoPilotV2FeatureFlag()
? offer.content.offerAutopilotSettings.maxThroughput
: offer.content.offerAutopilotSettings.maximumTierThroughput
: offer.content.offerThroughput; : offer.content.offerThroughput;
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id()); return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
@@ -348,13 +374,20 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
const isAutoPilot = this.isAutoPilotSelected(); const isAutoPilot = this.isAutoPilotSelected();
const isManual = !this.isAutoPilotSelected(); const isManual = !this.isAutoPilotSelected();
if (isAutoPilot) { if (isAutoPilot) {
if (!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) { if (
(!this.hasAutoPilotV2FeatureFlag() &&
!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
(this.hasAutoPilotV2FeatureFlag() && !AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()))
) {
return false; return false;
} }
if (this.isAutoPilotSelected.editableIsDirty()) { if (this.isAutoPilotSelected.editableIsDirty()) {
return true; return true;
} }
if (this.autoPilotThroughput.editableIsDirty()) { if (
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput.editableIsDirty()) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
) {
return true; return true;
} }
} }
@@ -378,7 +411,10 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
return true; return true;
} }
if (this.isAutoPilotSelected.editableIsDirty()) { if (
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected.editableIsDirty()) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
) {
return true; return true;
} }
} }
@@ -459,7 +495,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
databaseAccountName: userContext.databaseAccount.name, databaseAccountName: userContext.databaseAccount.name,
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseName: this.database.id(), databaseName: this.database.id(),
throughput: newThroughput throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
}; };
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
this.database.offer().content.offerThroughput = originalThroughputValue; this.database.offer().content.offerThroughput = originalThroughputValue;
@@ -506,15 +543,17 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
} }
}; };
public onRevertClick = (): Q.Promise<any> => { public onRevertClick = async (): Promise<any> => {
this.throughput.setBaseline(this.throughput.getEditableOriginalValue()); this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue()); this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue());
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue()); if (!this.hasAutoPilotV2FeatureFlag()) {
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue());
return Q(); } else {
this.selectedAutoPilotTier.setBaseline(this.selectedAutoPilotTier.getEditableOriginalValue());
}
}; };
public onActivate(): Q.Promise<any> { public onActivate(): Promise<any> {
return super.onActivate().then(async () => { return super.onActivate().then(async () => {
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings); this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
await this.database.loadOffer(); await this.database.loadOffer();
@@ -527,11 +566,17 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings; const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
this.throughput.setBaseline(offerThroughput); this.throughput.setBaseline(offerThroughput);
this.userCanChangeProvisioningTypes(true); this.userCanChangeProvisioningTypes(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput; if (this.hasAutoPilotV2FeatureFlag()) {
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot)); const selectedAutoPilotTier = offerAutopilotSettings && offerAutopilotSettings.tier;
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput); this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotTier(selectedAutoPilotTier));
this.selectedAutoPilotTier.setBaseline(selectedAutoPilotTier);
} else {
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput;
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot));
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput);
}
} }
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -340,24 +339,21 @@ export default class DocumentsTab extends TabsBase {
return true; return true;
} }
public onShowFilterClick(): Q.Promise<any> { public async onShowFilterClick(): Promise<any> {
this.isFilterCreated(true); this.isFilterCreated(true);
this.isFilterExpanded(true); this.isFilterExpanded(true);
$(".filterDocExpanded").addClass("active"); $(".filterDocExpanded").addClass("active");
$("#content").addClass("active"); $("#content").addClass("active");
$(".querydropdown").focus(); $(".querydropdown").focus();
return Q();
} }
public onHideFilterClick(): Q.Promise<any> { public async onHideFilterClick(): Promise<any> {
this.isFilterExpanded(false); this.isFilterExpanded(false);
$(".filterDocExpanded").removeClass("active"); $(".filterDocExpanded").removeClass("active");
$("#content").removeClass("active"); $("#content").removeClass("active");
$(".queryButton").focus(); $(".queryButton").focus();
return Q();
} }
public onCloseButtonKeyDown = (source: any, event: KeyboardEvent): boolean => { public onCloseButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
@@ -369,7 +365,7 @@ export default class DocumentsTab extends TabsBase {
return true; return true;
}; };
public onApplyFilterClick(): Q.Promise<any> { public onApplyFilterClick(): Promise<any> {
// clear documents grid // clear documents grid
this.documentIds([]); this.documentIds([]);
return this.createIterator() return this.createIterator()
@@ -398,7 +394,7 @@ export default class DocumentsTab extends TabsBase {
}); });
} }
public refreshDocumentsGrid(): Q.Promise<any> { public refreshDocumentsGrid(): Promise<any> {
return this.onApplyFilterClick(); return this.onApplyFilterClick();
} }
@@ -411,19 +407,17 @@ export default class DocumentsTab extends TabsBase {
return true; return true;
}; };
public onDocumentIdClick(clickedDocumentId: DocumentId): Q.Promise<any> { public async onDocumentIdClick(clickedDocumentId: DocumentId): Promise<any> {
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) { if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
return Q(); return;
} }
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
return Q();
} }
public onNewDocumentClick = (): Q.Promise<any> => { public onNewDocumentClick = async (): Promise<any> => {
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) { if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
return Q(); return;
} }
this.selectedDocumentId(null); this.selectedDocumentId(null);
@@ -431,11 +425,9 @@ export default class DocumentsTab extends TabsBase {
this.initialDocumentContent(defaultDocument); this.initialDocumentContent(defaultDocument);
this.selectedDocumentContent.setBaseline(defaultDocument); this.selectedDocumentContent.setBaseline(defaultDocument);
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid); this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
return Q();
}; };
public onSaveNewDocumentClick = (): Q.Promise<any> => { public onSaveNewDocumentClick = (): Promise<any> => {
this.isExecutionError(false); this.isExecutionError(false);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, { const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name, databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
@@ -493,15 +485,13 @@ export default class DocumentsTab extends TabsBase {
.finally(() => this.isExecuting(false)); .finally(() => this.isExecuting(false));
}; };
public onRevertNewDocumentClick = (): Q.Promise<any> => { public onRevertNewDocumentClick = async (): Promise<any> => {
this.initialDocumentContent(""); this.initialDocumentContent("");
this.selectedDocumentContent(""); this.selectedDocumentContent("");
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected); this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
return Q();
}; };
public onSaveExisitingDocumentClick = (): Q.Promise<any> => { public onSaveExisitingDocumentClick = (): Promise<any> => {
const selectedDocumentId = this.selectedDocumentId(); const selectedDocumentId = this.selectedDocumentId();
const documentContent = JSON.parse(this.selectedDocumentContent()); const documentContent = JSON.parse(this.selectedDocumentContent());
@@ -560,15 +550,13 @@ export default class DocumentsTab extends TabsBase {
.finally(() => this.isExecuting(false)); .finally(() => this.isExecuting(false));
}; };
public onRevertExisitingDocumentClick = (): Q.Promise<any> => { public onRevertExisitingDocumentClick = async (): Promise<any> => {
this.selectedDocumentContent.setBaseline(this.initialDocumentContent()); this.selectedDocumentContent.setBaseline(this.initialDocumentContent());
this.initialDocumentContent.valueHasMutated(); this.initialDocumentContent.valueHasMutated();
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
return Q();
}; };
public onDeleteExisitingDocumentClick = (): Q.Promise<any> => { public onDeleteExisitingDocumentClick = async (): Promise<any> => {
const selectedDocumentId = this.selectedDocumentId(); const selectedDocumentId = this.selectedDocumentId();
const msg = !this.isPreferredApiMongoDB const msg = !this.isPreferredApiMongoDB
? "Are you sure you want to delete the selected item ?" ? "Are you sure you want to delete the selected item ?"
@@ -577,30 +565,27 @@ export default class DocumentsTab extends TabsBase {
if (window.confirm(msg)) { if (window.confirm(msg)) {
return this._deleteDocument(selectedDocumentId); return this._deleteDocument(selectedDocumentId);
} }
return Q();
}; };
public onValidDocumentEdit(): Q.Promise<any> { public async onValidDocumentEdit(): Promise<any> {
if ( if (
this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid || this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid ||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid
) { ) {
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid); this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
return Q(); return;
} }
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid);
return Q();
} }
public onInvalidDocumentEdit(): Q.Promise<any> { public async onInvalidDocumentEdit(): Promise<any> {
if ( if (
this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid || this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid ||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid
) { ) {
this.editorState(ViewModels.DocumentExplorerState.newDocumentInvalid); this.editorState(ViewModels.DocumentExplorerState.newDocumentInvalid);
return Q(); return;
} }
if ( if (
@@ -608,22 +593,20 @@ export default class DocumentsTab extends TabsBase {
this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid this.editorState() === ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid
) { ) {
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid); this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid);
return Q(); return;
} }
return Q();
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Promise<any> {
return super.onTabClick().then(() => { return super.onTabClick().then(() => {
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
}); });
} }
public onActivate(): Q.Promise<any> { public onActivate(): Promise<any> {
return super.onActivate().then(() => { return super.onActivate().then(() => {
if (this._documentsIterator) { if (this._documentsIterator) {
return Q.resolve(this._documentsIterator); return this._documentsIterator;
} }
return this.createIterator().then( return this.createIterator().then(
@@ -653,7 +636,7 @@ export default class DocumentsTab extends TabsBase {
}); });
} }
public onRefreshClick(): Q.Promise<any> { public onRefreshClick(): Promise<any> {
return this.refreshDocumentsGrid().then(() => { return this.refreshDocumentsGrid().then(() => {
this.selectedDocumentContent(""); this.selectedDocumentContent("");
this.selectedDocumentId(null); this.selectedDocumentId(null);
@@ -665,11 +648,11 @@ export default class DocumentsTab extends TabsBase {
return window.confirm(msg); return window.confirm(msg);
}; };
protected __deleteDocument(documentId: DocumentId): Q.Promise<any> { protected __deleteDocument(documentId: DocumentId): Promise<any> {
return deleteDocument(this.collection, documentId); return deleteDocument(this.collection, documentId);
} }
private _deleteDocument(selectedDocumentId: DocumentId): Q.Promise<any> { private _deleteDocument(selectedDocumentId: DocumentId): Promise<any> {
this.isExecutionError(false); this.isExecutionError(false);
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, { const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name, databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
@@ -714,7 +697,7 @@ export default class DocumentsTab extends TabsBase {
.finally(() => this.isExecuting(false)); .finally(() => this.isExecuting(false));
} }
public createIterator(): Q.Promise<QueryIterator<ItemDefinition & Resource>> { public createIterator(): Promise<QueryIterator<ItemDefinition & Resource>> {
let filters = this.lastFilterContents(); let filters = this.lastFilterContents();
const filter: string = this.filterContent().trim(); const filter: string = this.filterContent().trim();
const query: string = this.buildQuery(filter); const query: string = this.buildQuery(filter);
@@ -728,14 +711,14 @@ export default class DocumentsTab extends TabsBase {
return queryDocuments(this.collection.databaseId, this.collection.id(), query, options); return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
} }
public selectDocument(documentId: DocumentId): Q.Promise<any> { public selectDocument(documentId: DocumentId): Promise<any> {
this.selectedDocumentId(documentId); this.selectedDocumentId(documentId);
return readDocument(this.collection, documentId).then((content: any) => { return readDocument(this.collection, documentId).then((content: any) => {
this.initDocumentEditor(documentId, content); this.initDocumentEditor(documentId, content);
}); });
} }
public loadNextPage(): Q.Promise<any> { public loadNextPage(): Promise<any> {
this.isExecuting(true); this.isExecuting(true);
this.isExecutionError(false); this.isExecutionError(false);
return this._loadNextPageInternal() return this._loadNextPageInternal()
@@ -809,8 +792,8 @@ export default class DocumentsTab extends TabsBase {
} }
}; };
protected _loadNextPageInternal(): Q.Promise<DataModels.DocumentId[]> { protected _loadNextPageInternal(): Promise<DataModels.DocumentId[]> {
return Q(this._documentsIterator.fetchNext().then(response => response.resources)); return this._documentsIterator.fetchNext().then(response => response.resources);
} }
protected _onEditorContentChange(newContent: string) { protected _onEditorContentChange(newContent: string) {
@@ -822,7 +805,7 @@ export default class DocumentsTab extends TabsBase {
} }
} }
public initDocumentEditor(documentId: DocumentId, documentContent: any): Q.Promise<any> { public async initDocumentEditor(documentId: DocumentId, documentContent: any): Promise<any> {
if (documentId) { if (documentId) {
const content: string = this.renderObjectForEditor(documentContent, null, 4); const content: string = this.renderObjectForEditor(documentContent, null, 4);
this.selectedDocumentContent.setBaseline(content); this.selectedDocumentContent.setBaseline(content);
@@ -832,8 +815,6 @@ export default class DocumentsTab extends TabsBase {
: ViewModels.DocumentExplorerState.newDocumentValid; : ViewModels.DocumentExplorerState.newDocumentValid;
this.editorState(newState); this.editorState(newState);
} }
return Q();
} }
public buildQuery(filter: string): string { public buildQuery(filter: string): string {

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as Q from "q";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter"; import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter";
@@ -114,7 +113,7 @@ export default class GraphTab extends TabsBase {
: `${account.name}.graphs.azure.com:443/`; : `${account.name}.graphs.azure.com:443/`;
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Promise<any> {
return super.onTabClick().then(() => { return super.onTabClick().then(() => {
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph); this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
}); });

View File

@@ -1,5 +1,4 @@
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Q from "q";
import MongoUtility from "../../Common/MongoUtility"; import MongoUtility from "../../Common/MongoUtility";
import QueryTab from "./QueryTab"; import QueryTab from "./QueryTab";
import * as HeadersUtility from "../../Common/HeadersUtility"; import * as HeadersUtility from "../../Common/HeadersUtility";
@@ -20,10 +19,10 @@ export default class MongoQueryTab extends QueryTab {
return MongoUtility.tojson(value, null, false); return MongoUtility.tojson(value, null, false);
} }
protected _initIterator(): Q.Promise<MinimalQueryIterator> { protected async _initIterator(): Promise<MinimalQueryIterator> {
let options: any = {}; let options: any = {};
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey(); options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
this._iterator = queryIterator(this.collection.databaseId, this.collection, this.sqlStatementToExecute()); this._iterator = queryIterator(this.collection.databaseId, this.collection, this.sqlStatementToExecute());
return Q(this._iterator); return this._iterator;
} }
} }

View File

@@ -3,7 +3,6 @@ import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import AuthHeadersUtil from "../../Platform/Hosted/Authorization"; import AuthHeadersUtil from "../../Platform/Hosted/Authorization";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import Q from "q";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -53,7 +52,7 @@ export default class MongoShellTab extends TabsBase {
// } // }
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Promise<any> {
return super.onTabClick().then(() => { return super.onTabClick().then(() => {
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
}); });

View File

@@ -1,5 +1,4 @@
import * as _ from "underscore"; import * as _ from "underscore";
import * as Q from "q";
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -84,7 +83,7 @@ export default class NotebookTabV2 extends TabsBase {
}); });
} }
public onCloseTabButtonClick(): Q.Promise<any> { public async onCloseTabButtonClick(): Promise<any> {
const cleanup = () => { const cleanup = () => {
this.notebookComponentAdapter.notebookShutdown(); this.notebookComponentAdapter.notebookShutdown();
this.isActive(false); this.isActive(false);
@@ -100,11 +99,11 @@ export default class NotebookTabV2 extends TabsBase {
"Cancel", "Cancel",
undefined undefined
); );
return Q.resolve(null);
} else { } else {
cleanup(); cleanup();
return Q.resolve(null);
} }
return null;
} }
public async reconfigureServiceEndpoints() { public async reconfigureServiceEndpoints() {

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -163,13 +162,13 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
this._buildCommandBarOptions(); this._buildCommandBarOptions();
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Promise<any> {
return super.onTabClick().then(() => { return super.onTabClick().then(() => {
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query); this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
}); });
} }
public onExecuteQueryClick = (): Q.Promise<any> => { public onExecuteQueryClick = (): Promise<any> => {
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent(); const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
this.sqlStatementToExecute(sqlStatement); this.sqlStatementToExecute(sqlStatement);
this.allResultsMetadata([]); this.allResultsMetadata([]);
@@ -191,7 +190,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open(); this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
}; };
public onFetchNextPageClick(): Q.Promise<any> { public onFetchNextPageClick(): Promise<any> {
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || []; const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1]; const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1; const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
@@ -265,7 +264,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
return true; return true;
}; };
private _executeQueryDocumentsPage(firstItemIndex: number): Q.Promise<any> { private _executeQueryDocumentsPage(firstItemIndex: number): Promise<any> {
this.errors([]); this.errors([]);
this.roundTrips(undefined); this.roundTrips(undefined);
if (this._iterator == null) { if (this._iterator == null) {
@@ -277,7 +276,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
} }
// TODO: Position and enable spinner when request is in progress // TODO: Position and enable spinner when request is in progress
private _queryDocumentsPage(firstItemIndex: number): Q.Promise<any> { private _queryDocumentsPage(firstItemIndex: number): Promise<any> {
this.isExecutionError(false); this.isExecutionError(false);
this._resetAggregateQueryMetrics(); this._resetAggregateQueryMetrics();
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, { const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
@@ -485,16 +484,14 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
} }
} }
protected _initIterator(): Q.Promise<MinimalQueryIterator> { protected _initIterator(): Promise<MinimalQueryIterator> {
const options: any = QueryTab.getIteratorOptions(this.collection); const options: any = QueryTab.getIteratorOptions(this.collection);
if (this._resourceTokenPartitionKey) { if (this._resourceTokenPartitionKey) {
options.partitionKey = this._resourceTokenPartitionKey; options.partitionKey = this._resourceTokenPartitionKey;
} }
return Q( return queryDocuments(this.collection.databaseId, this.collection.id(), this.sqlStatementToExecute(), options).then(
queryDocuments(this.collection.databaseId, this.collection.id(), this.sqlStatementToExecute(), options).then( iterator => (this._iterator = iterator)
iterator => (this._iterator = iterator)
)
); );
} }

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel"; import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel";
@@ -130,38 +129,38 @@ export default class QueryTablesTab extends TabsBase {
this.buildCommandBarOptions(); this.buildCommandBarOptions();
} }
public onExecuteQueryClick = (): Q.Promise<any> => { public onExecuteQueryClick = (): Promise<any> => {
this.queryViewModel().runQuery(); this.queryViewModel().runQuery();
return null; return null;
}; };
public onQueryBuilderClick = (): Q.Promise<any> => { public onQueryBuilderClick = (): Promise<any> => {
this.queryViewModel().selectHelper(); this.queryViewModel().selectHelper();
return null; return null;
}; };
public onQueryTextClick = (): Q.Promise<any> => { public onQueryTextClick = (): Promise<any> => {
this.queryViewModel().selectEditor(); this.queryViewModel().selectEditor();
return null; return null;
}; };
public onAddEntityClick = (): Q.Promise<any> => { public onAddEntityClick = (): Promise<any> => {
this.container.addTableEntityPane.tableViewModel = this.tableEntityListViewModel(); this.container.addTableEntityPane.tableViewModel = this.tableEntityListViewModel();
this.container.addTableEntityPane.open(); this.container.addTableEntityPane.open();
return null; return null;
}; };
public onEditEntityClick = (): Q.Promise<any> => { public onEditEntityClick = (): Promise<any> => {
this.tableCommands.editEntityCommand(this.tableEntityListViewModel()); this.tableCommands.editEntityCommand(this.tableEntityListViewModel());
return null; return null;
}; };
public onDeleteEntityClick = (): Q.Promise<any> => { public onDeleteEntityClick = (): Promise<any> => {
this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel()); this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel());
return null; return null;
}; };
public onActivate(): Q.Promise<any> { public onActivate(): Promise<any> {
return super.onActivate().then(() => { return super.onActivate().then(() => {
const columns = const columns =
!!this.tableEntityListViewModel() && !!this.tableEntityListViewModel() &&

View File

@@ -1,6 +1,5 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import Q from "q";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
import SaveIcon from "../../../images/save-cosmos.svg"; import SaveIcon from "../../../images/save-cosmos.svg";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -186,7 +185,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
this._setBaselines(); this._setBaselines();
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Promise<any> {
return super.onTabClick().then(() => { return super.onTabClick().then(() => {
if (this.isNew()) { if (this.isNew()) {
this.collection.selectedSubnodeKind(this.tabKind); this.collection.selectedSubnodeKind(this.tabKind);
@@ -197,13 +196,11 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
public abstract onSaveClick: () => Promise<any>; public abstract onSaveClick: () => Promise<any>;
public abstract onUpdateClick: () => Promise<any>; public abstract onUpdateClick: () => Promise<any>;
public onDiscard = (): Q.Promise<any> => { public onDiscard = async (): Promise<any> => {
this.setBaselines(); this.setBaselines();
const original = this.editorContent.getEditableOriginalValue(); const original = this.editorContent.getEditableOriginalValue();
const editorModel = this.editor() && this.editor().getModel(); const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(original); editorModel && editorModel.setValue(original);
return Q();
}; };
public onSaveOrUpdateClick(): Promise<any> { public onSaveOrUpdateClick(): Promise<any> {

View File

@@ -51,6 +51,7 @@
<div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion"> <div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion">
<!-- ko ifnot: isAutoScaleEnabled --> <!-- ko ifnot: isAutoScaleEnabled -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
testId: testId, testId: testId,
@@ -76,10 +77,94 @@
}" }"
> >
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
<!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<throughput-input
params="{
testId: testId,
class: 'scaleForm dirty',
value: throughput,
minimum: minRUs,
maximum: maxRUThroughputInputLimit,
isEnabled: !hasDatabaseSharedThroughput(),
canExceedMaximumValue: canThroughputExceedMaximumValue,
label: throughputTitle,
ariaLabel: throughputAriaLabel,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
throughputProvisionedRadioId: throughputProvisionedRadioId,
throughputModeRadioName: throughputModeRadioName,
showAutoPilot: userCanChangeProvisioningTypes,
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
<!-- /ko -->
<div class="storageCapacityTitle throughputStorageValue" data-bind="html: storageCapacityTitle"></div> <div class="storageCapacityTitle throughputStorageValue" data-bind="html: storageCapacityTitle"></div>
<!-- /ko --> <!-- /ko -->
<div data-bind="visible: rupmVisible">
<div class="formTitle">RU/m</div>
<div class="tabs" aria-label="RU/m">
<div class="tab">
<label
data-bind="
attr:{
for: rupmOnId
},
css: {
dirty: rupm.editableIsDirty,
selectedRadio: rupm() === 'on',
unselectedRadio: rupm() !== 'on'
}"
>On</label
>
<input
type="radio"
name="rupm"
value="on"
class="radio"
data-bind="
attr:{
id: rupmOnId
},
checked: rupm"
/>
</div>
<div class="tab">
<label
data-bind="
attr:{
for: rupmOffId
},
css: {
dirty: rupm.editableIsDirty,
selectedRadio: rupm() === 'off',
unselectedRadio: rupm() !== 'off'
}"
>Off</label
>
<input
type="radio"
name="rupm"
value="off"
class="radio"
data-bind="
attr:{
id: rupmOffId
},
checked: rupm"
/>
</div>
</div>
</div>
<!-- TODO: Replace link with call to the Azure Support blade --> <!-- TODO: Replace link with call to the Azure Support blade -->
<div data-bind="visible: isAutoScaleEnabled"> <div data-bind="visible: isAutoScaleEnabled">
<div class="autoScaleThroughputTitle">Throughput (RU/s)</div> <div class="autoScaleThroughputTitle">Throughput (RU/s)</div>

View File

@@ -79,6 +79,7 @@ describe("Settings tab", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });
it("single master, should not show conflict resolution", () => { it("single master, should not show conflict resolution", () => {
@@ -177,6 +178,7 @@ describe("Settings tab", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });
it("On TTL changed", () => { it("On TTL changed", () => {
@@ -249,6 +251,7 @@ describe("Settings tab", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });
it("null if it didnt change", () => { it("null if it didnt change", () => {
@@ -324,6 +327,7 @@ describe("Settings tab", () => {
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) { function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
const explorer = new Explorer(); const explorer = new Explorer();
explorer.defaultExperience(defaultApi); explorer.defaultExperience(defaultApi);
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
const offer: DataModels.Offer = null; const offer: DataModels.Offer = null;
const defaultTtl = 200; const defaultTtl = 200;
@@ -446,4 +450,158 @@ describe("Settings tab", () => {
expect(settingsTab.partitionKeyVisible()).toBe(false); expect(settingsTab.partitionKeyVisible()).toBe(false);
}); });
}); });
describe("AutoPilot", () => {
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
const explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorer.databaseAccount({
id: "test",
kind: "",
location: "",
name: "",
tags: "",
type: "",
properties: {
enableMultipleWriteLocations: true,
documentEndpoint: "",
cassandraEndpoint: "",
gremlinEndpoint: "",
tableEndpoint: ""
}
});
const offer: DataModels.Offer = {
id: "test",
_etag: "_etag",
_rid: "_rid",
_self: "_self",
_ts: "_ts",
content: {
offerThroughput: 0,
offerIsRUPerMinuteThroughputEnabled: false,
offerAutopilotSettings: {
tier: autoPilotTier
}
}
};
const container: DataModels.Collection = {
_rid: "_rid",
_self: "",
_etag: "",
_ts: 0,
id: "mycoll",
conflictResolutionPolicy: {
mode: DataModels.ConflictResolutionMode.LastWriterWins,
conflictResolutionPath: "/_ts"
}
};
return new Collection(explorer, "mydb", container, quotaInfo, offer);
}
function getSettingsTab(autoPilotTier: DataModels.AutopilotTier = DataModels.AutopilotTier.Tier1): SettingsTab {
return new SettingsTab({
tabKind: ViewModels.CollectionTabKind.Settings,
title: "Scale & Settings",
tabPath: "",
hashLocation: "",
isActive: ko.observable(false),
collection: getCollection(autoPilotTier),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
}
describe("Visible", () => {
it("no autopilot configured, should not be visible", () => {
const settingsTab1 = getSettingsTab(0);
expect(settingsTab1.isAutoPilotSelected()).toBe(false);
const settingsTab2 = getSettingsTab(2);
expect(settingsTab2.isAutoPilotSelected()).toBe(true);
});
});
describe("Autopilot Save", () => {
it("edit with valid new tier, save should be enabled", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier3);
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
});
it("edit with same tier, save should be disabled", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
});
it("edit with invalid tier, save should be disabled", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(5);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
});
});
describe("Autopilot Discard", () => {
it("edit tier, discard should be enabled and correctly dicard", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier3);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
settingsTab.onRevertClick();
expect(settingsTab.selectedAutoPilotTier()).toBe(DataModels.AutopilotTier.Tier2);
settingsTab.selectedAutoPilotTier(0);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
settingsTab.onRevertClick();
expect(settingsTab.selectedAutoPilotTier()).toBe(DataModels.AutopilotTier.Tier2);
});
});
it("On TTL changed", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.timeToLive("on");
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
});
it("On Index Policy changed", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.indexingPolicyContent({ somethingDifferent: "" });
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
});
it("On Conflict Resolution Mode changed", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.conflictResolutionPolicyPath("/somethingElse");
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
settingsTab.onRevertClick();
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom);
settingsTab.conflictResolutionPolicyProcedure("resolver");
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
});
});
}); });

View File

@@ -9,7 +9,6 @@ import * as SharedConstants from "../../Shared/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import DiscardIcon from "../../../images/discard.svg"; import DiscardIcon from "../../../images/discard.svg";
import editable from "../../Common/EditableUtility"; import editable from "../../Common/EditableUtility";
import Q from "q";
import SaveIcon from "../../../images/save-cosmos.svg"; import SaveIcon from "../../../images/save-cosmos.svg";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -24,21 +23,21 @@ import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/update
import { configContext, Platform } from "../../ConfigContext"; import { configContext, Platform } from "../../ConfigContext";
const ttlWarning: string = ` const ttlWarning: string = `
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application. The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
For more information see, <a target="_blank" href="https://aka.ms/cosmos-db-ttl">Time to Live (TTL) in Azure Cosmos DB</a>.`; For more information see, <a target="_blank" href="https://aka.ms/cosmos-db-ttl">Time to Live (TTL) in Azure Cosmos DB</a>.`;
const indexingPolicyTTLWarningMessage: string = ` const indexingPolicyTTLWarningMessage: string = `
Changing the Indexing Policy impacts query results while the index transformation occurs. Changing the Indexing Policy impacts query results while the index transformation occurs.
When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes.
For more information see, <a target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">Modifying Indexing Policies</a>.`; For more information see, <a target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">Modifying Indexing Policies</a>.`;
const updateThroughputBeyondLimitWarningMessage: string = ` const updateThroughputBeyondLimitWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity. You are about to request an increase in throughput beyond the pre-allocated capacity.
The service will scale out and increase throughput for the selected container. The service will scale out and increase throughput for the selected container.
This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`; This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`;
const updateThroughputDelayedApplyWarningMessage: string = ` const updateThroughputDelayedApplyWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity. You are about to request an increase in throughput beyond the pre-allocated capacity.
This operation will take some time to complete.`; This operation will take some time to complete.`;
// TODO: move to a utility classs and add unit tests // TODO: move to a utility classs and add unit tests
@@ -82,7 +81,7 @@ const throughputApplyDelayedMessage = (
collectionName: string, collectionName: string,
requestedThroughput: number requestedThroughput: number
): string => ` ): string => `
The request to increase the throughput has successfully been submitted. The request to increase the throughput has successfully been submitted.
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br /> This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
Database: ${databaseName}, Container: ${collectionName} ${currentThroughput( Database: ${databaseName}, Container: ${collectionName} ${currentThroughput(
isAutoscale, isAutoscale,
@@ -115,7 +114,7 @@ const throughputApplyLongDelayMessage = (
collectionName: string, collectionName: string,
requestedThroughput: number requestedThroughput: number
): string => ` ): string => `
A request to increase the throughput is currently in progress. A request to increase the throughput is currently in progress.
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br /> This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
Database: ${databaseName}, Container: ${collectionName} ${currentThroughput( Database: ${databaseName}, Container: ${collectionName} ${currentThroughput(
isAutoscale, isAutoscale,
@@ -143,9 +142,11 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
public geospatialVisible: ko.Computed<boolean>; public geospatialVisible: ko.Computed<boolean>;
public indexingPolicyContent: ViewModels.Editable<any>; public indexingPolicyContent: ViewModels.Editable<any>;
public isIndexingPolicyEditorInitializing: ko.Observable<boolean>; public isIndexingPolicyEditorInitializing: ko.Observable<boolean>;
public rupm: ViewModels.Editable<string>;
public conflictResolutionPolicyMode: ViewModels.Editable<string>; public conflictResolutionPolicyMode: ViewModels.Editable<string>;
public conflictResolutionPolicyPath: ViewModels.Editable<string>; public conflictResolutionPolicyPath: ViewModels.Editable<string>;
public conflictResolutionPolicyProcedure: ViewModels.Editable<string>; public conflictResolutionPolicyProcedure: ViewModels.Editable<string>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public saveSettingsButton: ViewModels.Button; public saveSettingsButton: ViewModels.Button;
public discardSettingsChangesButton: ViewModels.Button; public discardSettingsChangesButton: ViewModels.Button;
@@ -181,6 +182,9 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
public partitionKeyValue: ko.Observable<string>; public partitionKeyValue: ko.Observable<string>;
public isLargePartitionKeyEnabled: ko.Computed<boolean>; public isLargePartitionKeyEnabled: ko.Computed<boolean>;
public requestUnitsUsageCost: ko.Computed<string>; public requestUnitsUsageCost: ko.Computed<string>;
public rupmOnId: string;
public rupmOffId: string;
public rupmVisible: ko.Computed<boolean>;
public scaleExpanded: ko.Observable<boolean>; public scaleExpanded: ko.Observable<boolean>;
public settingsExpanded: ko.Observable<boolean>; public settingsExpanded: ko.Observable<boolean>;
public shouldDisplayPortalUsePrompt: ko.Computed<boolean>; public shouldDisplayPortalUsePrompt: ko.Computed<boolean>;
@@ -200,6 +204,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
public userCanChangeProvisioningTypes: ko.Observable<boolean>; public userCanChangeProvisioningTypes: ko.Observable<boolean>;
public warningMessage: ko.Computed<string>; public warningMessage: ko.Computed<string>;
public shouldShowKeyspaceSharedThroughputMessage: ko.Computed<boolean>; public shouldShowKeyspaceSharedThroughputMessage: ko.Computed<boolean>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public autoPilotThroughput: ko.Observable<number>; public autoPilotThroughput: ko.Observable<number>;
public autoPilotUsageCost: ko.Computed<string>; public autoPilotUsageCost: ko.Computed<string>;
@@ -225,6 +231,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
super(options); super(options);
this.container = options.collection && options.collection.container; this.container = options.collection && options.collection.container;
this.isIndexingPolicyEditorInitializing = ko.observable<boolean>(false); this.isIndexingPolicyEditorInitializing = ko.observable<boolean>(false);
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
@@ -237,6 +244,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.ttlOnId = `ttlOn${this.tabId}`; this.ttlOnId = `ttlOn${this.tabId}`;
this.changeFeedPolicyOffId = `changeFeedOff${this.tabId}`; this.changeFeedPolicyOffId = `changeFeedOff${this.tabId}`;
this.changeFeedPolicyOnId = `changeFeedOn${this.tabId}`; this.changeFeedPolicyOnId = `changeFeedOn${this.tabId}`;
this.rupmOnId = `rupmOn${this.tabId}`;
this.rupmOffId = `rupmOff${this.tabId}`;
this.conflictResolutionPolicyModeCustom = `conflictResolutionPolicyModeCustom${this.tabId}`; this.conflictResolutionPolicyModeCustom = `conflictResolutionPolicyModeCustom${this.tabId}`;
this.conflictResolutionPolicyModeLWW = `conflictResolutionPolicyModeLWW${this.tabId}`; this.conflictResolutionPolicyModeLWW = `conflictResolutionPolicyModeLWW${this.tabId}`;
this.conflictResolutionPolicyModeCRDT = `conflictResolutionPolicyModeCRDT${this.tabId}`; this.conflictResolutionPolicyModeCRDT = `conflictResolutionPolicyModeCRDT${this.tabId}`;
@@ -268,6 +277,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.analyticalStorageTtlSelection = editable.observable<string>(); this.analyticalStorageTtlSelection = editable.observable<string>();
this.analyticalStorageTtlSeconds = editable.observable<number>(); this.analyticalStorageTtlSeconds = editable.observable<number>();
this.indexingPolicyContent = editable.observable<any>(); this.indexingPolicyContent = editable.observable<any>();
this.rupm = editable.observable<string>();
// Mongo container with system partition key still treat as "Fixed" // Mongo container with system partition key still treat as "Fixed"
this._isFixedContainer = ko.pureComputed( this._isFixedContainer = ko.pureComputed(
() => () =>
@@ -277,17 +287,31 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.isAutoPilotSelected = ko.observable(false); this.isAutoPilotSelected = ko.observable(false);
this._wasAutopilotOriginallySet = ko.observable(false); this._wasAutopilotOriginallySet = ko.observable(false);
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>();
this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
const offer = this.collection && this.collection.offer && this.collection.offer(); const offer = this.collection && this.collection.offer && this.collection.offer();
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings; const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
this.userCanChangeProvisioningTypes = ko.observable(true); this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) { if (!this.hasAutoPilotV2FeatureFlag()) {
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) { if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
this.isAutoPilotSelected(true); if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
this._wasAutopilotOriginallySet(true); this.isAutoPilotSelected(true);
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput); this._wasAutopilotOriginallySet(true);
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
}
}
} else {
if (offerAutopilotSettings && offerAutopilotSettings.tier) {
if (AutoPilotUtils.isValidAutoPilotTier(offerAutopilotSettings.tier)) {
this.isAutoPilotSelected(true);
this._wasAutopilotOriginallySet(true);
this.selectedAutoPilotTier(offerAutopilotSettings.tier);
const availableAutoPilotTiers = AutoPilotUtils.getAvailableAutoPilotTiersOptions(offerAutopilotSettings.tier);
this.autoPilotTiersList(availableAutoPilotTiers);
}
} }
} }
@@ -302,10 +326,16 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
}); });
this.overrideWithAutoPilotSettings = ko.pureComputed(() => { this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
}); });
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => { this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
}); });
@@ -317,18 +347,25 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
if (!originalAutoPilotSettings) { if (!originalAutoPilotSettings) {
return false; return false;
} }
const originalAutoPilotSetting = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; const originalAutoPilotSetting = !this.hasAutoPilotV2FeatureFlag()
if (this.autoPilotThroughput() !== originalAutoPilotSetting) { ? originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput
: originalAutoPilotSettings && originalAutoPilotSettings.tier;
if (
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput() != originalAutoPilotSetting) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier() !== originalAutoPilotSetting)
) {
return true; return true;
} }
return false; return false;
}); });
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this.autoPilotThroughput(); const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, false /* isDatabaseThroughput */);
}); });
this.requestUnitsUsageCost = ko.pureComputed(() => { this.requestUnitsUsageCost = ko.pureComputed(() => {
@@ -339,6 +376,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
const serverId: string = this.container.serverId(); const serverId: string = this.container.serverId();
const offerThroughput: number = this.throughput(); const offerThroughput: number = this.throughput();
const rupmEnabled = this.rupm() === Constants.RUPMStates.on;
const regions = const regions =
(account && (account &&
@@ -356,7 +394,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput, this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput,
serverId, serverId,
regions, regions,
multimaster multimaster,
rupmEnabled
); );
} else { } else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -413,6 +452,32 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
}); });
this.rupmVisible = ko.computed(() => {
if (configContext.platform === Platform.Emulator) {
return false;
}
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
return true;
}
for (let i = 0, len = this.container.databases().length; i < len; i++) {
for (let j = 0, len2 = this.container.databases()[i].collections().length; j < len2; j++) {
const collectionOffer = this.container
.databases()
[i].collections()
[j].offer();
if (
collectionOffer &&
collectionOffer.content &&
collectionOffer.content.offerIsRUPerMinuteThroughputEnabled
) {
return true;
}
}
}
return false;
});
this.ttlVisible = ko.computed(() => { this.ttlVisible = ko.computed(() => {
return (this.container && !this.container.isPreferredApiCassandra()) || false; return (this.container && !this.container.isPreferredApiCassandra()) || false;
}); });
@@ -543,7 +608,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.throughputTitle = ko.pureComputed<string>(() => { this.throughputTitle = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
const minThroughput: string = this.minRUs().toLocaleString(); const minThroughput: string = this.minRUs().toLocaleString();
@@ -624,7 +689,12 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return true; return true;
} else if (this.isAutoPilotSelected()) { } else if (this.isAutoPilotSelected()) {
const validAutopilotChange = const validAutopilotChange =
this._isAutoPilotDirty() && AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput()); (!this.hasAutoPilotV2FeatureFlag() &&
this._isAutoPilotDirty() &&
AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
(this.hasAutoPilotV2FeatureFlag() &&
this._isAutoPilotDirty() &&
AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()));
if (validAutopilotChange) { if (validAutopilotChange) {
return true; return true;
} }
@@ -678,6 +748,14 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return false; return false;
} }
if (
this.rupm() === Constants.RUPMStates.on &&
this.throughput() >
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this.collection.quotaInfo()?.numPartitions
) {
return false;
}
if (this.timeToLive.editableIsDirty()) { if (this.timeToLive.editableIsDirty()) {
return true; return true;
} }
@@ -706,6 +784,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return true; return true;
} }
if (this.rupm.editableIsDirty()) {
return true;
}
return false; return false;
}), }),
@@ -755,6 +837,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return true; return true;
} }
if (this.rupm.editableIsDirty()) {
return true;
}
if ( if (
this.conflictResolutionPolicyMode.editableIsDirty() || this.conflictResolutionPolicyMode.editableIsDirty() ||
this.conflictResolutionPolicyPath.editableIsDirty() || this.conflictResolutionPolicyPath.editableIsDirty() ||
@@ -814,8 +900,14 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
offer.hasOwnProperty("headers") && offer.hasOwnProperty("headers") &&
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending] !!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
) { ) {
if (AutoPilotUtils.isValidV2AutoPilotOffer(offer)) {
return "Tier upgrade will take some time to complete.";
}
const throughput = offer.content.offerAutopilotSettings const throughput = offer.content.offerAutopilotSettings
? offer.content.offerAutopilotSettings.maxThroughput ? !this.hasAutoPilotV2FeatureFlag()
? offer.content.offerAutopilotSettings.maxThroughput
: offer.content.offerAutopilotSettings.maximumTierThroughput
: undefined; : undefined;
const targetThroughput = const targetThroughput =
@@ -834,7 +926,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
} }
if (this.overrideWithProvisionedThroughputSettings()) { if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
return AutoPilotUtils.manualToAutoscaleDisclaimer; return AutoPilotUtils.manualToAutoscaleDisclaimer;
} }
@@ -998,17 +1090,25 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
} }
} }
if (this.throughput.editableIsDirty() || this._isAutoPilotDirty() || this._hasProvisioningTypeChanged()) { if (
this.throughput.editableIsDirty() ||
this.rupm.editableIsDirty() ||
this._isAutoPilotDirty() ||
this._hasProvisioningTypeChanged()
) {
const newThroughput = this.throughput(); const newThroughput = this.throughput();
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer()); let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
const originalThroughputValue: number = this.throughput.getEditableOriginalValue(); const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
if (newOffer.content) { if (newOffer.content) {
newOffer.content.offerThroughput = newThroughput; newOffer.content.offerThroughput = newThroughput;
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
} else { } else {
newOffer = _.extend({}, newOffer, { newOffer = _.extend({}, newOffer, {
content: { content: {
offerThroughput: newThroughput offerThroughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
} }
}); });
} }
@@ -1016,12 +1116,18 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
const headerOptions: RequestOptions = { initialHeaders: {} }; const headerOptions: RequestOptions = { initialHeaders: {} };
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
newOffer.content.offerAutopilotSettings = { if (!this.hasAutoPilotV2FeatureFlag()) {
maxThroughput: this.autoPilotThroughput() newOffer.content.offerAutopilotSettings = {
}; maxThroughput: this.autoPilotThroughput()
};
} else {
newOffer.content.offerAutopilotSettings = {
tier: this.selectedAutoPilotTier()
};
}
// user has changed from provisioned --> autoscale // user has changed from provisioned --> autoscale
if (this._hasProvisioningTypeChanged()) { if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true"; headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
delete newOffer.content.offerAutopilotSettings; delete newOffer.content.offerAutopilotSettings;
} else { } else {
@@ -1029,10 +1135,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
} }
} else { } else {
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.userCanChangeProvisioningTypes(true); this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
// user has changed from autoscale --> provisioned // user has changed from autoscale --> provisioned
if (this._hasProvisioningTypeChanged()) { if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true"; headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
} else { } else {
delete newOffer.content.offerAutopilotSettings; delete newOffer.content.offerAutopilotSettings;
@@ -1050,7 +1156,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseName: this.collection.databaseId, databaseName: this.collection.databaseId,
collectionName: this.collection.id(), collectionName: this.collection.id(),
throughput: newThroughput throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
}; };
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
@@ -1114,7 +1221,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(), tabTitle: this.tabTitle(),
error: error.message error: error
}, },
startKey startKey
); );
@@ -1123,7 +1230,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.isExecuting(false); this.isExecuting(false);
}; };
public onRevertClick = (): Q.Promise<any> => { public onRevertClick = async (): Promise<any> => {
TelemetryProcessor.trace(Action.DiscardSettings, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.DiscardSettings, ActionModifiers.Mark, {
message: "Settings Discarded" message: "Settings Discarded"
}); });
@@ -1133,6 +1240,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.geospatialConfigType.setBaseline(this.geospatialConfigType.getEditableOriginalValue()); this.geospatialConfigType.setBaseline(this.geospatialConfigType.getEditableOriginalValue());
this.analyticalStorageTtlSelection.setBaseline(this.analyticalStorageTtlSelection.getEditableOriginalValue()); this.analyticalStorageTtlSelection.setBaseline(this.analyticalStorageTtlSelection.getEditableOriginalValue());
this.analyticalStorageTtlSeconds.setBaseline(this.analyticalStorageTtlSeconds.getEditableOriginalValue()); this.analyticalStorageTtlSeconds.setBaseline(this.analyticalStorageTtlSeconds.getEditableOriginalValue());
this.rupm.setBaseline(this.rupm.getEditableOriginalValue());
this.changeFeedPolicyToggled.setBaseline(this.changeFeedPolicyToggled.getEditableOriginalValue()); this.changeFeedPolicyToggled.setBaseline(this.changeFeedPolicyToggled.getEditableOriginalValue());
this.conflictResolutionPolicyMode.setBaseline(this.conflictResolutionPolicyMode.getEditableOriginalValue()); this.conflictResolutionPolicyMode.setBaseline(this.conflictResolutionPolicyMode.getEditableOriginalValue());
@@ -1157,24 +1265,25 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings; const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings;
const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; if (!this.hasAutoPilotV2FeatureFlag()) {
this.autoPilotThroughput(originalAutoPilotMaxThroughput); const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
this.autoPilotThroughput(originalAutoPilotMaxThroughput);
} else {
const originalAutoPilotTier = originalAutoPilotSettings && originalAutoPilotSettings.tier;
this.selectedAutoPilotTier(originalAutoPilotTier);
}
} }
return Q();
}; };
public onValidIndexingPolicyEdit(): Q.Promise<any> { public async onValidIndexingPolicyEdit(): Promise<any> {
this.indexingPolicyContent.editableIsValid(true); this.indexingPolicyContent.editableIsValid(true);
return Q();
} }
public onInvalidIndexingPolicyEdit(): Q.Promise<any> { public async onInvalidIndexingPolicyEdit(): Promise<any> {
this.indexingPolicyContent.editableIsValid(false); this.indexingPolicyContent.editableIsValid(false);
return Q();
} }
public onActivate(): Q.Promise<any> { public onActivate(): Promise<any> {
return super.onActivate().then(async () => { return super.onActivate().then(async () => {
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings); this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
const database: ViewModels.Database = this.collection.getDatabase(); const database: ViewModels.Database = this.collection.getDatabase();
@@ -1339,7 +1448,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
} }
private _getThroughputUnit(): string { private _getThroughputUnit(): string {
return "RU/s"; return this.rupm() === Constants.RUPMStates.on ? "RU/m" : "RU/s";
} }
public getUpdatedConflictResolutionPolicy(): DataModels.ConflictResolutionPolicy { public getUpdatedConflictResolutionPolicy(): DataModels.ConflictResolutionPolicy {
@@ -1456,6 +1565,13 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.collection.offer().content && this.collection.offer().content &&
this.collection.offer().content.offerThroughput; this.collection.offer().content.offerThroughput;
const offerIsRUPerMinuteThroughputEnabled =
this.collection &&
this.collection.offer &&
this.collection.offer() &&
this.collection.offer().content &&
this.collection.offer().content.offerIsRUPerMinuteThroughputEnabled;
const changeFeedPolicyToggled: ChangeFeedPolicyToggledState = this.changeFeedPolicyToggled(); const changeFeedPolicyToggled: ChangeFeedPolicyToggledState = this.changeFeedPolicyToggled();
this.changeFeedPolicyToggled.setBaseline(changeFeedPolicyToggled); this.changeFeedPolicyToggled.setBaseline(changeFeedPolicyToggled);
this.throughput.setBaseline(offerThroughput); this.throughput.setBaseline(offerThroughput);
@@ -1475,6 +1591,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionProcedure conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionProcedure
) )
); );
this.rupm.setBaseline(offerIsRUPerMinuteThroughputEnabled ? Constants.RUPMStates.on : Constants.RUPMStates.off);
const indexingPolicyContent = this.collection.indexingPolicy(); const indexingPolicyContent = this.collection.indexingPolicy();
const value: string = JSON.stringify(indexingPolicyContent, null, 4); const value: string = JSON.stringify(indexingPolicyContent, null, 4);
@@ -1495,15 +1612,17 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.GEOMETRY; this.GEOMETRY;
this.geospatialConfigType.setBaseline(geospatialConfigType); this.geospatialConfigType.setBaseline(geospatialConfigType);
const maxThroughput = if (!this.hasAutoPilotV2FeatureFlag()) {
this.collection && const maxThroughput =
this.collection.offer && this.collection &&
this.collection.offer() && this.collection.offer &&
this.collection.offer().content && this.collection.offer() &&
this.collection.offer().content.offerAutopilotSettings && this.collection.offer().content &&
this.collection.offer().content.offerAutopilotSettings.maxThroughput; this.collection.offer().content.offerAutopilotSettings &&
this.collection.offer().content.offerAutopilotSettings.maxThroughput;
this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput);
}
} }
private _createIndexingPolicyEditor() { private _createIndexingPolicyEditor() {

View File

@@ -41,7 +41,7 @@ export default class SettingsTabV2 extends TabsBase {
}); });
} }
public onActivate(): Q.Promise<unknown> { public onActivate(): Promise<unknown> {
this.isExecuting(true); this.isExecuting(true);
this.currentCollection.loadOffer().then( this.currentCollection.loadOffer().then(
() => { () => {

View File

@@ -1,6 +1,5 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as _ from "underscore"; import * as _ from "underscore";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg"; import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -61,13 +60,11 @@ export default class StoredProcedureTab extends ScriptTabBase {
}); });
}; };
public onDiscard = (): Q.Promise<any> => { public onDiscard = async (): Promise<any> => {
this.setBaselines(); this.setBaselines();
const original = this.editorContent.getEditableOriginalValue(); const original = this.editorContent.getEditableOriginalValue();
this.originalSprocBody(original); this.originalSprocBody(original);
this.originalSprocBody.valueHasMutated(); // trigger a re-render of the editor this.originalSprocBody.valueHasMutated(); // trigger a re-render of the editor
return Q();
}; };
public onUpdateClick = (): Promise<any> => { public onUpdateClick = (): Promise<any> => {
@@ -284,8 +281,7 @@ export default class StoredProcedureTab extends ScriptTabBase {
.finally(() => this.isExecuting(false)); .finally(() => this.isExecuting(false));
} }
public onDelete(): Q.Promise<any> { public async onDelete(): Promise<any> {
// TODO // TODO
return Q();
} }
} }

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
@@ -85,7 +84,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
explorer.tabsManager.closeTab(this.tabId, explorer); explorer.tabsManager.closeTab(this.tabId, explorer);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name, databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(), defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
@@ -94,9 +92,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}); });
} }
public onTabClick(): Q.Promise<any> { public async onTabClick(): Promise<any> {
this.getContainer().tabsManager.activateTab(this); this.getContainer().tabsManager.activateTab(this);
return Q();
} }
protected updateSelectedNode(): void { protected updateSelectedNode(): void {
@@ -128,7 +125,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick()); return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
}; };
public onActivate(): Q.Promise<any> { public async onActivate(): Promise<any> {
this.updateSelectedNode(); this.updateSelectedNode();
if (!!this.collection) { if (!!this.collection) {
this.collection.selectedSubnodeKind(this.tabKind); this.collection.selectedSubnodeKind(this.tabKind);
@@ -144,14 +141,12 @@ export default class TabsBase extends WaitsForTemplateViewModel {
this.updateNavbarWithTabsButtons(); this.updateNavbarWithTabsButtons();
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name, databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(), defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(), tabTitle: this.tabTitle(),
tabId: this.tabId tabId: this.tabId
}); });
return Q();
} }
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => { public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
@@ -174,9 +169,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
return true; return true;
}; };
public refresh(): Q.Promise<any> { public async refresh(): Promise<any> {
location.reload(); location.reload();
return Q();
} }
protected getContainer(): Explorer { protected getContainer(): Explorer {

View File

@@ -1,4 +1,3 @@
import Q from "q";
import * as ko from "knockout"; import * as ko from "knockout";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import DocumentId from "./DocumentId"; import DocumentId from "./DocumentId";
@@ -59,13 +58,13 @@ export default class ConflictId {
return; return;
} }
public loadConflict(): Q.Promise<any> { public async loadConflict(): Promise<any> {
const conflictsTab = this.container; const conflictsTab = this.container;
this.container.selectedConflictId(this); this.container.selectedConflictId(this);
if (this.operationType === Constants.ConflictOperationType.Create) { if (this.operationType === Constants.ConflictOperationType.Create) {
this.container.initDocumentEditorForCreate(this, this.content); this.container.initDocumentEditorForCreate(this, this.content);
return Q(); return;
} }
this.container.loadingConflictData(true); this.container.loadingConflictData(true);
@@ -88,10 +87,10 @@ export default class ConflictId {
this.operationType === Constants.ConflictOperationType.Delete this.operationType === Constants.ConflictOperationType.Delete
) { ) {
this.container.initDocumentEditorForNoOp(this); this.container.initDocumentEditorForNoOp(this);
return Q(); return;
} }
return Q.reject(reason); throw reason;
} }
); );
} }

View File

@@ -65,7 +65,7 @@ export default class DocumentId {
return JSON.stringify(partitionKeyValue); return JSON.stringify(partitionKeyValue);
} }
public loadDocument(): Q.Promise<any> { public loadDocument(): Promise<any> {
return this.container.selectDocument(this); return this.container.selectDocument(this);
} }
} }

View File

@@ -5,7 +5,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import DocumentId from "./DocumentId"; import DocumentId from "./DocumentId";
import DocumentsTab from "../Tabs/DocumentsTab"; import DocumentsTab from "../Tabs/DocumentsTab";
import Q from "q";
import QueryTab from "../Tabs/QueryTab"; import QueryTab from "../Tabs/QueryTab";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
@@ -41,9 +40,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
this.isCollectionExpanded = ko.observable<boolean>(true); this.isCollectionExpanded = ko.observable<boolean>(true);
} }
public expandCollection(): Q.Promise<void> { public async expandCollection(): Promise<void> {
if (this.isCollectionExpanded()) { if (this.isCollectionExpanded()) {
return Q(); return;
} }
this.isCollectionExpanded(true); this.isCollectionExpanded(true);
@@ -55,8 +54,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
return Q.resolve();
} }
public collapseCollection() { public collapseCollection() {

View File

@@ -126,6 +126,7 @@ export class OfferPricing {
Standard: { Standard: {
StartingPrice: 24 / hoursInAMonth, // per hour StartingPrice: 24 / hoursInAMonth, // per hour
PricePerRU: 0.00008, PricePerRU: 0.00008,
PricePerRUPM: (10 * 2) / 1000 / hoursInAMonth, // preview price: $2 per 1000 RU/m per month -> 100 RU/s
PricePerGB: 0.25 / hoursInAMonth PricePerGB: 0.25 / hoursInAMonth
} }
}, },
@@ -138,6 +139,7 @@ export class OfferPricing {
Standard: { Standard: {
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.00051, PricePerRU: 0.00051,
PricePerRUPM: (10 * 20) / 1000 / hoursInAMonth, // preview price: 20rmb per 1000 RU/m per month -> 100 RU/s
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth
} }
} }
@@ -154,6 +156,7 @@ export class CollectionCreation {
public static readonly MinRU7PartitionsTo25Partitions: number = 2500; public static readonly MinRU7PartitionsTo25Partitions: number = 2500;
public static readonly MinRUPerPartitionAbove25Partitions: number = 100; public static readonly MinRUPerPartitionAbove25Partitions: number = 100;
public static readonly MaxRUPerPartition: number = 10000; public static readonly MaxRUPerPartition: number = 10000;
public static readonly MaxRUPMPerPartition: number = 5000;
public static readonly MinPartitionedCollectionRUs: number = 2500; public static readonly MinPartitionedCollectionRUs: number = 2500;
public static readonly NumberOfPartitionsInFixedCollection: number = 1; public static readonly NumberOfPartitionsInFixedCollection: number = 1;

View File

@@ -1,13 +1,17 @@
import * as Constants from "./Constants"; import * as Constants from "./Constants";
export function computeRUUsagePrice(serverId: string, requestUnits: number): string { export function computeRUUsagePrice(serverId: string, rupmEnabled: boolean, requestUnits: number): string {
if (serverId === "mooncake") { if (serverId === "mooncake") {
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU; let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU,
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency; rupmCharge = rupmEnabled ? requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRUPM : 0;
return (
calculateEstimateNumber(ruCharge + rupmCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency
);
} }
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU,
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency; rupmCharge = rupmEnabled ? requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRUPM : 0;
return calculateEstimateNumber(ruCharge + rupmCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
} }
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string { export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {

View File

@@ -89,8 +89,7 @@ export enum Action {
ClickResourceTreeNodeContextMenuItem, ClickResourceTreeNodeContextMenuItem,
DiscardSettings, DiscardSettings,
SettingsV2Updated, SettingsV2Updated,
SettingsV2Discarded, SettingsV2Discarded
MongoIndexUpdated
} }
export const ActionModifiers = { export const ActionModifiers = {

View File

@@ -0,0 +1,119 @@
import * as AutoPilotUtils from "./AutoPilotUtils";
import * as Constants from "../Common/Constants";
import { AutopilotTier, Offer } from "../Contracts/DataModels";
describe("AutoPilotUtils", () => {
describe("isAutoPilotOfferUpgradedToV3", () => {
const legacyAutopilotOffer = {
tier: 1,
maximumTierThroughput: 20000,
maxThroughput: 20000
};
const v3AutopilotOffer = {
maximumTierThroughput: 20000,
maxThroughput: 20000
};
const v3AutopilotOfferDuringTransitionPhase = {
tier: 0,
maximumTierThroughput: 20000,
maxThroughput: 20000
};
it("should return false if the offer has a tier level and the tier level >= 1", () => {
expect(AutoPilotUtils.isAutoPilotOfferUpgradedToV3(legacyAutopilotOffer)).toEqual(false);
});
it("should return true if the autopilot offer does not have a tier level", () => {
expect(AutoPilotUtils.isAutoPilotOfferUpgradedToV3(v3AutopilotOffer)).toEqual(true);
});
it("should return true if the autopilot offer has a tier level and the tier level is === 0", () => {
expect(AutoPilotUtils.isAutoPilotOfferUpgradedToV3(v3AutopilotOfferDuringTransitionPhase)).toEqual(true);
});
});
describe("isValidAutoPilotOffer", () => {
function getOffer(): Offer {
const commonOffer: Offer = {
_etag: "_etag",
_rid: "_rid",
_self: "_self",
_ts: "_ts",
id: "id",
content: {
offerThroughput: 0,
offerIsRUPerMinuteThroughputEnabled: false,
offerAutopilotSettings: undefined
}
};
return commonOffer;
}
it("offer with autopilot", () => {
let offer = getOffer();
offer.content.offerAutopilotSettings = {
tier: 1
};
const isValid = AutoPilotUtils.isValidV2AutoPilotOffer(offer);
expect(isValid).toBe(true);
});
it("offer without autopilot", () => {
let offer = getOffer();
const isValid = AutoPilotUtils.isValidV2AutoPilotOffer(offer);
expect(isValid).toBe(false);
});
});
describe("isValidAutoPilotTier", () => {
it("invalid input, should return false", () => {
const isValid1 = AutoPilotUtils.isValidAutoPilotTier(0);
expect(isValid1).toBe(false);
const isValid2 = AutoPilotUtils.isValidAutoPilotTier(5);
expect(isValid2).toBe(false);
const isValid3 = AutoPilotUtils.isValidAutoPilotTier(undefined);
expect(isValid3).toBe(false);
});
it("valid input, should return true", () => {
const isValid1 = AutoPilotUtils.isValidAutoPilotTier(1);
expect(isValid1).toBe(true);
const isValid3 = AutoPilotUtils.isValidAutoPilotTier(AutopilotTier.Tier3);
expect(isValid3).toBe(true);
});
});
describe("getAutoPilotTextWithTier", () => {
it("invalid input, should return undefined", () => {
const text1 = AutoPilotUtils.getAutoPilotTextWithTier(0);
expect(text1).toBe(undefined);
const text2 = AutoPilotUtils.getAutoPilotTextWithTier(undefined);
expect(text2).toBe(undefined);
});
it("valid input, should return coreponding text", () => {
const text1 = AutoPilotUtils.getAutoPilotTextWithTier(1);
expect(text1).toBe(Constants.AutoPilot.tier1Text);
const text4 = AutoPilotUtils.getAutoPilotTextWithTier(AutopilotTier.Tier4);
expect(text4).toBe(Constants.AutoPilot.tier4Text);
});
});
describe("getAvailableAutoPilotTiersOptions", () => {
it("invalid input should return all options", () => {
const option1 = AutoPilotUtils.getAvailableAutoPilotTiersOptions(undefined);
expect(option1.length).toBe(4);
const option2 = AutoPilotUtils.getAvailableAutoPilotTiersOptions(5);
expect(option2.length).toBe(4);
});
it("valid input should return all available options", () => {
const option1 = AutoPilotUtils.getAvailableAutoPilotTiersOptions();
expect(option1.length).toBe(4);
const option2 = AutoPilotUtils.getAvailableAutoPilotTiersOptions(AutopilotTier.Tier3);
expect(option2.length).toBe(4);
});
});
});

View File

@@ -1,5 +1,6 @@
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { AutoPilotOfferSettings, Offer } from "../Contracts/DataModels"; import { AutoPilotOfferSettings, AutopilotTier, Offer } from "../Contracts/DataModels";
import { DropdownOption } from "../Contracts/ViewModels";
export const manualToAutoscaleDisclaimer = `The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. <a href="${Constants.Urls.autoscaleMigration}">Learn more</a>.`; export const manualToAutoscaleDisclaimer = `The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. <a href="${Constants.Urls.autoscaleMigration}">Learn more</a>.`;
@@ -7,6 +8,24 @@ export const minAutoPilotThroughput = 4000;
export const autoPilotIncrementStep = 1000; export const autoPilotIncrementStep = 1000;
const autoPilotTiers: Array<AutopilotTier> = [
AutopilotTier.Tier1,
AutopilotTier.Tier2,
AutopilotTier.Tier3,
AutopilotTier.Tier4
];
const autoPilotTierTextMap = {
[AutopilotTier.Tier1]: Constants.AutoPilot.tier1Text,
[AutopilotTier.Tier2]: Constants.AutoPilot.tier2Text,
[AutopilotTier.Tier3]: Constants.AutoPilot.tier3Text,
[AutopilotTier.Tier4]: Constants.AutoPilot.tier4Text
};
export function isAutoPilotOfferUpgradedToV3(offer: AutoPilotOfferSettings): boolean {
return offer && !offer.tier;
}
export function isValidV3AutoPilotOffer(offer: Offer): boolean { export function isValidV3AutoPilotOffer(offer: Offer): boolean {
const maxThroughput = const maxThroughput =
offer && offer &&
@@ -16,6 +35,36 @@ export function isValidV3AutoPilotOffer(offer: Offer): boolean {
return isValidAutoPilotThroughput(maxThroughput); return isValidAutoPilotThroughput(maxThroughput);
} }
export function isValidV2AutoPilotOffer(offer: Offer): boolean {
const tier =
offer && offer.content && offer.content.offerAutopilotSettings && offer.content.offerAutopilotSettings.tier;
if (!tier) {
return false;
}
return isValidAutoPilotTier(tier);
}
export function isValidAutoPilotTier(tier: number | AutopilotTier): boolean {
if (autoPilotTiers.indexOf(tier) >= 0) {
return true;
}
return false;
}
export function getAutoPilotTextWithTier(tier: AutopilotTier): string {
return !!autoPilotTierTextMap[tier] ? autoPilotTierTextMap[tier] : undefined;
}
export function getAvailableAutoPilotTiersOptions(
tier: AutopilotTier = AutopilotTier.Tier1
): DropdownOption<AutopilotTier>[] {
if (!isValidAutoPilotTier(tier)) {
tier = AutopilotTier.Tier1;
}
return autoPilotTiers.map((t: AutopilotTier) => ({ value: t, text: getAutoPilotTextWithTier(t) }));
}
export function isValidAutoPilotThroughput(maxThroughput: number): boolean { export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
if (!maxThroughput) { if (!maxThroughput) {
return false; return false;
@@ -37,6 +86,9 @@ export function getStorageBasedOnUserInput(throughput: number): number {
return Math.round(throughput && throughput * 0.01); return Math.round(throughput && throughput * 0.01);
} }
export function getAutoPilotHeaderText(): string { export function getAutoPilotHeaderText(isV2Model: boolean): string {
if (isV2Model) {
return "Throughput (Autopilot)";
}
return "Throughput (autoscale)"; return "Throughput (autoscale)";
} }

View File

@@ -25,37 +25,37 @@ describe("PricingUtils Tests", () => {
describe("computeRUUsagePriceHourly()", () => { describe("computeRUUsagePriceHourly()", () => {
it("should return 0 for NaN regions default cloud", () => { it("should return 0 for NaN regions default cloud", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, null, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, null, false);
expect(value).toBe(0); expect(value).toBe(0);
}); });
it("should return 0 for -1 regions", () => { it("should return 0 for -1 regions", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, -1, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, -1, false);
expect(value).toBe(0); expect(value).toBe(0);
}); });
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => { it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, false);
expect(value).toBe(0.00008); expect(value).toBe(0.00008);
}); });
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => { it("should return 0.00051 for Mooncake cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false); const value = PricingUtils.computeRUUsagePriceHourly("mooncake", false, 1, 1, false);
expect(value).toBe(0.00051); expect(value).toBe(0.00051);
}); });
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => { it("should return 0.00016 for default cloud, rupm disabled, 1RU, 2 regions, multimaster disabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, false);
expect(value).toBe(0.00016); expect(value).toBe(0.00016);
}); });
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => { it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster enabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, true);
expect(value).toBe(0.00008); expect(value).toBe(0.00008);
}); });
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => { it("should return 0.00048 for default cloud, rupm disabled, 1RU, 2 region, multimaster enabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, true);
expect(value).toBe(0.00048); expect(value).toBe(0.00048);
}); });
}); });
@@ -150,6 +150,18 @@ describe("PricingUtils Tests", () => {
}); });
}); });
describe("getPricePerRuPm()", () => {
it("should return 0.000027397260273972603 for default clouds", () => {
const value = PricingUtils.getPricePerRuPm("default");
expect(value).toBe(0.000027397260273972603);
});
it("should return 0.00027397260273972606 for mooncake", () => {
const value = PricingUtils.getPricePerRuPm("mooncake");
expect(value).toBe(0.00027397260273972606);
});
});
describe("getRegionMultiplier()", () => { describe("getRegionMultiplier()", () => {
describe("without multimaster", () => { describe("without multimaster", () => {
it("should return 0 for null", () => { it("should return 0 for null", () => {
@@ -242,48 +254,52 @@ describe("PricingUtils Tests", () => {
}); });
describe("getEstimatedSpendHtml()", () => { describe("getEstimatedSpendHtml()", () => {
it("should return 'Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)' for 1RU/s on default cloud, 1 region, with multimaster", () => { it("should return 'Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)' for 1RU/s on default cloud, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
1 /*RU/s*/, 1 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */ true /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)" "Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)"
); );
}); });
it("should return 'Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)' for 1RU/s on mooncake, 1 region, with multimaster", () => { it("should return 'Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)' for 1RU/s on mooncake, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
1 /*RU/s*/, 1 /*RU/s*/,
"mooncake" /* cloud */, "mooncake" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */ true /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)" "Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)"
); );
}); });
it("should return 'Estimated cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)' for 400RU/s on default cloud, 2 region, with multimaster", () => { it("should return 'Estimated cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)' for 400RU/s on default cloud, 2 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
true /* multimaster */ true /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)" "Estimated cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)"
); );
}); });
it("should return 'Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)' for 400RU/s on default cloud, 2 region, without multimaster", () => { it("should return 'Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)' for 400RU/s on default cloud, 2 region, without multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
false /* multimaster */ false /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)" "Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)"
@@ -292,45 +308,49 @@ describe("PricingUtils Tests", () => {
}); });
describe("getEstimatedSpendAcknowledgeString()", () => { describe("getEstimatedSpendAcknowledgeString()", () => {
it("should return 'I acknowledge the estimated $0.0019 daily cost for the throughput above.' for 1RU/s on default cloud, 1 region, with multimaster", () => { it("should return 'I acknowledge the estimated $0.0019 daily cost for the throughput above.' for 1RU/s on default cloud, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
1 /*RU/s*/, 1 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */, true /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated $0.0019 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated $0.0019 daily cost for the throughput above.");
}); });
it("should return 'I acknowledge the estimated ¥0.012 daily cost for the throughput above.' for 1RU/s on mooncake, 1 region, with multimaster", () => { it("should return 'I acknowledge the estimated ¥0.012 daily cost for the throughput above.' for 1RU/s on mooncake, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
1 /*RU/s*/, 1 /*RU/s*/,
"mooncake" /* cloud */, "mooncake" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */, true /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated ¥0.012 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated ¥0.012 daily cost for the throughput above.");
}); });
it("should return 'I acknowledge the estimated $3.07 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, with multimaster", () => { it("should return 'I acknowledge the estimated $3.07 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
true /* multimaster */, true /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above.");
}); });
it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster", () => { it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
false /* multimaster */, false /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above.");

View File

@@ -1,5 +1,6 @@
import * as AutoPilotUtils from "../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
import * as Constants from "../Shared/Constants"; import * as Constants from "../Shared/Constants";
import { AutopilotTier } from "../Contracts/DataModels";
/** /**
* Anything that is not a number should return 0 * Anything that is not a number should return 0
@@ -13,7 +14,10 @@ export function normalizeNumber(number: null | undefined | string | number): num
return Math.floor(Number(number)); return Math.floor(Number(number));
} }
export function getRuToolTipText(): string { export function getRuToolTipText(isV2AutoPilot: boolean): string {
if (isV2AutoPilot) {
return "Provisioned throughput is measured in Request Units per second (RU/s). 1 RU corresponds to the throughput of a read of a 1 KB document.";
}
return `Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage.`; return `Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage.`;
} }
@@ -49,6 +53,7 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
export function computeRUUsagePriceHourly( export function computeRUUsagePriceHourly(
serverId: string, serverId: string,
rupmEnabled: boolean,
requestUnits: number, requestUnits: number,
numberOfRegions: number, numberOfRegions: number,
multimasterEnabled: boolean multimasterEnabled: boolean
@@ -57,10 +62,12 @@ export function computeRUUsagePriceHourly(
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled); const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = getPricePerRu(serverId); const pricePerRu = getPricePerRu(serverId);
const pricePerRuPm = getPricePerRuPm(serverId);
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier; const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
const rupmCharge = rupmEnabled ? requestUnits * pricePerRuPm : 0;
return Number(ruCharge.toFixed(5)); return Number((ruCharge + rupmCharge).toFixed(5));
} }
export function getPriceCurrency(serverId: string): string { export function getPriceCurrency(serverId: string): string {
@@ -146,6 +153,34 @@ export function getPricePerRu(serverId: string): number {
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
} }
export function getPricePerRuPm(serverId: string): number {
if (serverId === "mooncake") {
return Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRUPM;
}
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRUPM;
}
export function getAutoPilotV2SpendHtml(autoPilotTier: AutopilotTier, isDatabaseThroughput: boolean): string {
if (!autoPilotTier) {
return "";
}
const resource: string = isDatabaseThroughput ? "database" : "container";
switch (autoPilotTier) {
case AutopilotTier.Tier1:
return `Your ${resource} throughput will automatically scale between 400 RU/s and 4,000 RU/s based on the workload needs, as long as your storage does not exceed 50GB. If your storage exceeds 50GB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
case AutopilotTier.Tier2:
return `Your ${resource} throughput will automatically scale between 2,000 RU/s and 20,000 RU/s based on the workload needs, as long as your storage does not exceed 200GB. If your storage exceeds 200GB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
case AutopilotTier.Tier3:
return `Your ${resource} throughput will automatically scale between 10,000 RU/s and 100,000 RU/s based on the workload needs, as long as your storage does not exceed 1TB. If your storage exceeds 1TB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
case AutopilotTier.Tier4:
return `Your ${resource} throughput will automatically scale between 50,000 RU/s and 500,000 RU/s based on the workload needs, as long as your storage does not exceed 5TB. If your storage exceeds 5TB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
default:
return `Your ${resource} throughput will automatically scale based on the workload needs.`;
}
}
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string { export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
if (!maxAutoPilotThroughputSet) { if (!maxAutoPilotThroughputSet) {
return ""; return "";
@@ -203,9 +238,10 @@ export function getEstimatedSpendHtml(
throughput: number, throughput: number,
serverId: string, serverId: string,
regions: number, regions: number,
multimaster: boolean multimaster: boolean,
rupmEnabled: boolean
): string { ): string {
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster); const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24; const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
const currency: string = getPriceCurrency(serverId); const currency: string = getPriceCurrency(serverId);
@@ -226,11 +262,12 @@ export function getEstimatedSpendAcknowledgeString(
serverId: string, serverId: string,
regions: number, regions: number,
multimaster: boolean, multimaster: boolean,
rupmEnabled: boolean,
isAutoscale: boolean isAutoscale: boolean
): string { ): string {
const hourlyPrice: number = isAutoscale const hourlyPrice: number = isAutoscale
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster) ? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
: computeRUUsagePriceHourly(serverId, throughput, regions, multimaster); : computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24; const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
const currencySign: string = getCurrencySign(serverId); const currencySign: string = getCurrencySign(serverId);

View File

@@ -1,4 +1,3 @@
import Q from "q";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
@@ -60,39 +59,34 @@ export class QueryUtils {
public static queryPagesUntilContentPresent( public static queryPagesUntilContentPresent(
firstItemIndex: number, firstItemIndex: number,
queryItems: (itemIndex: number) => Q.Promise<ViewModels.QueryResults> queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
): Q.Promise<ViewModels.QueryResults> { ): Promise<ViewModels.QueryResults> {
let roundTrips: number = 0; let roundTrips: number = 0;
let netRequestCharge: number = 0; let netRequestCharge: number = 0;
const doRequest = (itemIndex: number): Q.Promise<ViewModels.QueryResults> => const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> =>
queryItems(itemIndex).then( queryItems(itemIndex).then((results: ViewModels.QueryResults) => {
(results: ViewModels.QueryResults) => { roundTrips = roundTrips + 1;
roundTrips = roundTrips + 1; results.roundTrips = roundTrips;
results.roundTrips = roundTrips; results.requestCharge = Number(results.requestCharge) + netRequestCharge;
results.requestCharge = Number(results.requestCharge) + netRequestCharge; netRequestCharge = Number(results.requestCharge);
netRequestCharge = Number(results.requestCharge); const resultsMetadata: ViewModels.QueryResultsMetadata = {
const resultsMetadata: ViewModels.QueryResultsMetadata = { hasMoreResults: results.hasMoreResults,
hasMoreResults: results.hasMoreResults, itemCount: results.itemCount,
itemCount: results.itemCount, firstItemIndex: results.firstItemIndex,
firstItemIndex: results.firstItemIndex, lastItemIndex: results.lastItemIndex
lastItemIndex: results.lastItemIndex };
}; if (resultsMetadata.itemCount === 0 && resultsMetadata.hasMoreResults) {
if (resultsMetadata.itemCount === 0 && resultsMetadata.hasMoreResults) { return doRequest(resultsMetadata.lastItemIndex);
return doRequest(resultsMetadata.lastItemIndex);
}
return Q.resolve(results);
},
(error: any) => {
return Q.reject(error);
} }
); return results;
});
return doRequest(firstItemIndex); return doRequest(firstItemIndex);
} }
public static queryAllPages( public static queryAllPages(
queryItems: (itemIndex: number) => Q.Promise<ViewModels.QueryResults> queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
): Q.Promise<ViewModels.QueryResults> { ): Promise<ViewModels.QueryResults> {
const queryResults: ViewModels.QueryResults = { const queryResults: ViewModels.QueryResults = {
documents: [], documents: [],
activityId: undefined, activityId: undefined,
@@ -103,25 +97,20 @@ export class QueryUtils {
requestCharge: 0, requestCharge: 0,
roundTrips: 0 roundTrips: 0
}; };
const doRequest = (itemIndex: number): Q.Promise<ViewModels.QueryResults> => const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> =>
queryItems(itemIndex).then( queryItems(itemIndex).then((results: ViewModels.QueryResults) => {
(results: ViewModels.QueryResults) => { const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results;
const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results; queryResults.roundTrips = queryResults.roundTrips + 1;
queryResults.roundTrips = queryResults.roundTrips + 1; queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge);
queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge); queryResults.hasMoreResults = hasMoreResults;
queryResults.hasMoreResults = hasMoreResults; queryResults.itemCount = queryResults.itemCount + itemCount;
queryResults.itemCount = queryResults.itemCount + itemCount; queryResults.lastItemIndex = lastItemIndex;
queryResults.lastItemIndex = lastItemIndex; queryResults.documents = queryResults.documents.concat(documents);
queryResults.documents = queryResults.documents.concat(documents); if (queryResults.hasMoreResults) {
if (queryResults.hasMoreResults) { return doRequest(queryResults.lastItemIndex + 1);
return doRequest(queryResults.lastItemIndex + 1);
}
return Q.resolve(queryResults);
},
(error: any) => {
return Q.reject(error);
} }
); return queryResults;
});
return doRequest(0); return doRequest(0);
} }