Support Save As CSV/JSON/Excel/XML from Notebooks (#6627)

Updated existing serialization code so it actually supports serialization. Still needs work to replace the saveAs function when a QueryProvider doesn't support save as, but want to handle in separate PR.
Removed separate MainThread/ExtHostSerializationProvider code as the DataProtocol code is the right place to put this code
Plumbed support through the gridOutputComponent to use the new serialize method in the serialization provider
Refactored the resultSerializer so majority of code can be shared between both implementations (for example file save dialog -> save -> show file on completion)

* Update to latest SQLToolsService release
This commit is contained in:
Kevin Cunnane
2019-08-09 11:37:53 -07:00
committed by GitHub
parent b589abbea5
commit fb7ece006d
20 changed files with 482 additions and 253 deletions

View File

@@ -29,6 +29,9 @@ import { MimeModel } from 'sql/workbench/parts/notebook/common/models/mimemodel'
import { GridTableState } from 'sql/workbench/parts/query/common/gridPanelState';
import { GridTableBase } from 'sql/workbench/parts/query/browser/gridPanel';
import { getErrorMessage } from 'vs/base/common/errors';
import { ISerializationService, SerializeDataParams } from 'sql/platform/serialization/common/serializationService';
import { SaveResultAction } from 'sql/workbench/parts/query/browser/actions';
import { ResultSerializer, SaveResultsResponse } from 'sql/workbench/parts/query/common/resultSerializer';
@Component({
selector: GridOutputComponent.SELECTOR,
@@ -110,7 +113,8 @@ class DataResourceTable extends GridTableBase<any> {
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService editorService: IEditorService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@ISerializationService private _serializationService: ISerializationService
) {
super(state, createResultSet(source), contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
this._gridDataProvider = this.instantiationService.createInstance(DataResourceDataProvider, source, this.resultSet, documentUri);
@@ -125,7 +129,15 @@ class DataResourceTable extends GridTableBase<any> {
}
protected getContextActions(): IAction[] {
return [];
if (!this._serializationService.hasProvider()) {
return [];
}
return [
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
];
}
public get maximumSize(): number {
@@ -143,7 +155,9 @@ class DataResourceDataProvider implements IGridDataProvider {
@INotificationService private _notificationService: INotificationService,
@IClipboardService private _clipboardService: IClipboardService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService,
@ISerializationService private _serializationService: ISerializationService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
this.transformSource(source);
}
@@ -210,14 +224,67 @@ class DataResourceDataProvider implements IGridDataProvider {
}
get canSerialize(): boolean {
return false;
return this._serializationService.hasProvider();
}
serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable<void> {
throw new Error('Method not implemented.');
let serializer = this._instantiationService.createInstance(ResultSerializer);
return serializer.handleSerialization(this.documentUri, format, (filePath) => this.doSerialize(serializer, filePath, format, selection));
}
private async doSerialize(serializer: ResultSerializer, filePath: string, format: SaveFormat, selection: Slick.Range[]): Promise<SaveResultsResponse> {
// TODO implement selection support
let columns = this.resultSet.columnInfo;
let rowLength = this.rows.length;
let minRow = 0;
let maxRow = this.rows.length;
let singleSelection = selection && selection.length > 0 ? selection[0] : undefined;
if (singleSelection && this.isSelected(singleSelection)) {
rowLength = singleSelection.toRow - singleSelection.fromRow + 1;
minRow = singleSelection.fromRow;
maxRow = singleSelection.toRow + 1;
columns = columns.slice(singleSelection.fromCell, singleSelection.toCell + 1);
}
let getRows: ((index: number, rowCount: number) => azdata.DbCellValue[][]) = (index, rowCount) => {
// Offset for selections by adding the selection startRow to the index
index = index + minRow;
if (rowLength === 0 || index < 0 || index >= maxRow) {
return [];
}
let endIndex = index + rowCount;
if (endIndex > maxRow) {
endIndex = maxRow;
}
let result = this.rows.slice(index, endIndex).map(row => {
if (this.isSelected(singleSelection)) {
return row.slice(singleSelection.fromCell, singleSelection.toCell + 1);
}
return row;
});
return result;
};
let serializeRequestParams: SerializeDataParams = <SerializeDataParams>Object.assign(serializer.getBasicSaveParameters(format), <Partial<SerializeDataParams>>{
saveFormat: format,
columns: columns,
filePath: filePath,
getRowRange: (rowStart, numberOfRows) => getRows(rowStart, numberOfRows),
rowCount: rowLength
});
let result = await this._serializationService.serializeResults(serializeRequestParams);
return result;
}
/**
* Check if a range of cells were selected.
*/
private isSelected(selection: Slick.Range): boolean {
return (selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
}
}
function createResultSet(source: IDataResource): azdata.ResultSetSummary {
let columnInfo: azdata.IDbColumn[] = source.schema.fields.map(field => {
let column = new SimpleDbColumn(field.name);