mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-01 01:25:38 -05:00
Another code layering (#4037)
* working on formatting * fixed basic lint errors; starting moving things to their appropriate location * formatting * update tslint to match the version of vscode we have * remove unused code * work in progress fixing layering * formatting * moved connection management service to platform * formatting * add missing file * moving more servies * formatting * moving more services * formatting * wip * moving more services * formatting * move css file * add missing svgs * moved the rest of services * formatting * changing around some references * formatting * revert tslint * revert some changes that brake things * formatting * fix tests * fix testzx * fix tests * fix tests * fix compile issue
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
import { InsightsDialogController } from 'sql/workbench/services/insights/node/insightsDialogController';
|
||||
import { InsightsDialogView } from 'sql/workbench/services/insights/browser/insightsDialogView';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { IInsightsConfig } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { IInsightsDialogModel, IInsightsDialogService } from 'sql/workbench/services/insights/common/insightsDialogService';
|
||||
import { InsightsDialogModel } from 'sql/workbench/services/insights/common/insightsDialogModel';
|
||||
|
||||
export class InsightsDialogService implements IInsightsDialogService {
|
||||
_serviceBrand: any;
|
||||
private _insightsDialogController: InsightsDialogController;
|
||||
private _insightsDialogView: InsightsDialogView;
|
||||
private _insightsDialogModel: IInsightsDialogModel;
|
||||
|
||||
constructor( @IInstantiationService private _instantiationService: IInstantiationService) { }
|
||||
|
||||
// query string
|
||||
public show(input: IInsightsConfig, connectionProfile: IConnectionProfile): void {
|
||||
if (!this._insightsDialogView) {
|
||||
this._insightsDialogModel = new InsightsDialogModel();
|
||||
this._insightsDialogController = this._instantiationService.createInstance(InsightsDialogController, this._insightsDialogModel);
|
||||
this._insightsDialogView = this._instantiationService.createInstance(InsightsDialogView, this._insightsDialogModel);
|
||||
this._insightsDialogView.render();
|
||||
} else {
|
||||
this._insightsDialogModel.reset();
|
||||
this._insightsDialogView.reset();
|
||||
}
|
||||
|
||||
this._insightsDialogModel.insight = input.details;
|
||||
this._insightsDialogController.update(input.details, connectionProfile);
|
||||
this._insightsDialogView.open(input.details, connectionProfile);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._insightsDialogView.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/insightsDialog';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import { IInsightsConfigDetails } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { attachButtonStyler, attachModalDialogStyler, attachTableStyler, attachPanelStyler } from 'sql/platform/theme/common/styler';
|
||||
import { TaskRegistry } from 'sql/platform/tasks/common/tasks';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import { IInsightsDialogModel, ListResource, IInsightDialogActionContext, insertValueRegex } from 'sql/workbench/services/insights/common/insightsDialogService';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { CopyInsightDialogSelectionAction } from 'sql/workbench/services/insights/common/insightDialogActions';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { MenuRegistry, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const labelDisplay = nls.localize("insights.item", "Item");
|
||||
const valueDisplay = nls.localize("insights.value", "Value");
|
||||
|
||||
class InsightTableView<T> extends ViewletPanel {
|
||||
private _table: Table<T>;
|
||||
public get table(): Table<T> {
|
||||
return this._table;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private columns: Slick.Column<T>[],
|
||||
private data: IDisposableDataProvider<T> | Array<T>,
|
||||
private tableOptions: Slick.GridOptions<T>,
|
||||
options: IViewletPanelOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService);
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
this._table = new Table(container, {
|
||||
columns: this.columns,
|
||||
dataProvider: this.data
|
||||
}, this.tableOptions);
|
||||
}
|
||||
|
||||
protected layoutBody(size: number): void {
|
||||
this._table.layout(size, Orientation.VERTICAL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function stateFormatter(row: number, cell: number, value: any, columnDef: Slick.Column<ListResource>, resource: ListResource): string {
|
||||
// template
|
||||
const icon = DOM.$('span.icon-span');
|
||||
const badge = DOM.$('div.badge');
|
||||
const badgeContent = DOM.$('div.badge-content');
|
||||
DOM.append(badge, badgeContent);
|
||||
DOM.append(icon, badge);
|
||||
|
||||
// render icon if passed
|
||||
if (resource.icon) {
|
||||
icon.classList.add('icon');
|
||||
icon.classList.add(resource.icon);
|
||||
} else {
|
||||
icon.classList.remove('icon');
|
||||
}
|
||||
|
||||
//render state badge if present
|
||||
if (resource.stateColor) {
|
||||
badgeContent.style.backgroundColor = resource.stateColor;
|
||||
badgeContent.classList.remove('icon');
|
||||
} else if (resource.stateIcon) {
|
||||
badgeContent.style.backgroundColor = '';
|
||||
badgeContent.classList.add('icon');
|
||||
badgeContent.classList.add(resource.stateIcon);
|
||||
} else {
|
||||
badgeContent.classList.remove('icon');
|
||||
badgeContent.style.backgroundColor = '';
|
||||
}
|
||||
|
||||
return icon.outerHTML;
|
||||
}
|
||||
|
||||
export class InsightsDialogView extends Modal {
|
||||
|
||||
private _connectionProfile: IConnectionProfile;
|
||||
private _insight: IInsightsConfigDetails;
|
||||
private _splitView: SplitView;
|
||||
private _container: HTMLElement;
|
||||
private _closeButton: Button;
|
||||
private _topTable: Table<ListResource>;
|
||||
private _topTableData: TableDataView<ListResource>;
|
||||
private _bottomTable: Table<ListResource>;
|
||||
private _bottomTableData: TableDataView<ListResource>;
|
||||
private _taskButtonDisposables: IDisposable[] = [];
|
||||
private _topColumns: Array<Slick.Column<ListResource>> = [
|
||||
{
|
||||
name: '',
|
||||
field: 'state',
|
||||
id: 'state',
|
||||
width: 20,
|
||||
resizable: false,
|
||||
formatter: stateFormatter
|
||||
},
|
||||
{
|
||||
name: labelDisplay,
|
||||
field: 'label',
|
||||
id: 'label'
|
||||
},
|
||||
{
|
||||
name: valueDisplay,
|
||||
field: 'value',
|
||||
id: 'value'
|
||||
}
|
||||
];
|
||||
|
||||
private _bottomColumns: Array<Slick.Column<ListResource>> = [
|
||||
{
|
||||
name: nls.localize("property", "Property"),
|
||||
field: 'label',
|
||||
id: 'label'
|
||||
},
|
||||
{
|
||||
name: nls.localize("value", "Value"),
|
||||
field: 'value',
|
||||
id: 'value'
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private _model: IInsightsDialogModel,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IPartService partService: IPartService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
@IClipboardService clipboardService: IClipboardService
|
||||
) {
|
||||
super(nls.localize("InsightsDialogTitle", "Insights"), TelemetryKeys.Insights, partService, telemetryService, clipboardService, themeService, contextKeyService);
|
||||
this._model.onDataChange(e => this.build());
|
||||
}
|
||||
|
||||
private updateTopColumns(): void {
|
||||
let labelName = this.labelColumnName ? this.labelColumnName : labelDisplay;
|
||||
let valueName = this._insight.value ? this._insight.value : valueDisplay;
|
||||
this._topColumns = [
|
||||
{
|
||||
name: '',
|
||||
field: 'state',
|
||||
id: 'state',
|
||||
width: 20,
|
||||
resizable: false,
|
||||
formatter: stateFormatter
|
||||
},
|
||||
{
|
||||
name: labelName,
|
||||
field: 'label',
|
||||
id: 'label'
|
||||
},
|
||||
{
|
||||
name: valueName,
|
||||
field: 'value',
|
||||
id: 'value'
|
||||
}
|
||||
];
|
||||
this._topTable.columns = this._topColumns;
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
this._container = container;
|
||||
container.classList.add('monaco-panel-view');
|
||||
|
||||
this._splitView = new SplitView(container);
|
||||
|
||||
const itemsHeaderTitle = nls.localize("insights.dialog.items", "Items");
|
||||
const itemsDetailHeaderTitle = nls.localize("insights.dialog.itemDetails", "Item Details");
|
||||
|
||||
this._topTableData = new TableDataView();
|
||||
this._bottomTableData = new TableDataView();
|
||||
let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle, ariaHeaderLabel: itemsHeaderTitle }) as InsightTableView<ListResource>;
|
||||
topTableView.render();
|
||||
attachPanelStyler(topTableView, this._themeService);
|
||||
this._topTable = topTableView.table;
|
||||
this._topTable.setSelectionModel(new RowSelectionModel<ListResource>());
|
||||
let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle, ariaHeaderLabel: itemsDetailHeaderTitle }) as InsightTableView<ListResource>;
|
||||
bottomTableView.render();
|
||||
attachPanelStyler(bottomTableView, this._themeService);
|
||||
this._bottomTable = bottomTableView.table;
|
||||
this._bottomTable.setSelectionModel(new RowSelectionModel<ListResource>());
|
||||
|
||||
this._register(this._topTable.onSelectedRowsChanged((e: DOMEvent, data: Slick.OnSelectedRowsChangedEventArgs<ListResource>) => {
|
||||
if (data.rows.length === 1) {
|
||||
let element = this._topTableData.getItem(data.rows[0]);
|
||||
let resourceArray: ListResource[] = [];
|
||||
for (let i = 0; i < this._model.columns.length; i++) {
|
||||
resourceArray.push({ label: this._model.columns[i], value: element.data[i], data: element.data });
|
||||
}
|
||||
|
||||
this._bottomTableData.clear();
|
||||
this._bottomTableData.push(resourceArray);
|
||||
if (bottomTableView.isExpanded()) {
|
||||
bottomTableView.setExpanded(false);
|
||||
bottomTableView.setExpanded(true);
|
||||
}
|
||||
this._enableTaskButtons(true);
|
||||
} else {
|
||||
this._enableTaskButtons(false);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._topTable.onContextMenu(e => {
|
||||
if (this.hasActions()) {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => this.insightActions,
|
||||
getActionsContext: () => this.topInsightContext(this._topTableData.getItem(e.cell.row))
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._bottomTable.onContextMenu(e => {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => TPromise.as([this._instantiationService.createInstance(CopyInsightDialogSelectionAction, CopyInsightDialogSelectionAction.ID, CopyInsightDialogSelectionAction.LABEL)]),
|
||||
getActionsContext: () => this.bottomInsightContext(this._bottomTableData.getItem(e.cell.row), e.cell)
|
||||
});
|
||||
}));
|
||||
|
||||
this._splitView.addView(topTableView, Sizing.Distribute);
|
||||
this._splitView.addView(bottomTableView, Sizing.Distribute);
|
||||
|
||||
this._register(attachTableStyler(this._topTable, this._themeService));
|
||||
this._register(attachTableStyler(this._bottomTable, this._themeService));
|
||||
|
||||
this._topTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
topTableView.focus();
|
||||
e.stopImmediatePropagation();
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
bottomTableView.focus();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
this._bottomTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
bottomTableView.focus();
|
||||
e.stopImmediatePropagation();
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
let buttonFound = false;
|
||||
for (let index = 0; index < this._taskButtonDisposables.length; index++) {
|
||||
let element = this._taskButtonDisposables[index];
|
||||
if (element instanceof Button && element.enabled) {
|
||||
buttonFound = true;
|
||||
element.focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!buttonFound) {
|
||||
this._closeButton.focus();
|
||||
}
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
this._closeButton = this.addFooterButton('Close', () => this.close());
|
||||
this._register(attachButtonStyler(this._closeButton, this._themeService));
|
||||
this._register(attachModalDialogStyler(this, this._themeService));
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
this._splitView.layout(DOM.getContentHeight(this._container));
|
||||
}
|
||||
|
||||
// insight object
|
||||
public open(input: IInsightsConfigDetails, connectionProfile: IConnectionProfile): void {
|
||||
if (types.isUndefinedOrNull(input) || types.isUndefinedOrNull(connectionProfile)) {
|
||||
return;
|
||||
}
|
||||
this._insight = input;
|
||||
this._connectionProfile = connectionProfile;
|
||||
this.updateTopColumns();
|
||||
this.show();
|
||||
}
|
||||
|
||||
private build(): void {
|
||||
let labelIndex: number;
|
||||
let valueIndex: number;
|
||||
let columnName = this.labelColumnName;
|
||||
if (this._insight.label === undefined || (labelIndex = this._model.columns.indexOf(columnName)) === -1) {
|
||||
labelIndex = 0;
|
||||
}
|
||||
if (this._insight.value === undefined || (valueIndex = this._model.columns.indexOf(this._insight.value)) === -1) {
|
||||
valueIndex = 1;
|
||||
}
|
||||
// convert
|
||||
let inputArray = this._model.getListResources(labelIndex, valueIndex);
|
||||
this._topTableData.clear();
|
||||
this._topTableData.push(inputArray);
|
||||
if (this._insight.actions && this._insight.actions.types) {
|
||||
let tasks = TaskRegistry.getTasks();
|
||||
for (let action of this._insight.actions.types) {
|
||||
let task = tasks.includes(action);
|
||||
let commandAction = MenuRegistry.getCommand(action);
|
||||
let commandLabel = types.isString(commandAction.title) ? commandAction.title : commandAction.title.value;
|
||||
if (task && !this.findFooterButton(commandLabel)) {
|
||||
let button = this.addFooterButton(commandLabel, () => {
|
||||
let element = this._topTable.getSelectedRows();
|
||||
let resource: ListResource;
|
||||
if (element && element.length > 0) {
|
||||
resource = this._topTableData.getItem(element[0]);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let context = this.topInsightContext(resource);
|
||||
this._commandService.executeCommand(action, context);
|
||||
}, 'left');
|
||||
button.enabled = false;
|
||||
this._taskButtonDisposables.push(button);
|
||||
this._taskButtonDisposables.push(attachButtonStyler(button, this._themeService));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.layout();
|
||||
|
||||
// Select and focus the top row
|
||||
this._topTable.grid.gotoCell(0, 1);
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._topTableData.clear();
|
||||
this._bottomTableData.clear();
|
||||
}
|
||||
|
||||
private get labelColumnName(): string {
|
||||
return typeof this._insight.label === 'object' ? this._insight.label.column : this._insight.label;
|
||||
}
|
||||
|
||||
|
||||
public close() {
|
||||
this.hide();
|
||||
dispose(this._taskButtonDisposables);
|
||||
this._taskButtonDisposables = [];
|
||||
}
|
||||
|
||||
protected onClose(e: StandardKeyboardEvent) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
private hasActions(): boolean {
|
||||
return !!(this._insight && this._insight.actions && this._insight.actions.types
|
||||
&& this._insight.actions.types.length > 0);
|
||||
}
|
||||
|
||||
private get insightActions(): TPromise<IAction[]> {
|
||||
let tasks = TaskRegistry.getTasks();
|
||||
let actions = this._insight.actions.types;
|
||||
let returnActions: IAction[] = [];
|
||||
for (let action of actions) {
|
||||
let task = tasks.includes(action);
|
||||
let commandAction = MenuRegistry.getCommand(action);
|
||||
if (task) {
|
||||
returnActions.push(this._instantiationService.createInstance(ExecuteCommandAction, commandAction.id, commandAction.title));
|
||||
}
|
||||
}
|
||||
return TPromise.as(returnActions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the context that should be passed to the action passed on the selected element for the top table
|
||||
* @param element
|
||||
*/
|
||||
private topInsightContext(element: ListResource): IConnectionProfile {
|
||||
let database = this._insight.actions.database || this._connectionProfile.databaseName;
|
||||
let server = this._insight.actions.server || this._connectionProfile.serverName;
|
||||
let user = this._insight.actions.user || this._connectionProfile.userName;
|
||||
let match: Array<string>;
|
||||
match = database.match(insertValueRegex);
|
||||
if (match && match.length > 0) {
|
||||
let index = this._model.columns.indexOf(match[1]);
|
||||
if (index === -1) {
|
||||
error('Could not find column', match[1]);
|
||||
} else {
|
||||
database = database.replace(match[0], element.data[index]);
|
||||
}
|
||||
}
|
||||
|
||||
match = server.match(insertValueRegex);
|
||||
if (match && match.length > 0) {
|
||||
let index = this._model.columns.indexOf(match[1]);
|
||||
if (index === -1) {
|
||||
error('Could not find column', match[1]);
|
||||
} else {
|
||||
server = server.replace(match[0], element.data[index]);
|
||||
}
|
||||
}
|
||||
|
||||
match = user.match(insertValueRegex);
|
||||
if (match && match.length > 0) {
|
||||
let index = this._model.columns.indexOf(match[1]);
|
||||
if (index === -1) {
|
||||
error('Could not find column', match[1]);
|
||||
} else {
|
||||
user = user.replace(match[0], element.data[index]);
|
||||
}
|
||||
}
|
||||
|
||||
let currentProfile = this._connectionProfile as ConnectionProfile;
|
||||
let profile = new ConnectionProfile(this._capabilitiesService, currentProfile);
|
||||
profile.databaseName = database;
|
||||
profile.serverName = server;
|
||||
profile.userName = user;
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the context that should be passed to the action passed on the selected element for the bottom table
|
||||
* @param element
|
||||
*/
|
||||
private bottomInsightContext(element: ListResource, cell: Slick.Cell): IInsightDialogActionContext {
|
||||
|
||||
let cellData = element[this._bottomColumns[cell.cell].id];
|
||||
|
||||
return { profile: undefined, cellData };
|
||||
}
|
||||
|
||||
private _enableTaskButtons(val: boolean): void {
|
||||
for (let index = 0; index < this._taskButtonDisposables.length; index++) {
|
||||
let element = this._taskButtonDisposables[index];
|
||||
if (element instanceof Button) {
|
||||
element.enabled = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.insights span {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.insights .icon-span {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.insights .badge .badge-content {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 8px;
|
||||
min-width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.insights .badge {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 5px;
|
||||
overflow: hidden;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInsightDialogActionContext } from 'sql/workbench/services/insights/common/insightsDialogService';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
export class CopyInsightDialogSelectionAction extends Action {
|
||||
public static ID = 'workbench.action.insights.copySelection';
|
||||
public static LABEL = nls.localize('workbench.action.insights.copySelection', "Copy Cell");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IClipboardService private _clipboardService: IClipboardService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: IInsightDialogActionContext): TPromise<any> {
|
||||
this._clipboardService.writeText(event.cellData);
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInsightsDialogModel, ListResource } from 'sql/workbench/services/insights/common/insightsDialogService';
|
||||
import { IInsightsConfigDetails, IInsightsLabel } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { Conditional } from 'sql/parts/dashboard/common/interfaces';
|
||||
|
||||
import { Event, Emitter, debounceEvent } from 'vs/base/common/event';
|
||||
|
||||
export class InsightsDialogModel implements IInsightsDialogModel {
|
||||
private _rows: string[][];
|
||||
private _columns: string[];
|
||||
private _insight: IInsightsConfigDetails;
|
||||
|
||||
private _onDataChangeEmitter: Emitter<void> = new Emitter<void>();
|
||||
private _onDataChangeEvent: Event<void> = this._onDataChangeEmitter.event;
|
||||
public onDataChange: Event<void> = debounceEvent(this._onDataChangeEvent, (last, event) => event, 75, false);
|
||||
|
||||
public set insight(insight: IInsightsConfigDetails) {
|
||||
this._insight = insight;
|
||||
}
|
||||
|
||||
public set rows(val: string[][]) {
|
||||
this._rows = val;
|
||||
this._onDataChangeEmitter.fire();
|
||||
}
|
||||
|
||||
public get rows(): string[][] {
|
||||
return this._rows;
|
||||
}
|
||||
|
||||
public set columns(val: string[]) {
|
||||
this._columns = val;
|
||||
this._onDataChangeEmitter.fire();
|
||||
}
|
||||
|
||||
public get columns(): string[] {
|
||||
return this._columns;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._columns = [];
|
||||
this._rows = [];
|
||||
this._onDataChangeEmitter.fire();
|
||||
}
|
||||
|
||||
public getListResources(labelIndex: number, valueIndex: number): ListResource[] {
|
||||
return this.rows.map((item) => {
|
||||
let label = item[labelIndex];
|
||||
let value = item[valueIndex];
|
||||
let state = this.calcInsightState(value);
|
||||
let data = item;
|
||||
let icon = typeof this._insight.label === 'object' ? this._insight.label.icon : undefined;
|
||||
let rval = { title: false, label, value, icon, data };
|
||||
if (state) {
|
||||
rval[state.type] = state.val;
|
||||
}
|
||||
return rval;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the state of the item value passed based on the insight conditions
|
||||
* @param item item to determine state for
|
||||
* @returns json that specifies whether the state is an icon or color and the val of that state
|
||||
*/
|
||||
private calcInsightState(item: string): { type: 'stateColor' | 'stateIcon', val: string } {
|
||||
if (typeof this._insight.label === 'string') {
|
||||
return undefined;
|
||||
} else {
|
||||
let label = <IInsightsLabel>this._insight.label;
|
||||
for (let cond of label.state) {
|
||||
switch (Conditional[cond.condition.if]) {
|
||||
case Conditional.always:
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
case Conditional.equals:
|
||||
if (item === cond.condition.equals) {
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
}
|
||||
break;
|
||||
case Conditional.notEquals:
|
||||
if (item !== cond.condition.equals) {
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
}
|
||||
break;
|
||||
case Conditional.greaterThanOrEquals:
|
||||
if (parseInt(item) >= parseInt(cond.condition.equals)) {
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
}
|
||||
break;
|
||||
case Conditional.greaterThan:
|
||||
if (parseInt(item) > parseInt(cond.condition.equals)) {
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
}
|
||||
break;
|
||||
case Conditional.lessThanOrEquals:
|
||||
if (parseInt(item) <= parseInt(cond.condition.equals)) {
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
}
|
||||
break;
|
||||
case Conditional.lessThan:
|
||||
if (parseInt(item) < parseInt(cond.condition.equals)) {
|
||||
return cond.color
|
||||
? { type: 'stateColor', val: cond.color }
|
||||
: { type: 'stateIcon', val: cond.icon };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we got to this point, there was no matching conditionals therefore no valid state
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
import { IInsightsConfigDetails, IInsightsConfig } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { BaseActionContext } from 'sql/workbench/common/actions';
|
||||
|
||||
export interface IInsightsDialogModel {
|
||||
rows: string[][];
|
||||
columns: string[];
|
||||
getListResources(labelIndex: number, valueIndex: number): ListResource[];
|
||||
reset(): void;
|
||||
onDataChange: Event<void>;
|
||||
insight: IInsightsConfigDetails;
|
||||
}
|
||||
|
||||
export interface ListResource {
|
||||
value: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
data?: string[];
|
||||
stateColor?: string;
|
||||
stateIcon?: string;
|
||||
}
|
||||
|
||||
export const IInsightsDialogService = createDecorator<IInsightsDialogService>('insightsDialogService');
|
||||
|
||||
export interface IInsightsDialogService {
|
||||
_serviceBrand: any;
|
||||
show(input: IInsightsConfig, connectionProfile: IConnectionProfile): void;
|
||||
close();
|
||||
}
|
||||
|
||||
export interface IInsightDialogActionContext extends BaseActionContext {
|
||||
cellData: string;
|
||||
}
|
||||
|
||||
/* Regex that matches the form `${value}` */
|
||||
export const insertValueRegex: RegExp = /\${(.*?)\}/;
|
||||
@@ -0,0 +1,199 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { IInsightsConfigDetails } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import QueryRunner, { EventType as QREvents } from 'sql/platform/query/common/queryRunner';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import { IInsightsDialogModel, insertValueRegex } from 'sql/workbench/services/insights/common/insightsDialogService';
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
|
||||
import { DbCellValue, IDbColumn, QueryExecuteSubsetResult } from 'sqlops';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class InsightsDialogController {
|
||||
private _queryRunner: QueryRunner;
|
||||
private _connectionProfile: IConnectionProfile;
|
||||
private _connectionUri: string;
|
||||
private _columns: IDbColumn[];
|
||||
private _rows: DbCellValue[][];
|
||||
|
||||
constructor(
|
||||
private _model: IInsightsDialogModel,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IWorkspaceContextService private _workspaceContextService: IWorkspaceContextService
|
||||
) { }
|
||||
|
||||
public update(input: IInsightsConfigDetails, connectionProfile: IConnectionProfile): Thenable<void> {
|
||||
// execute string
|
||||
if (typeof input === 'object') {
|
||||
if (connectionProfile === undefined) {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize("insightsInputError", "No Connection Profile was passed to insights flyout")
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (types.isStringArray(input.query)) {
|
||||
return this.createQuery(input.query.join(' '), connectionProfile).catch(e => {
|
||||
this._errorMessageService.showDialog(Severity.Error, nls.localize("insightsError", "Insights error"), e);
|
||||
}).then(() => undefined);
|
||||
} else if (types.isString(input.query)) {
|
||||
return this.createQuery(input.query, connectionProfile).catch(e => {
|
||||
this._errorMessageService.showDialog(Severity.Error, nls.localize("insightsError", "Insights error"), e);
|
||||
}).then(() => undefined);
|
||||
} else if (types.isString(input.queryFile)) {
|
||||
let filePath = input.queryFile;
|
||||
// check for workspace relative path
|
||||
let match = filePath.match(insertValueRegex);
|
||||
if (match && match.length > 0 && match[1] === 'workspaceRoot') {
|
||||
filePath = filePath.replace(match[0], '');
|
||||
|
||||
switch (this._workspaceContextService.getWorkbenchState()) {
|
||||
case WorkbenchState.FOLDER:
|
||||
filePath = this._workspaceContextService.getWorkspace().folders[0].toResource(filePath).fsPath;
|
||||
break;
|
||||
case WorkbenchState.WORKSPACE:
|
||||
let filePathArray = filePath.split('/');
|
||||
// filter out empty sections
|
||||
filePathArray = filePathArray.filter(i => !!i);
|
||||
let folder = this._workspaceContextService.getWorkspace().folders.find(i => i.name === filePathArray[0]);
|
||||
if (!folder) {
|
||||
return Promise.reject<void>(new Error(`Could not find workspace folder ${filePathArray[0]}`));
|
||||
}
|
||||
// remove the folder name from the filepath
|
||||
filePathArray.shift();
|
||||
// rejoin the filepath after doing the work to find the right folder
|
||||
filePath = '/' + filePathArray.join('/');
|
||||
filePath = folder.toResource(filePath).fsPath;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
pfs.readFile(filePath).then(
|
||||
buffer => {
|
||||
this.createQuery(buffer.toString(), connectionProfile).catch(e => {
|
||||
this._errorMessageService.showDialog(Severity.Error, nls.localize("insightsError", "Insights error"), e);
|
||||
}).then(() => resolve());
|
||||
},
|
||||
error => {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize("insightsFileError", "There was an error reading the query file: ") + error
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
error('Error reading details Query: ', input);
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize("insightsConfigError", "There was an error parsing the insight config; could not find query array/string or queryfile")
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private async createQuery(queryString: string, connectionProfile: IConnectionProfile): Promise<void> {
|
||||
if (this._queryRunner) {
|
||||
if (!this._queryRunner.hasCompleted) {
|
||||
await this._queryRunner.cancelQuery();
|
||||
}
|
||||
try {
|
||||
await this.createNewConnection(connectionProfile);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
this._queryRunner.uri = this._connectionUri;
|
||||
} else {
|
||||
try {
|
||||
await this.createNewConnection(connectionProfile);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
this._queryRunner = this._instantiationService.createInstance(QueryRunner, this._connectionUri);
|
||||
this.addQueryEventListeners(this._queryRunner);
|
||||
}
|
||||
|
||||
return this._queryRunner.runQuery(queryString);
|
||||
}
|
||||
|
||||
private async createNewConnection(connectionProfile: IConnectionProfile): Promise<void> {
|
||||
// determine if we need to create a new connection
|
||||
if (!this._connectionProfile || connectionProfile.getOptionsKey() !== this._connectionProfile.getOptionsKey()) {
|
||||
if (this._connectionProfile) {
|
||||
try {
|
||||
await this._connectionManagementService.disconnect(this._connectionUri);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
this._connectionProfile = connectionProfile;
|
||||
this._connectionUri = Utils.generateUri(this._connectionProfile, 'insights');
|
||||
return this._connectionManagementService.connect(this._connectionProfile, this._connectionUri).then(result => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private addQueryEventListeners(queryRunner: QueryRunner): void {
|
||||
queryRunner.addListener(QREvents.COMPLETE, () => {
|
||||
this.queryComplete().catch(error => {
|
||||
this._errorMessageService.showDialog(Severity.Error, nls.localize("insightsError", "Insights error"), error);
|
||||
});
|
||||
});
|
||||
queryRunner.addListener(QREvents.MESSAGE, message => {
|
||||
if (message.isError) {
|
||||
this._errorMessageService.showDialog(Severity.Error, nls.localize("insightsError", "Insights error"), message.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async queryComplete(): Promise<void> {
|
||||
let batches = this._queryRunner.batchSets;
|
||||
// currently only support 1 batch set 1 resultset
|
||||
if (batches.length > 0) {
|
||||
let batch = batches[0];
|
||||
if (batch.resultSetSummaries.length > 0
|
||||
&& batch.resultSetSummaries[0].rowCount > 0
|
||||
) {
|
||||
let resultset = batch.resultSetSummaries[0];
|
||||
this._columns = resultset.columnInfo;
|
||||
let rows: QueryExecuteSubsetResult;
|
||||
try {
|
||||
rows = await this._queryRunner.getQueryRows(0, resultset.rowCount, batch.id, resultset.id);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
this._rows = rows.resultSubset.rows;
|
||||
this.updateModel();
|
||||
}
|
||||
}
|
||||
// TODO issue #2746 should ideally show a warning inside the dialog if have no data
|
||||
}
|
||||
|
||||
private updateModel(): void {
|
||||
let data = this._rows.map(r => r.map(c => c.displayValue));
|
||||
let columns = this._columns.map(c => c.columnName);
|
||||
|
||||
this._model.rows = data;
|
||||
this._model.columns = columns;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user