Skip to content

Commit

Permalink
Introducing Visualizer to SQL Query Editor (microsoft#6422)
Browse files Browse the repository at this point in the history
* extension recommendation on application launch

* Introducing Visualizer (SandDance) to the SQL Query Editor. (microsoft#6347)

* Created Visualizer icon in the results grid. Utilized a context key so that the icon only shows if Visualizer extensions (currently, just SandDance) is installed. Visualizer icon open up SandDance in a top-level document.

* When the user clicks on Charts, visualizer recommendation popup appears. User can click on "Install Extensions" to download the visualizer extensions.

* Enabled SQL Query Editor to pass query data to SandDance extension.

* Introducing Visualizer (SandDance) to the SQL Query Editor. (microsoft#6347)

* Created Visualizer icon in the results grid. Utilized a context key so that the icon only shows if Visualizer extensions (currently, just SandDance) is installed. Visualizer icon open up SandDance in a top-level document.

* When the user clicks on Charts, visualizer recommendation popup appears. User can click on "Install Extensions" to download the visualizer extensions.

* Enabled SQL Query Editor to pass query data to SandDance extension.

* Cleaned code; made changes according to PR comments

* removed the test service for extensions gallary

* Cleaned up code according to PR changes

* unid changes to build/azure-piplines

* Removed all the build/azure-pipelines changes

* removed changes on media/language.svg

* refactored extension recommendation system to allow it to be generic

* updated extensionsViews to support generic extension query search; added localized constants for visualizer extensions

* Made syntax and error message changes acccording to PR comments.

* Updated recommendation message according to scenario type
  • Loading branch information
RebeccaRunxinWang authored and rchlkm committed Jul 29, 2019
1 parent 720a7fb commit 2c8a22b
Show file tree
Hide file tree
Showing 28 changed files with 337 additions and 22 deletions.
5 changes: 5 additions & 0 deletions product.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
"IDERA.sqldm-performance-insights",
"SentryOne.plan-explorer"
],
"recommendedExtensionsByScenario": {
"visualizerExtensions": [
"msrvida.azdata-sanddance"
]
},
"extensionsGallery": {
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
}
Expand Down
12 changes: 10 additions & 2 deletions src/sql/azdata.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3974,10 +3974,18 @@ declare module 'azdata' {
export type QueryEvent =
| 'queryStart'
| 'queryStop'
| 'executionPlan';
| 'executionPlan'
| 'visualize';

/**
* args for each event type
* queryStart: undefined
* queryStop: undefined
* executionPlan: string
* visualize: ResultSetSummary
*/
export interface QueryEventListener {
onQueryEvent(type: QueryEvent, document: queryeditor.QueryDocument, args: any): void;
onQueryEvent(type: QueryEvent, document: queryeditor.QueryDocument, args: ResultSetSummary | string | undefined): void;
}

// new extensibility interfaces
Expand Down
9 changes: 9 additions & 0 deletions src/sql/base/browser/ui/table/media/slickColorTheme.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
background-image: url("viewChart.svg");
}

.vs .icon.viewVisualizer {
background-image: url("viewVisualizer.svg");
}

/* headers */
.vs .resultsMessageHeader {
background: var(--color-bg-header);
Expand Down Expand Up @@ -260,6 +264,11 @@
background-image: url("viewChart_inverse.svg");
}

.vs-dark .icon.viewVisualizer,
.hc-black .icon.viewVisualizer {
background-image: url("viewVisualizer_inverse.svg");
}

.grid-panel .action-label.icon {
height: 16px;
min-width: 28px;
Expand Down
1 change: 1 addition & 0 deletions src/sql/base/browser/ui/table/media/viewVisualizer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/sql/platform/query/common/queryModelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,15 @@ export class QueryModelService implements IQueryModelService {
this._onQueryEvent.fire(event);
});

queryRunner.onVisualize(resultSetInfo => {
let event: IQueryEvent = {
type: 'visualize',
uri: uri,
params: resultSetInfo
};
this._onQueryEvent.fire(event);
});

info.queryRunner = queryRunner;
info.dataService = this._instantiationService.createInstance(DataService, uri);
this._queryInfoMap.set(uri, info);
Expand Down
14 changes: 14 additions & 0 deletions src/sql/platform/query/common/queryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ export default class QueryRunner extends Disposable {
private _onQueryPlanAvailable = this._register(new Emitter<IQueryPlanInfo>());
public readonly onQueryPlanAvailable = this._onQueryPlanAvailable.event;

private _onVisualize = this._register(new Emitter<azdata.ResultSetSummary>());
public readonly onVisualize = this._onVisualize.event;

private _queryStartTime: Date;
public get queryStartTime(): Date {
return this._queryStartTime;
Expand Down Expand Up @@ -579,6 +582,17 @@ export default class QueryRunner extends Disposable {
public getGridDataProvider(batchId: number, resultSetId: number): IGridDataProvider {
return this.instantiationService.createInstance(QueryGridDataProvider, this, batchId, resultSetId);
}

public notifyVisualizeRequested(batchId: number, resultSetId: number): void {
let result: azdata.ResultSetSummary = {
batchId: batchId,
id: resultSetId,
columnInfo: this.batchSets[batchId].resultSetSummaries[resultSetId].columnInfo,
complete: true,
rowCount: this.batchSets[batchId].resultSetSummaries[resultSetId].rowCount
};
this._onVisualize.fire(result);
}
}

export class QueryGridDataProvider implements IGridDataProvider {
Expand Down
4 changes: 2 additions & 2 deletions src/sql/workbench/api/common/extHostQueryEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export class ExtHostQueryEditor implements ExtHostQueryEditorShape {
public $onQueryEvent(handle: number, fileUri: string, event: IQueryEvent): void {
let listener: azdata.queryeditor.QueryEventListener = this._queryListeners[handle];
if (listener) {
let planXml = event.params ? event.params.planXml : undefined;
listener.onQueryEvent(event.type, new ExtHostQueryDocument(mssqlProviderName, fileUri, this._proxy), planXml);
let params = event.params && event.params.planXml ? event.params.planXml : event.params;
listener.onQueryEvent(event.type, new ExtHostQueryDocument(mssqlProviderName, fileUri, this._proxy), params);
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/sql/workbench/contrib/extensions/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

export const visualizerExtensions = 'visualizerExtensions';
79 changes: 79 additions & 0 deletions src/sql/workbench/contrib/extensions/extensionsActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from 'vs/nls';
import { IAction, Action } from 'vs/base/common/actions';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/contrib/extensions/common/extensions';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionRecommendation, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { CancellationToken } from 'vs/base/common/cancellation';
import { PagedModel } from 'vs/base/common/paging';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';

function getScenarioID(scenarioType: string) {
return 'workbench.extensions.action.show' + scenarioType;
}

export class ShowRecommendedExtensionsByScenarioAction extends Action {
constructor(
private readonly scenarioType: string,
@IViewletService private readonly viewletService: IViewletService
) {
super(getScenarioID(scenarioType), localize('showRecommendations', "Show Recommendations"), undefined, true);
}

run(): Promise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('@' + this.scenarioType);
viewlet.focus();
});
}
}

export class InstallRecommendedExtensionsByScenarioAction extends Action {
private _recommendations: IExtensionRecommendation[] = [];
get recommendations(): IExtensionRecommendation[] { return this._recommendations; }
set recommendations(recommendations: IExtensionRecommendation[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; }

constructor(
private readonly scenarioType: string,
recommendations: IExtensionRecommendation[],
@IViewletService private readonly viewletService: IViewletService,
@INotificationService private readonly notificationService: INotificationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IOpenerService private readonly openerService: IOpenerService,
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
) {
super(getScenarioID(scenarioType), localize('Install Extensions', "Install Extensions"), 'extension-action');
this.recommendations = recommendations;
}

run(): Promise<any> {
if (!this.recommendations.length) { return Promise.resolve(); }
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('@' + this.scenarioType);
viewlet.focus();
const names = this.recommendations.map(({ extensionId }) => extensionId);
return this.extensionWorkbenchService.queryGallery({ names, source: 'install-' + this.scenarioType }, CancellationToken.None).then(pager => {
let installPromises: Promise<any>[] = [];
let model = new PagedModel(pager);
for (let i = 0; i < pager.total; i++) {
installPromises.push(model.resolve(i, CancellationToken.None).then(e => this.extensionWorkbenchService.install(e)));
}
return Promise.all(installPromises);
});
});
}
}
2 changes: 1 addition & 1 deletion src/sql/workbench/parts/charts/browser/chartTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ export class ChartTab implements IPanelTab {
public clear() {
this.view.clear();
}
}
}
1 change: 1 addition & 0 deletions src/sql/workbench/parts/grid/common/gridContentEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export const SaveAsJSON = 'SaveAsJSON';
export const SaveAsExcel = 'SaveAsExcel';
export const SaveAsXML = 'SaveAsXML';
export const ViewAsChart = 'ViewAsChart';
export const ViewAsVisualizer = 'ViewAsVisualizer';
export const GoToNextQueryOutputTab = 'GoToNextQueryOutputTab';
export const GoToNextGrid = 'GoToNextGrid';
1 change: 1 addition & 0 deletions src/sql/workbench/parts/grid/views/gridActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const TOGGLERESULTS_ID = 'grid.toggleResultPane';
export const TOGGLEMESSAGES_ID = 'grid.toggleMessagePane';
export const GOTONEXTQUERYOUTPUTTAB_ID = 'query.goToNextQueryOutputTab';
export const GRID_VIEWASCHART_ID = 'grid.viewAsChart';
export const GRID_VIEWASVISUALIZER_ID = 'grid.viewAsVisualizer';
export const GRID_GOTONEXTGRID_ID = 'grid.goToNextGrid';

export class GridActionProvider {
Expand Down
4 changes: 4 additions & 0 deletions src/sql/workbench/parts/grid/views/gridCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export const viewAsChart = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.ViewAsChart);
};

export const viewAsVisualizer = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.ViewAsVisualizer);
};

