diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts index 00c795b719..83f740b960 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts @@ -36,6 +36,7 @@ import { ChartView } from 'sql/workbench/contrib/charts/browser/chartView'; import { ToggleableAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; import { IInsightOptions } from 'sql/workbench/common/editor/query/chartState'; import { NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts'; +import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { values } from 'vs/base/common/collections'; import { URI } from 'vs/base/common/uri'; @@ -198,9 +199,10 @@ class DataResourceTable extends GridTableBase { @IInstantiationService protected instantiationService: IInstantiationService, @IEditorService editorService: IEditorService, @IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IQueryModelService queryModelService: IQueryModelService ) { - super(state, createResultSet(source), { actionOrientation: ActionsOrientation.HORIZONTAL }, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService); + super(state, createResultSet(source), { actionOrientation: ActionsOrientation.HORIZONTAL }, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService, queryModelService); this._gridDataProvider = this.instantiationService.createInstance(DataResourceDataProvider, source, this.resultSet, this.cellModel); this._chart = this.instantiationService.createInstance(ChartView, false); diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index 5ca284788f..14cb3fda26 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -48,6 +48,7 @@ import { Progress } from 'vs/platform/progress/common/progress'; import { ScrollableView, IView } from 'sql/base/browser/ui/scrollableView/scrollableView'; import { IQueryEditorConfiguration } from 'sql/platform/query/common/query'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; +import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; const ROW_HEIGHT = 29; const HEADER_HEIGHT = 26; @@ -373,7 +374,8 @@ export abstract class GridTableBase extends Disposable implements IView { @IInstantiationService protected readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IUntitledTextEditorService private readonly untitledEditorService: IUntitledTextEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IQueryModelService private readonly queryModelService: IQueryModelService ) { super(); let config = this.configurationService.getValue<{ rowHeight: number }>('resultsGrid'); @@ -513,10 +515,11 @@ export abstract class GridTableBase extends Disposable implements IView { }); this.rebuildActionBar(); - this.selectionModel.onSelectedRangesChanged.subscribe(e => { + this.selectionModel.onSelectedRangesChanged.subscribe(async e => { if (this.state) { this.state.selection = this.selectionModel.getSelectedRanges(); } + await this.notifyTableSelectionChanged(); }); this.table.grid.onScroll.subscribe((e, data) => { @@ -590,6 +593,20 @@ export abstract class GridTableBase extends Disposable implements IView { this.scrolled = false; } + private async notifyTableSelectionChanged() { + const selectedValues = []; + for (const range of this.state.selection) { + const subset = await this.gridDataProvider.getRowData(range.fromRow, range.toRow - range.fromRow + 1); + subset.rows.forEach(row => { + // start with range.fromCell -1 because we have row number column which is not available in the actual data + for (let i = range.fromCell - 1; i < range.toCell; i++) { + selectedValues.push(row[i]?.displayValue); + } + }); + } + this.queryModelService.notifyCellSelectionChanged(selectedValues); + } + private onTableClick(event: ITableMouseEvent) { // account for not having the number column let column = this.resultSet.columnInfo[event.cell.cell - 1]; @@ -769,9 +786,10 @@ class GridTable extends GridTableBase { @IContextKeyService private contextKeyService: IContextKeyService, @IEditorService editorService: IEditorService, @IUntitledTextEditorService untitledEditorService: IUntitledTextEditorService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IQueryModelService queryModelService: IQueryModelService ) { - super(state, resultSet, undefined, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService); + super(state, resultSet, undefined, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService, queryModelService); this._gridDataProvider = this.instantiationService.createInstance(QueryGridDataProvider, this._runner, resultSet.batchId, resultSet.id); } diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index 575c5972a9..73e0fc3bc4 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -27,7 +27,7 @@ import { localize } from 'vs/nls'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { TimeElapsedStatusBarContributions, RowCountStatusBarContributions, QueryStatusStatusBarContributions } from 'sql/workbench/contrib/query/browser/statusBarItems'; +import { TimeElapsedStatusBarContributions, RowCountStatusBarContributions, QueryStatusStatusBarContributions, QueryResultSelectionSummaryStatusBarContribution } from 'sql/workbench/contrib/query/browser/statusBarItems'; import { SqlFlavorStatusbarItem, ChangeFlavorAction } from 'sql/workbench/contrib/query/browser/flavorStatus'; import { IEditorInputFactoryRegistry, Extensions as EditorInputFactoryExtensions } from 'vs/workbench/common/editor'; import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput'; @@ -479,3 +479,4 @@ workbenchRegistry.registerWorkbenchContribution(TimeElapsedStatusBarContribution workbenchRegistry.registerWorkbenchContribution(RowCountStatusBarContributions, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(QueryStatusStatusBarContributions, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(SqlFlavorStatusbarItem, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(QueryResultSelectionSummaryStatusBarContribution, LifecyclePhase.Restored); diff --git a/src/sql/workbench/contrib/query/browser/statusBarItems.ts b/src/sql/workbench/contrib/query/browser/statusBarItems.ts index 31ab2cacbe..a297bd8bb1 100644 --- a/src/sql/workbench/contrib/query/browser/statusBarItems.ts +++ b/src/sql/workbench/contrib/query/browser/statusBarItems.ts @@ -3,18 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; -import { IntervalTimer } from 'vs/base/common/async'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { localize } from 'vs/nls'; -import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; import { parseNumAsTimeString } from 'sql/platform/connection/common/utils'; -import { Event } from 'vs/base/common/event'; import { QueryEditorInput } from 'sql/workbench/common/editor/query/queryEditorInput'; -import { IStatusbarService, IStatusbarEntryAccessor, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; - +import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; +import QueryRunner from 'sql/workbench/services/query/common/queryRunner'; +import { IntervalTimer } from 'vs/base/common/async'; +import { Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; export class TimeElapsedStatusBarContributions extends Disposable implements IWorkbenchContribution { private static readonly ID = 'status.query.timeElapsed'; @@ -253,3 +252,54 @@ export class QueryStatusStatusBarContributions extends Disposable implements IWo this.statusbarService.updateEntryVisibility(QueryStatusStatusBarContributions.ID, true); } } + +export class QueryResultSelectionSummaryStatusBarContribution extends Disposable implements IWorkbenchContribution { + private static readonly ID = 'status.query.selection-summary'; + private statusItem: IStatusbarEntryAccessor; + + constructor( + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IEditorService private editorService: IEditorService, + @IQueryModelService queryModelService: IQueryModelService + ) { + super(); + this.statusItem = this._register( + this.statusbarService.addEntry({ + text: '', + ariaLabel: '' + }, + QueryResultSelectionSummaryStatusBarContribution.ID, + localize('status.query.selection-summary', "Selection Summary"), + StatusbarAlignment.RIGHT, 100) + ); + this._register(editorService.onDidActiveEditorChange(() => { this.hide(); }, this)); + this._register(queryModelService.onRunQueryStart(() => { this.hide(); })); + this._register(queryModelService.onCellSelectionChanged((data: string[]) => { + this.onCellSelectionChanged(data); + })); + } + + private hide(): void { + this.statusbarService.updateEntryVisibility(QueryResultSelectionSummaryStatusBarContribution.ID, false); + } + + private show(): void { + this.statusbarService.updateEntryVisibility(QueryResultSelectionSummaryStatusBarContribution.ID, true); + } + + private onCellSelectionChanged(data: string[]): void { + const numericValues = data?.filter(value => !isNaN(parseFloat(value))).map(value => parseFloat(value)); + if (numericValues?.length < 2 || !(this.editorService.activeEditor instanceof QueryEditorInput)) { + this.hide(); + return; + } + + const sum = numericValues.reduce((previous, current, idx, array) => previous + current); + const summaryText = localize('status.query.summaryText', "Average: {0} Count: {1} Sum: {2}", sum / numericValues.length, data.length, sum); + this.statusItem.update({ + text: summaryText, + ariaLabel: summaryText + }); + this.show(); + } +} diff --git a/src/sql/workbench/services/query/common/queryModel.ts b/src/sql/workbench/services/query/common/queryModel.ts index 299cce4111..9e239df8c4 100644 --- a/src/sql/workbench/services/query/common/queryModel.ts +++ b/src/sql/workbench/services/query/common/queryModel.ts @@ -48,6 +48,9 @@ export interface IQueryEvent { export interface IQueryModelService { _serviceBrand: undefined; + onCellSelectionChanged: Event; + notifyCellSelectionChanged(selectedValues: string[]): void; + getQueryRunner(uri: string): QueryRunner | undefined; getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise; diff --git a/src/sql/workbench/services/query/common/queryModelService.ts b/src/sql/workbench/services/query/common/queryModelService.ts index 172684ffd7..b6cbdd52c3 100644 --- a/src/sql/workbench/services/query/common/queryModelService.ts +++ b/src/sql/workbench/services/query/common/queryModelService.ts @@ -62,6 +62,7 @@ export class QueryModelService implements IQueryModelService { private _onRunQueryComplete: Emitter; private _onQueryEvent: Emitter; private _onEditSessionReady: Emitter; + private _onCellSelectionChangedEmitter = new Emitter(); // EVENTS ///////////////////////////////////////////////////////////// public get onRunQueryStart(): Event { return this._onRunQueryStart.event; } @@ -69,6 +70,7 @@ export class QueryModelService implements IQueryModelService { public get onRunQueryComplete(): Event { return this._onRunQueryComplete.event; } public get onQueryEvent(): Event { return this._onQueryEvent.event; } public get onEditSessionReady(): Event { return this._onEditSessionReady.event; } + public get onCellSelectionChanged(): Event { return this._onCellSelectionChangedEmitter.event; } // CONSTRUCTOR ///////////////////////////////////////////////////////// constructor( @@ -96,6 +98,14 @@ export class QueryModelService implements IQueryModelService { return dataService; } + /** + * Notify the event subscribers about the new selected cell values + * @param selectedValues current selected cell values + */ + public notifyCellSelectionChanged(selectedValues: string[]): void { + this._onCellSelectionChangedEmitter.fire(selectedValues); + } + /** * Force all grids to re-render. This is needed to re-render the grids when switching * between different URIs. diff --git a/src/sql/workbench/services/query/test/common/testQueryModelService.ts b/src/sql/workbench/services/query/test/common/testQueryModelService.ts index 0480290887..73ae99cbb6 100644 --- a/src/sql/workbench/services/query/test/common/testQueryModelService.ts +++ b/src/sql/workbench/services/query/test/common/testQueryModelService.ts @@ -14,6 +14,10 @@ import { IRange } from 'vs/editor/common/core/range'; export class TestQueryModelService implements IQueryModelService { _serviceBrand: any; onRunQueryUpdate: Event; + onCellSelectionChanged: Event; + notifyCellSelectionChanged(selectedValues: string[]): void { + throw new Error('Method not implemented.'); + } getQueryRunner(uri: string): QueryRunner { throw new Error('Method not implemented.'); }