mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
table based explorer widget (#10279)
* bump sts * extend widget container * remove title * wip * refactoring * Revert "extend widget container" * showTitle option * fix properties widget error * icon column * icon and button columns * use textwithicon column * icon * refactor and filter * context menu * refactor * tests * fix hygiene * tests * comments
This commit is contained in:
@@ -366,6 +366,28 @@
|
||||
"displayName": "%onprem.serverProperties.osVersion%",
|
||||
"value": "osVersion"
|
||||
}
|
||||
],
|
||||
"databasesListProperties": [
|
||||
{
|
||||
"displayName": "%databasesListProperties.name%",
|
||||
"value": "name",
|
||||
"widthWeight": 60
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.status%",
|
||||
"value": "state",
|
||||
"widthWeight": 10
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.size%",
|
||||
"value": "sizeInMB",
|
||||
"widthWeight": 10
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.lastBackup%",
|
||||
"value": "lastBackup",
|
||||
"widthWeight": 20
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -404,6 +426,23 @@
|
||||
"displayName": "%cloud.serverProperties.serverEdition%",
|
||||
"value": "serverEdition"
|
||||
}
|
||||
],
|
||||
"databasesListProperties": [
|
||||
{
|
||||
"displayName": "%databasesListProperties.name%",
|
||||
"value": "name",
|
||||
"widthWeight": 60
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.status%",
|
||||
"value": "state",
|
||||
"widthWeight": 20
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.size%",
|
||||
"value": "sizeInMB",
|
||||
"widthWeight": 20
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -434,6 +473,23 @@
|
||||
"displayName": "%cloud.serverProperties.serverEdition%",
|
||||
"value": "serverEdition"
|
||||
}
|
||||
],
|
||||
"databasesListProperties": [
|
||||
{
|
||||
"displayName": "%databasesListProperties.name%",
|
||||
"value": "name",
|
||||
"widthWeight": 60
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.status%",
|
||||
"value": "state",
|
||||
"widthWeight": 20
|
||||
},
|
||||
{
|
||||
"displayName": "%databasesListProperties.size%",
|
||||
"value": "sizeInMB",
|
||||
"widthWeight": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -140,5 +140,10 @@
|
||||
"mssql.connectionOptions.packetSize.displayName": "Packet size",
|
||||
"mssql.connectionOptions.packetSize.description": "Size in bytes of the network packets used to communicate with an instance of SQL Server",
|
||||
"mssql.connectionOptions.typeSystemVersion.displayName": "Type system version",
|
||||
"mssql.connectionOptions.typeSystemVersion.description": "Indicates which server type system the provider will expose through the DataReader"
|
||||
"mssql.connectionOptions.typeSystemVersion.description": "Indicates which server type system the provider will expose through the DataReader",
|
||||
"databasesListProperties.name": "Name",
|
||||
"databasesListProperties.status": "Status",
|
||||
"databasesListProperties.size": "Size (MB)",
|
||||
"databasesListProperties.lastBackup": "Last backup",
|
||||
"objectsListProperties.name": "Name"
|
||||
}
|
||||
|
||||
@@ -162,3 +162,27 @@
|
||||
.hc-black .slick-header-menu {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.slick-icon-cell-content,
|
||||
.slick-button-cell-content {
|
||||
background-position: 7px center !important;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-left: 30px;
|
||||
color: inherit !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slick-icon-cell,
|
||||
.slick-button-cell {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.slick-button-cell-content {
|
||||
cursor: pointer;
|
||||
border-width: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
99
src/sql/base/browser/ui/table/plugins/buttonColumn.plugin.ts
Normal file
99
src/sql/base/browser/ui/table/plugins/buttonColumn.plugin.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextWithIconColumnDefinition } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export interface ButtonColumnDefinition<T extends Slick.SlickData> extends TextWithIconColumnDefinition<T> {
|
||||
}
|
||||
|
||||
export interface ButtonColumnOptions {
|
||||
iconCssClass?: string;
|
||||
title?: string;
|
||||
width?: number;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface ButtonClickEventArgs<T extends Slick.SlickData> {
|
||||
item: T;
|
||||
position: { x: number, y: number };
|
||||
}
|
||||
|
||||
export class ButtonColumn<T extends Slick.SlickData> implements Slick.Plugin<T> {
|
||||
private _handler = new Slick.EventHandler();
|
||||
private _definition: ButtonColumnDefinition<T>;
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _onClick = new Emitter<ButtonClickEventArgs<T>>();
|
||||
public onClick = this._onClick.event;
|
||||
|
||||
constructor(private options: ButtonColumnOptions) {
|
||||
this._definition = {
|
||||
id: options.id,
|
||||
resizable: false,
|
||||
name: '',
|
||||
formatter: (row: number, cell: number, value: any, columnDef: Slick.Column<T>, dataContext: T): string => {
|
||||
return this.formatter(row, cell, value, columnDef, dataContext);
|
||||
},
|
||||
width: options.width,
|
||||
selectable: false,
|
||||
iconCssClassField: options.iconCssClass
|
||||
};
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>): void {
|
||||
this._grid = grid;
|
||||
this._handler.subscribe(grid.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(args));
|
||||
this._handler.subscribe(grid.onKeyDown, (e: DOMEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyboardEvent(e as KeyboardEvent, args));
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private handleClick(args: Slick.OnClickEventArgs<T>): void {
|
||||
if (this.shouldFireClickEvent(args.cell)) {
|
||||
this._grid.setActiveCell(args.row, args.cell);
|
||||
this.fireClickEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyboardEvent(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) && this.shouldFireClickEvent(args.cell)) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.fireClickEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public get definition(): ButtonColumnDefinition<T> {
|
||||
return this._definition;
|
||||
}
|
||||
|
||||
private fireClickEvent(): void {
|
||||
const activeCell = this._grid.getActiveCell();
|
||||
const activeCellPosition = this._grid.getActiveCellPosition();
|
||||
if (activeCell && activeCellPosition) {
|
||||
this._onClick.fire({
|
||||
item: this._grid.getDataItem(activeCell.row),
|
||||
position: {
|
||||
x: (activeCellPosition.left + activeCellPosition.right) / 2,
|
||||
y: (activeCellPosition.bottom + activeCellPosition.top) / 2
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private shouldFireClickEvent(columnIndex: number): boolean {
|
||||
return this._grid.getColumns()[columnIndex].id === this.definition.id;
|
||||
}
|
||||
|
||||
private formatter(row: number, cell: number, value: any, columnDef: Slick.Column<T>, dataContext: T): string {
|
||||
const buttonColumn = columnDef as ButtonColumnDefinition<T>;
|
||||
return `<div class="codicon icon slick-button-cell-content ${buttonColumn.iconCssClassField}" aria-label="${this.options.title}"></div>`;
|
||||
}
|
||||
}
|
||||
46
src/sql/base/browser/ui/table/plugins/textWithIconColumn.ts
Normal file
46
src/sql/base/browser/ui/table/plugins/textWithIconColumn.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Definition for column with icon on the left of text.
|
||||
*/
|
||||
export interface TextWithIconColumnDefinition<T extends Slick.SlickData> extends Slick.Column<T> {
|
||||
iconCssClassField?: string;
|
||||
}
|
||||
|
||||
export interface TextWithIconColumnOptions {
|
||||
iconCssClassField?: string;
|
||||
field?: string;
|
||||
width?: number;
|
||||
id?: string;
|
||||
resizable?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class TextWithIconColumn<T extends Slick.SlickData> {
|
||||
|
||||
private _definition: TextWithIconColumnDefinition<T>;
|
||||
|
||||
constructor(options: TextWithIconColumnOptions) {
|
||||
this._definition = {
|
||||
id: options.id,
|
||||
field: options.field,
|
||||
resizable: options.resizable,
|
||||
formatter: this.formatter,
|
||||
width: options.width,
|
||||
name: options.name,
|
||||
iconCssClassField: options.iconCssClassField,
|
||||
cssClass: 'slick-icon-cell'
|
||||
};
|
||||
}
|
||||
private formatter(row: number, cell: number, value: any, columnDef: Slick.Column<T>, dataContext: T): string {
|
||||
const iconColumn = columnDef as TextWithIconColumnDefinition<T>;
|
||||
return `<div class="icon codicon slick-icon-cell-content ${dataContext[iconColumn.iconCssClassField]}">${value}</div>`;
|
||||
}
|
||||
|
||||
public get definition(): TextWithIconColumnDefinition<T> {
|
||||
return this._definition;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,9 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
|
||||
private _onClick = new Emitter<ITableMouseEvent>();
|
||||
public readonly onClick: Event<ITableMouseEvent> = this._onClick.event;
|
||||
|
||||
private _onDoubleClick = new Emitter<ITableMouseEvent>();
|
||||
public readonly onDoubleClick: Event<ITableMouseEvent> = this._onDoubleClick.event;
|
||||
|
||||
private _onHeaderClick = new Emitter<ITableMouseEvent>();
|
||||
public readonly onHeaderClick: Event<ITableMouseEvent> = this._onHeaderClick.event;
|
||||
|
||||
@@ -116,6 +119,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
|
||||
this.mapMouseEvent(this._grid.onContextMenu, this._onContextMenu);
|
||||
this.mapMouseEvent(this._grid.onClick, this._onClick);
|
||||
this.mapMouseEvent(this._grid.onHeaderClick, this._onHeaderClick);
|
||||
this.mapMouseEvent(this._grid.onDblClick, this._onDoubleClick);
|
||||
this._grid.onColumnsResized.subscribe(() => this._onColumnResize.fire());
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { BackupAction } from 'sql/workbench/contrib/backup/browser/backupActions
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
import { MssqlNodeContext } from 'sql/workbench/services/objectExplorer/browser/mssqlNodeContext';
|
||||
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
|
||||
@@ -24,7 +24,7 @@ dashboard-widget-wrapper .widgetHeader {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
dashboard-widget-wrapper .icon {
|
||||
dashboard-widget-wrapper .widgetHeader .icon {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
margin-left: 5px;
|
||||
|
||||
@@ -9,17 +9,57 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
|
||||
import { ProviderProperties } from 'sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component';
|
||||
import { DATABASE_DASHBOARD_TABS } from 'sql/workbench/contrib/dashboard/browser/pages/databaseDashboardPage.contribution';
|
||||
import { SERVER_DASHBOARD_TABS } from 'sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.contribution';
|
||||
import { DASHBOARD_CONFIG_ID, DASHBOARD_TABS_KEY_PROPERTY } from 'sql/workbench/contrib/dashboard/browser/pages/dashboardPageContribution';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
import { IDashboardTab, IDashboardTabGroup } from 'sql/workbench/services/dashboard/browser/common/interfaces';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export const Extensions = {
|
||||
DashboardContributions: 'dashboard.contributions'
|
||||
};
|
||||
|
||||
export interface ServerInfo {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface PropertiesConfig {
|
||||
properties: Array<Property>;
|
||||
}
|
||||
|
||||
export interface FlavorProperties {
|
||||
flavor: string;
|
||||
condition?: ConditionProperties;
|
||||
conditions?: Array<ConditionProperties>;
|
||||
databaseProperties: Array<Property>;
|
||||
serverProperties: Array<Property>;
|
||||
databasesListProperties?: Array<ObjectListViewProperty>;
|
||||
objectsListProperties?: Array<ObjectListViewProperty>;
|
||||
}
|
||||
|
||||
export interface ConditionProperties {
|
||||
field: string;
|
||||
operator: '==' | '<=' | '>=' | '!=';
|
||||
value: string | boolean;
|
||||
}
|
||||
|
||||
export interface ProviderProperties {
|
||||
provider: string;
|
||||
flavors: Array<FlavorProperties>;
|
||||
}
|
||||
|
||||
export interface Property {
|
||||
displayName: string;
|
||||
value: string;
|
||||
ignore?: Array<string>;
|
||||
default?: string;
|
||||
}
|
||||
|
||||
export interface ObjectListViewProperty extends Property {
|
||||
widthWeight?: number;
|
||||
}
|
||||
|
||||
export interface IDashboardRegistry {
|
||||
registerDashboardProvider(id: string, properties: ProviderProperties): void;
|
||||
getProperties(id: string): ProviderProperties;
|
||||
@@ -29,6 +69,84 @@ export interface IDashboardRegistry {
|
||||
tabGroups: Array<IDashboardTabGroup>;
|
||||
}
|
||||
|
||||
export function getFlavor(serverInfo: ServerInfo, logService: ILogService, provider: string): FlavorProperties | undefined {
|
||||
const dashboardRegistry = Registry.as<IDashboardRegistry>(Extensions.DashboardContributions);
|
||||
const providerProperties = dashboardRegistry.getProperties(provider);
|
||||
|
||||
if (!providerProperties) {
|
||||
logService.error('No property definitions found for provider', provider);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let flavor: FlavorProperties;
|
||||
|
||||
// find correct flavor
|
||||
if (providerProperties.flavors.length === 1) {
|
||||
flavor = providerProperties.flavors[0];
|
||||
} else if (providerProperties.flavors.length === 0) {
|
||||
logService.error('No flavor definitions found for "', provider,
|
||||
'. If there are not multiple flavors of this provider, add one flavor without a condition');
|
||||
return undefined;
|
||||
} else {
|
||||
const flavorArray = providerProperties.flavors.filter((item) => {
|
||||
|
||||
// For backward compatibility we are supporting array of conditions and single condition.
|
||||
// If nothing is specified, we return false.
|
||||
if (item.conditions) {
|
||||
let conditionResult = true;
|
||||
for (let i = 0; i < item.conditions.length; i++) {
|
||||
conditionResult = conditionResult && getConditionResult(logService, serverInfo, item, item.conditions[i]);
|
||||
}
|
||||
|
||||
return conditionResult;
|
||||
}
|
||||
else if (item.condition) {
|
||||
return getConditionResult(logService, serverInfo, item, item.condition);
|
||||
}
|
||||
else {
|
||||
logService.error('No condition was specified.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (flavorArray.length === 0) {
|
||||
logService.error('Could not determine flavor');
|
||||
return undefined;
|
||||
} else if (flavorArray.length > 1) {
|
||||
logService.error('Multiple flavors matched correctly for this provider', provider);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
flavor = flavorArray[0];
|
||||
}
|
||||
return flavor;
|
||||
}
|
||||
|
||||
function getConditionResult(logService: ILogService, serverInfo: ServerInfo, item: FlavorProperties, conditionItem: ConditionProperties): boolean {
|
||||
let condition = serverInfo[conditionItem.field];
|
||||
|
||||
// If we need to compare strings, then we should ensure that condition is string
|
||||
// Otherwise tripple equals/unequals would return false values
|
||||
if (typeof conditionItem.value === 'string') {
|
||||
condition = condition.toString();
|
||||
}
|
||||
|
||||
switch (conditionItem.operator) {
|
||||
case '==':
|
||||
return condition === conditionItem.value;
|
||||
case '!=':
|
||||
return condition !== conditionItem.value;
|
||||
case '>=':
|
||||
return condition >= conditionItem.value;
|
||||
case '<=':
|
||||
return condition <= conditionItem.value;
|
||||
default:
|
||||
logService.error('Could not parse operator: "', conditionItem.operator,
|
||||
'" on item "', item, '"');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardRegistry implements IDashboardRegistry {
|
||||
private _properties = new Map<string, ProviderProperties>();
|
||||
private _tabs = new Array<IDashboardTab>();
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MetadataType } from 'sql/platform/connection/common/connectionManagement';
|
||||
|
||||
export class ExplorerFilter {
|
||||
constructor(private context: string, private targetProperties: string[]) {
|
||||
}
|
||||
|
||||
public filter(filterString: string, data: Slick.SlickData[]): Slick.SlickData[] {
|
||||
if (filterString) {
|
||||
let metadataType: MetadataType;
|
||||
if (this.context === 'database' && filterString.indexOf(':') > -1) {
|
||||
const filterArray = filterString.split(':');
|
||||
|
||||
if (filterArray.length > 2) {
|
||||
filterString = filterArray.slice(1, filterArray.length - 1).join(':');
|
||||
} else {
|
||||
filterString = filterArray[1];
|
||||
}
|
||||
|
||||
switch (filterArray[0].toLowerCase()) {
|
||||
case 'v':
|
||||
metadataType = MetadataType.View;
|
||||
break;
|
||||
case 't':
|
||||
metadataType = MetadataType.Table;
|
||||
break;
|
||||
case 'sp':
|
||||
metadataType = MetadataType.SProc;
|
||||
break;
|
||||
case 'f':
|
||||
metadataType = MetadataType.Function;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return data.filter((item: Slick.SlickData) => {
|
||||
if (metadataType !== undefined && item.metadataType !== metadataType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let match = false;
|
||||
for (let i = 0; i < this.targetProperties.length; i++) {
|
||||
const property = this.targetProperties[i];
|
||||
const val = item[property];
|
||||
if (item[property] && typeof val === 'string' &&
|
||||
val.toLowerCase().indexOf(filterString.toLowerCase()) !== -1) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
});
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin';
|
||||
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||
import { TextWithIconColumn } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||
import { BaseActionContext, ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { getFlavor, ObjectListViewProperty } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
import { ExplorerFilter } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerFilter';
|
||||
import { ExplorerView, NameProperty } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerView';
|
||||
import { ObjectMetadataWrapper } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/objectMetadataWrapper';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { status } from 'vs/base/browser/ui/aria/aria';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as nls from 'vs/nls';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
const ShowActionsText: string = nls.localize('dashboard.explorer.actions', "Show Actions");
|
||||
const IconClassProperty: string = 'iconClass';
|
||||
export const ConnectionProfilePropertyName: string = 'connection_profile';
|
||||
|
||||
/**
|
||||
* Table for explorer widget
|
||||
*/
|
||||
export class ExplorerTable extends Disposable {
|
||||
private readonly contextKey = new ItemContextKey(this.contextKeyService);
|
||||
private _table: Table<Slick.SlickData>;
|
||||
private _view: TableDataView<Slick.SlickData>;
|
||||
private _actionsColumn: ButtonColumn<Slick.SlickData>;
|
||||
private _filterStr: string;
|
||||
private _explorerView: ExplorerView;
|
||||
private _displayProperties: ObjectListViewProperty[];
|
||||
|
||||
constructor(private parentElement: HTMLElement,
|
||||
private readonly router: Router,
|
||||
private readonly context: string,
|
||||
private readonly bootStrapService: CommonServiceInterface,
|
||||
readonly themeService: IThemeService,
|
||||
private readonly contextMenuService: IContextMenuService,
|
||||
private readonly menuService: IMenuService,
|
||||
private readonly contextKeyService: IContextKeyService,
|
||||
private readonly progressService: IEditorProgressService,
|
||||
private readonly logService: ILogService) {
|
||||
super();
|
||||
this._explorerView = new ExplorerView(this.context);
|
||||
this._table = new Table<Slick.SlickData>(parentElement, undefined, { forceFitColumns: true, rowHeight: 35 });
|
||||
this._table.setSelectionModel(new RowSelectionModel());
|
||||
this._actionsColumn = new ButtonColumn<Slick.SlickData>({
|
||||
id: 'actions',
|
||||
iconCssClass: 'toggle-more',
|
||||
title: ShowActionsText,
|
||||
width: 40
|
||||
});
|
||||
const connectionInfo = this.bootStrapService.connectionManagementService.connectionInfo;
|
||||
this._displayProperties = this._explorerView.getPropertyList(getFlavor(connectionInfo.serverInfo, this.logService, connectionInfo.providerId));
|
||||
const explorerFilter = new ExplorerFilter(this.context, this._displayProperties.map(p => p.value));
|
||||
this._table.registerPlugin(this._actionsColumn);
|
||||
this._register(this._actionsColumn.onClick((args) => {
|
||||
this.showContextMenu(args.item, args.position);
|
||||
}));
|
||||
this._register(this._table.onContextMenu((e) => {
|
||||
if (e.cell) {
|
||||
this.showContextMenu(this._view.getItem(e.cell.row), e.anchor);
|
||||
}
|
||||
}));
|
||||
this._register(this._table.onDoubleClick((e) => {
|
||||
if (e.cell) {
|
||||
this.handleDoubleClick(this._view.getItem(e.cell.row));
|
||||
}
|
||||
}));
|
||||
this._register(attachTableStyler(this._table, themeService));
|
||||
this._view = new TableDataView<Slick.SlickData>(undefined, undefined, undefined, (data: Slick.SlickData[]): Slick.SlickData[] => {
|
||||
return explorerFilter.filter(this._filterStr, data);
|
||||
});
|
||||
this._register(this._view);
|
||||
this._register(this._view.onRowCountChange(() => {
|
||||
this._table.updateRowCount();
|
||||
}));
|
||||
this._register(this._view.onFilterStateChange(() => {
|
||||
this._table.grid.invalidateAllRows();
|
||||
this._table.updateRowCount();
|
||||
}));
|
||||
}
|
||||
|
||||
private showContextMenu(item: Slick.SlickData, anchor: HTMLElement | { x: number, y: number }): void {
|
||||
const dataContext = (item instanceof ObjectMetadataWrapper) ? item : item[ConnectionProfilePropertyName] as ConnectionProfile;
|
||||
|
||||
this.contextKey.set({
|
||||
resource: dataContext,
|
||||
providerName: this.bootStrapService.connectionManagementService.connectionInfo.providerId,
|
||||
isCloud: this.bootStrapService.connectionManagementService.connectionInfo.serverInfo.isCloud,
|
||||
engineEdition: this.bootStrapService.connectionManagementService.connectionInfo.serverInfo.engineEditionId
|
||||
});
|
||||
|
||||
let context: ManageActionContext | BaseActionContext;
|
||||
|
||||
if (dataContext instanceof ObjectMetadataWrapper) {
|
||||
context = {
|
||||
object: dataContext,
|
||||
profile: this.bootStrapService.connectionManagementService.connectionInfo.connectionProfile
|
||||
};
|
||||
} else {
|
||||
context = {
|
||||
profile: dataContext,
|
||||
uri: this.bootStrapService.getUnderlyingUri()
|
||||
};
|
||||
}
|
||||
|
||||
const menu = this.menuService.createMenu(MenuId.ExplorerWidgetContext, this.contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline');
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => result.secondary,
|
||||
getActionsContext: () => context
|
||||
});
|
||||
}
|
||||
|
||||
private handleDoubleClick(item: Slick.SlickData): void {
|
||||
if (this.context === 'server') {
|
||||
this.progressService.showWhile(this.bootStrapService.connectionManagementService.changeDatabase(item[NameProperty]).then(result => {
|
||||
this.router.navigate(['database-dashboard']).catch(onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public filter(filterStr: string): void {
|
||||
this._filterStr = filterStr;
|
||||
this._view.clearFilter();
|
||||
this._view.filter();
|
||||
const count = this._view.getItems().length;
|
||||
let message: string;
|
||||
if (count === 0) {
|
||||
message = nls.localize('explorerSearchNoMatchResultMessage', "No matching item found");
|
||||
} else if (count === 1) {
|
||||
message = nls.localize('explorerSearchSingleMatchResultMessage', "Filtered search list to 1 item");
|
||||
} else {
|
||||
message = nls.localize('explorerSearchMatchResultMessage', "Filtered search list to {0} items", count);
|
||||
}
|
||||
status(message);
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this._table.layout(new DOM.Dimension(
|
||||
DOM.getContentWidth(this.parentElement),
|
||||
DOM.getContentHeight(this.parentElement)));
|
||||
this._table.columns = this.columnDefinitions;
|
||||
}
|
||||
|
||||
public setData(items: Slick.SlickData[]): void {
|
||||
this._table.columns = this.columnDefinitions;
|
||||
this._view.clear();
|
||||
this._view.clearFilter();
|
||||
items.forEach(item => {
|
||||
item[IconClassProperty] = this._explorerView.getIconClass(item);
|
||||
});
|
||||
this._table.setData(this._view);
|
||||
this._view.push(items);
|
||||
}
|
||||
|
||||
private get columnDefinitions(): Slick.Column<Slick.SlickData>[] {
|
||||
const totalWidth = DOM.getContentWidth(this.parentElement);
|
||||
let totalColumnWidthWeight: number = 0;
|
||||
this._displayProperties.forEach(p => {
|
||||
if (p.widthWeight) {
|
||||
totalColumnWidthWeight += p.widthWeight;
|
||||
}
|
||||
});
|
||||
|
||||
const columns: Slick.Column<Slick.SlickData>[] = this._displayProperties.map(property => {
|
||||
const columnWidth = property.widthWeight ? totalWidth * (property.widthWeight / totalColumnWidthWeight) : undefined;
|
||||
if (property.value === NameProperty) {
|
||||
const nameColumn = new TextWithIconColumn({
|
||||
id: property.value,
|
||||
iconCssClassField: IconClassProperty,
|
||||
width: columnWidth,
|
||||
field: property.value,
|
||||
name: property.displayName
|
||||
});
|
||||
return nameColumn.definition;
|
||||
} else {
|
||||
return <Slick.Column<Slick.SlickData>>{
|
||||
id: property.value,
|
||||
field: property.value,
|
||||
name: property.displayName,
|
||||
width: columnWidth
|
||||
};
|
||||
}
|
||||
});
|
||||
columns.push(this._actionsColumn.definition);
|
||||
return columns;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { MetadataType } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { SingleConnectionManagementService, CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { ManageActionContext, BaseActionContext } from 'sql/workbench/browser/actions';
|
||||
|
||||
import * as tree from 'vs/base/parts/tree/browser/tree';
|
||||
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ObjectMetadataWrapper } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/objectMetadataWrapper';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export declare type TreeResource = IConnectionProfile | ObjectMetadataWrapper;
|
||||
|
||||
// Empty class just for tree input
|
||||
export class ExplorerModel {
|
||||
public static readonly id = generateUuid();
|
||||
}
|
||||
|
||||
export class ExplorerController extends TreeDefaults.DefaultController {
|
||||
private readonly contextKey = new ItemContextKey(this.contextKeyService);
|
||||
|
||||
constructor(
|
||||
// URI for the dashboard for managing, should look into some other way of doing this
|
||||
private _uri: string,
|
||||
private _connectionService: SingleConnectionManagementService,
|
||||
private _router: Router,
|
||||
private readonly bootStrapService: CommonServiceInterface,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IEditorProgressService private readonly progressService: IEditorProgressService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected onLeftClick(tree: tree.ITree, element: TreeResource, event: IMouseEvent, origin: string = 'mouse'): boolean {
|
||||
const payload = { origin: origin };
|
||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||
// Cancel Event
|
||||
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
|
||||
|
||||
if (!isMouseDown) {
|
||||
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
tree.setFocus(element, payload);
|
||||
|
||||
if (!(element instanceof ObjectMetadataWrapper) && isDoubleClick) {
|
||||
event.preventDefault(); // focus moves to editor, we need to prevent default
|
||||
this.handleItemDoubleClick(element);
|
||||
} else {
|
||||
tree.setFocus(element, payload);
|
||||
tree.setSelection([element], payload);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public onContextMenu(tree: tree.ITree, element: TreeResource, event: tree.ContextMenuEvent): boolean {
|
||||
this.contextKey.set({
|
||||
resource: element,
|
||||
providerName: this.bootStrapService.connectionManagementService.connectionInfo.providerId,
|
||||
isCloud: this.bootStrapService.connectionManagementService.connectionInfo.serverInfo.isCloud,
|
||||
engineEdition: this.bootStrapService.connectionManagementService.connectionInfo.serverInfo.engineEditionId
|
||||
});
|
||||
|
||||
let context: ManageActionContext | BaseActionContext;
|
||||
|
||||
if (element instanceof ObjectMetadataWrapper) {
|
||||
context = {
|
||||
object: element,
|
||||
profile: this._connectionService.connectionInfo.connectionProfile
|
||||
};
|
||||
} else {
|
||||
context = {
|
||||
profile: element,
|
||||
uri: this._uri
|
||||
};
|
||||
}
|
||||
|
||||
const menu = this.menuService.createMenu(MenuId.ExplorerWidgetContext, this.contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline');
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => result.secondary,
|
||||
getActionsContext: () => context
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleItemDoubleClick(element: IConnectionProfile): void {
|
||||
this.progressService.showWhile(this._connectionService.changeDatabase(element.databaseName).then(result => {
|
||||
this._router.navigate(['database-dashboard']).catch(onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
protected onEnter(tree: tree.ITree, event: IKeyboardEvent): boolean {
|
||||
const result = super.onEnter(tree, event);
|
||||
if (result) {
|
||||
const focus = tree.getFocus();
|
||||
if (focus && !(focus instanceof ObjectMetadataWrapper)) {
|
||||
this._connectionService.changeDatabase(focus.databaseName).then(result => {
|
||||
this._router.navigate(['database-dashboard']).catch(onUnexpectedError);
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExplorerDataSource implements tree.IDataSource {
|
||||
private _data: TreeResource[];
|
||||
|
||||
public getId(tree: tree.ITree, element: TreeResource | ExplorerModel): string {
|
||||
if (element instanceof ObjectMetadataWrapper) {
|
||||
return element.urn || element.schema + element.name;
|
||||
} else if (element instanceof ExplorerModel) {
|
||||
return ExplorerModel.id;
|
||||
} else {
|
||||
return (element as IConnectionProfile).getOptionsKey();
|
||||
}
|
||||
}
|
||||
|
||||
public hasChildren(tree: tree.ITree, element: TreeResource | ExplorerModel): boolean {
|
||||
if (element instanceof ExplorerModel) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getChildren(tree: tree.ITree, element: TreeResource | ExplorerModel): Promise<TreeResource[]> {
|
||||
if (element instanceof ExplorerModel) {
|
||||
return Promise.resolve(this._data);
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public getParent(tree: tree.ITree, element: TreeResource | ExplorerModel): Promise<ExplorerModel> {
|
||||
if (element instanceof ExplorerModel) {
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
return Promise.resolve(new ExplorerModel());
|
||||
}
|
||||
}
|
||||
|
||||
public set data(data: TreeResource[]) {
|
||||
this._data = data;
|
||||
}
|
||||
}
|
||||
|
||||
enum TEMPLATEIDS {
|
||||
profile = 'profile',
|
||||
object = 'object'
|
||||
}
|
||||
|
||||
export interface IListTemplate {
|
||||
icon?: HTMLElement;
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
export class ExplorerRenderer implements tree.IRenderer {
|
||||
public getHeight(tree: tree.ITree, element: TreeResource): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: tree.ITree, element: TreeResource): string {
|
||||
if (element instanceof ObjectMetadataWrapper) {
|
||||
return TEMPLATEIDS.object;
|
||||
} else {
|
||||
return TEMPLATEIDS.profile;
|
||||
}
|
||||
}
|
||||
|
||||
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate {
|
||||
const row = $('.list-row');
|
||||
const label = $('.label');
|
||||
|
||||
let icon: HTMLElement;
|
||||
if (templateId === TEMPLATEIDS.object) {
|
||||
icon = $('div');
|
||||
} else {
|
||||
icon = $('.icon.database');
|
||||
}
|
||||
|
||||
row.appendChild(icon);
|
||||
row.appendChild(label);
|
||||
container.appendChild(row);
|
||||
|
||||
return { icon, label };
|
||||
}
|
||||
|
||||
public renderElement(tree: tree.ITree, element: TreeResource, templateId: string, templateData: IListTemplate): void {
|
||||
if (element instanceof ObjectMetadataWrapper) {
|
||||
switch (element.metadataType) {
|
||||
case MetadataType.Function:
|
||||
templateData.icon.className = 'icon scalarvaluedfunction';
|
||||
break;
|
||||
case MetadataType.SProc:
|
||||
templateData.icon.className = 'icon storedprocedure';
|
||||
break;
|
||||
case MetadataType.Table:
|
||||
templateData.icon.className = 'icon table';
|
||||
break;
|
||||
case MetadataType.View:
|
||||
templateData.icon.className = 'icon view';
|
||||
break;
|
||||
}
|
||||
templateData.label.innerText = element.schema + '.' + element.name;
|
||||
} else {
|
||||
templateData.label.innerText = element.databaseName;
|
||||
}
|
||||
templateData.label.title = templateData.label.innerText;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExplorerFilter implements tree.IFilter {
|
||||
private _filterString: string;
|
||||
|
||||
public isVisible(tree: tree.ITree, element: TreeResource): boolean {
|
||||
if (element instanceof ObjectMetadataWrapper) {
|
||||
return this._doIsVisibleObjectMetadata(element);
|
||||
} else {
|
||||
return this._doIsVisibleConnectionProfile(element);
|
||||
}
|
||||
}
|
||||
|
||||
// apply filter to databasename of the profile
|
||||
private _doIsVisibleConnectionProfile(element: IConnectionProfile): boolean {
|
||||
if (!this._filterString) {
|
||||
return true;
|
||||
}
|
||||
const filterString = this._filterString.trim().toLowerCase();
|
||||
return element.databaseName.toLowerCase().indexOf(filterString) > -1;
|
||||
}
|
||||
|
||||
// apply filter for objectmetadatawrapper
|
||||
// could be improved by pre-processing the filter string
|
||||
private _doIsVisibleObjectMetadata(element: ObjectMetadataWrapper): boolean {
|
||||
if (!this._filterString) {
|
||||
return true;
|
||||
}
|
||||
// freeze filter string for edge cases
|
||||
let filterString = this._filterString.trim().toLowerCase();
|
||||
|
||||
// determine if a filter is applied
|
||||
let metadataType: MetadataType;
|
||||
|
||||
if (filterString.indexOf(':') > -1) {
|
||||
const filterArray = filterString.split(':');
|
||||
|
||||
if (filterArray.length > 2) {
|
||||
filterString = filterArray.slice(1, filterArray.length - 1).join(':');
|
||||
} else {
|
||||
filterString = filterArray[1];
|
||||
}
|
||||
|
||||
switch (filterArray[0].toLowerCase()) {
|
||||
case 'v':
|
||||
metadataType = MetadataType.View;
|
||||
break;
|
||||
case 't':
|
||||
metadataType = MetadataType.Table;
|
||||
break;
|
||||
case 'sp':
|
||||
metadataType = MetadataType.SProc;
|
||||
break;
|
||||
case 'f':
|
||||
metadataType = MetadataType.Function;
|
||||
break;
|
||||
case 'a':
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadataType !== undefined) {
|
||||
return element.metadataType === metadataType && (element.schema + '.' + element.name).toLowerCase().indexOf(filterString) > -1;
|
||||
} else {
|
||||
return (element.schema + '.' + element.name).toLowerCase().indexOf(filterString) > -1;
|
||||
}
|
||||
}
|
||||
|
||||
public set filterString(val: string) {
|
||||
this._filterString = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MetadataType } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { FlavorProperties, ObjectListViewProperty } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
export const NameProperty: string = 'name';
|
||||
const NamePropertyDisplayText: string = nls.localize('dashboard.explorer.namePropertyDisplayValue', "Name");
|
||||
|
||||
export class ExplorerView {
|
||||
constructor(private context: string) {
|
||||
}
|
||||
|
||||
public getPropertyList(flavorProperties: FlavorProperties): ObjectListViewProperty[] {
|
||||
let propertyList;
|
||||
if (this.context === 'database') {
|
||||
if (flavorProperties && flavorProperties.objectsListProperties && flavorProperties.objectsListProperties.length > 0) {
|
||||
propertyList = flavorProperties.objectsListProperties;
|
||||
} else {
|
||||
propertyList = [{
|
||||
displayName: NamePropertyDisplayText,
|
||||
value: NameProperty,
|
||||
widthWeight: 60
|
||||
}, {
|
||||
displayName: nls.localize('dashboard.explorer.schemaDisplayValue', "Schema"),
|
||||
value: 'schema',
|
||||
widthWeight: 20
|
||||
}, {
|
||||
displayName: nls.localize('dashboard.explorer.objectTypeDisplayValue', "Type"),
|
||||
value: 'metadataTypeName',
|
||||
widthWeight: 20
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
if (flavorProperties && flavorProperties.databasesListProperties && flavorProperties.databasesListProperties.length > 0) {
|
||||
propertyList = flavorProperties.databasesListProperties;
|
||||
} else {
|
||||
propertyList = [{
|
||||
displayName: NamePropertyDisplayText,
|
||||
value: NameProperty,
|
||||
widthWeight: 80
|
||||
}];
|
||||
}
|
||||
}
|
||||
return propertyList;
|
||||
}
|
||||
|
||||
public getIconClass(item: Slick.SlickData): string {
|
||||
if (this.context === 'database') {
|
||||
let iconClass: string = undefined;
|
||||
switch (item.metadataType) {
|
||||
case MetadataType.Function:
|
||||
iconClass = 'scalarvaluedfunction';
|
||||
break;
|
||||
case MetadataType.SProc:
|
||||
iconClass = 'storedprocedure';
|
||||
break;
|
||||
case MetadataType.Table:
|
||||
iconClass = 'table';
|
||||
break;
|
||||
case MetadataType.View:
|
||||
iconClass = 'view';
|
||||
break;
|
||||
}
|
||||
return iconClass;
|
||||
} else {
|
||||
return 'database-colored';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div class="explorer-widget"
|
||||
style="display: flex; flex-flow: column; position: absolute; height:100%; width:100%; padding: 10px; box-sizing: border-box">
|
||||
<div class="explorer-widget" style="height:100%; width:100%; padding: 10px; box-sizing: border-box">
|
||||
<div #input style="width: 100%"></div>
|
||||
<div style="flex: 1 1 auto; position: relative">
|
||||
<div [style.height]="getTableHeight()">
|
||||
<loading-spinner [loading]="_loading" [loadingMessage]="_loadingMessage"
|
||||
[loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner>
|
||||
<div #table style="position: absolute; height: 100%; width: 100%"></div>
|
||||
<div #table style="width: 100%;height: 100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,31 +3,29 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/explorerWidget';
|
||||
|
||||
import { Component, Inject, forwardRef, OnInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ElementRef, forwardRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { ExplorerFilter, ExplorerRenderer, ExplorerDataSource, ExplorerController, ExplorerModel } from './explorerTree';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
|
||||
import { InputBox, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { getContentHeight } from 'vs/base/browser/dom';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { DatabaseInfo } from 'azdata';
|
||||
import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
|
||||
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
|
||||
import { ConnectionProfilePropertyName, ExplorerTable } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTable';
|
||||
import { NameProperty } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerView';
|
||||
import { ObjectMetadataWrapper } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/objectMetadataWrapper';
|
||||
import { status, alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { isStringArray } from 'vs/base/common/types';
|
||||
import 'vs/css!./media/explorerWidget';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
|
||||
@Component({
|
||||
selector: 'explorer-widget',
|
||||
@@ -35,17 +33,8 @@ import { isStringArray } from 'vs/base/common/types';
|
||||
})
|
||||
export class ExplorerWidget extends DashboardWidget implements IDashboardWidget, OnInit {
|
||||
private _input: InputBox;
|
||||
private _tree: Tree;
|
||||
private _table: ExplorerTable;
|
||||
private _filterDelayer = new Delayer<void>(200);
|
||||
private _treeController = this.instantiationService.createInstance(ExplorerController,
|
||||
this._bootstrap.getUnderlyingUri(),
|
||||
this._bootstrap.connectionManagementService,
|
||||
this._router,
|
||||
this._bootstrap
|
||||
);
|
||||
private _treeRenderer = new ExplorerRenderer();
|
||||
private _treeDataSource = new ExplorerDataSource();
|
||||
private _treeFilter = new ExplorerFilter();
|
||||
|
||||
@ViewChild('input') private _inputContainer: ElementRef;
|
||||
@ViewChild('table') private _tableContainer: ElementRef;
|
||||
@@ -57,8 +46,11 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
|
||||
@Inject(forwardRef(() => ElementRef)) private readonly _el: ElementRef,
|
||||
@Inject(IWorkbenchThemeService) private readonly themeService: IWorkbenchThemeService,
|
||||
@Inject(IContextViewService) private readonly contextViewService: IContextViewService,
|
||||
@Inject(IInstantiationService) private readonly instantiationService: IInstantiationService,
|
||||
@Inject(ICapabilitiesService) private readonly capabilitiesService: ICapabilitiesService,
|
||||
@Inject(ILogService) private readonly logService: ILogService,
|
||||
@Inject(IContextMenuService) private readonly contextMenuService: IContextMenuService,
|
||||
@Inject(IMenuService) private readonly menuService: IMenuService,
|
||||
@Inject(IContextKeyService) private readonly contextKeyService: IContextKeyService,
|
||||
@Inject(IEditorProgressService) private readonly progressService: IEditorProgressService,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef
|
||||
) {
|
||||
super(changeRef);
|
||||
@@ -70,60 +62,43 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
|
||||
ngOnInit() {
|
||||
this._inited = true;
|
||||
|
||||
const placeholderLabel = this._config.context === 'database' ? nls.localize('seachObjects', "Search by name of type (a:, t:, v:, f:, or sp:)") : nls.localize('searchDatabases', "Search databases");
|
||||
const placeholderLabel = this._config.context === 'database' ? nls.localize('seachObjects', "Search by name of type (t:, v:, f:, or sp:)") : nls.localize('searchDatabases', "Search databases");
|
||||
|
||||
const inputOptions: IInputOptions = {
|
||||
placeholder: placeholderLabel,
|
||||
ariaLabel: placeholderLabel
|
||||
};
|
||||
this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions);
|
||||
this._register(this._input.onDidChange(e => {
|
||||
this._filterDelayer.trigger(async () => {
|
||||
this._treeFilter.filterString = e;
|
||||
await this._tree.refresh();
|
||||
const navigator = this._tree.getNavigator();
|
||||
let item = navigator.next();
|
||||
let count = 0;
|
||||
while (item) {
|
||||
count++;
|
||||
item = navigator.next();
|
||||
}
|
||||
let message: string;
|
||||
if (count === 0) {
|
||||
message = nls.localize('explorerSearchNoMatchResultMessage', "No matching item found");
|
||||
} else if (count === 1) {
|
||||
message = nls.localize('explorerSearchSingleMatchResultMessage', "Filtered search list to 1 item");
|
||||
} else {
|
||||
message = nls.localize('explorerSearchMatchResultMessage', "Filtered search list to {0} items", count);
|
||||
}
|
||||
status(message);
|
||||
});
|
||||
}));
|
||||
this._tree = new Tree(this._tableContainer.nativeElement, {
|
||||
controller: this._treeController,
|
||||
dataSource: this._treeDataSource,
|
||||
filter: this._treeFilter,
|
||||
renderer: this._treeRenderer
|
||||
}, { horizontalScrollMode: ScrollbarVisibility.Auto });
|
||||
this._tree.layout(getContentHeight(this._tableContainer.nativeElement));
|
||||
this._table = new ExplorerTable(this._tableContainer.nativeElement,
|
||||
this._router,
|
||||
this._config.context,
|
||||
this._bootstrap,
|
||||
this.themeService,
|
||||
this.contextMenuService,
|
||||
this.menuService,
|
||||
this.contextKeyService,
|
||||
this.progressService,
|
||||
this.logService);
|
||||
this._register(this._input);
|
||||
this._register(attachInputBoxStyler(this._input, this.themeService));
|
||||
this._register(this._tree);
|
||||
this._register(attachListStyler(this._tree, this.themeService));
|
||||
this._register(this._table);
|
||||
this._register(this._input.onDidChange(e => {
|
||||
this._filterDelayer.trigger(async () => {
|
||||
this._table.filter(e);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
this.setLoadingStatus(true);
|
||||
|
||||
if (this._config.context === 'database') {
|
||||
this._register(subscriptionToDisposable(this._bootstrap.metadataService.metadata.subscribe(
|
||||
data => {
|
||||
if (data) {
|
||||
|
||||
const objectData = ObjectMetadataWrapper.createFromObjectMetadata(data.objectMetadata);
|
||||
objectData.sort(ObjectMetadataWrapper.sort);
|
||||
this._treeDataSource.data = objectData;
|
||||
this._tree.setInput(new ExplorerModel());
|
||||
this.setLoadingStatus(false);
|
||||
this.updateTable(objectData);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
@@ -131,22 +106,26 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
|
||||
}
|
||||
)));
|
||||
} else {
|
||||
const currentProfile = this._bootstrap.connectionManagementService.connectionInfo.connectionProfile;
|
||||
this._register(subscriptionToDisposable(this._bootstrap.metadataService.databases.subscribe(
|
||||
data => {
|
||||
// Handle the case where there is no metadata service
|
||||
data = data || [];
|
||||
if (!isStringArray(data)) {
|
||||
data = data.map(item => item.options['name'] as string);
|
||||
if (isStringArray(data)) {
|
||||
data = data.map(item => {
|
||||
const dbInfo: DatabaseInfo = { options: {} };
|
||||
dbInfo.options[NameProperty] = item;
|
||||
return dbInfo;
|
||||
});
|
||||
}
|
||||
const profileData = data.map(d => {
|
||||
const profile = new ConnectionProfile(this.capabilitiesService, currentProfile);
|
||||
profile.databaseName = d;
|
||||
return profile;
|
||||
});
|
||||
this._treeDataSource.data = profileData;
|
||||
this._tree.setInput(new ExplorerModel());
|
||||
this.setLoadingStatus(false);
|
||||
|
||||
const currentProfile = this._bootstrap.connectionManagementService.connectionInfo.connectionProfile;
|
||||
this.updateTable(data.map(d => {
|
||||
const item = assign({}, d.options);
|
||||
const profile = currentProfile.toIConnectionProfile();
|
||||
profile.databaseName = d.options[NameProperty];
|
||||
item[ConnectionProfilePropertyName] = profile;
|
||||
return item;
|
||||
}));
|
||||
},
|
||||
error => {
|
||||
this.showErrorMessage(nls.localize('dashboard.explorer.databaseError', "Unable to load databases"));
|
||||
@@ -155,13 +134,19 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
|
||||
}
|
||||
}
|
||||
|
||||
private updateTable(data: Slick.SlickData[]) {
|
||||
this._table.setData(data);
|
||||
this.setLoadingStatus(false);
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this._input.inputElement.value = '';
|
||||
this.init();
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
if (this._inited) {
|
||||
this._tree.layout(getContentHeight(this._tableContainer.nativeElement));
|
||||
this._table.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,4 +154,8 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
|
||||
(<HTMLElement>this._el.nativeElement).innerText = message;
|
||||
alert(message);
|
||||
}
|
||||
|
||||
public getTableHeight(): string {
|
||||
return `calc(100% - ${this._input.height}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { registerDashboardWidget } from 'sql/platform/dashboard/browser/widgetRegistry';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ExplorerManageAction } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeActions';
|
||||
import { ExplorerManageAction } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerActions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
|
||||
const explorerSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
|
||||
@@ -13,3 +13,7 @@ explorer-widget .list-row {
|
||||
align-items: center;
|
||||
margin-left: -33px;
|
||||
}
|
||||
|
||||
.explorer-widget .slick-cell {
|
||||
border-right-style: none;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ProviderProperties } from './propertiesWidget.component';
|
||||
import { ProviderProperties } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
|
||||
import * as nls from 'vs/nls';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
|
||||
|
||||
@@ -7,50 +7,17 @@ import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef, V
|
||||
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
|
||||
import { IDashboardRegistry, Extensions as DashboardExtensions } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
|
||||
import { Property, PropertiesConfig, getFlavor } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
|
||||
|
||||
import { DatabaseInfo, ServerInfo } from 'azdata';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
|
||||
import { PropertiesContainer, PropertyItem } from 'sql/base/browser/ui/propertiesContainer/propertiesContainer.component';
|
||||
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { PROPERTIES_CONTAINER_PROPERTY_NAME, PROPERTIES_CONTAINER_PROPERTY_VALUE } from 'vs/workbench/common/theme';
|
||||
|
||||
export interface PropertiesConfig {
|
||||
properties: Array<Property>;
|
||||
}
|
||||
|
||||
export interface FlavorProperties {
|
||||
flavor: string;
|
||||
condition?: ConditionProperties;
|
||||
conditions?: Array<ConditionProperties>;
|
||||
databaseProperties: Array<Property>;
|
||||
serverProperties: Array<Property>;
|
||||
}
|
||||
|
||||
export interface ConditionProperties {
|
||||
field: string;
|
||||
operator: '==' | '<=' | '>=' | '!=';
|
||||
value: string | boolean;
|
||||
}
|
||||
|
||||
export interface ProviderProperties {
|
||||
provider: string;
|
||||
flavors: Array<FlavorProperties>;
|
||||
}
|
||||
|
||||
export interface Property {
|
||||
displayName: string;
|
||||
value: string;
|
||||
ignore?: Array<string>;
|
||||
default?: string;
|
||||
}
|
||||
|
||||
const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
|
||||
|
||||
@Component({
|
||||
selector: 'properties-widget',
|
||||
template: `
|
||||
@@ -111,55 +78,10 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
|
||||
const config = <PropertiesConfig>this._config.widget['properties-widget'];
|
||||
propertyArray = config.properties;
|
||||
} else {
|
||||
const providerProperties = dashboardRegistry.getProperties(provider as string);
|
||||
|
||||
if (!providerProperties) {
|
||||
this.logService.error('No property definitions found for provider', provider);
|
||||
const flavor = getFlavor(this._connection.serverInfo, this.logService, provider as string);
|
||||
if (!flavor) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let flavor: FlavorProperties;
|
||||
|
||||
// find correct flavor
|
||||
if (providerProperties.flavors.length === 1) {
|
||||
flavor = providerProperties.flavors[0];
|
||||
} else if (providerProperties.flavors.length === 0) {
|
||||
this.logService.error('No flavor definitions found for "', provider,
|
||||
'. If there are not multiple flavors of this provider, add one flavor without a condition');
|
||||
return [];
|
||||
} else {
|
||||
const flavorArray = providerProperties.flavors.filter((item) => {
|
||||
|
||||
// For backward compatibility we are supporting array of conditions and single condition.
|
||||
// If nothing is specified, we return false.
|
||||
if (item.conditions) {
|
||||
let conditionResult = true;
|
||||
for (let i = 0; i < item.conditions.length; i++) {
|
||||
conditionResult = conditionResult && this.getConditionResult(item, item.conditions[i]);
|
||||
}
|
||||
|
||||
return conditionResult;
|
||||
}
|
||||
else if (item.condition) {
|
||||
return this.getConditionResult(item, item.condition);
|
||||
}
|
||||
else {
|
||||
this.logService.error('No condition was specified.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (flavorArray.length === 0) {
|
||||
this.logService.error('Could not determine flavor');
|
||||
return [];
|
||||
} else if (flavorArray.length > 1) {
|
||||
this.logService.error('Multiple flavors matched correctly for this provider', provider);
|
||||
return [];
|
||||
}
|
||||
|
||||
flavor = flavorArray[0];
|
||||
}
|
||||
|
||||
// determine what context we should be pulling from
|
||||
if (this._config.context === 'database') {
|
||||
if (!Array.isArray(flavor.databaseProperties)) {
|
||||
@@ -210,31 +132,6 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
|
||||
});
|
||||
}
|
||||
|
||||
private getConditionResult(item: FlavorProperties, conditionItem: ConditionProperties): boolean {
|
||||
let condition = this._connection.serverInfo[conditionItem.field];
|
||||
|
||||
// If we need to compare strings, then we should ensure that condition is string
|
||||
// Otherwise tripple equals/unequals would return false values
|
||||
if (typeof conditionItem.value === 'string') {
|
||||
condition = condition.toString();
|
||||
}
|
||||
|
||||
switch (conditionItem.operator) {
|
||||
case '==':
|
||||
return condition === conditionItem.value;
|
||||
case '!=':
|
||||
return condition !== conditionItem.value;
|
||||
case '>=':
|
||||
return condition >= conditionItem.value;
|
||||
case '<=':
|
||||
return condition <= conditionItem.value;
|
||||
default:
|
||||
this.logService.error('Could not parse operator: "', conditionItem.operator,
|
||||
'" on item "', item, '"');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private getValueOrDefault<T>(infoObject: ServerInfo | {}, propertyValue: string, defaultVal?: any): T {
|
||||
let val: T = undefined;
|
||||
if (infoObject) {
|
||||
|
||||
@@ -7,6 +7,9 @@ import { MetadataType } from 'sql/platform/connection/common/connectionManagemen
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ObjectMetadataWrapper } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/objectMetadataWrapper';
|
||||
import { ExplorerFilter } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerFilter';
|
||||
import { ExplorerView } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerView';
|
||||
import { FlavorProperties } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
|
||||
|
||||
suite('Explorer Widget Tests', () => {
|
||||
test('Sorting dashboard search objects works correctly', () => {
|
||||
@@ -56,4 +59,143 @@ suite('Explorer Widget Tests', () => {
|
||||
let expectedList = [testMetadata[1], testMetadata[4], testMetadata[0], testMetadata[2], testMetadata[3]];
|
||||
expectedList.forEach((expectedWrapper, index) => assert.equal(sortedMetadata[index], expectedWrapper));
|
||||
});
|
||||
|
||||
test('Filter is only performed on the specified properties', () => {
|
||||
const prop1 = 'prop1';
|
||||
const prop2 = 'prop2';
|
||||
const prop3 = 'prop3';
|
||||
const filter = new ExplorerFilter('server', [prop1, prop2]);
|
||||
const obj1 = {};
|
||||
obj1[prop1] = 'abc';
|
||||
obj1[prop2] = 'def';
|
||||
obj1[prop3] = 'MatCh';
|
||||
const obj2 = {};
|
||||
obj2[prop1] = 'abc';
|
||||
obj2[prop2] = 'Match';
|
||||
obj2[prop3] = 'cd';
|
||||
const result = filter.filter('ATc', [obj1, obj2]);
|
||||
assert.equal(result.length, 1, 'filtered result set should container 1 item');
|
||||
assert.equal(result[0], obj2, 'filtered result set does not match expectation');
|
||||
});
|
||||
|
||||
test('object type filter', () => {
|
||||
const testMetadata = ObjectMetadataWrapper.createFromObjectMetadata(
|
||||
[
|
||||
{
|
||||
metadataType: MetadataType.View,
|
||||
metadataTypeName: undefined,
|
||||
urn: undefined,
|
||||
name: 'testView',
|
||||
schema: undefined
|
||||
},
|
||||
{
|
||||
metadataType: MetadataType.Table,
|
||||
metadataTypeName: undefined,
|
||||
urn: undefined,
|
||||
name: 'testTable',
|
||||
schema: undefined
|
||||
},
|
||||
{
|
||||
metadataType: MetadataType.SProc,
|
||||
metadataTypeName: undefined,
|
||||
urn: undefined,
|
||||
name: 'testSProc',
|
||||
schema: undefined
|
||||
},
|
||||
{
|
||||
metadataType: MetadataType.Function,
|
||||
metadataTypeName: undefined,
|
||||
urn: undefined,
|
||||
name: 'testFunction',
|
||||
schema: undefined
|
||||
},
|
||||
{
|
||||
metadataType: MetadataType.View,
|
||||
metadataTypeName: undefined,
|
||||
urn: undefined,
|
||||
name: 'firstView',
|
||||
schema: undefined
|
||||
}
|
||||
]);
|
||||
const filter = new ExplorerFilter('database', ['name']);
|
||||
let result = filter.filter('t:', testMetadata);
|
||||
assert.equal(result.length, 1, 'table type filter should return only 1 item');
|
||||
assert.equal(result[0]['name'], 'testTable', 'table type filter does not return correct data');
|
||||
result = filter.filter('v:', testMetadata);
|
||||
assert.equal(result.length, 2, 'view type filter should return only 1 item');
|
||||
assert.equal(result[0]['name'], 'testView', 'view type filter does not return correct data');
|
||||
assert.equal(result[1]['name'], 'firstView', 'view type filter does not return correct data');
|
||||
result = filter.filter('sp:', testMetadata);
|
||||
assert.equal(result.length, 1, 'stored proc type filter should return only 1 item');
|
||||
assert.equal(result[0]['name'], 'testSProc', 'stored proc type filter does not return correct data');
|
||||
result = filter.filter('f:', testMetadata);
|
||||
assert.equal(result.length, 1, 'function type filter should return only 1 item');
|
||||
assert.equal(result[0]['name'], 'testFunction', 'function type filter does not return correct data');
|
||||
result = filter.filter('v:first', testMetadata);
|
||||
assert.equal(result.length, 1, 'view type and name filter should return only 1 item');
|
||||
assert.equal(result[0]['name'], 'firstView', 'view type and name filter does not return correct data');
|
||||
});
|
||||
|
||||
test('Icon css class test', () => {
|
||||
const serverView = new ExplorerView('server');
|
||||
let icon = serverView.getIconClass({});
|
||||
assert.equal(icon, 'database-colored');
|
||||
const databaseView = new ExplorerView('database');
|
||||
const obj = {};
|
||||
obj['metadataType'] = MetadataType.Function;
|
||||
icon = databaseView.getIconClass(obj);
|
||||
assert.equal(icon, 'scalarvaluedfunction');
|
||||
obj['metadataType'] = MetadataType.SProc;
|
||||
icon = databaseView.getIconClass(obj);
|
||||
assert.equal(icon, 'storedprocedure');
|
||||
obj['metadataType'] = MetadataType.Table;
|
||||
icon = databaseView.getIconClass(obj);
|
||||
assert.equal(icon, 'table');
|
||||
obj['metadataType'] = MetadataType.View;
|
||||
icon = databaseView.getIconClass(obj);
|
||||
assert.equal(icon, 'view');
|
||||
});
|
||||
|
||||
test('explorer property list', () => {
|
||||
const serverView = new ExplorerView('server');
|
||||
const emptyFlavor: FlavorProperties = {
|
||||
flavor: '',
|
||||
databaseProperties: [],
|
||||
serverProperties: [],
|
||||
databasesListProperties: [
|
||||
],
|
||||
objectsListProperties: []
|
||||
};
|
||||
|
||||
const flavor: FlavorProperties = {
|
||||
flavor: '',
|
||||
databaseProperties: [],
|
||||
serverProperties: [],
|
||||
databasesListProperties: [
|
||||
{
|
||||
displayName: '',
|
||||
value: 'dbprop1'
|
||||
}
|
||||
],
|
||||
objectsListProperties: [{
|
||||
displayName: '',
|
||||
value: 'objprop1'
|
||||
}]
|
||||
};
|
||||
let propertyList = serverView.getPropertyList(emptyFlavor);
|
||||
assert.equal(propertyList.length, 1, 'default database property list should contain 1 property');
|
||||
assert.equal(propertyList[0].value, 'name', 'default database property list should contain name property');
|
||||
propertyList = serverView.getPropertyList(flavor);
|
||||
assert.equal(propertyList.length, 1, 'database property list should contain 1 property');
|
||||
assert.equal(propertyList[0].value, 'dbprop1', 'database property list should contain dbprop1 property');
|
||||
const databaseView = new ExplorerView('database');
|
||||
propertyList = databaseView.getPropertyList(emptyFlavor);
|
||||
assert.equal(propertyList.length, 3, 'default object property list should contain 3 property');
|
||||
assert.equal(propertyList[0].value, 'name', 'default object property list should contain name property');
|
||||
assert.equal(propertyList[1].value, 'schema', 'default object property list should contain schema property');
|
||||
assert.equal(propertyList[2].value, 'metadataTypeName', 'default object property list should contain metadataTypeName property');
|
||||
propertyList = databaseView.getPropertyList(flavor);
|
||||
assert.equal(propertyList.length, 1, 'object property list should contain 1 property');
|
||||
assert.equal(propertyList[0].value, 'objprop1', 'object property list should contain objprop1 property');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ import { TreeViewItemHandleArg } from 'sql/workbench/common/views';
|
||||
import { ConnectedContext } from 'azdata';
|
||||
import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common/treeNodeContextKey';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
import { ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { MarkdownOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/markdownOutput.component';
|
||||
|
||||
@@ -41,7 +41,7 @@ import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common
|
||||
import { MssqlNodeContext } from 'sql/workbench/services/objectExplorer/browser/mssqlNodeContext';
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
|
||||
export const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.queryEditorVisibleId);
|
||||
export const ResultsGridFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsGridFocussedId));
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions';
|
||||
import { ConnectionContextKey } from 'sql/workbench/services/connection/common/connectionContextKey';
|
||||
import { ManageActionContext } from 'sql/workbench/browser/actions';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
import { ServerInfoContextKey } from 'sql/workbench/services/connection/common/serverInfoContextKey';
|
||||
import { DatabaseEngineEdition } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ConnectionContextKey } from 'sql/workbench/services/connection/common/c
|
||||
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
|
||||
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext';
|
||||
import { EditDataAction } from 'sql/workbench/browser/scriptingActions';
|
||||
import { DatabaseEngineEdition } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user