From 4f76f116ac5bd5abaac8cba0dfbe29d06bd29b5d Mon Sep 17 00:00:00 2001 From: Kevin Cunnane Date: Fri, 29 Jun 2018 16:04:57 -0700 Subject: [PATCH] Ensure dashboard tabs only show for supported providers (#1798) - Fixes #1764, fixes #1765 - Fixes bug where configModifiers were passed in the dashboard service in some places, and a different object with incompatible / missing fields in a different one. Now it always passes in the owner object, and this should have the required fields - Note: this doesn't fix the problem where the code does not fail to build, which I would have expected to be the case. - Adds "provider" as an option for TabConfig including in the schema - Filter by "provider" type when loading tabs. If provider is not set for a tab, will assume "MSSQL" since it's the default provider. This is a design decision - without this, we would either have to disable tabs that don't show this or have them incorrectly show for non-MSSQL provider types Notes: Does not override behavior for individual widgets, which still get the current provider set as their provider if not specified. This seems to be required so should be fine. We will fix tasks/widgets that aren't supported by a provider showing up in a separate PR, since it's a different issue --- src/sql/parts/connection/common/constants.ts | 2 ++ .../parts/dashboard/common/dashboardHelper.ts | 25 ++++++++++++++++--- .../common/dashboardPage.component.ts | 13 ++++------ .../common/dashboardTab.contribution.ts | 16 ++++++++++-- src/sql/parts/dashboard/common/interfaces.ts | 7 ++++++ .../dashboardNavSection.component.ts | 25 ++++++++++++------- .../dashboard/common/dashboardRegistry.ts | 2 +- 7 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/sql/parts/connection/common/constants.ts b/src/sql/parts/connection/common/constants.ts index 5d1f84e35d..4fbe1531a7 100644 --- a/src/sql/parts/connection/common/constants.ts +++ b/src/sql/parts/connection/common/constants.ts @@ -21,6 +21,8 @@ export const capabilitiesOptions = 'OPTIONS_METADATA'; export const configMaxRecentConnections = 'maxRecentConnections'; export const mssqlProviderName = 'MSSQL'; +export const anyProviderName = '*'; +export const connectionProviderContextKey = 'connectionProvider'; export const applicationName = 'sqlops'; diff --git a/src/sql/parts/dashboard/common/dashboardHelper.ts b/src/sql/parts/dashboard/common/dashboardHelper.ts index 62cd1e1720..a5a29bf366 100644 --- a/src/sql/parts/dashboard/common/dashboardHelper.ts +++ b/src/sql/parts/dashboard/common/dashboardHelper.ts @@ -19,9 +19,9 @@ import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebvi import { MODELVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.contribution'; import { CONTROLHOST_CONTAINER } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.contribution'; import { NAV_SECTION } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution'; -import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions, IDashboardContainer, registerContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; -import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry'; +import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service'; +import * as Constants from 'sql/parts/connection/common/constants'; const dashboardcontainerRegistry = Registry.as(DashboardContainerExtensions.dashboardContainerContributions); const containerTypes = [ @@ -166,9 +166,11 @@ export function addContext(config: WidgetConfig[], collection: any, context: str * Returns a filtered version of the widgets passed based on edition and provider * @param config widgets to filter */ -export function filterConfigs(config: T[], collection: K): Array { +export function filterConfigs(config: T[], collection: K): Array { return config.filter((item) => { - if (!item.when) { + if (!hasCompatibleProvider(item.provider, collection.contextKeyService)) { + return false; + } else if (!item.when) { return true; } else { return collection.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(item.when)); @@ -176,6 +178,21 @@ export function filterConfigs(Constants.connectionProviderContextKey); + if (connectionProvider) { + let providers = (provider instanceof Array) ? provider : [provider]; + let matchingProvider = providers.find((p) => p === connectionProvider || p === Constants.anyProviderName); + isCompatible = (matchingProvider !== undefined); + } // Else there's no connection context so skip the check + return isCompatible; +} + /** * Get registered container if it is specified as the key * @param container dashboard container diff --git a/src/sql/parts/dashboard/common/dashboardPage.component.ts b/src/sql/parts/dashboard/common/dashboardPage.component.ts index 3498e3f0d7..dece1f01f5 100644 --- a/src/sql/parts/dashboard/common/dashboardPage.component.ts +++ b/src/sql/parts/dashboard/common/dashboardPage.component.ts @@ -17,11 +17,12 @@ import { IDashboardRegistry, Extensions as DashboardExtensions, IDashboardTab } import { PinUnpinTabAction, AddFeatureTabAction } from './actions'; import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component'; import { AngularEventType, IAngularEventingService } from 'sql/services/angularEventing/angularEventingService'; -import { DashboardTab } from 'sql/parts/dashboard/common/interfaces'; +import { DashboardTab, IConfigModifierCollection } from 'sql/parts/dashboard/common/interfaces'; import * as dashboardHelper from 'sql/parts/dashboard/common/dashboardHelper'; import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution'; import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution'; import { AngularDisposable } from 'sql/base/common/lifecycle'; +import * as Constants from 'sql/parts/connection/common/constants'; import { Registry } from 'vs/platform/registry/common/platform'; import * as types from 'vs/base/common/types'; @@ -38,16 +39,11 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const dashboardRegistry = Registry.as(DashboardExtensions.DashboardContributions); -interface IConfigModifierCollection { - connectionManagementService: SingleConnectionManagementService; - contextKeyService: IContextKeyService; -} - @Component({ selector: 'dashboard-page', templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/common/dashboardPage.component.html')) }) -export abstract class DashboardPage extends AngularDisposable { +export abstract class DashboardPage extends AngularDisposable implements IConfigModifierCollection { protected tabs: Array = []; @@ -140,6 +136,7 @@ export abstract class DashboardPage extends AngularDisposable { // Create home tab let homeTab: TabConfig = { id: 'homeTab', + provider: Constants.anyProviderName, publisher: undefined, title: this.homeTabTitle, container: { 'widgets-container': homeWidgets }, @@ -218,7 +215,7 @@ export abstract class DashboardPage extends AngularDisposable { if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) { let configs = Object.values(containerResult.container)[0]; this._configModifiers.forEach(cb => { - configs = cb.apply(this, [configs, this.dashboardService, this.context]); + configs = cb.apply(this, [configs, this, this.context]); }); this._gridModifiers.forEach(cb => { configs = cb.apply(this, [configs]); diff --git a/src/sql/parts/dashboard/common/dashboardTab.contribution.ts b/src/sql/parts/dashboard/common/dashboardTab.contribution.ts index e353612411..64ef8d2102 100644 --- a/src/sql/parts/dashboard/common/dashboardTab.contribution.ts +++ b/src/sql/parts/dashboard/common/dashboardTab.contribution.ts @@ -7,6 +7,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { localize } from 'vs/nls'; import * as types from 'vs/base/common/types'; +import * as Constants from 'sql/parts/connection/common/constants'; import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry'; import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution'; @@ -17,6 +18,7 @@ export interface IDashboardTabContrib { id: string; title: string; container: object; + provider: string | string[]; when?: string; description?: string; alwaysShow?: boolean; @@ -41,6 +43,11 @@ const tabSchema: IJSONSchema = { description: localize('sqlops.extension.contributes.tab.when', 'Condition which must be true to show this item'), type: 'string' }, + provider: { + description: localize('sqlops.extension.contributes.tab.provider', 'Defines the connection types this tab is compatible with. Defaults to "MSSQL" if not set'), + type: ['string', 'array'] + + }, container: { description: localize('sqlops.extension.contributes.dashboard.tab.container', "The container that will be displayed in this tab."), type: 'object', @@ -67,7 +74,7 @@ const tabContributionSchema: IJSONSchema = { ExtensionsRegistry.registerExtensionPoint('dashboard.tabs', [], tabContributionSchema).setHandler(extensions => { function handleCommand(tab: IDashboardTabContrib, extension: IExtensionPointUser) { - let { description, container, title, when, id, alwaysShow } = tab; + let { description, container, provider, title, when, id, alwaysShow } = tab; // If always show is not specified, set it to true by default. if (!types.isBoolean(alwaysShow)) { @@ -88,6 +95,11 @@ ExtensionsRegistry.registerExtensionPoint DashboardNavSection) }], templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/containers/dashboardNavSection.component.html')) }) -export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit { +export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit, IConfigModifierCollection { @Input() private tab: TabConfig; protected tabs: Array = []; private _onResize = new Emitter(); @@ -38,7 +37,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh }; // a set of config modifiers - private readonly _configModifiers: Array<(item: Array, dashboardServer: DashboardServiceInterface, context: string) => Array> = [ + private readonly _configModifiers: Array<(item: Array, collection: IConfigModifierCollection, context: string) => Array> = [ dashboardHelper.removeEmpty, dashboardHelper.initExtensionConfigs, dashboardHelper.addProvider, @@ -103,7 +102,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) { let configs = Object.values(containerResult.container)[0]; this._configModifiers.forEach(cb => { - configs = cb.apply(this, [configs, this.dashboardService, this.tab.context]); + configs = cb.apply(this, [configs, this, this.tab.context]); }); this._gridModifiers.forEach(cb => { configs = cb.apply(this, [configs]); @@ -169,4 +168,12 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh }); } } + + public get connectionManagementService(): SingleConnectionManagementService { + return this.dashboardService.connectionManagementService; + } + + public get contextKeyService(): IContextKeyService { + return this.dashboardService.scopedContextKeyService; + } } diff --git a/src/sql/platform/dashboard/common/dashboardRegistry.ts b/src/sql/platform/dashboard/common/dashboardRegistry.ts index 65b54f801e..84b6f75762 100644 --- a/src/sql/platform/dashboard/common/dashboardRegistry.ts +++ b/src/sql/platform/dashboard/common/dashboardRegistry.ts @@ -7,7 +7,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtension } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import * as nls from 'vs/nls'; -import { deepClone } from 'vs/base/common/objects'; import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ProviderProperties } from 'sql/parts/dashboard/widgets/properties/propertiesWidget.component'; @@ -22,6 +21,7 @@ export const Extensions = { export interface IDashboardTab { id: string; title: string; + provider: string | string[]; publisher: string; description?: string; container?: object;