From b7cb0b55a60ab48be0f2cfddf48a910e80391277 Mon Sep 17 00:00:00 2001 From: vaidankarswapnil Date: Mon, 16 Aug 2021 13:27:30 +0530 Subject: [PATCH] Async and await implemented for loadEntities --- less/TableStyles/queryBuilder.less | 12 +- .../Panes/Tables/AddTableEntityPanel.tsx | 63 ++- .../Panes/Tables/EditTableEntityPanel.tsx | 58 ++- .../Tables/DataTable/DataTableViewModel.ts | 4 + .../DataTable/TableEntityListViewModel.ts | 376 +++++++++++------- .../Tables/QueryBuilder/QueryViewModel.tsx | 4 + src/Explorer/Tabs/QueryTablesTab.tsx | 285 ------------- .../QueryTablesTabComponent.tsx | 189 ++++++--- 8 files changed, 508 insertions(+), 483 deletions(-) delete mode 100644 src/Explorer/Tabs/QueryTablesTab.tsx diff --git a/less/TableStyles/queryBuilder.less b/less/TableStyles/queryBuilder.less index 61f9d6b75..5850e25be 100644 --- a/less/TableStyles/queryBuilder.less +++ b/less/TableStyles/queryBuilder.less @@ -535,11 +535,11 @@ input::-webkit-inner-spin-button { } .query-document-detail-list { - overflow-x: hidden; + // overflow-x: hidden; height: 100%; } .query-table-clause-container { - max-height: 80px; + max-height: 150px; overflow: scroll; overflow-x: hidden; } @@ -569,6 +569,14 @@ input::-webkit-inner-spin-button { } } } + +.noData { + background-color: #e3e2e6; + color: #e3e2e6; + padding-top: 1px; + height: 100%; + width: 100%; +} // .pagination > li > div { // } diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx index 63b9526d4..c303ed3ad 100644 --- a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx @@ -1,8 +1,10 @@ import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; import React, { FunctionComponent, useEffect, useState } from "react"; +import * as _ from "underscore"; import AddPropertyIcon from "../../../../images/Add-property.svg"; import RevertBackIcon from "../../../../images/RevertBack.svg"; +import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; import { TableEntity } from "../../../Common/TableEntity"; import { useSidePanel } from "../../../hooks/useSidePanel"; import { userContext } from "../../../UserContext"; @@ -11,6 +13,7 @@ import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel"; import * as Entities from "../../Tables/Entities"; import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient"; +import * as TableEntityProcessor from "../../Tables/TableEntityProcessor"; import * as Utilities from "../../Tables/Utilities"; import NewQueryTablesTab from "../../Tabs/QueryTablesTab/QueryTablesTab"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; @@ -115,10 +118,62 @@ export const AddTableEntityPanel: FunctionComponent = setIsExecuting(true); const entity: Entities.ITableEntity = entityFromAttributes(entities); - await tableDataClient.createDocument(queryTablesTab.collection, entity); - // await tableEntityListViewModel.addEntityToCache(newEntity); - reloadEntities(); - closeSidePanel(); + const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity); + try { + await tableEntityListViewModel.addEntityToCache(newEntity); + // if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { + // tableEntityListViewModel.redrawTableThrottled(); + reloadEntities(); + setFormError(""); + // } + } catch (error) { + const errorMessage = getErrorMessage(error); + setFormError(errorMessage); + handleError(errorMessage, "AddTableRow"); + throw error; + } finally { + setIsExecuting(false); + } + // try { + // await tableDataClient.createDocument(queryTablesTab.collection, entity); + // reloadEntities(); + // setFormError(""); + // closeSidePanel(); + // } catch (error) { + // const errorMessage = getErrorMessage(error); + // setFormError(errorMessage); + // handleError(errorMessage, "AddTableRow"); + // } finally { + // setIsExecuting(false); + // } + }; + + const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => { + let newHeaders: string[] = []; + const keys = Object.keys(newEntity); + keys && + keys.forEach((key: string) => { + if ( + !_.contains(viewModel.headers, key) && + key !== TableEntityProcessor.keyProperties.attachments && + key !== TableEntityProcessor.keyProperties.etag && + key !== TableEntityProcessor.keyProperties.resourceId && + key !== TableEntityProcessor.keyProperties.self && + (!(userContext.apiType === "Cassandra") || key !== TableConstants.EntityKeyNames.RowKey) + ) { + newHeaders.push(key); + } + }); + + let newHeadersInserted = false; + if (newHeaders.length) { + if (!DataTableUtilities.checkForDefaultHeader(viewModel.headers)) { + newHeaders = viewModel.headers.concat(newHeaders); + } + viewModel.updateHeaders(newHeaders, /* notifyColumnChanges */ true, /* enablePrompt */ false); + newHeadersInserted = true; + } + return newHeadersInserted; }; /* Add new entity row */ diff --git a/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx b/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx index fb94d6433..a144d723c 100644 --- a/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx @@ -4,10 +4,12 @@ import React, { FunctionComponent, useEffect, useState } from "react"; import * as _ from "underscore"; import AddPropertyIcon from "../../../../images/Add-property.svg"; import RevertBackIcon from "../../../../images/RevertBack.svg"; +import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; import { TableEntity } from "../../../Common/TableEntity"; import { useSidePanel } from "../../../hooks/useSidePanel"; import { userContext } from "../../../UserContext"; import * as TableConstants from "../../Tables/Constants"; +import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel"; import * as Entities from "../../Tables/Entities"; import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient"; @@ -213,9 +215,59 @@ export const EditTableEntityPanel: FunctionComponent const entity: Entities.ITableEntity = entityFromAttributes(entities); const newTableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : tableDataClient; const originalDocumentData = userContext.apiType === "Cassandra" ? originalDocument[0] : originalDocument; - await newTableDataClient.updateDocument(queryTablesTab.collection, originalDocumentData, entity); - reloadEntities(); - closeSidePanel(); + // await newTableDataClient.updateDocument(queryTablesTab.collection, originalDocumentData, entity); + + try { + const newEntity: Entities.ITableEntity = await newTableDataClient.updateDocument( + queryTablesTab.collection, + originalDocumentData, + entity + ); + await tableEntityListViewModel.updateCachedEntity(newEntity); + // if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { + // tableEntityListViewModel.redrawTableThrottled(); + reloadEntities(); + closeSidePanel(); + // } + tableEntityListViewModel.selected.removeAll(); + tableEntityListViewModel.selected.push(newEntity); + } catch (error) { + const errorMessage = getErrorMessage(error); + handleError(errorMessage, "EditTableRow"); + throw error; + } finally { + setIsExecuting(false); + } + // reloadEntities(); + // closeSidePanel(); + }; + + const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => { + let newHeaders: string[] = []; + const keys = Object.keys(newEntity); + keys && + keys.forEach((key: string) => { + if ( + !_.contains(viewModel.headers, key) && + key !== TableEntityProcessor.keyProperties.attachments && + key !== TableEntityProcessor.keyProperties.etag && + key !== TableEntityProcessor.keyProperties.resourceId && + key !== TableEntityProcessor.keyProperties.self && + (!(userContext.apiType === "Cassandra") || key !== TableConstants.EntityKeyNames.RowKey) + ) { + newHeaders.push(key); + } + }); + + let newHeadersInserted = false; + if (newHeaders.length) { + if (!DataTableUtilities.checkForDefaultHeader(viewModel.headers)) { + newHeaders = viewModel.headers.concat(newHeaders); + } + viewModel.updateHeaders(newHeaders, /* notifyColumnChanges */ true, /* enablePrompt */ false); + newHeadersInserted = true; + } + return newHeadersInserted; }; // Add new entity row diff --git a/src/Explorer/Tables/DataTable/DataTableViewModel.ts b/src/Explorer/Tables/DataTable/DataTableViewModel.ts index ec013650f..02b00ed35 100644 --- a/src/Explorer/Tables/DataTable/DataTableViewModel.ts +++ b/src/Explorer/Tables/DataTable/DataTableViewModel.ts @@ -170,6 +170,10 @@ abstract class DataTableViewModel { } protected renderPage(startIndex: number, pageSize: number) { + console.log( + "🚀 ~ file: DataTableViewModel.ts ~ line 179 ~ DataTableViewModel ~ renderPage ~ this.cache.data", + this.cache.data + ); var endIndex = pageSize < 0 ? this.cache.length : startIndex + pageSize; var renderData = this.cache.data.slice(startIndex, endIndex); diff --git a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts index 422745b77..b5665a81d 100644 --- a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts +++ b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts @@ -148,6 +148,97 @@ export default class TableEntityListViewModel extends DataTableViewModel { this.headers = newHeaders; } + public async a(): Promise { + const a = await this.b(); + console.log("🚀 ~ file: TableEntityListViewModel.ts ~ line 153 ~ TableEntityListViewModel ~ a ~ a", a); + return a; + } + + public async b(): Promise { + const b = await this.c(); + console.log("🚀 ~ file: TableEntityListViewModel.ts ~ line 157 ~ TableEntityListViewModel ~ b ~ b", b); + return b; + } + + public async c(tableQuery?: Entities.ITableQuery, downloadSize?: number): Promise { + var c: any; + var d: any; + if (this._documentIterator && this.continuationToken) { + // TODO handle Cassandra case + + d = await this._documentIterator.fetchNext(); + let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(d.resources); + let finalEntities: IListTableEntitiesSegmentedResult = { + Results: entities, + ContinuationToken: this._documentIterator.hasMoreResults(), + }; + c = finalEntities; + + // .fetchNext() + // .then((response) => response.resources) + // .then((documents: any[]) => { + // let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents); + // let finalEntities: IListTableEntitiesSegmentedResult = { + // Results: entities, + // ContinuationToken: this._documentIterator.hasMoreResults(), + // }; + // return Promise.resolve(finalEntities); + // }); + } else { + c = await this.queryTablesTab.container.tableDataClient.queryDocuments( + this.queryTablesTab.collection, + this.sqlQuery(), + true + ); + } + + // const c = await this.queryTablesTab.container.tableDataClient.queryDocuments( + // this.queryTablesTab.collection, + // this.sqlQuery(), + // true + // ); + const result = c; + if (result) { + if (!this._documentIterator) { + this._documentIterator = result.iterator; + } + var actualDownloadSize: number = 0; + var entities = result.Results; + actualDownloadSize = entities.length; + + this.continuationToken = this.isCancelled ? null : result.ContinuationToken; + + if (!this.continuationToken) { + this.allDownloaded = true; + } + if (this.isCacheValid(this.tableQuery)) { + // Append to cache. + this.cache.data = this.cache.data.concat(entities.slice(0)); + } else { + // Create cache. + this.cache.data = entities; + console.log( + "🚀 ~ file: TableEntityListViewModel.ts ~ line 797 ~ TableEntityListViewModel ~ .then ~ this.cache.data", + this.cache.data + ); + } + + this.cache.tableQuery = this.tableQuery; + this.cache.serverCallInProgress = false; + + var nextDownloadSize: number = this.downloadSize - actualDownloadSize; + if (nextDownloadSize === 0 && this.tableQuery.top) { + this.allDownloaded = true; + } + if (this.allDownloaded || nextDownloadSize === 0) { + return Promise.resolve(this.cache.data); + } + return this.c(this.tableQuery, nextDownloadSize); + } + console.log("🚀 ~ file: TableEntityListViewModel.ts ~ line 161 ~ TableEntityListViewModel ~ c ~ data", c); + return this.cache.data; + } + /** * This callback function called by datatable to fetch the next page of data and render. * sSource - ajax URL of data source, ignored in our case as we are not using ajax. @@ -155,10 +246,10 @@ export default class TableEntityListViewModel extends DataTableViewModel { * fnCallback - is the render callback with data to render. * oSetting: current settings used for table initialization. */ - public renderNextPageAndupdateCache(sSource?: any, aoData?: any, fnCallback?: any): Promise { - var tablePageSize: number = 100; + + public async renderNextPageAndupdateCache(): Promise { + var tablePageSize: number; var prefetchNeeded = true; - // var columnSortOrder: any; // Threshold(pages) for triggering cache prefetch. // If number remaining pages in cache falls below prefetchThreshold prefetch will be triggered. var prefetchThreshold = 10; @@ -169,12 +260,8 @@ export default class TableEntityListViewModel extends DataTableViewModel { // Check if prefetch needed. if (this.tablePageStartIndex + tablePageSize <= this.cache.length || this.allDownloaded) { prefetchNeeded = false; - // if (columnSortOrder && (!this.cache.sortOrder || !_.isEqual(this.cache.sortOrder, columnSortOrder))) { - // this.sortColumns(columnSortOrder, oSettings); - // } this.tablePageStartIndex = 0; - this.renderPage(this.tablePageStartIndex, tablePageSize); - // this.renderPage(0, 100); + this.renderPage(this.tablePageStartIndex, this.cache.length); if ( !this.allDownloaded && this.tablePageStartIndex > 0 && // This is a case now that we can hit this as we re-construct table when we update column @@ -191,24 +278,17 @@ export default class TableEntityListViewModel extends DataTableViewModel { if (prefetchNeeded) { var downloadSize = tableQuery.top || this.downloadSize; - this.prefetchAndRender( - tableQuery, - this.tablePageStartIndex, - tablePageSize, - downloadSize - // draw, - // oSettings, - // columnSortOrder - ); + return await this.prefetchAndRender(tableQuery, 0, tablePageSize, downloadSize); + } else { + return this.cache.data; } - return undefined; } public addEntityToCache(entity: Entities.ITableEntity): Q.Promise { // Delay the add operation if we are fetching data from server, so as to avoid race condition. if (this.cache.serverCallInProgress) { - return Utilities.delay(this.pollingInterval).then(() => { - return this.updateCachedEntity(entity); + Utilities.delay(this.pollingInterval).then(() => { + this.updateCachedEntity(entity); }); } @@ -224,8 +304,8 @@ export default class TableEntityListViewModel extends DataTableViewModel { public updateCachedEntity(entity: Entities.ITableEntity): Q.Promise { // Delay the add operation if we are fetching data from server, so as to avoid race condition. if (this.cache.serverCallInProgress) { - return Utilities.delay(this.pollingInterval).then(() => { - return this.updateCachedEntity(entity); + Utilities.delay(this.pollingInterval).then(() => { + this.updateCachedEntity(entity); }); } var oldEntityIndex: number = _.findIndex( @@ -245,8 +325,8 @@ export default class TableEntityListViewModel extends DataTableViewModel { // Delay the remove operation if we are fetching data from server, so as to avoid race condition. if (this.cache.serverCallInProgress) { - return Utilities.delay(this.pollingInterval).then(() => { - return this.removeEntitiesFromCache(entities); + Utilities.delay(this.pollingInterval).then(() => { + this.removeEntitiesFromCache(entities); }); } @@ -354,87 +434,89 @@ export default class TableEntityListViewModel extends DataTableViewModel { }); } - private prefetchAndRender( + private async prefetchAndRender( tableQuery: Entities.ITableQuery, tablePageStartIndex: number, tablePageSize: number, downloadSize: number - // draw: number,/ - // oSettings: any, - // columnSortOrder: any - ): void { + ): Promise { console.log("🚀 ~ file: TableEntityListViewModel.ts ~ line 366 ~ TableEntityListViewModel ~ prefetchAndRender"); this.queryErrorMessage(null); if (this.cache.serverCallInProgress) { - return; + return undefined; } - this.prefetchData(tableQuery, downloadSize, /* currentRetry */ 0) - .then((result: IListTableEntitiesSegmentedResult) => { - if (!result) { - return; + try { + const result = await this.prefetchData(tableQuery, downloadSize, /* currentRetry */ 0); + if (!result) { + return undefined; + } + // Cache is assigned using prefetchData + var entities = this.cache.data; + console.log( + "🚀 ~ file: TableEntityListViewModel.ts ~ line 430 ~ TableEntityListViewModel ~ .then ~ entities outside", + entities + ); + if (userContext.apiType === "Cassandra" && DataTableUtilities.checkForDefaultHeader(this.headers)) { + (this.queryTablesTab.container.tableDataClient) + .getTableSchema(this.queryTablesTab.collection) + .then((headers: CassandraTableKey[]) => { + console.log( + "🚀 ~ file: TableEntityListViewModel.ts ~ line 438 ~ TableEntityListViewModel ~ .then ~ headers", + headers + ); + this.updateHeaders( + headers.map((header) => header.property), + true + ); + }); + console.log( + "🚀 ~ file: TableEntityListViewModel.ts ~ line 430 ~ TableEntityListViewModel ~ .then ~ entities inside", + entities + ); + } else { + var selectedHeadersUnion: string[] = DataTableUtilities.getPropertyIntersectionFromTableEntities( + entities, + userContext.apiType === "Cassandra" + ); + var newHeaders: string[] = _.difference(selectedHeadersUnion, this.headers); + if (newHeaders.length > 0) { + // Any new columns found will be added into headers array, which will trigger a re-render of the DataTable. + // So there is no need to call it here. + this.updateHeaders(newHeaders, /* notifyColumnChanges */ true); } + } + this.renderPage(tablePageStartIndex, entities.length); - var entities = this.cache.data; - if (userContext.apiType === "Cassandra" && DataTableUtilities.checkForDefaultHeader(this.headers)) { - (this.queryTablesTab.container.tableDataClient) - .getTableSchema(this.queryTablesTab.collection) - .then((headers: CassandraTableKey[]) => { - this.updateHeaders( - headers.map((header) => header.property), - true - ); - }); - } else { - var selectedHeadersUnion: string[] = DataTableUtilities.getPropertyIntersectionFromTableEntities( - entities, - userContext.apiType === "Cassandra" - ); - var newHeaders: string[] = _.difference(selectedHeadersUnion, this.headers); - if (newHeaders.length > 0) { - // Any new columns found will be added into headers array, which will trigger a re-render of the DataTable. - // So there is no need to call it here. - this.updateHeaders(newHeaders, /* notifyColumnChanges */ true); - } else { - if (columnSortOrder) { - this.sortColumns(columnSortOrder, oSettings); - } - // this.renderPage(renderCallBack, draw, tablePageStartIndex, tablePageSize, oSettings); - } - this.renderPage(0, 100); - } - - if (result.ExceedMaximumRetries) { - var message: string = "We are having trouble getting your data. Please try again."; // localize - } - }) - .catch((error: any) => { - const parsedErrors = parseError(error); - var errors = parsedErrors.map((error) => { - return { - message: error.message, - start: error.location ? error.location.start : undefined, - end: error.location ? error.location.end : undefined, - code: error.code, - severity: error.severity, - }; - }); - this.queryErrorMessage(errors[0].message); - if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) { - TelemetryProcessor.traceFailure( - Action.Tab, - { - databaseName: this.queryTablesTab.collection.databaseId, - collectionName: this.queryTablesTab.collection.id(), - dataExplorerArea: Areas.Tab, - tabTitle: this.queryTablesTab.tabTitle(), - error: error, - }, - this.queryTablesTab.onLoadStartKey - ); - this.queryTablesTab.onLoadStartKey = null; - } - DataTableUtilities.turnOffProgressIndicator(); + return result; + } catch (error) { + const parsedErrors = parseError(error); + var errors = parsedErrors.map((error) => { + return { + message: error.message, + start: error.location ? error.location.start : undefined, + end: error.location ? error.location.end : undefined, + code: error.code, + severity: error.severity, + }; }); + this.queryErrorMessage(errors[0].message); + if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) { + TelemetryProcessor.traceFailure( + Action.Tab, + { + databaseName: this.queryTablesTab.collection.databaseId, + collectionName: this.queryTablesTab.collection.id(), + dataExplorerArea: Areas.Tab, + tabTitle: this.queryTablesTab.tabTitle(), + error: error, + }, + this.queryTablesTab.onLoadStartKey + ); + this.queryTablesTab.onLoadStartKey = null; + } + DataTableUtilities.turnOffProgressIndicator(); + } + // return undefined; } /** @@ -448,52 +530,54 @@ export default class TableEntityListViewModel extends DataTableViewModel { * Note that this also means that we can get less entities than the requested download size in a successful call. * See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx */ - private prefetchData( + + private async prefetchData( tableQuery: Entities.ITableQuery, downloadSize: number, currentRetry: number = 0 - ): Q.Promise { + ): Promise { console.log("🚀 ~ file: TableEntityListViewModel.ts ~ line 456 ~ TableEntityListViewModel ~ prefetchData"); + var entities: any; if (!this.cache.serverCallInProgress) { this.cache.serverCallInProgress = true; this.allDownloaded = false; this.lastPrefetchTime = new Date().getTime(); var time = this.lastPrefetchTime; - var promise: Q.Promise; - if (this._documentIterator && this.continuationToken) { - // TODO handle Cassandra case - - promise = Q(this._documentIterator.fetchNext().then((response) => response.resources)).then( - (documents: any[]) => { - let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents); - let finalEntities: IListTableEntitiesSegmentedResult = { - Results: entities, - ContinuationToken: this._documentIterator.hasMoreResults(), - }; - return Q.resolve(finalEntities); - } - ); - } else if (this.continuationToken && userContext.apiType === "Cassandra") { - promise = Q( - this.queryTablesTab.container.tableDataClient.queryDocuments( + var promise: Promise; + try { + if (this._documentIterator && this.continuationToken) { + // TODO handle Cassandra case + const fetchNext = await this._documentIterator.fetchNext(); + let fetchNextEntities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities( + fetchNext.resources + ); + let finalEntities: IListTableEntitiesSegmentedResult = { + Results: fetchNextEntities, + ContinuationToken: this._documentIterator.hasMoreResults(), + }; + entities = finalEntities; + } else if (this.continuationToken && userContext.apiType === "Cassandra") { + entities = this.queryTablesTab.container.tableDataClient.queryDocuments( this.queryTablesTab.collection, this.cqlQuery(), true, this.continuationToken - ) - ); - } else { - let query = this.sqlQuery(); - if (userContext.apiType === "Cassandra") { - query = this.cqlQuery(); + ); + } else { + let query = this.sqlQuery(); + if (userContext.apiType === "Cassandra") { + query = this.cqlQuery(); + } + entities = await this.queryTablesTab.container.tableDataClient.queryDocuments( + this.queryTablesTab.collection, + query, + true + ); } - promise = Q( - this.queryTablesTab.container.tableDataClient.queryDocuments(this.queryTablesTab.collection, query, true) - ); - } - return promise - .then((result: IListTableEntitiesSegmentedResult) => { + + const result = entities; + if (result) { if (!this._documentIterator) { this._documentIterator = result.iterator; } @@ -503,14 +587,14 @@ export default class TableEntityListViewModel extends DataTableViewModel { // And as another service call is during process, we don't set serverCallInProgress to false here. // Thus, end the prefetch. if (this.lastPrefetchTime !== time) { - return Q.resolve(null); + return Promise.resolve(undefined); } var entities = result.Results; actualDownloadSize = entities.length; // Queries can fetch no results and still return a continuation header. See prefetchAndRender() method. - this.continuationToken = this.isCancelled ? null : result.ContinuationToken; + this.continuationToken = this.isCancelled ? undefined : result.ContinuationToken; if (!this.continuationToken) { this.allDownloaded = true; @@ -519,9 +603,23 @@ export default class TableEntityListViewModel extends DataTableViewModel { if (this.isCacheValid(tableQuery)) { // Append to cache. this.cache.data = this.cache.data.concat(entities.slice(0)); + console.log( + "🚀 ~ file: TableEntityListViewModel.ts ~ line 667 ~ TableEntityListViewModel ~ .then ~ this.cache.data", + this.cache.data + ); + var p = new Promise((resolve) => { + if (this.cache.data) { + resolve(this.cache.data); + } + }); + return p; } else { // Create cache. this.cache.data = entities; + console.log( + "🚀 ~ file: TableEntityListViewModel.ts ~ line 671 ~ TableEntityListViewModel ~ .then ~ this.cache.data", + this.cache.data + ); } this.cache.tableQuery = tableQuery; @@ -532,30 +630,20 @@ export default class TableEntityListViewModel extends DataTableViewModel { this.allDownloaded = true; } - // There are three possible results for a prefetch: - // 1. Continuation token is null or fetched items' size reaches predefined. - // 2. Continuation token is not null and fetched items' size hasn't reach predefined. - // 2.1 Retry times has reached predefined maximum. - // 2.2 Retry times hasn't reached predefined maximum. - // Correspondingly, - // For #1, end prefetch. - // For #2.1, set prefetch exceeds maximum retry number and end prefetch. - // For #2.2, go to next round prefetch. if (this.allDownloaded || nextDownloadSize === 0) { - return Q.resolve(result); + return Promise.resolve(result); } if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) { result.ExceedMaximumRetries = true; - return Q.resolve(result); + return Promise.resolve(result); } return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1); - }) - .catch((error: Error) => { - this.cache.serverCallInProgress = false; - return Q.reject(error); - }); + } + } catch (error) { + this.cache.serverCallInProgress = false; + return Promise.reject(error); + } } - return null; } } diff --git a/src/Explorer/Tables/QueryBuilder/QueryViewModel.tsx b/src/Explorer/Tables/QueryBuilder/QueryViewModel.tsx index 45a169de5..c9481ff8c 100644 --- a/src/Explorer/Tables/QueryBuilder/QueryViewModel.tsx +++ b/src/Explorer/Tables/QueryBuilder/QueryViewModel.tsx @@ -170,6 +170,10 @@ export default class QueryViewModel { this._tableEntityListViewModel.oDataQuery(filter); this._tableEntityListViewModel.sqlQuery(this.setSqlFilter(queryTableRows)); this._tableEntityListViewModel.cqlQuery(filter); + console.log( + "🚀 ~ file: QueryViewModel.tsx ~ line 165 ~ QueryViewModel ~ this._tableEntityListViewModel.sqlQuery()", + this._tableEntityListViewModel.sqlQuery() + ); return userContext.apiType !== "Cassandra" ? this._tableEntityListViewModel.sqlQuery() diff --git a/src/Explorer/Tabs/QueryTablesTab.tsx b/src/Explorer/Tabs/QueryTablesTab.tsx deleted file mode 100644 index fb096e1a9..000000000 --- a/src/Explorer/Tabs/QueryTablesTab.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import * as ko from "knockout"; -import React from "react"; -import AddEntityIcon from "../../../images/AddEntity.svg"; -import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg"; -import EditEntityIcon from "../../../images/Edit-entity.svg"; -import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg"; -import QueryBuilderIcon from "../../../images/Query-Builder.svg"; -import QueryTextIcon from "../../../images/Query-Text.svg"; -import * as ViewModels from "../../Contracts/ViewModels"; -import { useSidePanel } from "../../hooks/useSidePanel"; -import { userContext } from "../../UserContext"; -import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; -import Explorer from "../Explorer"; -import { AddTableEntityPanel } from "../Panes/Tables/AddTableEntityPanel"; -import { EditTableEntityPanel } from "../Panes/Tables/EditTableEntityPanel"; -import TableCommands from "../Tables/DataTable/TableCommands"; -import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel"; -import QueryViewModel from "../Tables/QueryBuilder/QueryViewModel"; -import { CassandraAPIDataClient, TableDataClient } from "../Tables/TableDataClient"; -import template from "./QueryTablesTab.html"; -import TabsBase from "./TabsBase"; - -// Will act as table explorer class -export default class QueryTablesTab extends TabsBase { - public readonly html = template; - public collection: ViewModels.Collection; - public tableEntityListViewModel = ko.observable(); - public queryViewModel = ko.observable(); - public tableCommands: TableCommands; - public tableDataClient: TableDataClient; - - public queryText = ko.observable("PartitionKey eq 'partitionKey1'"); // Start out with an example they can modify - public selectedQueryText = ko.observable("").extend({ notify: "always" }); - - public executeQueryButton: ViewModels.Button; - public addEntityButton: ViewModels.Button; - public editEntityButton: ViewModels.Button; - public deleteEntityButton: ViewModels.Button; - public queryBuilderButton: ViewModels.Button; - public queryTextButton: ViewModels.Button; - public container: Explorer; - - constructor(options: ViewModels.TabOptions) { - super(options); - - this.container = options.collection && options.collection.container; - this.tableCommands = new TableCommands(this.container); - this.tableDataClient = this.container.tableDataClient; - this.tableEntityListViewModel(new TableEntityListViewModel(this.tableCommands, this)); - this.tableEntityListViewModel().queryTablesTab = this; - this.queryViewModel(new QueryViewModel(this)); - const sampleQuerySubscription = this.tableEntityListViewModel().items.subscribe(() => { - if (this.tableEntityListViewModel().items().length > 0 && userContext.apiType === "Tables") { - this.queryViewModel().queryBuilderViewModel().setExample(); - } - sampleQuerySubscription.dispose(); - }); - - this.executeQueryButton = { - enabled: ko.computed(() => { - return true; - }), - - visible: ko.computed(() => { - return true; - }), - }; - - this.queryBuilderButton = { - enabled: ko.computed(() => { - return true; - }), - - visible: ko.computed(() => { - return true; - }), - - isSelected: ko.computed(() => { - return this.queryViewModel() ? this.queryViewModel().isHelperActive() : false; - }), - }; - - this.queryTextButton = { - enabled: ko.computed(() => { - return true; - }), - - visible: ko.computed(() => { - return true; - }), - - isSelected: ko.computed(() => { - return this.queryViewModel() ? this.queryViewModel().isEditorActive() : false; - }), - }; - - this.addEntityButton = { - enabled: ko.computed(() => { - return true; - }), - - visible: ko.computed(() => { - return true; - }), - }; - - this.editEntityButton = { - enabled: ko.computed(() => { - return this.tableCommands.isEnabled( - TableCommands.editEntityCommand, - this.tableEntityListViewModel().selected() - ); - }), - - visible: ko.computed(() => { - return true; - }), - }; - - this.deleteEntityButton = { - enabled: ko.computed(() => { - return this.tableCommands.isEnabled( - TableCommands.deleteEntitiesCommand, - this.tableEntityListViewModel().selected() - ); - }), - - visible: ko.computed(() => { - return true; - }), - }; - - this.buildCommandBarOptions(); - } - - public onAddEntityClick = (): void => { - useSidePanel - .getState() - .openSidePanel( - "Add Table Row", - , - "700px" - ); - }; - - public onEditEntityClick = (): void => { - useSidePanel - .getState() - .openSidePanel( - "Edit Table Entity", - , - "700px" - ); - }; - - public onDeleteEntityClick = (): void => { - this.tableCommands.deleteEntitiesCommand(this.tableEntityListViewModel()); - }; - - public onActivate(): void { - super.onActivate(); - const columns = - !!this.tableEntityListViewModel() && - !!this.tableEntityListViewModel().table && - this.tableEntityListViewModel().table.columns; - if (columns) { - columns.adjust(); - $(window).resize(); - } - } - - protected getTabsButtons(): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = []; - if (this.queryBuilderButton.visible()) { - const label = userContext.apiType === "Cassandra" ? "CQL Query Builder" : "Query Builder"; - buttons.push({ - iconSrc: QueryBuilderIcon, - iconAlt: label, - onCommandClick: () => this.queryViewModel().selectHelper(), - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.queryBuilderButton.enabled(), - isSelected: this.queryBuilderButton.isSelected(), - }); - } - - if (this.queryTextButton.visible()) { - const label = userContext.apiType === "Cassandra" ? "CQL Query Text" : "Query Text"; - buttons.push({ - iconSrc: QueryTextIcon, - iconAlt: label, - onCommandClick: () => this.queryViewModel().selectEditor(), - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.queryTextButton.enabled(), - isSelected: this.queryTextButton.isSelected(), - }); - } - - if (this.executeQueryButton.visible()) { - const label = "Run Query"; - buttons.push({ - iconSrc: ExecuteQueryIcon, - iconAlt: label, - onCommandClick: () => this.queryViewModel().runQuery(), - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.executeQueryButton.enabled(), - }); - } - - if (this.addEntityButton.visible()) { - const label = userContext.apiType === "Cassandra" ? "Add Row" : "Add Entity"; - buttons.push({ - iconSrc: AddEntityIcon, - iconAlt: label, - onCommandClick: this.onAddEntityClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: !this.addEntityButton.enabled(), - }); - } - - if (this.editEntityButton.visible()) { - const label = userContext.apiType === "Cassandra" ? "Edit Row" : "Edit Entity"; - buttons.push({ - iconSrc: EditEntityIcon, - iconAlt: label, - onCommandClick: this.onEditEntityClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: !this.editEntityButton.enabled(), - }); - } - - if (this.deleteEntityButton.visible()) { - const label = userContext.apiType === "Cassandra" ? "Delete Rows" : "Delete Entities"; - buttons.push({ - iconSrc: DeleteEntitiesIcon, - iconAlt: label, - onCommandClick: this.onDeleteEntityClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: !this.deleteEntityButton.enabled(), - }); - } - return buttons; - } - - protected buildCommandBarOptions(): void { - ko.computed(() => - ko.toJSON([ - this.queryBuilderButton.visible, - this.queryBuilderButton.enabled, - this.queryTextButton.visible, - this.queryTextButton.enabled, - this.executeQueryButton.visible, - this.executeQueryButton.enabled, - this.addEntityButton.visible, - this.addEntityButton.enabled, - this.editEntityButton.visible, - this.editEntityButton.enabled, - this.deleteEntityButton.visible, - this.deleteEntityButton.enabled, - ]) - ).subscribe(() => this.updateNavbarWithTabsButtons()); - this.updateNavbarWithTabsButtons(); - } -} diff --git a/src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx b/src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx index 7ddb46530..877095c7b 100644 --- a/src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTablesTab/QueryTablesTabComponent.tsx @@ -63,7 +63,7 @@ export interface Button { isSelected?: boolean; } -const PAGESIZE = 10; +const PAGESIZE = 100; class QueryTablesTabComponent extends Component { public collection: ViewModels.Collection; @@ -100,12 +100,6 @@ class QueryTablesTabComponent extends Component { @@ -294,33 +288,77 @@ class QueryTablesTabComponent extends Component { + public async loadEntities(isInitialLoad: boolean, isRunQuery?: boolean): Promise { const { tableEntityListViewModel, selectedQueryText } = this.state; - // tableEntityListViewModel.renderNextPageAndupdateCache(); let headers: string[] = []; //eslint-disable-next-line let documents: any = {}; - try { - if (userContext.apiType === "Cassandra") { - documents = await this.props.queryTablesTab.container.tableDataClient.queryDocuments( - this.props.queryTablesTab.collection, - selectedQueryText, - true - ); - headers = this.getFormattedHeaders(documents.Results); - this.setupIntialEntities(documents.Results, headers, isInitialLoad); - } else { - const { collection } = this.props; - documents = await this.getDocuments(collection, selectedQueryText); - headers = this.getFormattedHeaders(documents.Results); - this.setupIntialEntities(documents.Results, headers, isInitialLoad); + // const data = await tableEntityListViewModel.a(); + // console.log( + // "🚀 ~ file: QueryTablesTabComponent.tsx ~ line 311 ~ QueryTablesTabComponent ~ loadEntities ~ data", + // data + // ); + + // setTimeout(() => { + // console.log("Items > ", this.state.tableEntityListViewModel.items()); + // }, 10000); + // await tableEntityListViewModel.renderNextPageAndupdateCache(selectedQueryText); + // setTimeout(() => { + // // console.log("Processing..."); + // this.isEntitiesAvailable(isInitialLoad); + // }, 0); + if (!isRunQuery) { + try { + documents = await tableEntityListViewModel.renderNextPageAndupdateCache(); + // setTimeout(() => { + // // console.log("Processing..."); + // this.isEntitiesAvailable(isInitialLoad); + // }, 0); + // const data = await tableEntityListViewModel.a(); + if (userContext.apiType === "Cassandra") { + console.log( + "🚀 ~ file: QueryTablesTabComponent.tsx ~ line 311 ~ QueryTablesTabComponent ~ loadEntities ~ data", + documents.Results + ); + headers = this.getFormattedHeaders(documents.Results); + this.setupIntialEntities(headers, documents.Results, isInitialLoad); + } else { + console.log( + "🚀 ~ file: QueryTablesTabComponent.tsx ~ line 311 ~ QueryTablesTabComponent ~ loadEntities ~ data", + documents + ); + headers = this.getFormattedHeaders(documents); + this.setupIntialEntities(headers, documents, isInitialLoad); + } + // this.isEntitiesAvailable(isInitialLoad, data); + } catch (error) { + this.setState({ + queryErrorMessage: error.responseText, + hasQueryError: true, + isLoading: false, + }); } - this.setState({ - queryErrorMessage: "", - hasQueryError: false, - }); - } catch (error) { - if (error.responseText) { + } else { + try { + if (userContext.apiType === "Cassandra") { + documents = await this.props.queryTablesTab.container.tableDataClient.queryDocuments( + this.props.queryTablesTab.collection, + selectedQueryText, + true + ); + headers = this.getFormattedHeaders(documents.Results); + this.setupIntialEntities(headers, documents.Results, isInitialLoad); + } else { + const { collection } = this.props; + documents = await this.getDocuments(collection, selectedQueryText); + headers = this.getFormattedHeaders(documents.Results); + this.setupIntialEntities(headers, documents.Results, isInitialLoad); + } + this.setState({ + queryErrorMessage: "", + hasQueryError: false, + }); + } catch (error) { this.setState({ queryErrorMessage: error.responseText, hasQueryError: true, @@ -331,17 +369,35 @@ class QueryTablesTabComponent extends Component { + let minWidth: number; + let maxWidth: number; + let documentItems: IDocument[] = []; + let filteredItems: IDocument[] = []; this.columns = []; headers.map((header) => { + switch (header) { + case "PartitionKey": + case "RowKey": + minWidth = 50; + maxWidth = 100; + break; + case "Timestamp": + minWidth = 200; + maxWidth = 200; + break; + default: + minWidth = 30; + maxWidth = 50; + } this.columns.push({ key: header, name: header, - minWidth: 100, - maxWidth: 200, + minWidth: minWidth, + maxWidth: maxWidth, data: "string", fieldName: header, isResizable: true, @@ -350,12 +406,14 @@ class QueryTablesTabComponent extends Component { + // return
{item[header] ? item[header] : " "}
; + // }, }); }); - const documentItems = this.generateDetailsList(entities); - - const filteredItems = documentItems.slice(0, PAGESIZE); + documentItems = this.generateDetailsList(entities); + filteredItems = documentItems.slice(0, PAGESIZE); this.setState( { @@ -369,6 +427,7 @@ class QueryTablesTabComponent extends Component { if (isInitialLoad && headers.length > 0) { @@ -379,6 +438,36 @@ class QueryTablesTabComponent extends Component { + // if (isInitialLoad && headers.length > 0) { + // this.loadFilterExample(); + // this.onItemsSelectionChanged(true); + // } + // } + // ); + // } + private onColumnClick = (ev: React.MouseEvent, column: IColumn): void => { const { columns, items } = this.state; const newColumns: IColumn[] = columns.slice(); @@ -444,6 +533,7 @@ class QueryTablesTabComponent extends Component { + obj = {}; this.columns.map((col) => { if (item[col.name]) { obj = { ...obj, ...{ [col.name]: item[col.name]._ } }; @@ -458,7 +548,7 @@ class QueryTablesTabComponent extends Component { @@ -472,7 +562,8 @@ class QueryTablesTabComponent extends Component this.reloadEntities()} - /> + />, + "700px" ); }; @@ -492,7 +583,8 @@ class QueryTablesTabComponent extends Component this.reloadEntities()} - /> + />, + "700px" ); }; @@ -511,10 +603,12 @@ class QueryTablesTabComponent extends Component { - this.setState({ - isLoading: true, + return this.state.tableEntityListViewModel.removeEntitiesFromCache(entitiesToDelete).then(() => { + this.setState({ + isLoading: true, + }); + this.loadEntities(false, false); }); - this.loadEntities(false); }); } }; @@ -525,7 +619,11 @@ class QueryTablesTabComponent extends Component { - this.loadEntities(false); + console.log( + "🚀 ~ file: QueryTablesTabComponent.tsx ~ line 651 ~ QueryTablesTabComponent ~ runQuery ~ selectedQueryText", + this.state.selectedQueryText + ); + this.loadEntities(false, queryTableRows.length > 0 ? true : false); }, 2000); this.setState({ queryText: this.state.queryViewModel.queryText(), @@ -1005,7 +1103,7 @@ class QueryTablesTabComponent extends Component -
+
{this.state.isLoading && } {this.state.items.length > 0 && !this.state.isLoading && ( false} /> )} {this.state.items.length === 0 && !this.state.isLoading && (