[UD] Make some features togglable, add optional FAQ button (#15907)

* Make some UD features togglable.

Allows to configure in `product.json` whether to show workspace sharing tab and kubernetes namespace selector or not.

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* Add optional FAQ button into footer

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* Update README.md

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* fixup! Make some UD features togglable.
7.20.x
Oleksii Kurinnyi 2020-02-10 17:26:35 +02:00 committed by GitHub
parent 95754848f6
commit 0dfe9bee39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 213 additions and 48 deletions

View File

@ -274,3 +274,17 @@ The `"configuration.prefetch"` section allows to define resources that UD should
...
]
```
The `"configuration.features.disabled"` field defines features that should be disabled and not displayed in User Dashboard. Available values are `"workspaceSharing"`, `"kubernetesNamespaceSelector"`.
For example, this config disables kubernetes namespace selector:
```json
{
"configuration": {
"features": {
"disabled": ["kubernetesNamespaceSelector"]
}
}
}
```

View File

@ -17,6 +17,8 @@ import {RandomSvc} from '../../../../components/utils/random.service';
import {NamespaceSelectorSvc} from '../ready-to-go-stacks/namespace-selector/namespace-selector.service';
import { CheKubernetesNamespace } from '../../../../components/api/che-kubernetes-namespace.factory';
import { CheWorkspace } from '../../../../components/api/workspace/che-workspace.factory';
import { CheDashboardConfigurationService } from '../../../../components/branding/che-dashboard-configuration.service';
import { TogglableFeature } from '../../../../components/branding/che-branding.factory';
/**
* This class is handling the controller for stack importing directive.
@ -26,18 +28,18 @@ import { CheWorkspace } from '../../../../components/api/workspace/che-workspace
export class ImportStackController implements IImportStackScopeBindings {
static $inject = [
'cheDashboardConfigurationService',
'cheKubernetesNamespace',
'cheWorkspace',
'createWorkspaceSvc',
'namespaceSelectorSvc',
'randomSvc'
'randomSvc',
];
onChange: IImportStackScopeOnChange;
infrastructureNamespaceHint: string;
ephemeralMode: boolean;
enabledKubernetesNamespaceSelector: boolean = false;
/**
* Kubernetes Namespace API interaction.
@ -59,6 +61,10 @@ export class ImportStackController implements IImportStackScopeBindings {
* Workspace creation service.
*/
private createWorkspaceSvc: CreateWorkspaceSvc;
/**
* Dashboard configuration service.
*/
private cheDashboardConfigurationService: CheDashboardConfigurationService;
/**
* The selected source for devfile importing(URL or YAML).
*/
@ -88,12 +94,14 @@ export class ImportStackController implements IImportStackScopeBindings {
* Default constructor that is using resource injection
*/
constructor(
cheDashboardConfigurationService: CheDashboardConfigurationService,
cheKubernetesNamespace: CheKubernetesNamespace,
cheWorkspace: CheWorkspace,
createWorkspaceSvc: CreateWorkspaceSvc,
namespaceSelectorSvc: NamespaceSelectorSvc,
randomSvc: RandomSvc
randomSvc: RandomSvc,
) {
this.cheDashboardConfigurationService = cheDashboardConfigurationService;
this.cheKubernetesNamespace = cheKubernetesNamespace;
this.cheWorkspace = cheWorkspace;
this.createWorkspaceSvc = createWorkspaceSvc;
@ -106,6 +114,9 @@ export class ImportStackController implements IImportStackScopeBindings {
this.cheWorkspace.fetchWorkspaceSettings().then((settings: che.IWorkspaceSettings) => {
this.ephemeralMode = settings['che.workspace.persist_volumes.default'] === 'false';
});
this.cheDashboardConfigurationService.ready.then(() => {
this.enabledKubernetesNamespaceSelector = this.cheDashboardConfigurationService.enabledFeature(TogglableFeature.KUBERNETES_NAMESPACE_SELECTOR);
});
}
updateDevfileFromRemote(devfile: che.IWorkspaceDevfile, attrs: { factoryurl?: string } | undefined): void {

View File

@ -16,7 +16,10 @@
</che-label-container>
<!-- Kubernetes namespace selector -->
<che-label-container che-label-name="Kubernetes Namespace" che-label-description="{{importStackController.infrastructureNamespaceHint}}">
<che-label-container
ng-if="importStackController.enabledKubernetesNamespaceSelector"
che-label-name="Kubernetes Namespace"
che-label-description="{{importStackController.infrastructureNamespaceHint}}">
<kubernetes-namespace-selector on-change="importStackController.onInfrastructureNamespaceChange(namespaceId)"></kubernetes-namespace-selector>
</che-label-container>

View File

@ -18,6 +18,8 @@ import { IReadyToGoStacksScopeBindings, IReadyToGoStacksScopeOnChange } from './
import { ProjectSourceSelectorService } from './project-source-selector/project-source-selector.service';
import { CheKubernetesNamespace } from '../../../../components/api/che-kubernetes-namespace.factory';
import { CheWorkspace } from '../../../../components/api/workspace/che-workspace.factory';
import { CheDashboardConfigurationService } from '../../../../components/branding/che-dashboard-configuration.service';
import { TogglableFeature } from '../../../../components/branding/che-branding.factory';
/**
* This class is handling the controller for predefined stacks.
@ -27,12 +29,13 @@ import { CheWorkspace } from '../../../../components/api/workspace/che-workspace
export class ReadyToGoStacksController implements IReadyToGoStacksScopeBindings {
static $inject = [
'cheDashboardConfigurationService',
'cheKubernetesNamespace',
'cheWorkspace',
'createWorkspaceSvc',
'namespaceSelectorSvc',
'projectSourceSelectorService',
'randomSvc'
'randomSvc',
];
/**
@ -53,10 +56,12 @@ export class ReadyToGoStacksController implements IReadyToGoStacksScopeBindings
WORKSPACE_NAME_FORM = 'workspaceName';
infrastructureNamespaceHint: string = '';
ephemeralMode: boolean;
enabledKubernetesNamespaceSelector: boolean = false;
/**
* Injected dependencies.
*/
private cheDashboardConfigurationService: CheDashboardConfigurationService;
private cheKubernetesNamespace: CheKubernetesNamespace;
private cheWorkspace: CheWorkspace;
private createWorkspaceSvc: CreateWorkspaceSvc;
@ -101,13 +106,15 @@ export class ReadyToGoStacksController implements IReadyToGoStacksScopeBindings
* Default constructor that is using resource injection
*/
constructor(
cheDashboardConfigurationService: CheDashboardConfigurationService,
cheKubernetesNamespace: CheKubernetesNamespace,
cheWorkspace: CheWorkspace,
createWorkspaceSvc: CreateWorkspaceSvc,
namespaceSelectorSvc: NamespaceSelectorSvc,
projectSourceSelectorService: ProjectSourceSelectorService,
randomSvc: RandomSvc
randomSvc: RandomSvc,
) {
this.cheDashboardConfigurationService = cheDashboardConfigurationService;
this.cheKubernetesNamespace = cheKubernetesNamespace;
this.cheWorkspace = cheWorkspace;
this.createWorkspaceSvc = createWorkspaceSvc;
@ -131,6 +138,9 @@ export class ReadyToGoStacksController implements IReadyToGoStacksScopeBindings
this.cheWorkspace.fetchWorkspaceSettings().then((settings: che.IWorkspaceSettings) => {
this.ephemeralMode = settings['che.workspace.persist_volumes.default'] === 'false';
});
this.cheDashboardConfigurationService.ready.then(() => {
this.enabledKubernetesNamespaceSelector = this.cheDashboardConfigurationService.enabledFeature(TogglableFeature.KUBERNETES_NAMESPACE_SELECTOR);
});
}
/**

View File

@ -37,7 +37,10 @@
</che-label-container>
<!-- Kubernetes namespace selector -->
<che-label-container che-label-name="Kubernetes Namespace" che-label-description="{{readyToGoStacksController.infrastructureNamespaceHint}}">
<che-label-container
ng-if="readyToGoStacksController.enabledKubernetesNamespaceSelector"
che-label-name="Kubernetes Namespace"
che-label-description="{{readyToGoStacksController.infrastructureNamespaceHint}}">
<kubernetes-namespace-selector on-change="readyToGoStacksController.onInfrastructureNamespaceChanged(namespaceId)"></kubernetes-namespace-selector>
</che-label-container>

View File

@ -246,7 +246,10 @@ describe(`WorkspaceDetailsController >`, () => {
menu: {
disabled: []
},
prefetch: {}
prefetch: {},
features: {
disabled: []
}
};
}
};

View File

@ -19,6 +19,8 @@ import {CheService} from '../../../components/api/che-service.factory';
import {PluginRegistry} from '../../../components/api/plugin-registry.factory';
import {WorkspaceDataManager} from '../../../components/api/workspace/workspace-data-manager';
import { ConfirmDialogService } from '../../../components/service/confirm-dialog/confirm-dialog.service';
import { CheDashboardConfigurationService } from '../../../components/branding/che-dashboard-configuration.service';
import { TogglableFeature } from '../../../components/branding/che-branding.factory';
interface IPage {
title: string;
@ -84,14 +86,15 @@ export class WorkspaceDetailsService {
static $inject = [
'$log',
'$q',
'cheWorkspace',
'cheDashboardConfigurationService',
'cheNotification',
'ideSvc',
'workspaceDetailsProjectsService',
'cheService',
'chePermissions',
'cheService',
'cheWorkspace',
'confirmDialogService',
'pluginRegistry'
'ideSvc',
'pluginRegistry',
'workspaceDetailsProjectsService',
];
/**
@ -102,6 +105,10 @@ export class WorkspaceDetailsService {
* Promises service.
*/
private $q: ng.IQService;
/**
* Dashboard configuration service.
*/
private cheDashboardConfigurationService: CheDashboardConfigurationService;
/**
* Workspace API interaction.
*/
@ -156,23 +163,25 @@ export class WorkspaceDetailsService {
constructor (
$log: ng.ILogService,
$q: ng.IQService,
cheWorkspace: CheWorkspace,
cheDashboardConfigurationService: CheDashboardConfigurationService,
cheNotification: CheNotification,
ideSvc: IdeSvc,
workspaceDetailsProjectsService: WorkspaceDetailsProjectsService,
cheService: CheService,
chePermissions: che.api.IChePermissions,
cheService: CheService,
cheWorkspace: CheWorkspace,
confirmDialogService: ConfirmDialogService,
pluginRegistry: PluginRegistry
ideSvc: IdeSvc,
pluginRegistry: PluginRegistry,
workspaceDetailsProjectsService: WorkspaceDetailsProjectsService,
) {
this.$log = $log;
this.$q = $q;
this.cheWorkspace = cheWorkspace;
this.cheDashboardConfigurationService = cheDashboardConfigurationService;
this.cheNotification = cheNotification;
this.cheWorkspace = cheWorkspace;
this.confirmDialogService = confirmDialogService;
this.ideSvc = ideSvc;
this.pluginRegistry = pluginRegistry;
this.workspaceDetailsProjectsService = workspaceDetailsProjectsService;
this.confirmDialogService = confirmDialogService;
this.observable = new Observable<any>();
@ -181,7 +190,9 @@ export class WorkspaceDetailsService {
this.observable = new Observable();
cheService.fetchServices().finally(() => {
if (cheService.isServiceAvailable(chePermissions.getPermissionsServicePath())) {
const sharingEnabled = this.cheDashboardConfigurationService.enabledFeature(TogglableFeature.WORKSPACE_SHARING);
const permissionServiceAvailable = cheService.isServiceAvailable(chePermissions.getPermissionsServicePath());
if (sharingEnabled && permissionServiceAvailable) {
this.addPage('Share', '<share-workspace></share-workspace>', 'icon-ic_folder_shared_24px');
}
});

View File

@ -16,6 +16,8 @@ import {ConfirmDialogService} from '../../../../components/service/confirm-dialo
import {NamespaceSelectorSvc} from '../../create-workspace/ready-to-go-stacks/namespace-selector/namespace-selector.service';
import {WorkspaceDetailsService} from '../workspace-details.service';
import { CheKubernetesNamespace } from '../../../../components/api/che-kubernetes-namespace.factory';
import { CheDashboardConfigurationService } from '../../../../components/branding/che-dashboard-configuration.service';
import { TogglableFeature } from '../../../../components/branding/che-branding.factory';
const STARTING = WorkspaceStatus[WorkspaceStatus.STARTING];
const RUNNING = WorkspaceStatus[WorkspaceStatus.RUNNING];
@ -35,6 +37,7 @@ export class WorkspaceDetailsOverviewController {
'$route',
'$scope',
'$timeout',
'cheDashboardConfigurationService',
'cheKubernetesNamespace',
'cheNotification',
'cheWorkspace',
@ -49,12 +52,14 @@ export class WorkspaceDetailsOverviewController {
* Displaying name of infrastructure namespace.
*/
infrastructureNamespace: string;
enabledKubernetesNamespaceSelector: boolean = false;
private $location: ng.ILocationService;
private $q: ng.IQService;
private $route: ng.route.IRouteService;
private $scope: ng.IScope;
private $timeout: ng.ITimeoutService;
private cheDashboardConfigurationService: CheDashboardConfigurationService;
private cheKubernetesNamespace: CheKubernetesNamespace;
private cheNotification: CheNotification;
private cheWorkspace: CheWorkspace;
@ -83,6 +88,7 @@ export class WorkspaceDetailsOverviewController {
$route: ng.route.IRouteService,
$scope: ng.IScope,
$timeout: ng.ITimeoutService,
cheDashboardConfigurationService: CheDashboardConfigurationService,
cheKubernetesNamespace: CheKubernetesNamespace,
cheNotification: CheNotification,
cheWorkspace: CheWorkspace,
@ -95,6 +101,7 @@ export class WorkspaceDetailsOverviewController {
this.$route = $route;
this.$scope = $scope;
this.$timeout = $timeout;
this.cheDashboardConfigurationService = cheDashboardConfigurationService;
this.cheKubernetesNamespace = cheKubernetesNamespace;
this.cheNotification = cheNotification;
this.cheWorkspace = cheWorkspace;
@ -121,6 +128,10 @@ export class WorkspaceDetailsOverviewController {
deRegistrationFn();
});
this.cheDashboardConfigurationService.ready.then(() => {
this.enabledKubernetesNamespaceSelector = this.cheDashboardConfigurationService.enabledFeature(TogglableFeature.KUBERNETES_NAMESPACE_SELECTOR);
});
this.init();
}

View File

@ -29,7 +29,9 @@
<div che-compile="section.content"></div>
</che-label-container>
<!-- Infrastructure Namespace -->
<che-label-container class="infrastructure-namespace-input-container"
<che-label-container
ng-if="workspaceDetailsOverviewController.enabledKubernetesNamespaceSelector"
class="infrastructure-namespace-input-container"
che-label-name="Kubernetes Namespace"
ng-if="workspaceDetailsOverviewController.infrastructureNamespace">
<che-input

View File

@ -41,6 +41,7 @@ interface IBrandingDocs {
organization?: string;
general?: string;
converting?: string;
faq?: string;
}
interface IBrandingWorkspace {
priorityStacks?: Array<string>;
@ -60,6 +61,14 @@ interface IBrandingConfiguration {
cheCDN: string;
resources: string[];
};
features: {
disabled: TogglableFeature[];
};
}
export enum TogglableFeature {
WORKSPACE_SHARING = 'workspaceSharing',
KUBERNETES_NAMESPACE_SELECTOR = 'kubernetesNamespaceSelector',
}
const ASSET_PREFIX = 'assets/branding/';
@ -321,7 +330,8 @@ export class CheBranding {
factory: this.branding.docs && this.branding.docs.factory ? this.branding.docs.factory : DEFAULT_DOCS_FACTORY,
organization: this.branding.docs && this.branding.docs.organization ? this.branding.docs.organization : DEFAULT_DOCS_ORGANIZATION,
general: this.branding.docs && this.branding.docs.general ? this.branding.docs.general : DEFAULT_DOCS_GENERAL,
converting: this.branding.docs && this.branding.docs.converting ? this.branding.docs.converting : DEFAULT_DOCS_CONVERTING
converting: this.branding.docs && this.branding.docs.converting ? this.branding.docs.converting : DEFAULT_DOCS_CONVERTING,
faq: this.branding.docs && this.branding.docs.faq ? this.branding.docs.faq : undefined
};
}
@ -344,10 +354,18 @@ export class CheBranding {
menu: {
disabled:
this.branding.configuration &&
this.branding.configuration.menu && this.branding.configuration.menu.disabled
this.branding.configuration.menu && this.branding.configuration.menu.disabled
? this.branding.configuration.menu.disabled
: []
},
features: {
disabled:
this.branding.configuration &&
this.branding.configuration.features &&
this.branding.configuration.features.disabled
? this.branding.configuration.features.disabled
: []
},
prefetch: {
cheCDN: this.branding.configuration &&
this.branding.configuration.prefetch &&

View File

@ -11,7 +11,12 @@
*/
'use strict';
import { CheBranding } from './che-branding.factory';
import { CheBranding, TogglableFeature } from './che-branding.factory';
export type FooterLink = {
title: string;
reference: string;
};
/**
* This class handles configuration data of Dashboard.
@ -35,6 +40,10 @@ export class CheDashboardConfigurationService {
this.cheBranding = cheBranding;
}
get ready(): ng.IPromise<void> {
return this.cheBranding.ready;
}
allowedMenuItem(menuItem: che.ConfigurableMenuItem | string): boolean {
const disabledItems = this.cheBranding.getConfiguration().menu.disabled;
return (disabledItems as string[]).indexOf(menuItem) === -1;
@ -48,4 +57,45 @@ export class CheDashboardConfigurationService {
});
}
enabledFeature(feature: TogglableFeature): boolean {
const disabledFeatures = this.cheBranding.getConfiguration().features.disabled;
return disabledFeatures.indexOf(feature) === -1;
}
getFooterLinks(): { [key: string]: FooterLink } {
const links: { [key: string]: FooterLink } = {};
if (this.cheBranding.getProductSupportEmail()) {
links.supportEmail = {
title: 'Make a wish',
reference: this.cheBranding.getProductSupportEmail()
};
}
if (this.cheBranding.getFooter().email) {
const email = this.cheBranding.getFooter().email;
links.email = {
title: email.title,
reference: email.address
};
}
if (this.cheBranding.getDocs().general) {
links.docs = {
title: 'Docs',
reference: this.cheBranding.getDocs().general
};
}
if (this.cheBranding.getDocs().faq) {
links.faq = {
title: 'FAQ',
reference: this.cheBranding.getDocs().faq
};
}
if (this.cheBranding.getProductHelpPath() && this.cheBranding.getProductHelpTitle()) {
links.supportPath = {
title: this.cheBranding.getProductHelpTitle(),
reference: this.cheBranding.getProductHelpPath()
};
}
return links;
}
}

View File

@ -11,6 +11,7 @@
*/
'use strict';
import { CheDashboardConfigurationService, FooterLink } from '../../branding/che-dashboard-configuration.service';
/**
* This class is handling the controller for the footer.
@ -19,7 +20,25 @@
*/
export class CheFooterController {
$onInit(): void { }
static $inject = [
'cheDashboardConfigurationService',
];
links: { [key: string]: FooterLink };
private cheDashboardConfigurationService: CheDashboardConfigurationService;
constructor(
cheDashboardConfigurationService: CheDashboardConfigurationService,
) {
this.cheDashboardConfigurationService = cheDashboardConfigurationService;
}
$onInit(): void {
this.cheDashboardConfigurationService.ready.then(() => {
this.links = this.cheDashboardConfigurationService.getFooterLinks();
});
}
/**
* Returns 'Make a wish' email subject.

View File

@ -77,15 +77,9 @@ export class CheFooter {
this.controllerAs = 'cheFooterController';
this.scope = {
supportHelpPath: '@cheSupportHelpPath',
supportHelpTitle: '@cheSupportHelpTitle',
supportEmail: '@cheSupportEmail',
logo: '@cheLogo',
docs: '@cheDocs',
version: '@cheVersion',
productName: '@cheProductName',
links: '=cheLinks',
email: '=?cheEmail'
};
}
}

View File

@ -16,15 +16,37 @@
flex-order-gt-sm="2" flex-order-sm="1" flex-order-xs="1"
layout="row" layout-align="start center">
<ng-transclude id="footer-content"></ng-transclude>
<a class="che-footer-button-blue che-footer-button" id="footer-email-wish" ng-if="cheFooterController.supportEmail"
ng-href="{{'mailto:' + cheFooterController.supportEmail + cheFooterController.getWishEmailSubject(cheFooterController.productName)}}" target="_blank">Make a wish</a>
<a class="che-footer-button-blue che-footer-button" id="footer-email" ng-if="cheFooterController.email"
ng-href="{{'mailto:' + cheFooterController.email.address + cheFooterController.getEmailSubject(cheFooterController.email.subject)}}">{{cheFooterController.email.title}}</a>
<a class="che-footer-button-blue che-footer-button" id="footer-docs" ng-if="cheFooterController.docs" ng-href="{{cheFooterController.docs}}" target="_blank">Docs</a>
<a class="che-footer-button-blue che-footer-button" id="footer-support" ng-if="cheFooterController.supportHelpPath && cheFooterController.supportHelpTitle"
ng-href="{{cheFooterController.supportHelpPath}}" target="_blank">{{cheFooterController.supportHelpTitle}}</a>
<a ng-repeat="link in cheFooterController.links" class="che-footer-button-blue che-footer-button" ng-href="{{link.location}}" target="_blank">{{link.title}}</a>
<a ng-if="cheFooterController.links.supportEmail"
id="footer-email-wish"
class="che-footer-button-blue che-footer-button"
ng-href="{{'mailto:' + cheFooterController.links.supportEmail.reference + cheFooterController.getWishEmailSubject(cheFooterController.productName)}}"
target="_blank">{{cheFooterController.links.supportEmail.title}}</a>
<a ng-if="cheFooterController.links.email"
ng-href="{{'mailto:' + cheFooterController.links.email.reference + cheFooterController.getEmailSubject(cheFooterController.email.subject)}}"
class="che-footer-button-blue che-footer-button"
id="footer-email">{{cheFooterController.links.email.title}}</a>
<a ng-if="cheFooterController.links.docs"
class="che-footer-button-blue che-footer-button"
id="footer-docs"
ng-href="{{cheFooterController.links.docs.reference}}"
target="_blank">{{cheFooterController.links.docs.title}}</a>
<a ng-if="cheFooterController.links.faq"
class="che-footer-button-blue che-footer-button"
id="footer-faq"
ng-href="{{cheFooterController.links.faq.reference}}"
target="_blank"
rel="noopener noreferrer">{{cheFooterController.links.faq.title}}</a>
<a ng-if="cheFooterController.links.supportPath"
class="che-footer-button-blue che-footer-button"
id="footer-support"
ng-href="{{cheFooterController.links.supportPath.reference}}"
target="_blank">{{cheFooterController.links.supportPath.title}}</a>
</div>
</div>

View File

@ -49,15 +49,9 @@
<div class="main-page ide-show" layout="column" flex>
<div id="main-content" role="main" ng-view layout="column" flex ng-show="waitingLoaded && !showIDE"></div>
<ide-iframe id="ide-iframe-window" ng-show="showIDE && ideIframeLink" flex style="height: 100%"></ide-iframe>
<che-footer che-support-help-path="{{branding.helpPath}}"
che-support-help-title="{{branding.helpTitle}}"
che-support-email="{{branding.supportEmail}}"
che-logo="{{branding.logoURL}}"
<che-footer che-logo="{{branding.logoURL}}"
che-version="{{productVersion}}"
che-product-name="Eclipse Che"
che-links="branding.footer.links"
che-email="branding.footer.email"
che-docs="{{branding.docs.general}}"
ng-show="waitingLoaded && !showIDE">
{{branding.footer.content}}
</che-footer>