mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Error handling for dashboard tab contributions (#851)
* support tab without title * address comments * formatting * support error validation for dashboard tab and container contributions * formatting
This commit is contained in:
@@ -172,20 +172,17 @@ export function filterConfigs<T extends { when?: string }>(config: T[], dashboar
|
||||
* Get registered container if it is specified as the key
|
||||
* @param container dashboard container
|
||||
*/
|
||||
export function getDashboardContainer(container: object): object {
|
||||
if (Object.keys(container).length !== 1) {
|
||||
error(nls.localize('moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space'));
|
||||
}
|
||||
|
||||
export function getDashboardContainer(container: object): { result: boolean, message: string, container: object } {
|
||||
let key = Object.keys(container)[0];
|
||||
let containerTypeFound = containerTypes.find(c => (c === key));
|
||||
if (!containerTypeFound) {
|
||||
let dashboardContainer = dashboardcontainerRegistry.getRegisteredContainer(key);
|
||||
if (!dashboardContainer) {
|
||||
error(nls.localize('unknownDashboardContainerError', 'The specified dashboard container is unknown.'));
|
||||
let error = nls.localize('unknownDashboardContainerError', '{0} is an unknown container.', key);
|
||||
return { result: false, message: error, container: null };
|
||||
} else {
|
||||
container = dashboardContainer.container;
|
||||
}
|
||||
}
|
||||
return container;
|
||||
return { result: true, message: null, container: container };
|
||||
}
|
||||
@@ -189,11 +189,16 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
private loadNewTabs(dashboardTabs: IDashboardTab[], openLastTab: boolean = false) {
|
||||
if (dashboardTabs && dashboardTabs.length > 0) {
|
||||
let selectedTabs = dashboardTabs.map(v => {
|
||||
let container = dashboardHelper.getDashboardContainer(v.container);
|
||||
let key = Object.keys(container)[0];
|
||||
let containerResult = dashboardHelper.getDashboardContainer(v.container);
|
||||
if (!containerResult.result) {
|
||||
let errorTitle = nls.localize('dashboardPage_loadTabError', 'Cannot open {0}. ', v.title);
|
||||
this.dashboardService.messageService.show(Severity.Error, errorTitle + containerResult.message);
|
||||
return null;
|
||||
}
|
||||
let key = Object.keys(containerResult.container)[0];
|
||||
|
||||
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
|
||||
let configs = <WidgetConfig[]>Object.values(container)[0];
|
||||
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs, this.dashboardService, this.context]);
|
||||
});
|
||||
@@ -207,27 +212,33 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
return { id: v.id, title: v.title, container: { 'grid-container': configs }, alwaysShow: v.alwaysShow };
|
||||
}
|
||||
}
|
||||
return { id: v.id, title: v.title, container: container, alwaysShow: v.alwaysShow };
|
||||
return { id: v.id, title: v.title, container: containerResult.container, alwaysShow: v.alwaysShow };
|
||||
}).map(v => {
|
||||
let actions = [];
|
||||
if (!v.alwaysShow) {
|
||||
let pinnedTab = this._pinnedTabs.find(i => i.tabId === v.id);
|
||||
actions.push(this.dashboardService.instantiationService.createInstance(PinUnpinTabAction, v.id, this.dashboardService.getUnderlyingUri(), !!pinnedTab));
|
||||
}
|
||||
if (v) {
|
||||
let actions = [];
|
||||
if (!v.alwaysShow) {
|
||||
let pinnedTab = this._pinnedTabs.find(i => i.tabId === v.id);
|
||||
actions.push(this.dashboardService.instantiationService.createInstance(PinUnpinTabAction, v.id, this.dashboardService.getUnderlyingUri(), !!pinnedTab));
|
||||
}
|
||||
|
||||
let config = v as TabConfig;
|
||||
config.context = this.context;
|
||||
config.editable = false;
|
||||
config.canClose = true;
|
||||
config.actions = actions;
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
let config = v as TabConfig;
|
||||
config.context = this.context;
|
||||
config.editable = false;
|
||||
config.canClose = true;
|
||||
config.actions = actions;
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (openLastTab) {
|
||||
// put this immediately on the stack so that is ran *after* the tab is rendered
|
||||
setTimeout(() => {
|
||||
this._panel.selectTab(selectedTabs.pop().id);
|
||||
let selectedLastTab = selectedTabs.pop();
|
||||
if (selectedLastTab) {
|
||||
this._panel.selectTab(selectedLastTab.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import { localize } from 'vs/nls';
|
||||
|
||||
import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { NAV_SECTION, validateNavSectionContribution } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
|
||||
export interface IDashboardTabContrib {
|
||||
id: string;
|
||||
@@ -92,16 +95,43 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
|
||||
alwaysShow = alwaysShow || false;
|
||||
let publisher = extension.description.publisher;
|
||||
if (!title) {
|
||||
extension.collector.error('No title specified for extension.');
|
||||
extension.collector.error(localize('dashboardTab.contribution.noTitleError', 'No title specified for extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!description) {
|
||||
extension.collector.warn('No description specified to show.');
|
||||
extension.collector.warn(localize('dashboardTab.contribution.noDescriptionWarning', 'No description specified to show.'));
|
||||
}
|
||||
|
||||
if (!container) {
|
||||
extension.collector.warn('No container specified to show.');
|
||||
extension.collector.error(localize('dashboardTab.contribution.noContainerError', 'No container specified for extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(container).length !== 1) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space'));
|
||||
return;
|
||||
}
|
||||
|
||||
let result = true;
|
||||
let containerkey = Object.keys(container)[0];
|
||||
let containerValue = Object.values(container)[0];
|
||||
|
||||
switch (containerkey) {
|
||||
case WIDGETS_CONTAINER:
|
||||
result = validateWidgetContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case GRID_CONTAINER:
|
||||
result = validateGridContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case NAV_SECTION:
|
||||
result = validateNavSectionContribution(extension, containerValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
registerTab({ description, title, container, edition, provider, id, alwaysShow, publisher });
|
||||
}
|
||||
registerTab({ description, title, container, edition, provider, id, alwaysShow, publisher });
|
||||
}
|
||||
|
||||
for (let extension of extensions) {
|
||||
|
||||
@@ -10,6 +10,17 @@ import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
import { registerContainer, generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { NAV_SECTION, validateNavSectionContribution } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
||||
|
||||
const containerTypes = [
|
||||
WIDGETS_CONTAINER,
|
||||
GRID_CONTAINER,
|
||||
WEBVIEW_CONTAINER,
|
||||
NAV_SECTION
|
||||
];
|
||||
|
||||
export type IUserFriendlyIcon = string | { light: string; dark: string; };
|
||||
|
||||
@@ -48,10 +59,45 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardContainerContrib | IDashboar
|
||||
|
||||
function handleCommand(dashboardContainer: IDashboardContainerContrib, extension: IExtensionPointUser<any>) {
|
||||
let { id, container } = dashboardContainer;
|
||||
if (!container) {
|
||||
extension.collector.warn('No container specified to show.');
|
||||
if (!id) {
|
||||
extension.collector.error(localize('dashboardContainer.contribution.noIdError', 'No id in dashboard container specified for extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!container) {
|
||||
extension.collector.error(localize('dashboardContainer.contribution.noContainerError', 'No container in dashboard container specified for extension.'));
|
||||
return;
|
||||
}
|
||||
if (Object.keys(container).length !== 1) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space.'));
|
||||
return;
|
||||
}
|
||||
|
||||
let result = true;
|
||||
let containerkey = Object.keys(container)[0];
|
||||
let containerValue = Object.values(container)[0];
|
||||
|
||||
let containerTypeFound = containerTypes.find(c => (c === containerkey));
|
||||
if (!containerTypeFound) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.unKnownContainerType', 'Unknown container type defines in dashboard container for extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (containerkey) {
|
||||
case WIDGETS_CONTAINER:
|
||||
result = validateWidgetContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case GRID_CONTAINER:
|
||||
result = validateGridContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case NAV_SECTION:
|
||||
result = validateNavSectionContribution(extension, containerValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
registerContainer({ id, container });
|
||||
}
|
||||
registerContainer({ id, container });
|
||||
}
|
||||
|
||||
for (let extension of extensions) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
@@ -18,3 +19,17 @@ let gridContainersSchema: IJSONSchema = {
|
||||
|
||||
registerContainerType(GRID_CONTAINER, gridContainersSchema);
|
||||
registerNavSectionContainerType(GRID_CONTAINER, gridContainersSchema);
|
||||
|
||||
export function validateGridContainerContribution(extension: IExtensionPointUser<any>, gridConfigs: object[]): boolean {
|
||||
let result = true;
|
||||
gridConfigs.forEach(widgetConfig => {
|
||||
let allKeys = Object.keys(widgetConfig);
|
||||
let widgetOrWebviewKey = allKeys.find(key => key === 'widget' || key === 'webview');
|
||||
if (!widgetOrWebviewKey) {
|
||||
result = false;
|
||||
extension.collector.error(nls.localize('gridContainer.invalidInputs', 'widgets or webviews are expected inside widgets-container for extension.'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import * as dashboardHelper from 'sql/parts/dashboard/common/dashboardHelper';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-nav-section',
|
||||
@@ -86,11 +88,16 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
if (dashboardTabs && dashboardTabs.length > 0) {
|
||||
let selectedTabs = dashboardTabs.map(v => {
|
||||
|
||||
let container = dashboardHelper.getDashboardContainer(v.container);
|
||||
let key = Object.keys(container)[0];
|
||||
let containerResult = dashboardHelper.getDashboardContainer(v.container);
|
||||
if (!containerResult.result) {
|
||||
let errorTitle = nls.localize('dashboardNavSection_loadTabError', 'There is an error while loading {0} section. ', v.title);
|
||||
this.dashboardService.messageService.show(Severity.Error, errorTitle + containerResult.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
let key = Object.keys(containerResult.container)[0];
|
||||
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
|
||||
let configs = <WidgetConfig[]>Object.values(container)[0];
|
||||
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs, this.dashboardService, this.tab.context]);
|
||||
});
|
||||
@@ -104,20 +111,25 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
return { id: v.id, title: v.title, container: { 'grid-container': configs } };
|
||||
}
|
||||
}
|
||||
return { id: v.id, title: v.title, container: container };
|
||||
return { id: v.id, title: v.title, container: containerResult.container };
|
||||
}).map(v => {
|
||||
let config = v as TabConfig;
|
||||
config.context = this.tab.context;
|
||||
config.editable = false;
|
||||
config.canClose = false;
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
if (v) {
|
||||
let config = v as TabConfig;
|
||||
config.context = this.tab.context;
|
||||
config.editable = false;
|
||||
config.canClose = false;
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// put this immediately on the stack so that is ran *after* the tab is rendered
|
||||
setTimeout(() => {
|
||||
this._panel.selectTab(selectedTabs[0].id);
|
||||
});
|
||||
if (selectedTabs && selectedTabs[0]) {
|
||||
// put this immediately on the stack so that is ran *after* the tab is rendered
|
||||
setTimeout(() => {
|
||||
this._panel.selectTab(selectedTabs[0].id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { NavSectionConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { registerContainerType, generateNavSectionContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
||||
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
|
||||
export const NAV_SECTION = 'nav-section';
|
||||
|
||||
@@ -53,4 +58,41 @@ let NavSectionSchema: IJSONSchema = {
|
||||
items: navSectionContainerSchema
|
||||
};
|
||||
|
||||
registerContainerType(NAV_SECTION, NavSectionSchema);
|
||||
registerContainerType(NAV_SECTION, NavSectionSchema);
|
||||
|
||||
export function validateNavSectionContribution(extension: IExtensionPointUser<any>, navSectionConfigs: NavSectionConfig[]): boolean {
|
||||
let result = true;
|
||||
navSectionConfigs.forEach(section => {
|
||||
if (!section.title) {
|
||||
result = false;
|
||||
extension.collector.error(nls.localize('navSection.missingTitle_error', 'No title in nav section specified for extension.'));
|
||||
}
|
||||
|
||||
if (!section.container) {
|
||||
result = false;
|
||||
extension.collector.error(nls.localize('navSection.missingContainer_error', 'No container in nav section specified for extension.'));
|
||||
}
|
||||
|
||||
if (Object.keys(section.container).length !== 1) {
|
||||
result = false;
|
||||
extension.collector.error(nls.localize('navSection.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space.'));
|
||||
}
|
||||
|
||||
let containerKey = Object.keys(section.container)[0];
|
||||
let containerValue = Object.values(section.container)[0];
|
||||
|
||||
switch (containerKey) {
|
||||
case WIDGETS_CONTAINER:
|
||||
result = result && validateWidgetContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case GRID_CONTAINER:
|
||||
result = result && validateGridContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case NAV_SECTION:
|
||||
result = false;
|
||||
extension.collector.error(nls.localize('navSection.invalidContainer_error', 'NAV_SECTION within NAV_SECTION is an invalid container for extension.'));
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { generateDashboardWidgetSchema } from 'sql/parts/dashboard/pages/dashboardPageContribution';
|
||||
import { registerContainerType, registerNavSectionContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
|
||||
export const WIDGETS_CONTAINER = 'widgets-container';
|
||||
|
||||
@@ -18,3 +20,16 @@ let widgetsSchema: IJSONSchema = {
|
||||
|
||||
registerContainerType(WIDGETS_CONTAINER, widgetsSchema);
|
||||
registerNavSectionContainerType(WIDGETS_CONTAINER, widgetsSchema);
|
||||
|
||||
export function validateWidgetContainerContribution(extension: IExtensionPointUser<any>, WidgetConfigs: object[]): boolean {
|
||||
let result = true;
|
||||
WidgetConfigs.forEach(widgetConfig => {
|
||||
let allKeys = Object.keys(widgetConfig);
|
||||
let widgetKey = allKeys.find(key => key === 'widget');
|
||||
if (!widgetKey) {
|
||||
result = false;
|
||||
extension.collector.error(nls.localize('widgetContainer.invalidInputs', 'The list of widgets is expected inside widgets-container for extension.'));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user