From f8ede0cc1e5ff522bcb1cab2fa455edcc0fc0863 Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Thu, 28 Jan 2021 10:13:26 -0800 Subject: [PATCH] Remove Q from ViewModels (#390) I got cold feet at the thought of merging #324 in one go, so I'm going to split it into smaller chunks and keep rebasing the large one until there's no more Q. --- src/Contracts/ViewModels.ts | 7 +- .../Settings/SettingsComponent.test.tsx | 5 +- src/Explorer/Tree/Collection.ts | 150 ++++++++---------- src/Explorer/Tree/ResourceTokenCollection.ts | 6 +- src/RouteHandlers/TabRouteHandler.ts | 24 ++- 5 files changed, 77 insertions(+), 115 deletions(-) diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 7e8a16688..3244ad4bb 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -5,7 +5,6 @@ import { TriggerDefinition, UserDefinedFunctionDefinition, } from "@azure/cosmos"; -import Q from "q"; import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent"; import Explorer from "../Explorer/Explorer"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; @@ -109,7 +108,7 @@ export interface CollectionBase extends TreeNode { onDocumentDBDocumentsClick(): void; onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void; - expandCollection(): Q.Promise; + expandCollection(): void; collapseCollection(): void; getDatabase(): Database; } @@ -176,7 +175,7 @@ export interface Collection extends CollectionBase { onDragOver(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void; - uploadFiles(fileList: FileList): Q.Promise; + uploadFiles(fileList: FileList): Promise; getLabel(): string; } @@ -294,7 +293,7 @@ export interface DocumentsTabOptions extends TabOptions { } export interface SettingsTabV2Options extends TabOptions { - getPendingNotification: Q.Promise; + getPendingNotification: Promise; } export interface ConflictsTabOptions extends TabOptions { diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx index e1d71690c..6e439e5b3 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx @@ -31,7 +31,6 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({ })); import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types"; -import Q from "q"; jest.mock("../../../Common/dataAccess/updateOffer", () => ({ updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer), })); @@ -47,9 +46,7 @@ describe("SettingsComponent", () => { hashLocation: "settings", isActive: ko.observable(false), onUpdateTabsButtons: undefined, - getPendingNotification: Q.Promise(() => { - return; - }), + getPendingNotification: Promise.resolve(undefined), }), }; diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 5311e8b3f..b079c5912 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -1,6 +1,5 @@ import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos"; import * as ko from "knockout"; -import Q from "q"; import * as _ from "underscore"; import UploadWorker from "worker-loader!../../workers/upload"; import { AuthType } from "../../AuthType"; @@ -254,9 +253,9 @@ export default class Collection implements ViewModels.Collection { }); } - public expandCollection(): Q.Promise { + public expandCollection(): void { if (this.isCollectionExpanded()) { - return Q(); + return; } this.isCollectionExpanded(true); @@ -268,8 +267,6 @@ export default class Collection implements ViewModels.Collection { defaultExperience: this.container.defaultExperience(), dataExplorerArea: Constants.Areas.ResourceTree, }); - - return Q.resolve(); } public onDocumentDBDocumentsClick() { @@ -547,7 +544,7 @@ export default class Collection implements ViewModels.Collection { }); const tabTitle = !this.offer() ? "Settings" : "Scale & Settings"; - const pendingNotificationsPromise: Q.Promise = this._getPendingThroughputSplitNotification(); + const pendingNotificationsPromise: Promise = this._getPendingThroughputSplitNotification(); const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, (tab) => { return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id(); }); @@ -580,7 +577,7 @@ export default class Collection implements ViewModels.Collection { settingsTabV2: SettingsTabV2, traceStartData: any, settingsTabOptions: ViewModels.TabOptions, - getPendingNotification: Q.Promise + getPendingNotification: Promise ): void => { const settingsTabV2Options: ViewModels.SettingsTabV2Options = { ...settingsTabOptions, @@ -980,19 +977,19 @@ export default class Collection implements ViewModels.Collection { this.container.deleteCollectionConfirmationPane.open(); } - public uploadFiles = (fileList: FileList): Q.Promise => { + public uploadFiles = (fileList: FileList): Promise => { // TODO: right now web worker is not working with AAD flow. Use main thread for upload for now until we have backend upload capability if (configContext.platform === Platform.Hosted && window.authType === AuthType.AAD) { return this._uploadFilesCors(fileList); } const documentUploader: Worker = new UploadWorker(); - const deferred: Q.Deferred = Q.defer(); let inProgressNotificationId: string = ""; if (!fileList || fileList.length === 0) { - return Q.reject("No files specified"); + return Promise.reject("No files specified"); } - documentUploader.onmessage = (event: MessageEvent) => { + + const onmessage = (resolve: (value: UploadDetails) => void, reject: (reason: any) => void, event: MessageEvent) => { const numSuccessful: number = event.data.numUploadsSuccessful; const numFailed: number = event.data.numUploadsFailed; const runtimeError: string = event.data.runtimeError; @@ -1001,31 +998,26 @@ export default class Collection implements ViewModels.Collection { NotificationConsoleUtils.clearInProgressMessageWithId(inProgressNotificationId); documentUploader.terminate(); if (!!runtimeError) { - deferred.reject(runtimeError); + reject(runtimeError); } else if (numSuccessful === 0) { // all uploads failed - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Failed to upload all documents to container ${this.id()}` - ); + NotificationConsoleUtils.logConsoleError(`Failed to upload all documents to container ${this.id()}`); } else if (numFailed > 0) { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, + NotificationConsoleUtils.logConsoleError( `Failed to upload ${numFailed} of ${numSuccessful + numFailed} documents to container ${this.id()}` ); } else { - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Info, + NotificationConsoleUtils.logConsoleInfo( `Successfully uploaded all ${numSuccessful} documents to container ${this.id()}` ); } this._logUploadDetailsInConsole(uploadDetails); - deferred.resolve(uploadDetails); + resolve(uploadDetails); }; - documentUploader.onerror = (event: ErrorEvent): void => { + function onerror(reject: (reason: any) => void, event: ErrorEvent) { documentUploader.terminate(); - deferred.reject(event.error); - }; + reject(event.error); + } const uploaderMessage: StartUploadMessageParams = { files: fileList, @@ -1040,42 +1032,33 @@ export default class Collection implements ViewModels.Collection { }, }; - documentUploader.postMessage(uploaderMessage); - inProgressNotificationId = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - `Uploading and creating documents in container ${this.id()}` - ); + return new Promise((resolve, reject) => { + documentUploader.onmessage = onmessage.bind(null, resolve, reject); + documentUploader.onerror = onerror.bind(null, reject); - return deferred.promise; + documentUploader.postMessage(uploaderMessage); + inProgressNotificationId = NotificationConsoleUtils.logConsoleMessage( + ConsoleDataType.InProgress, + `Uploading and creating documents in container ${this.id()}` + ); + }); }; - private _uploadFilesCors(files: FileList): Q.Promise { - const deferred: Q.Deferred = Q.defer(); - const promises: Array> = []; + private async _uploadFilesCors(files: FileList): Promise { + const data = await Promise.all(Array.from(files).map((file) => this._uploadFile(file))); - for (let i = 0; i < files.length; i++) { - promises.push(this._uploadFile(files[i])); - } - Q.all(promises).then((uploadDetails: Array) => { - deferred.resolve({ data: uploadDetails }); - }); - - return deferred.promise; + return { data }; } - private _uploadFile(file: File): Q.Promise { - const deferred: Q.Deferred = Q.defer(); - + private _uploadFile(file: File): Promise { const reader = new FileReader(); - reader.onload = (evt: any): void => { + const onload = (resolve: (value: UploadDetailsRecord) => void, evt: any): void => { const fileData: string = evt.target.result; - this._createDocumentsFromFile(file.name, fileData).then((record) => { - deferred.resolve(record); - }); + this._createDocumentsFromFile(file.name, fileData).then((record) => resolve(record)); }; - reader.onerror = (evt: ProgressEvent): void => { - deferred.resolve({ + const onerror = (resolve: (value: UploadDetailsRecord) => void, evt: ProgressEvent): void => { + resolve({ fileName: file.name, numSucceeded: 0, numFailed: 1, @@ -1083,9 +1066,11 @@ export default class Collection implements ViewModels.Collection { }); }; - reader.readAsText(file); - - return deferred.promise; + return new Promise((resolve) => { + reader.onload = onload.bind(this, resolve); + reader.onerror = onerror.bind(this, resolve); + reader.readAsText(file); + }); } private async _createDocumentsFromFile(fileName: string, documentContent: string): Promise { @@ -1119,46 +1104,35 @@ export default class Collection implements ViewModels.Collection { } } - private _getPendingThroughputSplitNotification(): Q.Promise { + private async _getPendingThroughputSplitNotification(): Promise { if (!this.container) { - return Q.resolve(undefined); + return undefined; } - const deferred: Q.Deferred = Q.defer(); - fetchPortalNotifications().then( - (notifications: DataModels.Notification[]) => { - if (!notifications || notifications.length === 0) { - deferred.resolve(undefined); - return; - } - - const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => { - const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress"); - return ( - notification.kind === "message" && - notification.collectionName === this.id() && - notification.description && - throughputUpdateRegExp.test(notification.description) - ); - }); - - deferred.resolve(pendingNotification); - }, - (error: any) => { - Logger.logError( - JSON.stringify({ - error: getErrorMessage(error), - accountName: this.container && this.container.databaseAccount(), - databaseName: this.databaseId, - collectionName: this.id(), - }), - "Settings tree node" - ); - deferred.resolve(undefined); + const throughputUpdateRegExp = new RegExp("Throughput update (.*) in progress"); + try { + const notifications = await fetchPortalNotifications(); + if (!notifications) { + return undefined; } - ); - return deferred.promise; + return notifications.find( + ({ kind, collectionName, description = "" }) => + kind === "message" && collectionName === this.id() && throughputUpdateRegExp.test(description) + ); + } catch (error) { + Logger.logError( + JSON.stringify({ + error: getErrorMessage(error), + accountName: this.container && this.container.databaseAccount(), + databaseName: this.databaseId, + collectionName: this.id(), + }), + "Settings tree node" + ); + } + + return undefined; } private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void { diff --git a/src/Explorer/Tree/ResourceTokenCollection.ts b/src/Explorer/Tree/ResourceTokenCollection.ts index f929b3e7d..2d7962d01 100644 --- a/src/Explorer/Tree/ResourceTokenCollection.ts +++ b/src/Explorer/Tree/ResourceTokenCollection.ts @@ -41,9 +41,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas this.isCollectionExpanded = ko.observable(true); } - public expandCollection(): Q.Promise { + public expandCollection(): void { if (this.isCollectionExpanded()) { - return Q(); + return; } this.isCollectionExpanded(true); @@ -55,8 +55,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas defaultExperience: this.container.defaultExperience(), dataExplorerArea: Constants.Areas.ResourceTree, }); - - return Q.resolve(); } public collapseCollection() { diff --git a/src/RouteHandlers/TabRouteHandler.ts b/src/RouteHandlers/TabRouteHandler.ts index 2632d93d3..2c74c53fe 100644 --- a/src/RouteHandlers/TabRouteHandler.ts +++ b/src/RouteHandlers/TabRouteHandler.ts @@ -288,11 +288,9 @@ export class TabRouteHandler { private _openSprocTabForResource(databaseId: string, collectionId: string, sprocId: string): void { this._executeActionHelper(() => { const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId); - collection && - collection.expandCollection().then(() => { - const storedProcedure = collection && collection.findStoredProcedureWithId(sprocId); - storedProcedure && storedProcedure.open(); - }); + collection && collection.expandCollection(); + const storedProcedure = collection && collection.findStoredProcedureWithId(sprocId); + storedProcedure && storedProcedure.open(); }); } @@ -319,11 +317,9 @@ export class TabRouteHandler { private _openTriggerTabForResource(databaseId: string, collectionId: string, triggerId: string): void { this._executeActionHelper(() => { const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId); - collection && - collection.expandCollection().then(() => { - const trigger = collection && collection.findTriggerWithId(triggerId); - trigger && trigger.open(); - }); + collection && collection.expandCollection(); + const trigger = collection && collection.findTriggerWithId(triggerId); + trigger && trigger.open(); }); } @@ -350,11 +346,9 @@ export class TabRouteHandler { private _openUserDefinedFunctionTabForResource(databaseId: string, collectionId: string, udfId: string): void { this._executeActionHelper(() => { const collection: ViewModels.Collection = this._findMatchingCollectionForResource(databaseId, collectionId); - collection && - collection.expandCollection().then(() => { - const userDefinedFunction = collection && collection.findUserDefinedFunctionWithId(udfId); - userDefinedFunction && userDefinedFunction.open(); - }); + collection && collection.expandCollection(); + const userDefinedFunction = collection && collection.findUserDefinedFunctionWithId(udfId); + userDefinedFunction && userDefinedFunction.open(); }); }