Refactor Data Access Utility (#112)

This commit is contained in:
Steve Faulkner
2020-07-24 16:45:48 -05:00
committed by GitHub
parent 6dcdacc8c4
commit 6d142f16f9
25 changed files with 825 additions and 1014 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@ import * as ViewModels from "../Contracts/ViewModels";
import Q from "q";
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { DataAccessUtilityBase } from "./DataAccessUtilityBase";
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { sendMessage } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
@@ -16,15 +16,13 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
// TODO: Log all promise resolutions and errors with verbosity levels
export default class DocumentClientUtilityBase {
constructor(private _dataAccessUtility: DataAccessUtilityBase) {}
public queryDocuments(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
return this._dataAccessUtility.queryDocuments(databaseId, containerId, query, options);
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
}
public queryConflicts(
@@ -33,7 +31,7 @@ export default class DocumentClientUtilityBase {
query: string,
options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
return this._dataAccessUtility.queryConflicts(databaseId, containerId, query, options);
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
}
public getEntityName() {
@@ -54,8 +52,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Querying stored procedures for container ${collection.id()}`
);
this._dataAccessUtility
.readStoredProcedures(collection, options)
DataAccessUtilityBase.readStoredProcedures(collection, options)
.then(
(storedProcedures: DataModels.StoredProcedure[]) => {
deferred.resolve(storedProcedures);
@@ -82,7 +79,7 @@ export default class DocumentClientUtilityBase {
requestedResource: DataModels.Resource,
options?: any
): Q.Promise<DataModels.StoredProcedure> {
return this._dataAccessUtility.readStoredProcedure(collection, requestedResource, options);
return DataAccessUtilityBase.readStoredProcedure(collection, requestedResource, options);
}
public readUserDefinedFunctions(
@@ -94,8 +91,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Querying user defined functions for collection ${collection.id()}`
);
this._dataAccessUtility
.readUserDefinedFunctions(collection, options)
DataAccessUtilityBase.readUserDefinedFunctions(collection, options)
.then(
(userDefinedFunctions: DataModels.UserDefinedFunction[]) => {
deferred.resolve(userDefinedFunctions);
@@ -122,7 +118,7 @@ export default class DocumentClientUtilityBase {
requestedResource: DataModels.Resource,
options: any
): Q.Promise<DataModels.UserDefinedFunction> {
return this._dataAccessUtility.readUserDefinedFunction(collection, requestedResource, options);
return DataAccessUtilityBase.readUserDefinedFunction(collection, requestedResource, options);
}
public readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
@@ -132,8 +128,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Querying triggers for container ${collection.id()}`
);
this._dataAccessUtility
.readTriggers(collection, options)
DataAccessUtilityBase.readTriggers(collection, options)
.then(
(triggers: DataModels.Trigger[]) => {
deferred.resolve(triggers);
@@ -160,7 +155,7 @@ export default class DocumentClientUtilityBase {
requestedResource: DataModels.Resource,
options?: any
): Q.Promise<DataModels.Trigger> {
return this._dataAccessUtility.readTrigger(collection, requestedResource, options);
return DataAccessUtilityBase.readTrigger(collection, requestedResource, options);
}
public executeStoredProcedure(
@@ -175,8 +170,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Executing stored procedure ${storedProcedure.id()}`
);
this._dataAccessUtility
.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
.then(
(response: any) => {
deferred.resolve(response);
@@ -250,8 +244,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Reading ${entityName} ${documentId.id()}`
);
this._dataAccessUtility
.readDocument(collection, documentId)
DataAccessUtilityBase.readDocument(collection, documentId)
.then(
(document: any) => {
deferred.resolve(document);
@@ -283,8 +276,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Updating container ${collection.id()}`
);
this._dataAccessUtility
.updateCollection(databaseId, collection.id(), newCollection)
DataAccessUtilityBase.updateCollection(databaseId, collection.id(), newCollection)
.then(
(replacedCollection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -321,8 +313,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Updating ${entityName} ${documentId.id()}`
);
this._dataAccessUtility
.updateDocument(collection, documentId, newDocument)
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
.then(
(updatedDocument: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -358,8 +349,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Updating offer for resource ${offer.resource}`
);
this._dataAccessUtility
.updateOffer(offer, newOffer, options)
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
.then(
(replacedOffer: DataModels.Offer) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -393,10 +383,7 @@ export default class DocumentClientUtilityBase {
return deferred.promise;
}
public updateOfferThroughputBeyondLimit(
requestPayload: DataModels.UpdateOfferThroughputRequest,
options: any = {}
): Q.Promise<void> {
public updateOfferThroughputBeyondLimit(requestPayload: DataModels.UpdateOfferThroughputRequest): Q.Promise<void> {
const deferred: Q.Deferred<void> = Q.defer<void>();
const resourceDescriptionInfo: string = requestPayload.collectionName
? `database ${requestPayload.databaseName} and container ${requestPayload.collectionName}`
@@ -405,8 +392,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Requesting increase in throughput to ${requestPayload.throughput} for ${resourceDescriptionInfo}`
);
this._dataAccessUtility
.updateOfferThroughputBeyondLimit(requestPayload, options)
DataAccessUtilityBase.updateOfferThroughputBeyondLimit(requestPayload)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
@@ -439,8 +425,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Updating stored procedure ${storedProcedure.id}`
);
this._dataAccessUtility
.updateStoredProcedure(collection, storedProcedure, options)
DataAccessUtilityBase.updateStoredProcedure(collection, storedProcedure, options)
.then(
(updatedStoredProcedure: DataModels.StoredProcedure) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -476,8 +461,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Updating user defined function ${userDefinedFunction.id}`
);
this._dataAccessUtility
.updateUserDefinedFunction(collection, userDefinedFunction, options)
DataAccessUtilityBase.updateUserDefinedFunction(collection, userDefinedFunction, options)
.then(
(updatedUserDefinedFunction: DataModels.UserDefinedFunction) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -506,8 +490,7 @@ export default class DocumentClientUtilityBase {
public updateTrigger(collection: ViewModels.Collection, trigger: DataModels.Trigger): Q.Promise<DataModels.Trigger> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Updating trigger ${trigger.id}`);
this._dataAccessUtility
.updateTrigger(collection, trigger)
DataAccessUtilityBase.updateTrigger(collection, trigger)
.then(
(updatedTrigger: DataModels.Trigger) => {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Updated trigger ${trigger.id}`);
@@ -537,8 +520,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Creating new ${entityName} for container ${collection.id()}`
);
this._dataAccessUtility
.createDocument(collection, newDocument)
DataAccessUtilityBase.createDocument(collection, newDocument)
.then(
(savedDocument: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -574,8 +556,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Creating stored procedure for container ${collection.id()}`
);
this._dataAccessUtility
.createStoredProcedure(collection, newStoredProcedure, options)
DataAccessUtilityBase.createStoredProcedure(collection, newStoredProcedure, options)
.then(
(createdStoredProcedure: DataModels.StoredProcedure) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -611,8 +592,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Creating user defined function for container ${collection.id()}`
);
this._dataAccessUtility
.createUserDefinedFunction(collection, newUserDefinedFunction, options)
DataAccessUtilityBase.createUserDefinedFunction(collection, newUserDefinedFunction, options)
.then(
(createdUserDefinedFunction: DataModels.UserDefinedFunction) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -648,8 +628,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Creating trigger for container ${collection.id()}`
);
this._dataAccessUtility
.createTrigger(collection, newTrigger, options)
DataAccessUtilityBase.createTrigger(collection, newTrigger, options)
.then(
(createdTrigger: DataModels.Trigger) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -682,8 +661,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Deleting ${entityName} ${documentId.id()}`
);
this._dataAccessUtility
.deleteDocument(collection, documentId)
DataAccessUtilityBase.deleteDocument(collection, documentId)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -720,8 +698,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Deleting conflict ${conflictId.id()}`
);
this._dataAccessUtility
.deleteConflict(collection, conflictId, options)
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -754,8 +731,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Deleting container ${collection.id()}`
);
this._dataAccessUtility
.deleteCollection(collection, options)
DataAccessUtilityBase.deleteCollection(collection, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -788,8 +764,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Deleting database ${database.id()}`
);
this._dataAccessUtility
.deleteDatabase(database, options)
DataAccessUtilityBase.deleteDatabase(database, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -826,8 +801,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Deleting stored procedure ${storedProcedure.id}`
);
this._dataAccessUtility
.deleteStoredProcedure(collection, storedProcedure, options)
DataAccessUtilityBase.deleteStoredProcedure(collection, storedProcedure, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -863,8 +837,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Deleting user defined function ${userDefinedFunction.id}`
);
this._dataAccessUtility
.deleteUserDefinedFunction(collection, userDefinedFunction, options)
DataAccessUtilityBase.deleteUserDefinedFunction(collection, userDefinedFunction, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -897,8 +870,7 @@ export default class DocumentClientUtilityBase {
): Q.Promise<DataModels.Trigger> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Deleting trigger ${trigger.id}`);
this._dataAccessUtility
.deleteTrigger(collection, trigger, options)
DataAccessUtilityBase.deleteTrigger(collection, trigger, options)
.then(
(response: any) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -925,11 +897,11 @@ export default class DocumentClientUtilityBase {
}
public refreshCachedResources(options: any = {}): Q.Promise<void> {
return this._dataAccessUtility.refreshCachedResources(options);
return DataAccessUtilityBase.refreshCachedResources(options);
}
public refreshCachedOffers(): Q.Promise<void> {
return this._dataAccessUtility.refreshCachedOffers();
return DataAccessUtilityBase.refreshCachedOffers();
}
public readCollections(database: ViewModels.Database, options: any = {}): Q.Promise<DataModels.Collection[]> {
@@ -938,8 +910,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Querying containers for database ${database.id()}`
);
this._dataAccessUtility
.readCollections(database, options)
DataAccessUtilityBase.readCollections(database, options)
.then(
(collections: DataModels.Collection[]) => {
deferred.resolve(collections);
@@ -968,8 +939,7 @@ export default class DocumentClientUtilityBase {
`Querying container ${collectionId}`
);
this._dataAccessUtility
.readCollection(databaseId, collectionId)
DataAccessUtilityBase.readCollection(databaseId, collectionId)
.then(
(collection: DataModels.Collection) => {
deferred.resolve(collection);
@@ -1001,8 +971,7 @@ export default class DocumentClientUtilityBase {
ConsoleDataType.InProgress,
`Querying quota info for container ${collection.id}`
);
this._dataAccessUtility
.readCollectionQuotaInfo(collection, options)
DataAccessUtilityBase.readCollectionQuotaInfo(collection, options)
.then(
(quota: DataModels.CollectionQuotaInfo) => {
deferred.resolve(quota);
@@ -1028,8 +997,7 @@ export default class DocumentClientUtilityBase {
var deferred = Q.defer<DataModels.Offer[]>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offers");
this._dataAccessUtility
.readOffers(options)
DataAccessUtilityBase.readOffers(options)
.then(
(offers: DataModels.Offer[]) => {
deferred.resolve(offers);
@@ -1055,8 +1023,7 @@ export default class DocumentClientUtilityBase {
var deferred = Q.defer<DataModels.OfferWithHeaders>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying offer");
this._dataAccessUtility
.readOffer(requestedResource, options)
DataAccessUtilityBase.readOffer(requestedResource, options)
.then(
(offer: DataModels.OfferWithHeaders) => {
deferred.resolve(offer);
@@ -1081,8 +1048,7 @@ export default class DocumentClientUtilityBase {
public readDatabases(options: any): Q.Promise<DataModels.Database[]> {
var deferred = Q.defer<any>();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Querying databases");
this._dataAccessUtility
.readDatabases(options)
DataAccessUtilityBase.readDatabases(options)
.then(
(databases: DataModels.Database[]) => {
deferred.resolve(databases);
@@ -1114,8 +1080,7 @@ export default class DocumentClientUtilityBase {
`Creating a new container ${request.collectionId} for database ${request.databaseId}`
);
this._dataAccessUtility
.getOrCreateDatabaseAndCollection(request, options)
DataAccessUtilityBase.getOrCreateDatabaseAndCollection(request, options)
.then(
(collection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -1146,8 +1111,7 @@ export default class DocumentClientUtilityBase {
`Creating a new database ${request.databaseId}`
);
this._dataAccessUtility
.createDatabase(request, options)
DataAccessUtilityBase.createDatabase(request, options)
.then(
(database: DataModels.Database) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -1175,7 +1139,7 @@ export default class DocumentClientUtilityBase {
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
return;
}
MessageHandler.sendMessage({
sendMessage({
type: MessageTypes.ForbiddenError,
reason: error && error.message ? error.message : error
});

View File

@@ -1,46 +1,26 @@
jest.mock("./MessageHandler");
import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { sendMessage } from "./MessageHandler";
describe("Logger", () => {
let sendMessageSpy: jasmine.Spy;
beforeEach(() => {
sendMessageSpy = spyOn(MessageHandler, "sendMessage");
});
afterEach(() => {
sendMessageSpy = null;
jest.resetAllMocks();
});
it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Verbose);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test info");
expect(sendMessage).toBeCalled();
});
it("should log error messages", () => {
Logger.logError("Test error", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Error);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test error");
expect(sendMessage).toBeCalled();
});
it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Warning);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test warning");
expect(sendMessage).toBeCalled();
});
});

View File

@@ -1,4 +1,4 @@
import { MessageHandler } from "./MessageHandler";
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web";
@@ -33,7 +33,7 @@ export function logError(message: string | Error, area: string, code?: number):
}
function _logEntry(entry: Diagnostics.LogEntry): void {
MessageHandler.sendMessage({
sendMessage({
type: MessageTypes.LogInfo,
data: JSON.stringify(entry)
});

View File

@@ -1,65 +1,29 @@
import Q from "q";
import { CachedDataPromise, MessageHandler } from "./MessageHandler";
import * as MessageHandler from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
class MockMessageHandler extends MessageHandler {
public static addToMap(key: string, value: CachedDataPromise<any>): void {
MessageHandler.RequestMap[key] = value;
}
public static mapContainsKey(key: string): boolean {
return MessageHandler.RequestMap[key] != null;
}
public static clearAllEntries(): void {
MessageHandler.RequestMap = {};
}
public static runGarbageCollector(): void {
MessageHandler.runGarbageCollector();
}
}
describe("Message Handler", () => {
beforeEach(() => {
MockMessageHandler.clearAllEntries();
});
xit("should send cached data message", (done: any) => {
const testValidationCallback = (e: MessageEvent) => {
expect(e.data.data).toEqual(
jasmine.objectContaining({ type: MessageTypes.AllDatabases, params: ["some param"] })
);
e.currentTarget.removeEventListener(e.type, testValidationCallback);
done();
};
window.parent.addEventListener("message", testValidationCallback);
MockMessageHandler.sendCachedDataMessage(MessageTypes.AllDatabases, ["some param"]);
});
it("should handle cached message", () => {
let mockPromise: CachedDataPromise<any> = {
it("should handle cached message", async () => {
let mockPromise = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
};
let mockMessage = { message: { id: "123", data: "{}" } };
MockMessageHandler.addToMap(mockPromise.id, mockPromise);
MockMessageHandler.handleCachedDataMessage(mockMessage);
MessageHandler.RequestMap[mockPromise.id] = mockPromise;
MessageHandler.handleCachedDataMessage(mockMessage);
expect(mockPromise.deferred.promise.isFulfilled()).toBe(true);
});
it("should delete fulfilled promises on running the garbage collector", () => {
let mockPromise: CachedDataPromise<any> = {
it("should delete fulfilled promises on running the garbage collector", async () => {
let message = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
};
MockMessageHandler.addToMap(mockPromise.id, mockPromise);
mockPromise.deferred.reject("some error");
MockMessageHandler.runGarbageCollector();
expect(MockMessageHandler.mapContainsKey(mockPromise.id)).toBe(false);
MessageHandler.handleCachedDataMessage(message);
MessageHandler.runGarbageCollector();
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
});
});

View File

@@ -9,77 +9,65 @@ export interface CachedDataPromise<T> {
id: string;
}
/**
* For some reason, typescript emits a Map() in the compiled js output(despite the target being set to ES5) forcing us to define our own polyfill,
* so we define our own custom implementation of the ES6 Map to work around it.
*/
type Map = { [key: string]: CachedDataPromise<any> };
export const RequestMap: Record<string, CachedDataPromise<any>> = {};
export class MessageHandler {
protected static RequestMap: Map = {};
public static handleCachedDataMessage(message: any): void {
const messageContent = message && message.message;
if (
message == null ||
messageContent == null ||
messageContent.id == null ||
!MessageHandler.RequestMap[messageContent.id]
) {
return;
}
const cachedDataPromise = MessageHandler.RequestMap[messageContent.id];
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
}
MessageHandler.runGarbageCollector();
export function handleCachedDataMessage(message: any): void {
const messageContent = message && message.message;
if (message == null || messageContent == null || messageContent.id == null || !RequestMap[messageContent.id]) {
return;
}
public static sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes,
params: Object[],
timeoutInMs?: number
): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(),
id: _.uniqueId()
};
MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
const cachedDataPromise = RequestMap[messageContent.id];
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
}
runGarbageCollector();
}
//TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout(
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
"Timed out while waiting for response from portal"
export function sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes,
params: Object[],
timeoutInMs?: number
): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(),
id: _.uniqueId()
};
RequestMap[cachedDataPromise.id] = cachedDataPromise;
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
//TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout(
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
"Timed out while waiting for response from portal"
);
}
export function sendMessage(data: any): void {
if (canSendMessage()) {
window.parent.postMessage(
{
signature: "pcIframe",
data: data
},
window.document.referrer
);
}
public static sendMessage(data: any): void {
if (MessageHandler.canSendMessage()) {
window.parent.postMessage(
{
signature: "pcIframe",
data: data
},
window.document.referrer
);
}
}
public static canSendMessage(): boolean {
return window.parent !== window;
}
protected static runGarbageCollector() {
Object.keys(MessageHandler.RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) {
delete MessageHandler.RequestMap[key];
}
});
}
}
export function canSendMessage(): boolean {
return window.parent !== window;
}
// TODO: This is exported just for testing. It should not be.
export function runGarbageCollector() {
Object.keys(RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) {
delete RequestMap[key];
}
});
}

View File

@@ -12,7 +12,7 @@ import { config } from "../Config";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import { CosmosClient } from "./CosmosClient";
import { MessageHandler } from "./MessageHandler";
import { sendMessage } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
@@ -408,7 +408,7 @@ async function errorHandling(response: Response, action: string, params: unknown
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
);
if (response.status === HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return;
}
throw new Error(errorMessage);