tasks widget updates (#9860)

* fix toolbar and remove tasks widget

* update refresh action

* remove contribution

* fix missing learn more menu item

* Alanren/refresh widgets new (#9863)

* refresh widgets

* dashboard refresh

* update
This commit is contained in:
Alan Ren
2020-04-06 12:25:09 -07:00
committed by GitHub
parent f95864ff82
commit 19a11ba94b
17 changed files with 131 additions and 373 deletions

View File

@@ -235,8 +235,10 @@ export class DashboardGridContainer extends DashboardTab implements OnDestroy {
widget.rowspan = '1'; widget.rowspan = '1';
} }
}); });
this.rows = this.createIndexes(this._contents.map(w => w.row)); if (this._contents.length > 0) {
this.cols = this.createIndexes(this._contents.map(w => w.col)); this.rows = this.createIndexes(this._contents.map(w => w.row));
this.cols = this.createIndexes(this._contents.map(w => w.col));
}
} }
} }

View File

@@ -69,4 +69,9 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
this._scrollable.layout(); this._scrollable.layout();
} }
} }
public refresh(): void {
super.refresh();
this._propertiesClass.refresh();
}
} }

View File

@@ -20,7 +20,6 @@ import { AngularDisposable } from 'sql/base/browser/lifecycle';
/* Widgets */ /* Widgets */
import { PropertiesWidgetComponent } from 'sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component'; import { PropertiesWidgetComponent } from 'sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component';
import { ExplorerWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component'; import { ExplorerWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component';
import { TasksWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.component';
import { InsightsWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component'; import { InsightsWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component';
import { WebviewWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component'; import { WebviewWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component';
@@ -42,7 +41,6 @@ import { IColorTheme } from 'vs/platform/theme/common/themeService';
const componentMap: { [x: string]: Type<IDashboardWidget> } = { const componentMap: { [x: string]: Type<IDashboardWidget> } = {
'properties-widget': PropertiesWidgetComponent, 'properties-widget': PropertiesWidgetComponent,
'explorer-widget': ExplorerWidget, 'explorer-widget': ExplorerWidget,
'tasks-widget': TasksWidget,
'insights-widget': InsightsWidget, 'insights-widget': InsightsWidget,
'webview-widget': WebviewWidget 'webview-widget': WebviewWidget
}; };

View File

@@ -82,7 +82,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
// tslint:disable:no-unused-variable // tslint:disable:no-unused-variable
private readonly homeTabTitle: string = nls.localize('home', "Home"); private readonly homeTabTitle: string = nls.localize('home', "Home");
private readonly homeTabId: string = 'homeTab'; private readonly homeTabId: string = 'homeTab';
private tabToolbarActionsConfig = new Map<string, WidgetConfig>(); private tabToolbarActionsConfig = new Map<string, any[]>();
private tabContents = new Map<string, string>(); private tabContents = new Map<string, string>();
static tabName = new RawContextKey<string>('tabName', undefined); static tabName = new RawContextKey<string>('tabName', undefined);
@@ -145,12 +145,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
}); });
} else { } else {
let tempWidgets = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.')); let tempWidgets = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.'));
// remove tasks widget because those will be shown in the toolbar this.processTasksWidgets(tempWidgets, this.homeTabId);
const index = tempWidgets.findIndex(c => c.widget['tasks-widget']);
if (index !== -1) {
tempWidgets.splice(index, 1);
}
this._originalConfig = objects.deepClone(tempWidgets); this._originalConfig = objects.deepClone(tempWidgets);
let properties = this.getProperties(); let properties = this.getProperties();
this._configModifiers.forEach((cb) => { this._configModifiers.forEach((cb) => {
@@ -170,8 +165,6 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
} }
this.showToolbar = true; this.showToolbar = true;
const homeToolbarConfig = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.'))[0].widget['tasks-widget'];
this.tabToolbarActionsConfig.set(this.homeTabId, homeToolbarConfig);
this.createToolbar(this.toolbarContainer.nativeElement, this.homeTabId); this.createToolbar(this.toolbarContainer.nativeElement, this.homeTabId);
this._register(this.themeService.onDidColorThemeChange((event: IColorTheme) => { this._register(this.themeService.onDidColorThemeChange((event: IColorTheme) => {
@@ -179,58 +172,52 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
})); }));
} }
private getExtensionContributedHomeToolbarContent(content: ITaskbarContent[]): void { private getContributedTasks(tabId: string): ITaskbarContent[] {
let primary: IAction[] = []; const tasks: ITaskbarContent[] = [];
let secondary: IAction[] = []; // for now we only allow contributing to the home tab toolbar.
const menu = this.menuService.createMenu(MenuId.DashboardToolbar, this.contextKeyService); if (tabId === this.homeTabId) {
let groups = menu.getActions({ arg: null, shouldForwardArgs: true }); let primary: IAction[] = [];
fillInActions(groups, { primary, secondary }, false, (group: string) => group === undefined || group === ''); let secondary: IAction[] = [];
const menu = this.menuService.createMenu(MenuId.DashboardToolbar, this.contextKeyService);
let groups = menu.getActions({ arg: null, shouldForwardArgs: true });
fillInActions(groups, { primary, secondary }, false, (group: string) => group === undefined || group === '');
primary.forEach(a => { primary.forEach(a => {
if (a instanceof MenuItemAction) { if (a instanceof MenuItemAction) {
// Need to ensure that we don't add the same action multiple times // Need to ensure that we don't add the same action multiple times
let foundIndex = firstIndex(content, act => act.action && act.action.id === a.id); let foundIndex = firstIndex(tasks, act => act.action && act.action.id === a.id);
if (foundIndex < 0) { if (foundIndex < 0) {
content.push({ action: a }); tasks.push({ action: a });
}
} }
});
if (primary.length > 0) {
let separator: HTMLElement = Taskbar.createTaskbarSeparator();
tasks.push({ element: separator });
} }
});
if (primary.length > 0) {
let separator: HTMLElement = Taskbar.createTaskbarSeparator();
content.push({ element: separator });
} }
return tasks;
} }
private hasExtensionContributedToolbarContent(): boolean { private createToolbar(parentElement: HTMLElement, tabId: string): void {
let primary: IAction[] = [];
let secondary: IAction[] = [];
const menu = this.menuService.createMenu(MenuId.DashboardToolbar, this.contextKeyService);
let groups = menu.getActions({ arg: null, shouldForwardArgs: true });
fillInActions(groups, { primary, secondary }, false, (group: string) => group === undefined || group === '');
return primary.length > 0 || secondary.length > 0;
}
private createToolbar(parentElement: HTMLElement, tabName: string): void {
// clear out toolbar // clear out toolbar
DOM.clearNode(parentElement); DOM.clearNode(parentElement);
this.toolbar = this._register(new Taskbar(parentElement, { actionViewItemProvider: action => this.createActionItemProvider(action as Action) })); this.toolbar = this._register(new Taskbar(parentElement, { actionViewItemProvider: action => this.createActionItemProvider(action as Action) }));
let content = []; let content = [];
content = this.getToolbarContent(this.tabToolbarActionsConfig.get(tabName)); content = this.getToolbarContent(tabId);
if (tabId === this.homeTabId) {
if (tabName === this.homeTabId) {
const configureDashboardCommand = MenuRegistry.getCommand('configureDashboard'); const configureDashboardCommand = MenuRegistry.getCommand('configureDashboard');
const configureDashboardAction = new ToolbarAction(configureDashboardCommand.id, configureDashboardCommand.title.toString(), TaskRegistry.getOrCreateTaskIconClassName(configureDashboardCommand), this.runAction, this, this.logService); const configureDashboardAction = new ToolbarAction(configureDashboardCommand.id, configureDashboardCommand.title.toString(), TaskRegistry.getOrCreateTaskIconClassName(configureDashboardCommand), this.runAction, this, this.logService);
content.push({ action: configureDashboardAction }); content.push({ action: configureDashboardAction });
} }
this.toolbar.setContent(content); this.toolbar.setContent(content);
} }
private getToolbarContent(toolbarTasks: WidgetConfig): ITaskbarContent[] { private getToolbarContent(tabId: string): ITaskbarContent[] {
const toolbarTasks = this.tabToolbarActionsConfig.get(tabId);
let tasks = TaskRegistry.getTasks(); let tasks = TaskRegistry.getTasks();
let content; let content = [];
if (types.isArray(toolbarTasks) && toolbarTasks.length > 0) { if (types.isArray(toolbarTasks) && toolbarTasks.length > 0) {
tasks = toolbarTasks.map(i => { tasks = toolbarTasks.map(i => {
if (types.isString(i)) { if (types.isString(i)) {
@@ -250,9 +237,12 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
} }
// get extension actions contributed to the page's toolbar // get extension actions contributed to the page's toolbar
this.getExtensionContributedHomeToolbarContent(content); const contributedTasks = this.getContributedTasks(tabId);
content.push(...contributedTasks);
const refreshAction = new RefreshWidgetAction(this.refresh, this); const refreshAction = new RefreshWidgetAction(() => {
this.refresh();
}, this);
content.push({ action: refreshAction }); content.push({ action: refreshAction });
return content; return content;
} }
@@ -456,12 +446,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
configs = cb.apply(this, [configs]); configs = cb.apply(this, [configs]);
}); });
// remove tasks widget because the tasks will be shown in the toolbar this.processTasksWidgets(configs, value.id);
const index = configs.findIndex(c => c.widget['tasks-widget']);
if (index !== -1) {
this.tabToolbarActionsConfig.set(value.id, configs[index].widget['tasks-widget']);
configs.splice(index, 1);
}
if (key === WIDGETS_CONTAINER) { if (key === WIDGETS_CONTAINER) {
return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow, iconClass: value.iconClass }; return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow, iconClass: value.iconClass };
@@ -473,6 +458,28 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow, iconClass: value.iconClass }; return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow, iconClass: value.iconClass };
} }
/**
* Process the tasks widgets, tasks widgets has been deprecated and the tasks are now in toolbar.
* @param widgets widgets
* @param tabId tab id
*/
private processTasksWidgets(widgets: WidgetConfig[], tabId: string): void {
let index;
const allTasks = [];
// do this in a while loop since there might be multiple tasks widgets in a tab
do {
index = widgets.findIndex(c => c.widget['tasks-widget']);
if (index !== -1) {
const tasks = widgets[index].widget['tasks-widget'];
if (Array.isArray(tasks)) {
allTasks.push(...tasks);
}
widgets.splice(index, 1);
}
} while (index !== -1);
this.tabToolbarActionsConfig.set(tabId, allTasks);
}
protected getContentType(tab: TabConfig): string { protected getContentType(tab: TabConfig): string {
return tab.container ? Object.keys(tab.container)[0] : ''; return tab.container ? Object.keys(tab.container)[0] : '';
} }
@@ -525,8 +532,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
public handleTabChange(tab: TabComponent): void { public handleTabChange(tab: TabComponent): void {
this._tabName.set(tab.identifier); this._tabName.set(tab.identifier);
const tabContent = this.tabContents.get(tab.identifier); const tabContent = this.tabContents.get(tab.identifier);
if (tab.identifier === this.homeTabId || tabContent === WIDGETS_CONTAINER || tabContent === GRID_CONTAINER || tabContent === NAV_SECTION if (tab.identifier === this.homeTabId || tabContent === WIDGETS_CONTAINER || tabContent === GRID_CONTAINER || tabContent === NAV_SECTION) {
|| this.hasExtensionContributedToolbarContent()) {
this.showToolbar = true; this.showToolbar = true;
this.createToolbar(this.toolbarContainer.nativeElement, tab.identifier); this.createToolbar(this.toolbarContainer.nativeElement, tab.identifier);
} else { // hide toolbar } else { // hide toolbar
@@ -534,9 +540,6 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
} }
this._cd.detectChanges(); this._cd.detectChanges();
const localtab = this._tabs.find(i => i.id === tab.identifier);
this._editEnabled.fire(localtab.editable);
this._cd.detectChanges();
} }
public handleTabClose(tab: TabComponent): void { public handleTabClose(tab: TabComponent): void {

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { InjectionToken, OnDestroy } from '@angular/core'; import { InjectionToken, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { NgGridItemConfig } from 'angular2-grid'; import { NgGridItemConfig } from 'angular2-grid';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
@@ -63,6 +63,14 @@ export interface TabSettingConfig {
export abstract class DashboardWidget extends Disposable implements OnDestroy { export abstract class DashboardWidget extends Disposable implements OnDestroy {
protected _config: WidgetConfig; protected _config: WidgetConfig;
protected _loading: boolean;
protected _inited: boolean = false;
protected _loadingMessage: string;
protected _loadingCompletedMessage: string;
constructor(protected _changeRef: ChangeDetectorRef) {
super();
}
get actions(): Array<Action> { get actions(): Array<Action> {
return []; return [];
@@ -71,4 +79,11 @@ export abstract class DashboardWidget extends Disposable implements OnDestroy {
ngOnDestroy() { ngOnDestroy() {
this.dispose(); this.dispose();
} }
protected setLoadingStatus(loading: boolean): void {
this._loading = loading;
if (this._inited) {
this._changeRef.detectChanges();
}
}
} }

View File

@@ -82,7 +82,6 @@ const pageComponents = [ServerDashboardPage, DatabaseDashboardPage];
/* Widget Components */ /* Widget Components */
import { PropertiesWidgetComponent } from 'sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component'; import { PropertiesWidgetComponent } from 'sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component';
import { ExplorerWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component'; import { ExplorerWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component';
import { TasksWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.component';
import { InsightsWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component'; import { InsightsWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component';
import { WebviewWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component'; import { WebviewWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component';
import { JobStepsViewComponent } from 'sql/workbench/contrib/jobManagement/browser/jobStepsView.component'; import { JobStepsViewComponent } from 'sql/workbench/contrib/jobManagement/browser/jobStepsView.component';
@@ -93,7 +92,6 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
const widgetComponents = [ const widgetComponents = [
PropertiesWidgetComponent, PropertiesWidgetComponent,
ExplorerWidget, ExplorerWidget,
TasksWidget,
InsightsWidget, InsightsWidget,
WebviewWidget WebviewWidget
]; ];

View File

@@ -4,11 +4,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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="display: flex; flex-flow: column; position: absolute; height:100%; width:100%; padding: 10px; box-sizing: border-box">
<div #input style="width: 100%"></div> <div #input style="width: 100%"></div>
<div style="flex: 1 1 auto; position: relative"> <div style="flex: 1 1 auto; position: relative">
<loading-spinner [loading]="loading" [loadingMessage]="loadingMessage" <loading-spinner [loading]="_loading" [loadingMessage]="_loadingMessage"
[loadingCompletedMessage]="loadingCompletedMessage"></loading-spinner> [loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner>
<div #table style="position: absolute; height: 100%; width: 100%"></div> <div #table style="position: absolute; height: 100%; width: 100%"></div>
</div> </div>
</div> </div>

View File

@@ -47,11 +47,6 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
private _treeDataSource = new ExplorerDataSource(); private _treeDataSource = new ExplorerDataSource();
private _treeFilter = new ExplorerFilter(); private _treeFilter = new ExplorerFilter();
private _inited = false;
public loading: boolean = false;
public loadingMessage: string;
public loadingCompletedMessage: string;
@ViewChild('input') private _inputContainer: ElementRef; @ViewChild('input') private _inputContainer: ElementRef;
@ViewChild('table') private _tableContainer: ElementRef; @ViewChild('table') private _tableContainer: ElementRef;
@@ -64,11 +59,11 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
@Inject(IContextViewService) private readonly contextViewService: IContextViewService, @Inject(IContextViewService) private readonly contextViewService: IContextViewService,
@Inject(IInstantiationService) private readonly instantiationService: IInstantiationService, @Inject(IInstantiationService) private readonly instantiationService: IInstantiationService,
@Inject(ICapabilitiesService) private readonly capabilitiesService: ICapabilitiesService, @Inject(ICapabilitiesService) private readonly capabilitiesService: ICapabilitiesService,
@Inject(forwardRef(() => ChangeDetectorRef)) private readonly _cd: ChangeDetectorRef @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef
) { ) {
super(); super(changeRef);
this.loadingMessage = this._config.context === 'database' ? nls.localize('loadingObjects', "loading objects") : nls.localize('loadingDatabases', "loading databases"); this._loadingMessage = this._config.context === 'database' ? nls.localize('loadingObjects', "loading objects") : nls.localize('loadingDatabases', "loading databases");
this.loadingCompletedMessage = this._config.context === 'database' ? nls.localize('loadingObjectsCompleted', "loading objects completed.") : nls.localize('loadingDatabasesCompleted', "loading databases completed."); this._loadingCompletedMessage = this._config.context === 'database' ? nls.localize('loadingObjectsCompleted', "loading objects completed.") : nls.localize('loadingDatabasesCompleted', "loading databases completed.");
this.init(); this.init();
} }
@@ -167,14 +162,6 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
} }
} }
private setLoadingStatus(loading: boolean): void {
this.loading = loading;
if (this._inited) {
this._cd.detectChanges();
}
}
private showErrorMessage(message: string): void { private showErrorMessage(message: string): void {
(<HTMLElement>this._el.nativeElement).innerText = message; (<HTMLElement>this._el.nativeElement).innerText = message;
alert(message); alert(message);

View File

@@ -49,8 +49,8 @@ interface IStorageResult {
<div *ngIf="lastUpdated" style="font-style: italic; font-size: 80%; margin-left: 5px">{{lastUpdated}}</div> <div *ngIf="lastUpdated" style="font-style: italic; font-size: 80%; margin-left: 5px">{{lastUpdated}}</div>
<div *ngIf="autoRefreshStatus" style="font-style: italic; font-size: 80%; margin-left: 5px">{{autoRefreshStatus}}</div> <div *ngIf="autoRefreshStatus" style="font-style: italic; font-size: 80%; margin-left: 5px">{{autoRefreshStatus}}</div>
<div style="margin: 10px; width: calc(100% - 20px); height: calc(100% - 20px)"> <div style="margin: 10px; width: calc(100% - 20px); height: calc(100% - 20px)">
<ng-template component-host></ng-template> <ng-template *ngIf="!_loading" component-host></ng-template>
<loading-spinner [loading]="_loading"></loading-spinner> <loading-spinner [loading]="_loading" [loadingMessage]="_loadingMessage" [loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner>
</div>`, </div>`,
styles: [':host { width: 100%; height: 100% }'] styles: [':host { width: 100%; height: 100% }']
}) })
@@ -60,8 +60,6 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
@ViewChild(ComponentHostDirective) private componentHost: ComponentHostDirective; @ViewChild(ComponentHostDirective) private componentHost: ComponentHostDirective;
private _typeKey: string; private _typeKey: string;
private _init: boolean = false;
public _loading: boolean = true;
private _intervalTimer: IntervalTimer; private _intervalTimer: IntervalTimer;
public error: string; public error: string;
@@ -72,16 +70,17 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver, @Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
@Inject(forwardRef(() => CommonServiceInterface)) private dashboardService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) private dashboardService: CommonServiceInterface,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig, @Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => Injector)) private _injector: Injector, @Inject(forwardRef(() => Injector)) private _injector: Injector,
@Inject(IInstantiationService) private instantiationService: IInstantiationService, @Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IStorageService) private storageService: IStorageService, @Inject(IStorageService) private storageService: IStorageService,
@Inject(IConfigurationService) private readonly _configurationService: IConfigurationService, @Inject(IConfigurationService) private readonly _configurationService: IConfigurationService,
@Inject(IFileService) private readonly fileService: IFileService @Inject(IFileService) private readonly fileService: IFileService
) { ) {
super(); super(changeRef);
this.insightConfig = <IInsightsConfig>this._config.widget['insights-widget']; this.insightConfig = <IInsightsConfig>this._config.widget['insights-widget'];
this._loadingMessage = nls.localize('insightsWidgetLoadingMessage', "Loading {0}", this._config.name);
this._loadingCompletedMessage = nls.localize('insightsWidgetLoadingCompletedMessage', "Loading {0} completed", this._config.name);
this._verifyConfig(); this._verifyConfig();
this._parseConfig().then(() => { this._parseConfig().then(() => {
@@ -91,8 +90,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
const cancelablePromise = createCancelablePromise(() => { const cancelablePromise = createCancelablePromise(() => {
return promise.then( return promise.then(
result => { result => {
this._loading = false; if (this._inited) {
if (this._init) {
this._updateChild(result); this._updateChild(result);
this.setupInterval(); this.setupInterval();
} else { } else {
@@ -100,37 +98,36 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
} }
}, },
error => { error => {
this._loading = false;
if (isPromiseCanceledError(error)) { if (isPromiseCanceledError(error)) {
return; return;
} }
if (this._init) { if (this._inited) {
this.showError(error); this.showError(error);
} else { } else {
this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(error)); this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(error));
} }
} }
).then(() => this._cd.detectChanges()); ).then(() => this._changeRef.detectChanges());
}); });
this._register(toDisposable(() => cancelablePromise.cancel())); this._register(toDisposable(() => cancelablePromise.cancel()));
} }
}, error => { }, error => {
this._loading = false; this.setLoadingStatus(false);
this.showError(error); this.showError(error);
}); });
} }
ngAfterContentInit() { ngAfterContentInit() {
this._init = true; this._inited = true;
if (this.queryObv) { if (this.queryObv) {
this._register(subscriptionToDisposable(this.queryObv.subscribe( this._register(subscriptionToDisposable(this.queryObv.subscribe(
result => { result => {
this._loading = false; this.setLoadingStatus(false);
this._updateChild(result); this._updateChild(result);
this.setupInterval(); this.setupInterval();
}, },
error => { error => {
this._loading = false; this.setLoadingStatus(false);
this.showError(error); this.showError(error);
} }
))); )));
@@ -156,13 +153,13 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
let newState = autoRefreshOn ? '' : nls.localize('insights.autoRefreshOffState', "Auto Refresh: OFF"); let newState = autoRefreshOn ? '' : nls.localize('insights.autoRefreshOffState', "Auto Refresh: OFF");
if (this.autoRefreshStatus !== newState) { if (this.autoRefreshStatus !== newState) {
this.autoRefreshStatus = newState; this.autoRefreshStatus = newState;
this._cd.detectChanges(); this._changeRef.detectChanges();
} }
} }
private showError(error: string): void { private showError(error: string): void {
this.error = error; this.error = error;
this._cd.detectChanges(); this._changeRef.detectChanges();
} }
get actions(): Array<Action> { get actions(): Array<Action> {
@@ -189,7 +186,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
results: result results: result
}; };
this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", currentTime.toLocaleTimeString(), currentTime.toLocaleDateString()); this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", currentTime.toLocaleTimeString(), currentTime.toLocaleDateString());
this._cd.detectChanges(); this._changeRef.detectChanges();
this.storageService.store(this._getStorageKey(), JSON.stringify(store), StorageScope.GLOBAL); this.storageService.store(this._getStorageKey(), JSON.stringify(store), StorageScope.GLOBAL);
} }
return result; return result;
@@ -202,11 +199,10 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
const storedResult: IStorageResult = JSON.parse(storage); const storedResult: IStorageResult = JSON.parse(storage);
const date = new Date(storedResult.date); const date = new Date(storedResult.date);
this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", date.toLocaleTimeString(), date.toLocaleDateString()); this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", date.toLocaleTimeString(), date.toLocaleDateString());
this._loading = false; if (this._inited) {
if (this._init) {
this._updateChild(storedResult.results); this._updateChild(storedResult.results);
this.setupInterval(); this.setupInterval();
this._cd.detectChanges(); this._changeRef.detectChanges();
} else { } else {
this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(JSON.parse(storage))); this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(JSON.parse(storage)));
} }
@@ -231,11 +227,14 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
} }
private _runQuery(): Promise<SimpleExecuteResult> { private _runQuery(): Promise<SimpleExecuteResult> {
this.setLoadingStatus(true);
return Promise.resolve(this.dashboardService.queryManagementService.runQueryAndReturn(this.insightConfig.query as string).then( return Promise.resolve(this.dashboardService.queryManagementService.runQueryAndReturn(this.insightConfig.query as string).then(
result => { result => {
this.setLoadingStatus(false);
return this._storeResult(result); return this._storeResult(result);
}, },
error => { error => {
this.setLoadingStatus(false);
throw error; throw error;
} }
)); ));
@@ -244,7 +243,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
private _updateChild(result: SimpleExecuteResult): void { private _updateChild(result: SimpleExecuteResult): void {
this.componentHost.viewContainerRef.clear(); this.componentHost.viewContainerRef.clear();
this.error = undefined; this.error = undefined;
this._cd.detectChanges(); this._changeRef.detectChanges();
if (result.rowCount === 0) { if (result.rowCount === 0) {
this.showError(nls.localize('noResults', "No results to show")); this.showError(nls.localize('noResults', "No results to show"));

View File

@@ -4,7 +4,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
--> -->
<div #parent style="position: absolute; height: 100%; width: 100%;"> <loading-spinner [loading]="_loading" [loadingMessage]="_loadingMessage"
[loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner>
<div #parent style="position: absolute; height: 100%; width: 100%;" [style.display]="_loading ? 'none':'block'">
<div #container [style.margin-right.px]="_clipped ? 30 : 0" [style.width]="_clipped ? 94 + '%' : '100%'" style="overflow: hidden; padding-bottom: 10px"> <div #container [style.margin-right.px]="_clipped ? 30 : 0" [style.width]="_clipped ? 94 + '%' : '100%'" style="overflow: hidden; padding-bottom: 10px">
<span #child style="white-space : nowrap; width: fit-content"> <span #child style="white-space : nowrap; width: fit-content">
<ng-template ngFor let-item [ngForOf]="properties"> <ng-template ngFor let-item [ngForOf]="properties">

View File

@@ -68,7 +68,6 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
private _databaseInfo: DatabaseInfo; private _databaseInfo: DatabaseInfo;
public _clipped: boolean; public _clipped: boolean;
private properties: Array<DisplayProperty>; private properties: Array<DisplayProperty>;
private _hasInit = false;
@ViewChild('child', { read: ElementRef }) private _child: ElementRef; @ViewChild('child', { read: ElementRef }) private _child: ElementRef;
@ViewChild('parent', { read: ElementRef }) private _parent: ElementRef; @ViewChild('parent', { read: ElementRef }) private _parent: ElementRef;
@@ -76,18 +75,20 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
constructor( constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef, @Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig, @Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@Inject(ILogService) private logService: ILogService, @Inject(ILogService) private logService: ILogService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) { ) {
super(); super(changeRef);
this._loadingMessage = nls.localize('loadingProperties', "Loading properties");
this._loadingCompletedMessage = nls.localize('loadingPropertiesCompleted', "Loading properties completed");
this.init(); this.init();
} }
ngOnInit() { ngOnInit() {
this._hasInit = true; this._inited = true;
this._register(addDisposableListener(window, EventType.RESIZE, () => this.handleClipping())); this._register(addDisposableListener(window, EventType.RESIZE, () => this.handleClipping()));
this._changeRef.detectChanges(); this._changeRef.detectChanges();
@@ -106,14 +107,17 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
private init(): void { private init(): void {
this._connection = this._bootstrap.connectionManagementService.connectionInfo; this._connection = this._bootstrap.connectionManagementService.connectionInfo;
this.setLoadingStatus(true);
this._register(subscriptionToDisposable(this._bootstrap.adminService.databaseInfo.subscribe(data => { this._register(subscriptionToDisposable(this._bootstrap.adminService.databaseInfo.subscribe(data => {
this._databaseInfo = data; this._databaseInfo = data;
this._changeRef.detectChanges(); this._changeRef.detectChanges();
this.parseProperties(); this.parseProperties();
if (this._hasInit) { if (this._inited) {
this.handleClipping(); this.handleClipping();
} }
this.setLoadingStatus(false);
}, error => { }, error => {
this.setLoadingStatus(false);
(<HTMLElement>this._el.nativeElement).innerText = nls.localize('dashboard.properties.error', "Unable to load dashboard properties"); (<HTMLElement>this._el.nativeElement).innerText = nls.localize('dashboard.properties.error', "Unable to load dashboard properties");
}))); })));
} }
@@ -238,7 +242,7 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
this.properties.push(<DisplayProperty>assignProperty); this.properties.push(<DisplayProperty>assignProperty);
} }
if (this._hasInit) { if (this._inited) {
this._changeRef.detectChanges(); this._changeRef.detectChanges();
} }
} }

View File

@@ -1,32 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
tasks-widget .tile-container {
position: relative;
display: flex;
flex-flow: row wrap;
align-content: flex-start;
}
tasks-widget .task-tile {
cursor: pointer;
display: flex;
flex-flow: row;
align-items: center;
text-align: center;
margin-top: 10px;
margin-left: 18px;
}
tasks-widget .task-tile > div {
flex: 1 1 auto;
display: flex;
flex-flow: column;
align-items: center;
}
tasks-widget .task-tile .codicon {
padding: 15px;
}

View File

@@ -1,8 +0,0 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div #container style="position: absolute; height: 100%; width: 100%">
</div>

View File

@@ -1,178 +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 'vs/css!sql/media/icons/common-icons';
import 'vs/css!./media/taskWidget';
/* Node Modules */
import { Component, Inject, forwardRef, ViewChild, OnInit, ElementRef } from '@angular/core';
/* SQL imports */
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 { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
/* VS imports */
import * as themeColors from 'vs/workbench/common/theme';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService';
import * as types from 'vs/base/common/types';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import * as DOM from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { TaskRegistry } from 'sql/workbench/services/tasks/browser/tasksRegistry';
interface ITask {
name: string;
when: string;
}
const selector = 'tasks-widget';
@Component({
selector,
templateUrl: decodeURI(require.toUrl('./tasksWidget.component.html'))
})
export class TasksWidget extends DashboardWidget implements IDashboardWidget, OnInit {
private _size: number = 98;
private _tasks: Array<ICommandAction> = [];
private _profile: IConnectionProfile;
private _scrollableElement: ScrollableElement;
private _tileContainer: HTMLElement;
static readonly ICON_PATH_TO_CSS_RULES: Map<string /* path*/, string /* CSS rule */> = new Map<string, string>();
private _inited = false;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor(
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@Inject(forwardRef(() => CommonServiceInterface)) private readonly _bootstrap: CommonServiceInterface,
@Inject(ICommandService) private readonly commandService: ICommandService,
@Inject(IContextKeyService) readonly contextKeyService: IContextKeyService
) {
super();
this._profile = this._bootstrap.connectionManagementService.connectionInfo.connectionProfile;
const tasksConfig = this._config.widget[selector] as Array<string | ITask>;
let tasks = TaskRegistry.getTasks();
if (types.isArray(tasksConfig) && tasksConfig.length > 0) {
tasks = tasksConfig.map(i => {
if (types.isString(i)) {
if (tasks.some(x => x === i)) {
return i;
}
} else {
if (tasks.some(x => x === i.name) && contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(i.when))) {
return i.name;
}
}
return undefined;
}).filter(i => !!i);
}
this._tasks = tasks.map(i => MenuRegistry.getCommand(i)).filter(v => !!v);
}
ngOnInit() {
this._inited = true;
this._register(registerThemingParticipant(this.registerThemeing));
this._computeContainer();
this._tasks.map(a => {
this._tileContainer.append(this._createTile(a));
});
this._scrollableElement = this._register(new ScrollableElement(this._tileContainer, {
horizontal: ScrollbarVisibility.Auto,
vertical: ScrollbarVisibility.Hidden,
scrollYToX: true,
useShadows: false
}));
this._scrollableElement.onScroll(e => {
this._tileContainer.style.right = e.scrollLeft + 'px';
});
(this._container.nativeElement as HTMLElement).appendChild(this._scrollableElement.getDomNode());
// Update scrollbar
this._scrollableElement.setScrollDimensions({
width: DOM.getContentWidth(this._container.nativeElement),
scrollWidth: DOM.getContentWidth(this._tileContainer) + 18 // right padding
});
}
private _computeContainer(): void {
const height = DOM.getContentHeight(this._container.nativeElement);
const tilesHeight = Math.floor(height / (this._size + 10));
const width = (this._size + 18) * Math.ceil(this._tasks.length / tilesHeight);
if (!this._tileContainer) {
this._tileContainer = DOM.$('.tile-container');
}
this._tileContainer.style.height = height + 'px';
this._tileContainer.style.width = width + 'px';
}
private _createTile(action: ICommandAction): HTMLElement {
const label = DOM.$('div');
label.innerText = types.isString(action.title) ? action.title : action.title.value;
const tile = DOM.$('.task-tile');
tile.style.height = this._size + 'px';
tile.style.width = this._size + 'px';
const innerTile = DOM.$('div');
const iconClassName = TaskRegistry.getOrCreateTaskIconClassName(action);
if (iconClassName) {
const icon = DOM.$('span.codicon');
DOM.addClass(icon, iconClassName);
innerTile.append(icon);
}
innerTile.append(label);
tile.append(innerTile);
tile.setAttribute('tabindex', '0');
this._register(DOM.addDisposableListener(tile, DOM.EventType.CLICK, () => this.runTask(action)));
this._register(DOM.addDisposableListener(tile, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
this.runTask(action);
e.stopImmediatePropagation();
}
}));
return tile;
}
private registerThemeing(theme: IColorTheme, collector: ICssStyleCollector) {
const contrastBorder = theme.getColor(colors.contrastBorder);
const sideBarColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND);
if (contrastBorder) {
const contrastBorderString = contrastBorder.toString();
collector.addRule(`tasks-widget .task-tile { border: 1px solid ${contrastBorderString} }`);
} else {
const sideBarColorString = sideBarColor.toString();
collector.addRule(`tasks-widget .task-tile { background-color: ${sideBarColorString} }`);
}
}
public runTask(task: ICommandAction) {
this.commandService.executeCommand(task.id, this._profile);
}
public layout(): void {
if (this._inited) {
this._computeContainer();
// Update scrollbar
this._scrollableElement.setScrollDimensions({
width: DOM.getContentWidth(this._container.nativeElement),
scrollWidth: DOM.getContentWidth(this._tileContainer) + 18 // right padding
});
}
}
}

View File

@@ -1,38 +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 { registerDashboardWidget } from 'sql/platform/dashboard/browser/widgetRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { TaskRegistry } from 'sql/workbench/services/tasks/browser/tasksRegistry';
const singleTaskSchema: IJSONSchema = {
type: 'string',
enum: TaskRegistry.getTasks()
};
const tasksSchema: IJSONSchema = {
type: 'array',
items: {
anyOf: [
singleTaskSchema,
{
type: 'object',
properties: {
name: singleTaskSchema,
when: {
type: 'string'
}
}
}
]
}
};
TaskRegistry.onTaskRegistered(e => {
singleTaskSchema.enum.push(e);
});
registerDashboardWidget('tasks-widget', '', tasksSchema);

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Component, Inject, forwardRef, OnInit, ElementRef } from '@angular/core'; import { Component, Inject, forwardRef, OnInit, ElementRef, ChangeDetectorRef } from '@angular/core';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
@@ -41,9 +41,10 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget,
@Inject(WIDGET_CONFIG) protected readonly _config: WidgetConfig, @Inject(WIDGET_CONFIG) protected readonly _config: WidgetConfig,
@Inject(forwardRef(() => ElementRef)) private readonly _el: ElementRef, @Inject(forwardRef(() => ElementRef)) private readonly _el: ElementRef,
@Inject(IDashboardViewService) private readonly dashboardViewService: IDashboardViewService, @Inject(IDashboardViewService) private readonly dashboardViewService: IDashboardViewService,
@Inject(IWebviewService) private readonly webviewService: IWebviewService @Inject(IWebviewService) private readonly webviewService: IWebviewService,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef
) { ) {
super(); super(changeRef);
this._id = (_config.widget[selector] as IWebviewWidgetConfig).id; this._id = (_config.widget[selector] as IWebviewWidgetConfig).id;
} }

View File

@@ -435,7 +435,6 @@ import 'sql/workbench/contrib/dashboard/browser/widgets/insights/views/tableInsi
import 'sql/workbench/contrib/dashboard/browser/dashboard.contribution'; import 'sql/workbench/contrib/dashboard/browser/dashboard.contribution';
import 'sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.contribution'; import 'sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.contribution';
import 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.contribution'; import 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.contribution';
import 'sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.contribution';
import 'sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.contribution'; import 'sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.contribution';
import 'sql/workbench/contrib/dashboard/browser/containers/dashboardWebviewContainer.contribution'; import 'sql/workbench/contrib/dashboard/browser/containers/dashboardWebviewContainer.contribution';
import 'sql/workbench/contrib/dashboard/browser/containers/dashboardControlHostContainer.contribution'; import 'sql/workbench/contrib/dashboard/browser/containers/dashboardControlHostContainer.contribution';