Share Notebook grid rendering with Query editor (#6241)

This is a staged refactor to use the exact same grid logic in the Notebook and query editors, including context menu support, font settings, and sizing logic. The goal long term is:
- As the core Query grid is updated, Notebook can benefit from the changes
- As we add in support for contributions like new buttons & actions working on the grid, can share the logic
- Ideally if and when we refactor things like the action bar for grid results, we can apply in both places though this is TBD.

Fixes a number of issues:
- Fixes #5755 Grids don't respond to font settings. @anthonydresser can we remove setting from each query results editor and just use Notebook Styles since these are global (not scoped) settings?
- Fixes #5501 Copy from grid settings. 
- Fixes #4938 SQL Notebook result sets are missing the actions provide for SQL File results sets. this now has the core ability to solve this, and separate work items for specific asks (serialization, charting) are tracked.

Currently hidden:
- Save as... options in context menu
- All right toolbar actions (save as, chart).

Remaining issues to address in future commits:
- Need to implement support for serialization (#5137). 
- Need to add charting support
- Need to solve the layout of buttons on the right hand side when a small number of columns are output. It doesn't look right that buttons are so far away from the results
  - Will work with UX on this. For now, mitigating this by hiding all buttons, but will need to solve in the future
- Would like to make buttons contributable via extension, but need to refactor similar to ObjectExplorer context menu so that we can serialize context menu options across to extension host while still having internal actions with full support
This commit is contained in:
Kevin Cunnane
2019-07-03 14:34:03 -07:00
committed by GitHub
parent 8a8cb3ab27
commit 10b066d300
12 changed files with 643 additions and 183 deletions

View File

@@ -54,6 +54,7 @@ export class OutputComponent extends AngularDisposable implements OnInit, AfterV
ngOnInit() {
this._register(this._themeService.onThemeChange(event => this.updateTheme(event)));
this.loadComponent();
this.layout();
this._initialized = true;
this._register(Event.debounce(this.cellModel.notebookModel.layoutChanged, (l, e) => e, 50, /*leading=*/false)
@@ -62,10 +63,6 @@ export class OutputComponent extends AngularDisposable implements OnInit, AfterV
ngAfterViewInit() {
this.updateTheme(this._themeService.getTheme());
if (this.componentHost) {
this.loadComponent();
}
this._changeref.detectChanges();
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {

View File

@@ -19,6 +19,7 @@ import product from 'vs/platform/product/node/product';
import { registerComponentType } from 'sql/workbench/parts/notebook/outputs/mimeRegistry';
import { MimeRendererComponent as MimeRendererComponent } from 'sql/workbench/parts/notebook/outputs/mimeRenderer.component';
import { MarkdownOutputComponent } from 'sql/workbench/parts/notebook/outputs/markdownOutput.component';
import { GridOutputComponent } from 'sql/workbench/parts/notebook/outputs/gridOutput.component';
import { PlotlyOutputComponent } from 'sql/workbench/parts/notebook/outputs/plotlyOutput.component';
// Model View editor registration
@@ -126,16 +127,31 @@ registerComponentType({
* A mime renderer component for grid data.
* This will be replaced by a dedicated component in the future
*/
registerComponentType({
mimeTypes: [
'application/vnd.dataresource+json',
'application/vnd.dataresource'
],
rank: 40,
safe: true,
ctor: MimeRendererComponent,
selector: MimeRendererComponent.SELECTOR
});
if (product.quality !== 'stable') {
registerComponentType({
mimeTypes: [
'application/vnd.dataresource+json',
'application/vnd.dataresource'
],
rank: 40,
safe: true,
ctor: GridOutputComponent,
selector: GridOutputComponent.SELECTOR
});
} else {
// Default to existing grid view until we're sure the new
// implementation is fully stable
registerComponentType({
mimeTypes: [
'application/vnd.dataresource+json',
'application/vnd.dataresource'
],
rank: 40,
safe: true,
ctor: MimeRendererComponent,
selector: MimeRendererComponent.SELECTOR
});
}
/**
* A mime renderer component for LaTeX.

View File

@@ -9,8 +9,12 @@ import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, EDITOR_GROUP_H
import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder } from 'vs/platform/theme/common/colorRegistry';
import { IDisposable } from 'vscode-xterm';
import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BareResultsGridInfo, getBareResultsGridInfoStyles } from 'sql/workbench/parts/query/browser/queryResultsEditor';
import { getZoomLevel } from 'vs/base/browser/browser';
import * as types from 'vs/base/common/types';
export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDisposable {
export function registerNotebookThemes(overrideEditorThemeSetting: boolean, configurationService: IConfigurationService): IDisposable {
return registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
let lightBoxShadow = '0px 4px 6px 0px rgba(0, 0, 0, 0.14)';
@@ -231,5 +235,23 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi
}
`);
}
// Results grid options. Putting these here since query editor only adds them on query editor load.
// We may want to remove from query editor as it can just live here and be loaded once, instead of once
// per editor group which is inefficient
let rawOptions = BareResultsGridInfo.createFromRawSettings(configurationService.getValue('resultsGrid'), getZoomLevel());
let cssRuleText = '';
if (types.isNumber(rawOptions.cellPadding)) {
cssRuleText = rawOptions.cellPadding + 'px';
} else {
cssRuleText = rawOptions.cellPadding.join('px ') + 'px;';
}
collector.addRule(`.grid-panel .monaco-table .slick-cell {
padding: ${cssRuleText}
}
.grid-panel .monaco-table, .message-tree {
${getBareResultsGridInfoStyles(rawOptions)}
}`);
});
}

View File

@@ -0,0 +1,282 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OnInit, Component, Input, Inject, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import * as azdata from 'azdata';
import { AngularDisposable } from 'sql/base/node/lifecycle';
import { IMimeComponent } from 'sql/workbench/parts/notebook/outputs/mimeRegistry';
import { MimeModel } from 'sql/workbench/parts/notebook/outputs/common/mimemodel';
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
import { GridTableBase, GridTableState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/gridDataProvider';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import { IDataResource } from 'sql/workbench/services/notebook/sql/sqlSessionManager';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { getEolString, shouldIncludeHeaders, shouldRemoveNewLines } from 'sql/platform/query/common/queryRunner';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { getErrorMessage } from 'sql/workbench/parts/notebook/notebookUtils';
import { localize } from 'vs/nls';
import { IAction } from 'vs/base/common/actions';
@Component({
selector: GridOutputComponent.SELECTOR,
template: `<div #output class="notebook-cellTable"></div>`
})
export class GridOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
public static readonly SELECTOR: string = 'grid-output';
@ViewChild('output', { read: ElementRef }) private output: ElementRef;
private _initialized: boolean = false;
private _cellModel: ICellModel;
private _bundleOptions: MimeModel.IOptions;
private _table: DataResourceTable;
constructor(
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IThemeService) private readonly themeService: IThemeService
) {
super();
}
@Input() set bundleOptions(value: MimeModel.IOptions) {
this._bundleOptions = value;
if (this._initialized) {
this.renderGrid();
}
}
@Input() mimeType: string;
get cellModel(): ICellModel {
return this._cellModel;
}
@Input() set cellModel(value: ICellModel) {
this._cellModel = value;
if (this._initialized) {
this.renderGrid();
}
}
ngOnInit() {
this.renderGrid();
}
renderGrid(): void {
if (!this._bundleOptions || !this._cellModel || !this.mimeType) {
return;
}
if (!this._table) {
let source = <IDataResource><any>this._bundleOptions.data[this.mimeType];
let state = new GridTableState(0, 0);
this._table = this.instantiationService.createInstance(DataResourceTable, source, this.cellModel.notebookModel.notebookUri.toString(), state);
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.appendChild(this._table.element);
this._register(attachTableStyler(this._table, this.themeService));
this.layout();
this._table.onAdd();
this._initialized = true;
}
}
layout(): void {
if (this._table) {
let maxSize = Math.min(this._table.maximumSize, 500);
this._table.layout(maxSize);
}
}
}
class DataResourceTable extends GridTableBase<any> {
private _gridDataProvider: IGridDataProvider;
constructor(source: IDataResource,
documentUri: string,
state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService editorService: IEditorService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IConfigurationService configurationService: IConfigurationService
) {
super(state, createResultSet(source), contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
this._gridDataProvider = this.instantiationService.createInstance(DataResourceDataProvider, source, this.resultSet, documentUri);
}
get gridDataProvider(): IGridDataProvider {
return this._gridDataProvider;
}
protected getCurrentActions(): IAction[] {
return [];
}
protected getContextActions(): IAction[] {
return [];
}
public get maximumSize(): number {
// Overriding action bar size calculation for now.
// When we add this back in, we should update this calculation
return Math.max(this.maxSize, /* ACTIONBAR_HEIGHT + BOTTOM_PADDING */ 0);
}
}
class DataResourceDataProvider implements IGridDataProvider {
private rows: azdata.DbCellValue[][];
constructor(source: IDataResource,
private resultSet: azdata.ResultSetSummary,
private documentUri: string,
@INotificationService private _notificationService: INotificationService,
@IClipboardService private _clipboardService: IClipboardService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService
) {
this.transformSource(source);
}
private transformSource(source: IDataResource): void {
this.rows = source.data.map(row => {
let rowData: azdata.DbCellValue[] = [];
Object.keys(row).forEach((val, index) => {
let displayValue = String(Object.values(row)[index]);
// Since the columns[0] represents the row number, start at 1
rowData.push({
displayValue: displayValue,
isNull: false,
invariantCultureDisplayValue: displayValue
});
});
return rowData;
});
}
getRowData(rowStart: number, numberOfRows: number): Thenable<azdata.QueryExecuteSubsetResult> {
let rowEnd = rowStart + numberOfRows;
if (rowEnd > this.rows.length) {
rowEnd = this.rows.length;
}
let resultSubset: azdata.QueryExecuteSubsetResult = {
message: undefined,
resultSubset: {
rowCount: rowEnd - rowStart,
rows: this.rows.slice(rowStart, rowEnd)
}
};
return Promise.resolve(resultSubset);
}
copyResults(selection: Slick.Range[], includeHeaders?: boolean): void {
this.copyResultsAsync(selection, includeHeaders);
}
private async copyResultsAsync(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
try {
let results = await getResultsString(this, selection, includeHeaders);
this._clipboardService.writeText(results);
} catch (error) {
this._notificationService.error(localize('copyFailed', "Copy failed with error {0}", getErrorMessage(error)));
}
}
getEolString(): string {
return getEolString(this._textResourcePropertiesService, this.documentUri);
}
shouldIncludeHeaders(includeHeaders: boolean): boolean {
return shouldIncludeHeaders(includeHeaders, this._configurationService);
}
shouldRemoveNewLines(): boolean {
return shouldRemoveNewLines(this._configurationService);
}
getColumnHeaders(range: Slick.Range): string[] {
let headers: string[] = this.resultSet.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
return info.columnName;
});
return headers;
}
get canSerialize(): boolean {
return false;
}
serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable<void> {
throw new Error('Method not implemented.');
}
}
function createResultSet(source: IDataResource): azdata.ResultSetSummary {
let columnInfo: azdata.IDbColumn[] = source.schema.fields.map(field => {
let column = new SimpleDbColumn(field.name);
if (field.type) {
switch (field.type) {
case 'xml':
column.isXml = true;
break;
case 'json':
column.isJson = true;
break;
default:
// Only handling a few cases for now
break;
}
}
return column;
});
let summary: azdata.ResultSetSummary = {
batchId: 0,
id: 0,
complete: true,
rowCount: source.data.length,
columnInfo: columnInfo
};
return summary;
}
class SimpleDbColumn implements azdata.IDbColumn {
constructor(columnName: string) {
this.columnName = columnName;
}
allowDBNull?: boolean;
baseCatalogName: string;
baseColumnName: string;
baseSchemaName: string;
baseServerName: string;
baseTableName: string;
columnName: string;
columnOrdinal?: number;
columnSize?: number;
isAliased?: boolean;
isAutoIncrement?: boolean;
isExpression?: boolean;
isHidden?: boolean;
isIdentity?: boolean;
isKey?: boolean;
isBytes?: boolean;
isChars?: boolean;
isSqlVariant?: boolean;
isUdt?: boolean;
dataType: string;
isXml?: boolean;
isJson?: boolean;
isLong?: boolean;
isReadOnly?: boolean;
isUnique?: boolean;
numericPrecision?: number;
numericScale?: number;
udtAssemblyQualifiedName: string;
dataTypeName: string;
}

View File

@@ -66,6 +66,9 @@ export class MarkdownOutputComponent extends AngularDisposable implements IMimeC
@Input() set cellModel(value: ICellModel) {
this._cellModel = value;
if (this._initialized) {
this.updatePreview();
}
}
public get isTrusted(): boolean {

View File

@@ -85,7 +85,7 @@ export function renderDataResource(
}
// SlickGrid requires columns and data to be in a very specific format; this code was adapted from tableInsight.component.ts
function transformData(rows: any[], columns: Slick.Column<any>[]): { [key: string]: string }[] {
export function transformData(rows: any[], columns: Slick.Column<any>[]): { [key: string]: string }[] {
return rows.map(row => {
let dataWithSchema = {};
Object.keys(row).forEach((val, index) => {
@@ -101,7 +101,7 @@ function transformData(rows: any[], columns: Slick.Column<any>[]): { [key: strin
});
}
function transformColumns(columns: string[]): Slick.Column<any>[] {
export function transformColumns(columns: string[]): Slick.Column<any>[] {
return columns.map((col, index) => {
return <Slick.Column<any>>{
name: col,

View File

@@ -9,7 +9,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import { Table } from 'sql/base/browser/ui/table/table';
import { GridTableState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
@@ -17,16 +16,18 @@ import { QueryEditor } from './queryEditor';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { isWindows } from 'vs/base/common/platform';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { IGridDataProvider } from 'sql/platform/query/common/gridDataProvider';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IGridActionContext {
cell: { row: number; cell: number; };
selection: Slick.Range[];
runner: QueryRunner;
gridDataProvider: IGridDataProvider;
table: Table<any>;
tableState: GridTableState;
cell?: { row: number; cell: number; };
selection?: Slick.Range[];
selectionModel?: CellSelectionModel<any>;
batchId: number;
resultId: number;
table: Table<any>;
selectionModel: CellSelectionModel<any>;
tableState: GridTableState;
}
export interface IMessagesActionContext {
@@ -64,19 +65,17 @@ export class SaveResultAction extends Action {
label: string,
icon: string,
private format: SaveFormat,
private accountForNumberColumn = true
@INotificationService private notificationService: INotificationService
) {
super(id, label, icon);
}
public run(context: IGridActionContext): Promise<boolean> {
if (this.accountForNumberColumn) {
context.runner.serializeResults(context.batchId, context.resultId, this.format,
mapForNumberColumn(context.selection));
} else {
context.runner.serializeResults(context.batchId, context.resultId, this.format, context.selection);
public async run(context: IGridActionContext): Promise<boolean> {
if (!context.gridDataProvider.canSerialize) {
this.notificationService.warn(localize('saveToFileNotSupported', "Save to file is not supported by the backing data source"));
}
return Promise.resolve(true);
await context.gridDataProvider.serializeResults(this.format, mapForNumberColumn(context.selection));
return true;
}
}
@@ -98,11 +97,11 @@ export class CopyResultAction extends Action {
public run(context: IGridActionContext): Promise<boolean> {
if (this.accountForNumberColumn) {
context.runner.copyResults(
context.gridDataProvider.copyResults(
mapForNumberColumn(context.selection),
context.batchId, context.resultId, this.copyHeader);
this.copyHeader);
} else {
context.runner.copyResults(context.selection, context.batchId, context.resultId, this.copyHeader);
context.gridDataProvider.copyResults(context.selection, this.copyHeader);
}
return Promise.resolve(true);
}

View File

@@ -59,7 +59,7 @@ export class BareResultsGridInfo extends BareFontInfo {
}
}
function getBareResultsGridInfoStyles(info: BareResultsGridInfo): string {
export function getBareResultsGridInfoStyles(info: BareResultsGridInfo): string {
let content = '';
if (info.fontFamily) {
content += `font-family: ${info.fontFamily};`;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import QueryRunner, { QueryGridDataProvider } from 'sql/platform/query/common/queryRunner';
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
import { Table } from 'sql/base/browser/ui/table/table';
import { ScrollableSplitView, IView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview';
@@ -39,6 +39,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IAction } from 'vs/base/common/actions';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ILogService } from 'vs/platform/log/common/log';
import { localize } from 'vs/nls';
import { IGridDataProvider } from 'sql/platform/query/common/gridDataProvider';
import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -362,7 +364,12 @@ export class GridPanel {
}
}
class GridTable<T> extends Disposable implements IView {
export interface IDataSet {
rowCount: number;
columnInfo: azdata.IDbColumn[];
}
export abstract class GridTableBase<T> extends Disposable implements IView {
private table: Table<T>;
private actionBar: ActionBar;
private container = document.createElement('div');
@@ -390,24 +397,19 @@ class GridTable<T> extends Disposable implements IView {
public isOnlyTable: boolean = true;
public get resultSet(): azdata.ResultSetSummary {
return this._resultSet;
}
// this handles if the row count is small, like 4-5 rows
private get maxSize(): number {
protected get maxSize(): number {
return ((this.resultSet.rowCount) * this.rowHeight) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
}
constructor(
private runner: QueryRunner,
private _resultSet: azdata.ResultSetSummary,
state: GridTableState,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorService private editorService: IEditorService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IConfigurationService private configurationService: IConfigurationService
protected _resultSet: azdata.ResultSetSummary,
protected contextMenuService: IContextMenuService,
protected instantiationService: IInstantiationService,
protected editorService: IEditorService,
protected untitledEditorService: IUntitledEditorService,
protected configurationService: IConfigurationService
) {
super();
let config = this.configurationService.getValue<{ rowHeight: number }>('resultsGrid');
@@ -423,7 +425,7 @@ class GridTable<T> extends Disposable implements IView {
return <Slick.Column<T>>{
id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan'
? localize('xmlShowplan', "XML Showplan")
: escape(c.columnName),
field: i.toString(),
formatter: isLinked ? hyperLinkFormatter : textFormatter,
@@ -432,6 +434,12 @@ class GridTable<T> extends Disposable implements IView {
});
}
abstract get gridDataProvider(): IGridDataProvider;
public get resultSet(): azdata.ResultSetSummary {
return this._resultSet;
}
public onAdd() {
this.visible = true;
let collection = new VirtualizedCollection(
@@ -505,28 +513,27 @@ class GridTable<T> extends Disposable implements IView {
this.table.style(this.styles);
}
let actions = this.getCurrentActions();
let actionBarContainer = document.createElement('div');
actionBarContainer.style.width = ACTIONBAR_WIDTH + 'px';
actionBarContainer.style.display = 'inline-block';
actionBarContainer.style.height = '100%';
actionBarContainer.style.verticalAlign = 'top';
this.container.appendChild(actionBarContainer);
let context: IGridActionContext = {
gridDataProvider: this.gridDataProvider,
table: this.table,
tableState: this.state,
batchId: this.resultSet.batchId,
resultId: this.resultSet.id
};
this.actionBar = new ActionBar(actionBarContainer, {
orientation: ActionsOrientation.VERTICAL, context: {
runner: this.runner,
batchId: this.resultSet.batchId,
resultId: this.resultSet.id,
table: this.table,
tableState: this.state
}
orientation: ActionsOrientation.VERTICAL, context: context
});
// update context before we run an action
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
this.actionBar.context = this.generateContext();
});
this.actionBar.push(actions, { icon: true, label: false });
this.rebuildActionBar();
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
if (this.state) {
@@ -605,7 +612,7 @@ class GridTable<T> extends Disposable implements IView {
let column = this.resultSet.columnInfo[event.cell.cell - 1];
// handle if a showplan link was clicked
if (column && (column.isXml || column.isJson)) {
this.runner.getQueryRows(event.cell.row, 1, this.resultSet.batchId, this.resultSet.id).then(async d => {
this.gridDataProvider.getRowData(event.cell.row, 1).then(async d => {
let value = d.resultSubset.rows[0][event.cell.cell - 1];
let content = value.displayValue;
@@ -631,9 +638,7 @@ class GridTable<T> extends Disposable implements IView {
return <IGridActionContext>{
cell,
selection,
runner: this.runner,
batchId: this.resultSet.batchId,
resultId: this.resultSet.id,
gridDataProvider: this.gridDataProvider,
table: this.table,
tableState: this.state,
selectionModel: this.selectionModel
@@ -646,28 +651,9 @@ class GridTable<T> extends Disposable implements IView {
this.actionBar.push(actions, { icon: true, label: false });
}
private getCurrentActions(): IAction[] {
protected abstract getCurrentActions(): IAction[];
let actions = [];
if (this.state.canBeMaximized) {
if (this.state.maximized) {
actions.splice(1, 0, new RestoreTableAction());
} else {
actions.splice(1, 0, new MaximizeTableAction());
}
}
actions.push(
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
this.instantiationService.createInstance(ChartDataAction)
);
return actions;
}
protected abstract getContextActions(): IAction[];
public layout(size?: number): void {
if (!this.table) {
@@ -692,7 +678,7 @@ class GridTable<T> extends Disposable implements IView {
}
private loadData(offset: number, count: number): Thenable<T[]> {
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
return this.gridDataProvider.getRowData(offset, count).then(response => {
if (!response.resultSubset) {
return [];
}
@@ -716,17 +702,19 @@ class GridTable<T> extends Disposable implements IView {
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => {
let actions = [
let actions: IAction[] = [
new SelectAllGridAction(),
new Separator(),
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
new Separator(),
new Separator()
];
let contributedActions: IAction[] = this.getContextActions();
if (contributedActions && contributedActions.length > 0) {
actions.push(...contributedActions);
actions.push(new Separator());
}
actions.push(
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false),
new CopyResultAction(CopyResultAction.COPYWITHHEADERS_ID, CopyResultAction.COPYWITHHEADERS_LABEL, true)
];
);
if (this.state.canBeMaximized) {
if (this.state.maximized) {
@@ -787,3 +775,56 @@ class GridTable<T> extends Disposable implements IView {
super.dispose();
}
}
class GridTable<T> extends GridTableBase<T> {
private _gridDataProvider: IGridDataProvider;
constructor(
runner: QueryRunner,
resultSet: azdata.ResultSetSummary,
state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService editorService: IEditorService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IConfigurationService configurationService: IConfigurationService
) {
super(state, resultSet, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
this._gridDataProvider = this.instantiationService.createInstance(QueryGridDataProvider, runner, resultSet.batchId, resultSet.id);
}
get gridDataProvider(): IGridDataProvider {
return this._gridDataProvider;
}
protected getCurrentActions(): IAction[] {
let actions = [];
if (this.state.canBeMaximized) {
if (this.state.maximized) {
actions.splice(1, 0, new RestoreTableAction());
} else {
actions.splice(1, 0, new MaximizeTableAction());
}
}
actions.push(
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),
this.instantiationService.createInstance(ChartDataAction)
);
return actions;
}
protected getContextActions(): IAction[] {
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),
];
}
}

View File

@@ -195,7 +195,8 @@ export class NotebookService extends Disposable implements INotebookService {
if (this._configurationService) {
this.updateNotebookThemes();
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(OVERRIDE_EDITOR_THEMING_SETTING)) {
if (e.affectsConfiguration(OVERRIDE_EDITOR_THEMING_SETTING)
|| e.affectsConfiguration('resultsGrid')) {
this.updateNotebookThemes();
}
}));
@@ -229,7 +230,7 @@ export class NotebookService extends Disposable implements INotebookService {
this._themeParticipant.dispose();
}
this._overrideEditorThemeSetting = overrideEditorSetting;
this._themeParticipant = registerNotebookThemes(overrideEditorSetting);
this._themeParticipant = registerNotebookThemes(overrideEditorSetting, this._configurationService);
}
}