Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing dashboard tabs not loading when the extension is not activated. #24674

Merged
merged 9 commits into from
Oct 20, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import { LabeledMenuItemActionItem } from 'sql/platform/actions/browser/menuEntr
import { DASHBOARD_BORDER, TOOLBAR_OVERFLOW_SHADOW } from 'sql/workbench/common/theme';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';

const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
const homeTabGroupId = 'home';
Expand Down Expand Up @@ -130,7 +132,9 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IMenuService) private menuService: IMenuService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) private instantiationService: IInstantiationService
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IExtensionService) private extensionService: IExtensionService,
@Inject(IProgressService) private progressService: IProgressService
) {
super();
this._tabName = DashboardPage.tabName.bindTo(contextKeyService);
Expand Down Expand Up @@ -295,54 +299,61 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
return undefined;
}

private createTabs(homeWidgets: WidgetConfig[]) {
// Clear all tabs
this.tabs = [];
this._tabSettingConfigs = [];
this._tabsDispose.forEach(i => i.dispose());
this._tabsDispose = [];
private async createTabs(homeWidgets: WidgetConfig[]) {
this.progressService.withProgress({
location: ProgressLocation.Notification,
title: nls.localize("dashboard.loading", "Loading dashboard"),
cancellable: false
}, async (progress) => {
// Clear all tabs
this.tabs = [];
this._tabSettingConfigs = [];
this._tabsDispose.forEach(i => i.dispose());
this._tabsDispose = [];

let allTabs = dashboardHelper.filterConfigs(dashboardRegistry.tabs, this);
let allTabs = dashboardHelper.filterConfigs(dashboardRegistry.tabs, this);

// Before separating tabs into pinned / shown, ensure that the home tab is always set up as expected
allTabs = this.setAndRemoveHomeTab(allTabs, homeWidgets);
// Before separating tabs into pinned / shown, ensure that the home tab is always set up as expected
allTabs = this.setAndRemoveHomeTab(allTabs, homeWidgets);

this.loadNewTabs(allTabs.filter((tab) => tab.group === homeTabGroupId));
await this.loadNewTabs(allTabs.filter((tab) => tab.group === homeTabGroupId));

// Load tab setting configs
this._tabSettingConfigs = this.dashboardService.getSettings<Array<TabSettingConfig>>([this.context, 'tabs'].join('.'));
// Load tab setting configs
this._tabSettingConfigs = this.dashboardService.getSettings<Array<TabSettingConfig>>([this.context, 'tabs'].join('.'));

this.addCustomTabGroups(allTabs);
this.addExtensionsTabGroup(allTabs);
await this.addCustomTabGroups(allTabs);
await this.addExtensionsTabGroup(allTabs);

this.panelActions = [];
this.panelActions = [];

this._cd.detectChanges();
this._cd.detectChanges();

this._tabsDispose.push(this.dashboardService.onPinUnpinTab(e => {
const tabConfig = this._tabSettingConfigs.find(i => i.tabId === e.tabId);
if (tabConfig) {
tabConfig.isPinned = e.isPinned;
} else {
this._tabSettingConfigs.push(e);
}
this.rewriteConfig();
}));
this._tabsDispose.push(this.dashboardService.onPinUnpinTab(e => {
const tabConfig = this._tabSettingConfigs.find(i => i.tabId === e.tabId);
if (tabConfig) {
tabConfig.isPinned = e.isPinned;
} else {
this._tabSettingConfigs.push(e);
}
this.rewriteConfig();
}));

this._tabsDispose.push(this.dashboardService.onAddNewTabs(e => {
this.loadNewTabs(e, true);
}));
this._tabsDispose.push(this.dashboardService.onAddNewTabs(e => {
this.loadNewTabs(e, true);
}));
});
}

/**
* Add the custom tab groups and their child tabs.
* @param allTabs The available tabs
*/
private addCustomTabGroups(allTabs: IDashboardTab[]): void {
dashboardRegistry.tabGroups.forEach((tabGroup) => {
private async addCustomTabGroups(allTabs: IDashboardTab[]): Promise<void> {
for (let i = 0; i < dashboardRegistry.tabGroups.length; i++) {
const tabGroup = dashboardRegistry.tabGroups[i];
const tabs = allTabs.filter(tab => tab.group === tabGroup.id);
if (tabs.length > 0) {
this.addNewTab({
await this.addNewTab({
id: tabGroup.id,
provider: Constants.anyProviderName,
originalConfig: [],
Expand All @@ -354,16 +365,16 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
canClose: false,
actions: []
});
this.loadNewTabs(tabs);
await this.loadNewTabs(tabs);
}
});
}
}

/**
* Add the "Extensions" tab group, tabs without a group will be added here.
* @param allTabs The available tabs
*/
private addExtensionsTabGroup(allTabs: IDashboardTab[]): void {
private async addExtensionsTabGroup(allTabs: IDashboardTab[]): Promise<void> {
const tabs = allTabs.filter(tab => !tab.group);
if (tabs.length > 0) {
this.addNewTab({
Expand All @@ -378,7 +389,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
canClose: false,
actions: []
});
this.loadNewTabs(tabs);
await this.loadNewTabs(tabs);
}
}

Expand Down Expand Up @@ -416,17 +427,20 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
this.dashboardService.writeSettings([this.context, 'tabs'].join('.'), writeableConfig, target);
}

private loadNewTabs(dashboardTabs: IDashboardTab[], openLastTab: boolean = false) {
private async loadNewTabs(dashboardTabs: IDashboardTab[], openLastTab: boolean = false) {
if (dashboardTabs && dashboardTabs.length > 0) {
const selectedTabs = dashboardTabs.map(v => this.initTabComponents(v)).map(v => {
const tabs = dashboardTabs.map(v => this.initTabComponents(v));
const selectedTabs = [];
for (let i = 0; i < tabs.length; i++) {
const v = tabs[i];
const config = v as TabConfig;
config.context = this.context;
config.editable = false;
config.canClose = false;
config.actions = [];
this.addNewTab(config);
return config;
});
await this.addNewTab(config);
selectedTabs.push(config);
}

if (openLastTab) {
// put this immediately on the stack so that is ran *after* the tab is rendered
Expand Down Expand Up @@ -495,14 +509,68 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
return tab.container ? Object.keys(tab.container)[0] : '';
}

private addNewTab(tab: TabConfig): void {
const existedTab = this.tabs.find(i => i.id === tab.id);
if (!existedTab) {
if (!tab.iconClass && tab.type !== 'group-header') {
tab.iconClass = 'default-tab-icon';
private async addNewTab(tab: TabConfig): Promise<void> {

try {
// Finding the owning extension for the tab
const owningExtension = this.extensionService.extensions.find(extension => {
if (extension?.contributes !== undefined && extension.contributes['dashboard.tabs']) {
const tabIndex = extension.contributes['dashboard.tabs'].findIndex(eTab => eTab.id === tab.id);
if (tabIndex !== -1) {
return true;
}
}
return false;
});

// If the tab is contributed by an extension, we'll wait for the extension to be activated before adding the tab.
// Not doing so causes issues with the tab UI not being rendered correctly.
if (owningExtension) {
// wait for the extension to be activated
await new Promise<void>((resolve, reject) => {
// If the extension is already activated, we can resolve immediately
if (this.extensionService.getExtensionsStatus()[owningExtension.id]?.activationTimes?.activateResolvedTime !== undefined) {
aasimkhan30 marked this conversation as resolved.
Show resolved Hide resolved
resolve();
}

// Otherwise, we'll wait for the extension to be activated
const disposable = this.extensionService.onDidChangeExtensionsStatus(e => {
e.forEach(extension => {
if (extension.value === owningExtension.id) {
disposable.dispose();
resolve();
}
})
});

/**
* If the extension doesn't activate within 60 seconds, we'll reject the promise.
* While this seems like a long time, it is possible for some extensions to take a
* while to activate, especially if it has to download a big service file.
*/
setTimeout(() => {
aasimkhan30 marked this conversation as resolved.
Show resolved Hide resolved
disposable.dispose();
reject();
}, 60000);
});
}
this.tabs.push(tab);
this._cd.detectChanges();

const existedTab = this.tabs.find(i => i.id === tab.id);
if (!existedTab) {
aasimkhan30 marked this conversation as resolved.
Show resolved Hide resolved
if (!tab.iconClass && tab.type !== 'group-header') {
tab.iconClass = 'default-tab-icon';
}
this.tabs.push(tab);
this._cd.detectChanges();
}

} catch (error) {
// If we fail to load a tab, we'll just log the error and continue
this.notificationService.notify({
severity: Severity.Error,
message: nls.localize('dashboard.tabLoadingError', "Error loading tab {0}", tab.title)
aasimkhan30 marked this conversation as resolved.
Show resolved Hide resolved
});
this.logService.error(error);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IProgressService } from 'vs/platform/progress/common/progress';

export class DatabaseDashboardPage extends DashboardPage implements OnInit {
protected propertiesWidget: WidgetConfig = {
Expand Down Expand Up @@ -52,9 +54,11 @@ export class DatabaseDashboardPage extends DashboardPage implements OnInit {
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IMenuService) menuService: IMenuService,
@Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) instantiationService: IInstantiationService
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IExtensionService) extensionService: IExtensionService,
@Inject(IProgressService) progressService: IProgressService
) {
super(dashboardService, el, _cd, notificationService, angularEventingService, logService, commandService, contextKeyService, menuService, themeService, instantiationService);
super(dashboardService, el, _cd, notificationService, angularEventingService, logService, commandService, contextKeyService, menuService, themeService, instantiationService, extensionService, progressService);
this._register(dashboardService.onUpdatePage(() => {
this.refresh(true);
this._cd.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { IMenuService } from 'vs/platform/actions/common/actions';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IProgressService } from 'vs/platform/progress/common/progress';

export class ServerDashboardPage extends DashboardPage implements OnInit {
protected propertiesWidget: WidgetConfig = {
Expand Down Expand Up @@ -55,9 +57,11 @@ export class ServerDashboardPage extends DashboardPage implements OnInit {
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IMenuService) menuService: IMenuService,
@Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) instantiationService: IInstantiationService
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IExtensionService) extensionService: IExtensionService,
@Inject(IProgressService) progressService: IProgressService
) {
super(dashboardService, el, _cd, notificationService, angularEventingService, logService, commandService, contextKeyService, menuService, themeService, instantiationService);
super(dashboardService, el, _cd, notificationService, angularEventingService, logService, commandService, contextKeyService, menuService, themeService, instantiationService, extensionService, progressService);

// special-case handling for MSSQL data provider
const connInfo = this.dashboardService.connectionManagementService.connectionInfo;
Expand Down