mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 09:35:38 -05:00
limit the data size for chart rendering (#14949)
* limit the rows feed to charts * add telemetry and option to hide * fix typo * updates * comments * notebook fix
This commit is contained in:
@@ -2,33 +2,34 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/chartView';
|
||||
|
||||
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
import { Insight } from './insight';
|
||||
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
|
||||
import { ICellValue, VisualizationOptions } from 'sql/workbench/services/query/common/query';
|
||||
import { ChartOptions, IChartOption, ControlType } from './chartOptions';
|
||||
import { Extensions, IInsightRegistry, IInsightData } from 'sql/platform/dashboard/browser/insightRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { CreateInsightAction, CopyAction, SaveImageAction, IChartActionContext, ConfigureChartAction } from 'sql/workbench/contrib/charts/browser/actions';
|
||||
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import { IInsightOptions, ChartType, InsightType } from 'sql/workbench/contrib/charts/common/interfaces';
|
||||
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { ITaskbarContent, Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { Extensions, IInsightData, IInsightRegistry } from 'sql/platform/dashboard/browser/insightRegistry';
|
||||
import { ChartState } from 'sql/workbench/common/editor/query/chartState';
|
||||
import { ConfigureChartAction, CopyAction, CreateInsightAction, IChartActionContext, SaveImageAction } from 'sql/workbench/contrib/charts/browser/actions';
|
||||
import { getChartMaxRowCount } from 'sql/workbench/contrib/charts/browser/utils';
|
||||
import { ChartType, IInsightOptions, InsightType } from 'sql/workbench/contrib/charts/common/interfaces';
|
||||
import { ICellValue, VisualizationOptions } from 'sql/workbench/services/query/common/query';
|
||||
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ChartOptions, ControlType, IChartOption } from './chartOptions';
|
||||
import { Insight } from './insight';
|
||||
|
||||
|
||||
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
|
||||
|
||||
@@ -89,7 +90,8 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IThemeService private _themeService: IThemeService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@INotificationService private readonly _notificationService: INotificationService
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this.taskbarContainer = DOM.$('div.taskbar-container');
|
||||
@@ -195,7 +197,7 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
public chart(dataId: { batchId: number, resultId: number }) {
|
||||
this.state.dataId = dataId;
|
||||
this._currentData = dataId;
|
||||
this.shouldGraph();
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
@@ -209,7 +211,7 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
|
||||
public set queryRunner(runner: QueryRunner) {
|
||||
this._queryRunner = runner;
|
||||
this.shouldGraph();
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
public setData(rows: ICellValue[][], columns: string[]): void {
|
||||
@@ -228,7 +230,7 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldGraph() {
|
||||
private fetchData(): void {
|
||||
// Check if we have the necessary information
|
||||
if (this._currentData && this._queryRunner) {
|
||||
// check if we are being asked to graph something that is available
|
||||
@@ -236,7 +238,7 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
if (batch) {
|
||||
let summary = batch.resultSetSummaries[this._currentData.resultId];
|
||||
if (summary) {
|
||||
this._queryRunner.getQueryRows(0, summary.rowCount, this._currentData.batchId, this._currentData.resultId).then(d => {
|
||||
this._queryRunner.getQueryRows(0, Math.min(getChartMaxRowCount(this._configurationService), summary.rowCount), this._currentData.batchId, this._currentData.resultId).then(d => {
|
||||
let rows = d.rows;
|
||||
let columns = summary.columnInfo.map(c => c.columnName);
|
||||
this.setData(rows, columns);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Extensions, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
|
||||
const chartsConfiguration: IConfigurationNode = {
|
||||
id: 'builtinCharts',
|
||||
type: 'object',
|
||||
title: nls.localize('builtinChartsConfigurationTitle', "Built-in Charts"),
|
||||
properties: {
|
||||
'builtinCharts.maxRowCount': {
|
||||
type: 'number',
|
||||
default: 300,
|
||||
description: nls.localize('builtinCharts.maxRowCountDescription', "The maximum number of rows for charts to display. Warning: increasing this may impact performance.")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
configurationRegistry.registerConfiguration(chartsConfiguration);
|
||||
@@ -46,3 +46,7 @@ export interface IInsightCtor {
|
||||
new <Services extends BrandedService[]>(container: HTMLElement, options: IInsightOptions, ...services: Services): IInsight;
|
||||
readonly types: Array<InsightType | ChartType>;
|
||||
}
|
||||
|
||||
export interface IChartsConfiguration {
|
||||
readonly maxRowCount: number;
|
||||
}
|
||||
|
||||
35
src/sql/workbench/contrib/charts/browser/utils.ts
Normal file
35
src/sql/workbench/contrib/charts/browser/utils.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChartsConfiguration } from 'sql/workbench/contrib/charts/browser/interfaces';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
/**
|
||||
* Gets the max allowed row count for chart rendering.
|
||||
*/
|
||||
export function getChartMaxRowCount(configurationService: IConfigurationService): number {
|
||||
return configurationService.getValue<IChartsConfiguration>('builtinCharts').maxRowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a toast notification about the max row count for chart has exceeded.
|
||||
*/
|
||||
export function notifyMaxRowCountExceeded(storageService: IStorageService, notificationService: INotificationService, configurationService: IConfigurationService): void {
|
||||
const storageKey = 'charts/ignoreMaxRowCountExceededNotification';
|
||||
if (!storageService.getBoolean(storageKey, StorageScope.GLOBAL, false)) {
|
||||
notificationService.prompt(Severity.Info,
|
||||
nls.localize('charts.maxAllowedRowsExceeded', "Maximum row count for built-in charts has been exceeded, only the first {0} rows are used. To configure the value, you can open user settings and search for: 'builtinCharts.maxRowCount'.", getChartMaxRowCount(configurationService)),
|
||||
[{
|
||||
label: nls.localize('charts.neverShowAgain', "Don't Show Again"),
|
||||
isSecondary: true,
|
||||
run: () => {
|
||||
storageService.store(storageKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}]);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
|
||||
suite('Chart View', () => {
|
||||
test('initializes without error', () => {
|
||||
@@ -40,6 +41,7 @@ function createChartView(isQueryEditorChart: boolean): ChartView {
|
||||
const themeService = new TestThemeService();
|
||||
const instantiationService = new TestInstantiationService();
|
||||
const notificationService = new TestNotificationService();
|
||||
const configurationService = new TestConfigurationService();
|
||||
instantiationService.stub(IThemeService, themeService);
|
||||
return new ChartView(isQueryEditorChart, contextViewService, themeService, instantiationService, notificationService);
|
||||
return new ChartView(isQueryEditorChart, contextViewService, themeService, instantiationService, notificationService, configurationService);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ import { assign } from 'vs/base/common/objects';
|
||||
import { QueryResultId } from 'sql/workbench/services/notebook/browser/models/cell';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IDisposableDataProvider } from 'sql/base/common/dataProvider';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { getChartMaxRowCount, notifyMaxRowCountExceeded } from 'sql/workbench/contrib/charts/browser/utils';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
|
||||
@Component({
|
||||
selector: GridOutputComponent.SELECTOR,
|
||||
template: `<div #output class="notebook-cellTable"></div>`
|
||||
@@ -284,8 +289,9 @@ class DataResourceTable extends GridTableBase<any> {
|
||||
|
||||
public updateChartData(rowCount: number, columnCount: number, gridDataProvider: IGridDataProvider): void {
|
||||
if (this.chartDisplayed) {
|
||||
gridDataProvider.getRowData(0, rowCount).then(result => {
|
||||
let range = new Slick.Range(0, 0, rowCount - 1, columnCount - 1);
|
||||
const actualRowCount = Math.min(getChartMaxRowCount(this.configurationService), rowCount);
|
||||
gridDataProvider.getRowData(0, actualRowCount).then(result => {
|
||||
let range = new Slick.Range(0, 0, actualRowCount - 1, columnCount - 1);
|
||||
let columns = gridDataProvider.getColumnHeaders(range);
|
||||
this._chart.setData(result.rows, columns);
|
||||
});
|
||||
@@ -535,7 +541,11 @@ export class NotebookChartAction extends ToggleableAction {
|
||||
public static SHOWTABLE_LABEL = localize('notebook.showTable', "Show table");
|
||||
public static SHOWTABLE_ICON = 'table';
|
||||
|
||||
constructor(private resourceTable: DataResourceTable) {
|
||||
constructor(private resourceTable: DataResourceTable,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService) {
|
||||
super(NotebookChartAction.ID, {
|
||||
toggleOnLabel: NotebookChartAction.SHOWTABLE_LABEL,
|
||||
toggleOnClass: NotebookChartAction.SHOWTABLE_ICON,
|
||||
@@ -549,9 +559,17 @@ export class NotebookChartAction extends ToggleableAction {
|
||||
this.resourceTable.toggleChartVisibility();
|
||||
this.toggle(!this.state.isOn);
|
||||
if (this.state.isOn) {
|
||||
let rowCount = context.table.getData().getLength();
|
||||
let columnCount = context.table.columns.length;
|
||||
this.resourceTable.updateChartData(rowCount, columnCount, context.gridDataProvider);
|
||||
const rowCount = context.table.getData().getLength();
|
||||
const columnCount = context.table.columns.length;
|
||||
const maxRowCount = getChartMaxRowCount(this.configurationService);
|
||||
const maxRowCountExceeded = rowCount > maxRowCount;
|
||||
if (maxRowCountExceeded) {
|
||||
notifyMaxRowCountExceeded(this.storageService, this.notificationService, this.configurationService);
|
||||
}
|
||||
this.adsTelemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.TelemetryAction.ShowChart).withAdditionalProperties(
|
||||
{ [TelemetryKeys.TelemetryPropertyName.ChartMaxRowCountExceeded]: maxRowCountExceeded }
|
||||
).send();
|
||||
this.resourceTable.updateChartData(Math.min(rowCount, maxRowCount), columnCount, context.gridDataProvider);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ let sqlTocItems: ITOCEntry<string>[] = [{
|
||||
id: 'data/profiler',
|
||||
label: localize('profiler', "Profiler"),
|
||||
settings: ['profiler.*']
|
||||
},
|
||||
{
|
||||
id: 'data/builtinCharts',
|
||||
label: localize('builtinCharts', "Built-in Charts"),
|
||||
settings: ['builtinCharts.*']
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
@@ -20,6 +20,9 @@ import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IEncodingSupport } from 'vs/workbench/common/editor';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { getChartMaxRowCount, notifyMaxRowCountExceeded } from 'sql/workbench/contrib/charts/browser/utils';
|
||||
|
||||
export interface IGridActionContext {
|
||||
gridDataProvider: IGridDataProvider;
|
||||
@@ -167,7 +170,11 @@ export class ChartDataAction extends Action {
|
||||
|
||||
constructor(
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IExtensionRecommendationsService private readonly extensionTipsService: IExtensionRecommendationsService
|
||||
@IExtensionRecommendationsService private readonly extensionTipsService: IExtensionRecommendationsService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(ChartDataAction.ID, ChartDataAction.LABEL, ChartDataAction.ICON);
|
||||
}
|
||||
@@ -175,7 +182,15 @@ export class ChartDataAction extends Action {
|
||||
public run(context: IGridActionContext): Promise<boolean> {
|
||||
// show the visualizer extension recommendation notification
|
||||
this.extensionTipsService.promptRecommendedExtensionsByScenario(Constants.visualizerExtensions);
|
||||
|
||||
const maxRowCount = getChartMaxRowCount(this.configurationService);
|
||||
const rowCount = context.table.getData().getLength();
|
||||
const maxRowCountExceeded = rowCount > maxRowCount;
|
||||
if (maxRowCountExceeded) {
|
||||
notifyMaxRowCountExceeded(this.storageService, this.notificationService, this.configurationService);
|
||||
}
|
||||
this.adsTelemetryService.createActionEvent(TelemetryKeys.TelemetryView.ResultsPanel, TelemetryKeys.TelemetryAction.ShowChart).withAdditionalProperties(
|
||||
{ [TelemetryKeys.TelemetryPropertyName.ChartMaxRowCountExceeded]: maxRowCountExceeded }
|
||||
).send();
|
||||
const activeEditor = this.editorService.activeEditorPane as QueryEditor;
|
||||
activeEditor.chart({ batchId: context.batchId, resultId: context.resultId });
|
||||
return Promise.resolve(true);
|
||||
|
||||
@@ -383,7 +383,7 @@ export abstract class GridTableBase<T> 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 protected readonly configurationService: IConfigurationService,
|
||||
@IQueryModelService private readonly queryModelService: IQueryModelService,
|
||||
@IThemeService private readonly themeService: IThemeService
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user