mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 17:22:59 -05:00
Notebooks: re-factor grid streaming (#12937)
* refactor grid streaming (convert to data first) * change convertRowsToHtml method to return value * remove griddataconversioncomplete checks * send row data from STS to gridoutput component * clean up code * send data updates to cell model * serialize cell output at the end of cell execution * remove unused parameters * update output contents instead of output reference * remove unnecessary promise * move azdata changes to proposed * update comment
This commit is contained in:
@@ -12,7 +12,7 @@ import { localize } from 'vs/nls';
|
||||
import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils';
|
||||
import { CellTypes, CellType, NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts';
|
||||
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||
import { ICellModel, IOutputChangedEvent, CellExecutionState, ICellModelOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { ICellModel, IOutputChangedEvent, CellExecutionState, ICellModelOptions, ITableUpdatedEvent } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
@@ -28,6 +28,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { tryMatchCellMagic, extractCellMagicCommandPlusArgs } from 'sql/workbench/services/notebook/browser/utils';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ResultSetSummary } from 'sql/workbench/services/query/common/query';
|
||||
|
||||
let modelId = 0;
|
||||
const ads_execute_command = 'ads_execute_command';
|
||||
@@ -44,6 +45,7 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
private _renderedOutputTextContent: string[] = [];
|
||||
private _isEditMode: boolean;
|
||||
private _onOutputsChanged = new Emitter<IOutputChangedEvent>();
|
||||
private _onTableUpdated = new Emitter<ITableUpdatedEvent>();
|
||||
private _onCellModeChanged = new Emitter<boolean>();
|
||||
private _onExecutionStateChanged = new Emitter<CellExecutionState>();
|
||||
private _isTrusted: boolean;
|
||||
@@ -66,7 +68,6 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
private _showPreview: boolean = true;
|
||||
private _showMarkdown: boolean = false;
|
||||
private _cellSourceChanged: boolean = false;
|
||||
private _gridDataConversionComplete: Promise<void>[] = [];
|
||||
private _defaultToWYSIWYG: boolean;
|
||||
private _isParameter: boolean;
|
||||
private _onParameterStateChanged = new Emitter<boolean>();
|
||||
@@ -113,6 +114,10 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
return this._onOutputsChanged.event;
|
||||
}
|
||||
|
||||
public get onTableUpdated(): Event<ITableUpdatedEvent> {
|
||||
return this._onTableUpdated.event;
|
||||
}
|
||||
|
||||
public get onCellModeChanged(): Event<boolean> {
|
||||
return this._onCellModeChanged.event;
|
||||
}
|
||||
@@ -441,8 +446,6 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
|
||||
public async runCell(notificationService?: INotificationService, connectionManagementService?: IConnectionManagementService): Promise<boolean> {
|
||||
try {
|
||||
// Clear grid data conversion promises from previous execution results
|
||||
this._gridDataConversionComplete = [];
|
||||
if (!this.active && this !== this.notebookModel.activeCell) {
|
||||
this.notebookModel.updateActiveCell(this);
|
||||
this.active = true;
|
||||
@@ -536,6 +539,8 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
} finally {
|
||||
this.disposeFuture();
|
||||
this.fireExecutionStateChanged();
|
||||
// Serialize cell output once the cell is done executing
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellOutputUpdated);
|
||||
this.notifyExecutionComplete();
|
||||
}
|
||||
|
||||
@@ -609,9 +614,7 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
shouldScroll: !!shouldScroll
|
||||
};
|
||||
this._onOutputsChanged.fire(outputEvent);
|
||||
if (this.outputs.length !== 0) {
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellOutputUpdated);
|
||||
} else {
|
||||
if (this.outputs.length === 0) {
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellOutputCleared);
|
||||
}
|
||||
}
|
||||
@@ -626,25 +629,6 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
return this._outputs;
|
||||
}
|
||||
|
||||
public updateOutputData(batchId: number, id: number, data: any) {
|
||||
for (let i = 0; i < this._outputs.length; i++) {
|
||||
if (this._outputs[i].output_type === 'execute_result'
|
||||
&& (<nb.IExecuteResult>this._outputs[i]).batchId === batchId
|
||||
&& (<nb.IExecuteResult>this._outputs[i]).id === id) {
|
||||
(<nb.IExecuteResult>this._outputs[i]).data = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get gridDataConversionComplete(): Promise<void> {
|
||||
return Promise.all(this._gridDataConversionComplete).then();
|
||||
}
|
||||
|
||||
public addGridDataConversionPromise(complete: Promise<void>): void {
|
||||
this._gridDataConversionComplete.push(complete);
|
||||
}
|
||||
|
||||
public get renderedOutputTextContent(): string[] {
|
||||
return this._renderedOutputTextContent;
|
||||
}
|
||||
@@ -665,9 +649,49 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
private handleIOPub(msg: nb.IIOPubMessage): void {
|
||||
let msgType = msg.header.msg_type;
|
||||
let output: nb.ICellOutput;
|
||||
let added = false;
|
||||
switch (msgType) {
|
||||
case 'execute_result':
|
||||
output = msg.content as nb.ICellOutput;
|
||||
output.output_type = msgType;
|
||||
// Check if the table already exists
|
||||
for (let i = 0; i < this._outputs.length; i++) {
|
||||
if (this._outputs[i].output_type === 'execute_result') {
|
||||
let resultSet: ResultSetSummary = this._outputs[i].metadata.resultSet;
|
||||
let newResultSet: ResultSetSummary = output.metadata.resultSet;
|
||||
if (resultSet.batchId === newResultSet.batchId && resultSet.id === newResultSet.id) {
|
||||
// If it does, update output with data resource and html table
|
||||
(<nb.IExecuteResult>this._outputs[i]).data = (<nb.IExecuteResult>output).data;
|
||||
this._outputs[i].metadata = output.metadata;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'execute_result_update':
|
||||
let update = msg.content as nb.IExecuteResultUpdate;
|
||||
// Send update to gridOutput component
|
||||
this._onTableUpdated.fire({
|
||||
resultSet: update.resultSet,
|
||||
rows: update.data
|
||||
});
|
||||
break;
|
||||
case 'display_data':
|
||||
output = msg.content as nb.ICellOutput;
|
||||
output.output_type = msgType;
|
||||
// Display message outputs before grid outputs
|
||||
if (this._outputs.length > 0) {
|
||||
for (let i = 0; i < this._outputs.length; i++) {
|
||||
if (this._outputs[i].output_type === 'execute_result') {
|
||||
this._outputs.splice(i, 0, this.rewriteOutputUrls(output));
|
||||
this.fireOutputsChanged();
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'stream':
|
||||
case 'error':
|
||||
output = msg.content as nb.ICellOutput;
|
||||
@@ -698,25 +722,10 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
// targets.push(model.length - 1);
|
||||
// this._displayIdMap.set(displayId, targets);
|
||||
// }
|
||||
if (output) {
|
||||
if (output && !added) {
|
||||
// deletes transient node in the serialized JSON
|
||||
delete output['transient'];
|
||||
// display message outputs before grid outputs
|
||||
if (output.output_type === 'display_data' && this._outputs.length > 0) {
|
||||
let added = false;
|
||||
for (let i = 0; i < this._outputs.length; i++) {
|
||||
if (this._outputs[i].output_type === 'execute_result') {
|
||||
this._outputs.splice(i, 0, this.rewriteOutputUrls(output));
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
this._outputs.push(this.rewriteOutputUrls(output));
|
||||
}
|
||||
} else {
|
||||
this._outputs.push(this.rewriteOutputUrls(output));
|
||||
}
|
||||
this._outputs.push(this.rewriteOutputUrls(output));
|
||||
// Only scroll on 1st output being added
|
||||
let shouldScroll = this._outputs.length === 1;
|
||||
this.fireOutputsChanged(shouldScroll);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit
|
||||
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import type { FutureInternal } from 'sql/workbench/services/notebook/browser/interfaces';
|
||||
import { ICellValue, ResultSetSummary } from 'sql/workbench/services/query/common/query';
|
||||
|
||||
export interface ICellRange {
|
||||
readonly start: number;
|
||||
@@ -237,10 +238,6 @@ export interface INotebookModel {
|
||||
* Promise indicating when client session is ready to use.
|
||||
*/
|
||||
readonly sessionLoadFinished: Promise<void>;
|
||||
/**
|
||||
* Promise indicating when output grid data is converted to mimeType and html.
|
||||
*/
|
||||
gridDataConversionComplete: Promise<any>;
|
||||
/**
|
||||
* LanguageInfo saved in the notebook
|
||||
*/
|
||||
@@ -449,6 +446,11 @@ export interface IOutputChangedEvent {
|
||||
shouldScroll: boolean;
|
||||
}
|
||||
|
||||
export interface ITableUpdatedEvent {
|
||||
resultSet: ResultSetSummary;
|
||||
rows: ICellValue[][];
|
||||
}
|
||||
|
||||
export interface ICellModel {
|
||||
cellUri: URI;
|
||||
id: string;
|
||||
@@ -464,6 +466,7 @@ export interface ICellModel {
|
||||
readonly outputs: ReadonlyArray<nb.ICellOutput>;
|
||||
renderedOutputTextContent?: string[];
|
||||
readonly onOutputsChanged: Event<IOutputChangedEvent>;
|
||||
readonly onTableUpdated: Event<ITableUpdatedEvent>;
|
||||
readonly onExecutionStateChange: Event<CellExecutionState>;
|
||||
readonly executionState: CellExecutionState;
|
||||
readonly notebookModel: NotebookModel;
|
||||
@@ -491,9 +494,6 @@ export interface ICellModel {
|
||||
readonly onCellMarkdownModeChanged: Event<boolean>;
|
||||
sendChangeToNotebook(change: NotebookChangeType): void;
|
||||
cellSourceChanged: boolean;
|
||||
gridDataConversionComplete: Promise<void>;
|
||||
addGridDataConversionPromise(complete: Promise<void>): void;
|
||||
updateOutputData(batchId: number, id: number, data: any): void;
|
||||
}
|
||||
|
||||
export interface IModelFactory {
|
||||
|
||||
@@ -270,17 +270,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
return this._sessionLoadFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates all result grid output has been converted to mimeType and html.
|
||||
*/
|
||||
public get gridDataConversionComplete(): Promise<any> {
|
||||
let promises = [];
|
||||
for (let cell of this._cells) {
|
||||
promises.push(cell.gridDataConversionComplete);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies when the client session is ready for use
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { nb, IResultMessage } from 'azdata';
|
||||
import { localize } from 'vs/nls';
|
||||
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
|
||||
import { ResultSetSummary, ResultSetSubset, IColumn, BatchSummary } from 'sql/workbench/services/query/common/query';
|
||||
import { ResultSetSummary, IColumn, BatchSummary, ICellValue } from 'sql/workbench/services/query/common/query';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
@@ -368,6 +368,16 @@ class SqlKernel extends Disposable implements nb.IKernel {
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._register(queryRunner.onResultSet(resultSet => {
|
||||
if (this._future) {
|
||||
this._future.handleResultSet(resultSet);
|
||||
}
|
||||
}));
|
||||
this._register(queryRunner.onResultSetUpdate(resultSet => {
|
||||
if (this._future) {
|
||||
this._future.handleResultSetUpdate(resultSet);
|
||||
}
|
||||
}));
|
||||
this._register(queryRunner.onBatchEnd(batch => {
|
||||
if (this._future) {
|
||||
this._future.handleBatchEnd(batch);
|
||||
@@ -403,9 +413,14 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
||||
private doneDeferred = new Deferred<nb.IShellMessage>();
|
||||
private configuredMaxRows: number = MAX_ROWS;
|
||||
private _outputAddedPromises: Promise<void>[] = [];
|
||||
private _querySubsetResultMap: Map<number, ResultSetSubset> = new Map<number, ResultSetSubset>();
|
||||
private _errorOccurred: boolean = false;
|
||||
private _stopOnError: boolean = true;
|
||||
private _lastRowCountMap: Map<string, number> = new Map<string, number>();
|
||||
// Map containing data resource and html table to be saved in notebook
|
||||
private _dataToSaveMap: Map<string, any> = new Map<string, any>();
|
||||
// Map containing row data returned from SQL Tools Service and used for table rendering
|
||||
private _rowsMap: Map<string, any> = new Map<string, any>();
|
||||
|
||||
constructor(
|
||||
private _queryRunner: QueryRunner | undefined,
|
||||
private _executionCount: number | undefined,
|
||||
@@ -463,7 +478,6 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
||||
this.doneHandler.handle(msg);
|
||||
}
|
||||
this.doneDeferred.resolve(msg);
|
||||
this._querySubsetResultMap.clear();
|
||||
}
|
||||
|
||||
sendInputReply(content: nb.IInputReply): void {
|
||||
@@ -496,60 +510,81 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
||||
}
|
||||
}
|
||||
|
||||
public handleBatchEnd(batch: BatchSummary): void {
|
||||
if (this.ioHandler) {
|
||||
this._outputAddedPromises.push(this.processResultSets(batch));
|
||||
public handleResultSet(resultSet: ResultSetSummary | ResultSetSummary[]): void {
|
||||
let resultSets: ResultSetSummary[];
|
||||
if (!Array.isArray(resultSet)) {
|
||||
resultSets = [resultSet];
|
||||
} else {
|
||||
resultSets = resultSet?.splice(0);
|
||||
}
|
||||
for (let set of resultSets) {
|
||||
let key = set.batchId + '-' + set.id;
|
||||
this._lastRowCountMap.set(key, 0);
|
||||
// Convert the headers to data resource and html and send to cell model
|
||||
let data = {
|
||||
'application/vnd.dataresource+json': this.convertHeaderToDataResource(set.columnInfo),
|
||||
'text/html': this.convertHeaderToHtmlTable(set.columnInfo)
|
||||
};
|
||||
this._dataToSaveMap.set(key, data);
|
||||
this._rowsMap.set(key, []);
|
||||
this.sendIOPubMessage(data, set);
|
||||
}
|
||||
}
|
||||
|
||||
private async processResultSets(batch: BatchSummary): Promise<void> {
|
||||
try {
|
||||
let queryRowsPromises: Promise<void>[] = [];
|
||||
for (let resultSet of batch.resultSetSummaries) {
|
||||
let rowCount = resultSet.rowCount > this.configuredMaxRows ? this.configuredMaxRows : resultSet.rowCount;
|
||||
if (rowCount === this.configuredMaxRows) {
|
||||
this.handleMessage(localize('sqlMaxRowsDisplayed', "Displaying Top {0} rows.", rowCount));
|
||||
public handleResultSetUpdate(resultSet: ResultSetSummary | ResultSetSummary[]): void {
|
||||
let resultSets: ResultSetSummary[];
|
||||
if (!Array.isArray(resultSet)) {
|
||||
resultSets = [resultSet];
|
||||
} else {
|
||||
resultSets = resultSet?.splice(0);
|
||||
}
|
||||
for (let set of resultSets) {
|
||||
if (set.rowCount > this.configuredMaxRows) {
|
||||
set.rowCount = this.configuredMaxRows;
|
||||
}
|
||||
let key = set.batchId + '-' + set.id;
|
||||
if (set.rowCount !== this._lastRowCountMap.get(key)) {
|
||||
this._outputAddedPromises.push(this.queryAndConvertData(set, this._lastRowCountMap.get(key)));
|
||||
this._lastRowCountMap.set(key, set.rowCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public handleBatchEnd(batch: BatchSummary): void {
|
||||
if (this.ioHandler) {
|
||||
for (let set of batch.resultSetSummaries) {
|
||||
if (set.rowCount > this.configuredMaxRows) {
|
||||
this.handleMessage(localize('sqlMaxRowsDisplayed', "Displaying Top {0} rows.", this.configuredMaxRows));
|
||||
}
|
||||
queryRowsPromises.push(this.getAllQueryRows(rowCount, resultSet));
|
||||
}
|
||||
// We want to display table in the same order
|
||||
let i = 0;
|
||||
for (let resultSet of batch.resultSetSummaries) {
|
||||
await queryRowsPromises[i];
|
||||
this.sendResultSetAsIOPub(resultSet);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async queryAndConvertData(resultSet: ResultSetSummary, lastRowCount: number): Promise<void> {
|
||||
try {
|
||||
let key = resultSet.batchId + '-' + resultSet.id;
|
||||
// Query for rows and send rows to cell model
|
||||
let queryResult = await this._queryRunner.getQueryRows(lastRowCount, resultSet.rowCount - lastRowCount, resultSet.batchId, resultSet.id);
|
||||
this.sendIOPubUpdateMessage(queryResult.rows, resultSet);
|
||||
let rows = this._rowsMap.get(key);
|
||||
this._rowsMap.set(key, rows.concat(queryResult.rows));
|
||||
|
||||
// Convert rows to data resource and html and send to cell model to be saved
|
||||
let dataResourceRows = this.convertRowsToDataResource(queryResult.rows);
|
||||
let saveData = this._dataToSaveMap.get(key);
|
||||
saveData['application/vnd.dataresource+json'].data = saveData['application/vnd.dataresource+json'].data.concat(dataResourceRows);
|
||||
let htmlRows = this.convertRowsToHtml(queryResult.rows, key);
|
||||
// Last value in array is '</table>' so we want to add row data before that
|
||||
saveData['text/html'].splice(saveData['text/html'].length - 1, 0, ...htmlRows);
|
||||
this._dataToSaveMap.set(key, saveData);
|
||||
this.sendIOPubMessage(saveData, resultSet);
|
||||
} catch (err) {
|
||||
// TODO should we output this somewhere else?
|
||||
this.logService.error(`Error outputting result sets from Notebook query: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getAllQueryRows(rowCount: number, resultSet: ResultSetSummary): Promise<void> {
|
||||
let deferred: Deferred<void> = new Deferred<void>();
|
||||
if (rowCount > 0) {
|
||||
this._queryRunner.getQueryRows(0, rowCount, resultSet.batchId, resultSet.id).then((result) => {
|
||||
this._querySubsetResultMap.set(resultSet.id, result);
|
||||
deferred.resolve();
|
||||
}, (err) => {
|
||||
this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] });
|
||||
deferred.reject(err);
|
||||
});
|
||||
} else {
|
||||
this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] });
|
||||
deferred.resolve();
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
|
||||
private sendResultSetAsIOPub(resultSet: ResultSetSummary): void {
|
||||
if (this._querySubsetResultMap && this._querySubsetResultMap.get(resultSet.id)) {
|
||||
let subsetResult = this._querySubsetResultMap.get(resultSet.id);
|
||||
this.sendIOPubMessage(subsetResult, resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
private sendIOPubMessage(subsetResult: ResultSetSubset, resultSet: ResultSetSummary): void {
|
||||
private sendIOPubMessage(data: any, resultSet: ResultSetSummary): void {
|
||||
let msg: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
@@ -559,18 +594,35 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
||||
},
|
||||
content: <nb.IExecuteResult>{
|
||||
output_type: 'execute_result',
|
||||
metadata: {},
|
||||
metadata: {
|
||||
resultSet: resultSet
|
||||
},
|
||||
execution_count: this._executionCount,
|
||||
data: {
|
||||
'application/vnd.dataresource+json': this.convertToDataResource(resultSet.columnInfo, subsetResult),
|
||||
'text/html': this.convertToHtmlTable(resultSet.columnInfo, subsetResult)
|
||||
}
|
||||
data: data
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
this.ioHandler.handle(msg);
|
||||
}
|
||||
|
||||
private sendIOPubUpdateMessage(rows: any, resultSet: ResultSetSummary): void {
|
||||
let msg: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader>{
|
||||
msg_id: undefined,
|
||||
msg_type: 'execute_result_update'
|
||||
},
|
||||
content: <nb.IExecuteResultUpdate>{
|
||||
output_type: 'execute_result_update',
|
||||
resultSet: resultSet,
|
||||
data: rows
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
this.ioHandler?.handle(msg);
|
||||
this._querySubsetResultMap.delete(resultSet.id);
|
||||
}
|
||||
|
||||
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||
@@ -584,48 +636,53 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
||||
// no-op
|
||||
}
|
||||
|
||||
private convertToDataResource(columns: IColumn[], subsetResult: ResultSetSubset): IDataResource {
|
||||
private convertHeaderToDataResource(columns: IColumn[]): IDataResource {
|
||||
let columnsResources: IDataResourceSchema[] = [];
|
||||
columns.forEach(column => {
|
||||
columnsResources.push({ name: escape(column.columnName) });
|
||||
});
|
||||
let columnsFields: IDataResourceFields = { fields: undefined };
|
||||
columnsFields.fields = columnsResources;
|
||||
let columnsFields: IDataResourceFields = { fields: columnsResources };
|
||||
return {
|
||||
schema: columnsFields,
|
||||
data: subsetResult.rows.map(row => {
|
||||
let rowObject: { [key: string]: any; } = {};
|
||||
row.forEach((val, index) => {
|
||||
rowObject[index] = val.displayValue;
|
||||
});
|
||||
return rowObject;
|
||||
})
|
||||
data: []
|
||||
};
|
||||
}
|
||||
|
||||
private convertToHtmlTable(columns: IColumn[], d: ResultSetSubset): string[] {
|
||||
// Adding 3 for <table>, column title rows, </table>
|
||||
let htmlStringArr: string[] = new Array(d.rowCount + 3);
|
||||
htmlStringArr[0] = '<table>';
|
||||
private convertHeaderToHtmlTable(columns: IColumn[]): string[] {
|
||||
let htmlTable: string[] = new Array(3);
|
||||
htmlTable[0] = '<table>';
|
||||
if (columns.length > 0) {
|
||||
let columnHeaders = '<tr>';
|
||||
for (let column of columns) {
|
||||
columnHeaders += `<th>${escape(column.columnName)}</th>`;
|
||||
}
|
||||
columnHeaders += '</tr>';
|
||||
htmlStringArr[1] = columnHeaders;
|
||||
htmlTable[1] = columnHeaders;
|
||||
}
|
||||
let i = 2;
|
||||
for (const row of d.rows) {
|
||||
htmlTable[2] = '</table>';
|
||||
return htmlTable;
|
||||
}
|
||||
|
||||
private convertRowsToDataResource(rows: ICellValue[][]): any[] {
|
||||
return rows.map(row => {
|
||||
let rowObject: { [key: string]: any; } = {};
|
||||
row.forEach((val, index) => {
|
||||
rowObject[index] = val.displayValue;
|
||||
});
|
||||
return rowObject;
|
||||
});
|
||||
}
|
||||
|
||||
private convertRowsToHtml(rows: ICellValue[][], key: string): string[] {
|
||||
let htmlStringArr = [];
|
||||
for (const row of rows) {
|
||||
let rowData = '<tr>';
|
||||
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
|
||||
for (let columnIndex = 0; columnIndex < row.length; columnIndex++) {
|
||||
rowData += `<td>${escape(row[columnIndex].displayValue)}</td>`;
|
||||
}
|
||||
rowData += '</tr>';
|
||||
htmlStringArr[i] = rowData;
|
||||
i++;
|
||||
htmlStringArr.push(rowData);
|
||||
}
|
||||
htmlStringArr[htmlStringArr.length - 1] = '</table>';
|
||||
return htmlStringArr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user