export const goToNextGrid = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.GoToNextGrid);
};
6 changes: 6 additions & 0 deletions src/sql/workbench/parts/grid/views/gridParentComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ export abstract class GridParentComponent {
case GridContentEvents.ViewAsChart:
self.showChartForGrid(self.activeGrid);
break;
case GridContentEvents.ViewAsVisualizer:
self.showVisualizerForGrid(self.activeGrid);
break;
case GridContentEvents.GoToNextGrid:
self.goToNextGrid();
break;
Expand Down Expand Up @@ -277,6 +280,9 @@ export abstract class GridParentComponent {
protected showChartForGrid(index: number) {
}

protected showVisualizerForGrid(index: number) {
}

protected goToNextGrid() {
if (this.renderedDataSets.length > 0) {
let next = this.activeGrid + 1;
Expand Down
32 changes: 30 additions & 2 deletions src/sql/workbench/parts/query/browser/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { localize } from 'vs/nls';
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 { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import { Table } from 'sql/base/browser/ui/table/table';
import { QueryEditor } from './queryEditor';
Expand All @@ -17,7 +17,10 @@ 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';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import product from 'vs/platform/product/node/product';
import { GridTableState } from 'sql/workbench/parts/query/common/gridPanelState';
import * as Constants from 'sql/workbench/contrib/extensions/constants';

export interface IGridActionContext {
gridDataProvider: IGridDataProvider;
Expand Down Expand Up @@ -199,13 +202,38 @@ export class ChartDataAction extends Action {
public static LABEL = localize('chart', "Chart");
public static ICON = 'viewChart';

constructor(@IEditorService private editorService: IEditorService) {
constructor(
@IEditorService private editorService: IEditorService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService
) {
super(ChartDataAction.ID, ChartDataAction.LABEL, ChartDataAction.ICON);
}

public run(context: IGridActionContext): Promise<boolean> {
const activeEditor = this.editorService.activeControl as QueryEditor;
if (product.quality !== 'stable') {
this.extensionTipsService.promptRecommendedExtensionsByScenario(Constants.visualizerExtensions);
}
activeEditor.chart({ batchId: context.batchId, resultId: context.resultId });
return Promise.resolve(true);
}
}

export class VisualizerDataAction extends Action {
public static ID = 'grid.visualizer';
public static LABEL = localize("visualizer", "Visualizer");
public static ICON = 'viewVisualizer';

constructor(
private runner: QueryRunner,
@IEditorService private editorService: IEditorService,

) {
super(VisualizerDataAction.ID, VisualizerDataAction.LABEL, VisualizerDataAction.ICON);
}

public run(context: IGridActionContext): Promise<boolean> {
this.runner.notifyVisualizeRequested(context.batchId, context.resultId);
return Promise.resolve(true);
}
}
12 changes: 9 additions & 3 deletions src/sql/workbench/parts/query/browser/gridPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ScrollableSplitView, IView } from 'sql/base/browser/ui/scrollableSplitv
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction } from 'sql/workbench/parts/query/browser/actions';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction, VisualizerDataAction } from 'sql/workbench/parts/query/browser/actions';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { escape } from 'sql/base/common/strings';
Expand All @@ -24,6 +24,7 @@ import * as azdata from 'azdata';

import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Emitter, Event } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { isUndefinedOrNull } from 'vs/base/common/types';
Expand Down Expand Up @@ -723,17 +724,18 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
class GridTable<T> extends GridTableBase<T> {
private _gridDataProvider: IGridDataProvider;
constructor(
runner: QueryRunner,
private _runner: QueryRunner,
resultSet: azdata.ResultSetSummary,
state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService private contextKeyService: IContextKeyService,
@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);
this._gridDataProvider = this.instantiationService.createInstance(QueryGridDataProvider, this._runner, resultSet.batchId, resultSet.id);
}

get gridDataProvider(): IGridDataProvider {
Expand All @@ -760,6 +762,10 @@ class GridTable<T> extends GridTableBase<T> {
this.instantiationService.createInstance(ChartDataAction)
);

if (this.contextKeyService.getContextKeyValue('showVisualizer')) {
actions.push(this.instantiationService.createInstance(VisualizerDataAction, this._runner));
}

return actions;
}

Expand Down
4 changes: 3 additions & 1 deletion src/sql/workbench/parts/query/browser/queryActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import Severity from 'vs/base/common/severity';
import { append, $ } from 'vs/base/browser/dom';

Expand Down Expand Up @@ -107,7 +108,8 @@ export class RunQueryAction extends QueryTaskbarAction {
constructor(
editor: QueryEditor,
@IQueryModelService protected readonly queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
@IConnectionManagementService connectionManagementService: IConnectionManagementService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService
) {
super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass);
this.label = nls.localize('runQueryLabel', "Run");
Expand Down
1 change: 0 additions & 1 deletion src/sql/workbench/parts/query/browser/queryResultsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ class ResultsView extends Disposable implements IPanelView {
this.gridPanel.state = val;
}
}

class ResultsTab implements IPanelTab {
public readonly title = nls.localize('resultsTabTitle', "Results");
public readonly identifier = 'resultsTab';
Expand Down
Loading

0 comments on commit 2c8a22b

Please sign in to comment.