diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile index de30cd95e47..2f26f15e80b 100644 --- a/dashboard/Dockerfile +++ b/dashboard/Dockerfile @@ -14,16 +14,18 @@ # $ docker run --rm eclipse-che-dashboard | tar -C target/ -zxf - FROM node:8.16.0 -RUN apt-get update && \ - apt-get install -y git \ +RUN apt-get update \ + && apt-get install -y git \ && apt-get -y clean \ && rm -rf /var/lib/apt/lists/* + COPY package.json /dashboard/ COPY yarn.lock /dashboard/ COPY typings.json /dashboard/ WORKDIR /dashboard RUN yarn install --ignore-optional COPY . /dashboard/ + RUN yarn build && yarn test RUN cd /dashboard/target/ && tar zcf /tmp/dashboard.tar.gz dist/ diff --git a/dashboard/src/app/factories/factory-details/factory-details.html b/dashboard/src/app/factories/factory-details/factory-details.html index 50309aae7bd..c99ddcd82e6 100644 --- a/dashboard/src/app/factories/factory-details/factory-details.html +++ b/dashboard/src/app/factories/factory-details/factory-details.html @@ -11,8 +11,8 @@ Red Hat, Inc. - initial API and implementation --> - - This factory is using old workspace definition format which is not compatible anymore. + + This factory is using old workspace definition format which is not compatible anymore. Please follow the documentation to update the definition of the workspace and benefits from the latest capabilities. A workspace is where your projects live and run. Create workspaces from stacks that define projects, runtimes, and commands. - - +
; private pluginRegistry: string; private TAB: Array; + private cheBranding: CheBranding; /** * There are unsaved changes to apply (with restart) when is't true. */ private unsavedChangesToApply: boolean; + /** + * There is selected deprecated editor when is't true. + */ + private hasSelectedDeprecatedEditor: boolean; + /** + * There are selected deprecated plugins when is't true. + */ + private hasSelectedDeprecatedPlugins: boolean; /** * Default constructor that is using resource injection */ constructor($location: ng.ILocationService, $log: ng.ILogService, + $sce: ng.ISCEService, $scope: ng.IScope, lodash: any, cheNotification: CheNotification, @@ -83,8 +95,10 @@ export class WorkspaceDetailsController { workspaceDetailsService: WorkspaceDetailsService, initData: IInitData, $timeout: ng.ITimeoutService, + cheBranding: CheBranding, workspacesService: WorkspacesService) { this.$log = $log; + this.$sce = $sce; this.$scope = $scope; this.$timeout = $timeout; this.$location = $location; @@ -94,6 +108,7 @@ export class WorkspaceDetailsController { this.ideSvc = ideSvc; this.workspaceDetailsService = workspaceDetailsService; this.workspacesService = workspacesService; + this.cheBranding = cheBranding; this.pluginRegistry = cheWorkspace.getWorkspaceSettings() != null ? cheWorkspace.getWorkspaceSettings().cheWorkspacePluginRegistryUrl : null; if (!initData.workspaceDetails) { @@ -116,12 +131,14 @@ export class WorkspaceDetailsController { this.workspaceDetails = angular.copy(newWorkspaceDetails); } this.checkEditMode(); + this.updateDeprecatedInfo(); }; this.cheWorkspace.subscribeOnWorkspaceChange(initData.workspaceDetails.id, action); this.originWorkspaceDetails = angular.copy(initData.workspaceDetails); this.workspaceDetails = angular.copy(initData.workspaceDetails); this.checkEditMode(); + this.updateDeprecatedInfo(); this.TAB = this.workspaceDetails.config ? ['Overview', 'Projects', 'Containers', 'Servers', 'Env_Variables', 'Volumes', 'Config', 'SSH', 'Plugins', 'Editors'] : ['Overview', 'Projects', 'Plugins', 'Editors', 'Devfile']; this.updateTabs(); @@ -181,11 +198,11 @@ export class WorkspaceDetailsController { return this.workspacesService.isSupported(this.workspaceDetails); } - isSupportedVersion(): boolean { + get isSupportedVersion(): boolean { return this.workspacesService.isSupportedVersion(this.workspaceDetails); } - isSupportedRecipeType(): boolean { + get isSupportedRecipeType(): boolean { return this.workspacesService.isSupportedRecipeType(this.workspaceDetails); } @@ -366,11 +383,11 @@ export class WorkspaceDetailsController { * @returns {string} */ getOverlayMessage(failedTabs?: string[]): string { - if (!this.isSupportedRecipeType()) { + if (!this.isSupportedRecipeType) { return `Current infrastructure doesn't support this workspace recipe type.`; } - if (!this.isSupportedVersion()) { + if (!this.isSupportedVersion) { return `This workspace is using old definition format which is not compatible anymore.`; } @@ -447,6 +464,8 @@ export class WorkspaceDetailsController { const failedTabs = this.checkForFailedTabs(); // update overlay this.updateEditModeOverlayConfig(configIsDiffer, failedTabs); + // update info(editor and plugins) + this.updateDeprecatedInfo(); // publish changes this.workspaceDetailsService.publishWorkspaceChange(this.workspaceDetails); }, 500); @@ -586,4 +605,37 @@ export class WorkspaceDetailsController { return tabs.some((tabKey: string) => this.checkFormsNotValid(tabKey)); } + + /** + * Builds and returns the warning message for che-description(warning info) component. + * + * @returns {string} + */ + get warningMessage(): any { + let message = ''; + + if (!this.isSupportedVersion) { + message += `This workspace is using old definition format which is not compatible anymore. + Please follow the documentation + to update the definition of the workspace and benefits from the latest capabilities.`; + } else if (this.hasSelectedDeprecatedPlugins) { + message += `The workspace uses deprecated plugins${this.hasSelectedDeprecatedEditor ? ' and editor' : ''}.`; + } else if (this.hasSelectedDeprecatedEditor) { + message += `The workspace uses deprecated editor.` + } + + return this.$sce.trustAsHtml(message); + } + + get hasWarningMessage(): boolean { + return !this.isSupportedVersion || this.hasSelectedDeprecatedEditor || this.hasSelectedDeprecatedPlugins; + } + + private updateDeprecatedInfo() { + const deprecatedEditor = this.workspaceDetailsService.getSelectedDeprecatedEditor(this.workspaceDetails); + this.hasSelectedDeprecatedEditor = deprecatedEditor !== ''; + + const deprecatedPlugins = this.workspaceDetailsService.getSelectedDeprecatedPlugins(this.workspaceDetails); + this.hasSelectedDeprecatedPlugins = deprecatedPlugins.length > 0; + } } diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts b/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts index fae4eb8ac89..843b81ff6b5 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts @@ -210,18 +210,44 @@ describe(`WorkspaceDetailsController >`, () => { this.getSupportedRecipeTypes = () => { return ['dockerimage', 'dockerfile', 'compose']; }; + this.fetchWorkspaceSettings = (): any => { + // todo: rework to use Angular promise instead of native one + return Promise.resolve({ + cheWorkspacePluginRegistryUrl: 'cheWorkspacePluginRegistryUrl' + }); + }; this.getWorkspaceSettings = () => { return {}; }; - this.getWorkspaceDataManager = () => { return { getName(data: che.IWorkspace): string { return 'name'; + }, + getEditor() { + return ''; + }, + getPlugins() { + return []; } }; }; }) + .factory('pluginRegistry', function () { + return { + fetchPlugins: (url: string) => { + return $q.when([]); + } + } + }) + .factory('cheBranding', function () { + return { + getDocs: () => { + const converting = 'converting-a-che-6-workspace-to-a-che-7-devfile'; + return {converting}; + } + } + }) // terminal directives which prevent to execute an original ones .directive('mdTab', function () { // this directive produces timeout task which cannot be flushed diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.html b/dashboard/src/app/workspaces/workspace-details/workspace-details.html index 013fe030b5e..3679ead5066 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-details.html +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.html @@ -1,6 +1,6 @@ - - This workspace is using old definition format which is not compatible anymore. - Please follow the documentation to update the definition of the workspace and benefits from the latest capabilities. + + @@ -22,9 +22,9 @@
-
- +
+ +
- Env Variables @@ -124,7 +124,7 @@ - @@ -158,8 +158,10 @@ + md-on-select="workspaceDetailsController.onSelectTab(workspaceDetailsController.tab.Plugins);"> + Plugins @@ -174,6 +176,8 @@ + Editors @@ -185,7 +189,7 @@ - diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.service.spec.ts b/dashboard/src/app/workspaces/workspace-details/workspace-details.service.spec.ts new file mode 100644 index 00000000000..01d9e2bd9cb --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.service.spec.ts @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2015-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +'use strict'; + +import {IPlugin} from '../../../components/api/plugin-registry.factory'; +import {WorkspaceDetailsService} from './workspace-details.service'; +import {CheHttpBackend} from '../../../components/api/test/che-http-backend'; + +/** + * WorkspaceDetailsService tests. + * + * @author Oleksii Orel + */ +describe(`WorkspaceDetailsService >`, () => { + + /** + * Service to test. + */ + let workspaceDetailsService: WorkspaceDetailsService; + + let cheHttpBackend: CheHttpBackend; + + let $httpBackend: ng.IHttpBackendService; + + function getPlugins(): Array { + return [{ + id: 'camel-tooling/vscode-apache-camel/0.0.14', + deprecate: { + automigrate: true, + migrateTo: 'camel-tooling/vscode-apache-camel/latest' + }, + name: 'vscode-apache-camel', + publisher: 'camel-tooling', + displayName: 'Language Support for Apache Camel', + type: 'VS Code extension' + }, { + id: 'camel-tooling/vscode-apache-camel/latest', + name: 'vscode-apache-camel', + publisher: 'camel-tooling', + displayName: 'Language Support for Apache Camel', + type: 'VS Code extension' + }, { + id: 'eclipse/che-theia/7.2.0', + deprecate: { + automigrate: true, + migrateTo: 'eclipse/che-theia/latest' + }, + name: 'che-theia', + publisher: 'eclipse', + displayName: 'theia-ide', + type: 'Che Editor' + }, { + id: 'eclipse/che-theia/latest', + name: 'che-theia', + publisher: 'eclipse', + displayName: 'theia-ide', + type: 'Che Editor' + }]; + } + + function getNewWorkspace(): che.IWorkspace { + return { + devfile: { + metadata: { + name: 'wksp-i8cb55' + }, + apiVersion: '1.0.0', + components: [ + { + id: 'eclipse/che-theia/latest', + type: 'cheEditor' + }, { + id: 'camel-tooling/vscode-apache-camel/latest', + type: 'chePlugin' + } + ] + }, + id: 'workspacefbymkrh72ptwudud', + namespace: 'che' + } + } + + function getWorkspaceWithDeprecatedPlugins(): che.IWorkspace { + return { + devfile: { + metadata: { + name: 'wksp-i8cb55' + }, + apiVersion: '1.0.0', + components: [ + { + id: 'eclipse/che-theia/latest', + type: 'cheEditor' + }, { + id: 'camel-tooling/vscode-apache-camel/0.0.14', + type: 'chePlugin' + } + ] + }, + id: 'workspacefbymkrh72ptwudud', + namespace: 'che' + } + } + + function getWorkspaceWithDeprecatedEditor(): che.IWorkspace { + return { + devfile: { + metadata: { + name: 'wksp-i8cb55' + }, + apiVersion: '1.0.0', + components: [ + { + id: 'eclipse/che-theia/7.2.0', + type: 'cheEditor' + }, { + id: 'camel-tooling/vscode-apache-camel/latest', + type: 'chePlugin' + } + ] + }, + id: 'workspacefbymkrh72ptwudud', + namespace: 'che' + } + } + + /** + * Setup module + */ + beforeEach(() => { + angular.mock.module('userDashboard'); + }); + + beforeEach(inject((_workspaceDetailsService_: WorkspaceDetailsService, + _cheHttpBackend_: CheHttpBackend) => { + + workspaceDetailsService = _workspaceDetailsService_; + cheHttpBackend = _cheHttpBackend_; + $httpBackend = cheHttpBackend.getHttpBackend(); + + cheHttpBackend.setPlugins(getPlugins()); + cheHttpBackend.setup(); + + $httpBackend.flush(); + })); + + /** + * Check assertion after the test + */ + afterEach(() => { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + describe(`deprecated workspace plugins >`, () => { + + it(`returns an empty array if workspace doesn't have any selected deprecated plugin`, () => { + const newWorkspace = getNewWorkspace(); + + expect(workspaceDetailsService.getSelectedDeprecatedPlugins(newWorkspace)).toEqual([]); + }); + + it(`returns an array with deprecated plugins which is selected in the workspace`, () => { + const workspaceWithDeprecatedPlugins = getWorkspaceWithDeprecatedPlugins(); + + expect(workspaceDetailsService.getSelectedDeprecatedPlugins(workspaceWithDeprecatedPlugins)).toEqual(['camel-tooling/vscode-apache-camel/0.0.14']); + }); + + }); + + describe(`deprecated workspace editor >`, () => { + + it(`returns an empty string if workspace doesn't have a selected deprecated editor`, () => { + const newWorkspace = getNewWorkspace(); + + expect(workspaceDetailsService.getSelectedDeprecatedEditor(newWorkspace)).toEqual(''); + }); + + it(`returns a string with editorId if workspace has a selected deprecated editor`, () => { + const workspaceWithDeprecatedEditor = getWorkspaceWithDeprecatedEditor(); + + expect(workspaceDetailsService.getSelectedDeprecatedEditor(workspaceWithDeprecatedEditor)).toEqual('eclipse/che-theia/7.2.0'); + }); + + }); +}); diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts b/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts index 01c837f9ac2..85cf7ff6944 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts @@ -17,6 +17,8 @@ import {IObservable, IObservableCallbackFn, Observable} from '../../../component import {WorkspaceDetailsProjectsService} from './workspace-projects/workspace-details-projects.service'; import {CheWorkspace, WorkspaceStatus} from '../../../components/api/workspace/che-workspace.factory'; 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'; interface IPage { title: string; @@ -39,7 +41,7 @@ interface ISection { */ export class WorkspaceDetailsService { - static $inject = ['$log', '$q', 'cheWorkspace', 'cheNotification', 'ideSvc', 'createWorkspaceSvc', 'workspaceDetailsProjectsService', 'cheService', 'chePermissions']; + static $inject = ['$log', '$q', 'cheWorkspace', 'cheNotification', 'ideSvc', 'createWorkspaceSvc', 'workspaceDetailsProjectsService', 'cheService', 'chePermissions', 'pluginRegistry']; /** * Logging service. @@ -80,6 +82,22 @@ export class WorkspaceDetailsService { * This workspaces should be restarted for new config to be applied. */ private restartToApply: string[] = []; + /** + * Array of deprecated editors. + */ + private deprecatedEditors: string[] = []; + /** + * Array of deprecated plugins. + */ + private deprecatedPlugins: string[] = []; + /** + * Plugin registry API interaction. + */ + private pluginRegistry: PluginRegistry; + /** + * Workspace data manager. + */ + private workspaceDataManager: WorkspaceDataManager; /** * Default constructor that is using resource @@ -93,7 +111,8 @@ export class WorkspaceDetailsService { createWorkspaceSvc: CreateWorkspaceSvc, workspaceDetailsProjectsService: WorkspaceDetailsProjectsService, cheService: CheService, - chePermissions: che.api.IChePermissions + chePermissions: che.api.IChePermissions, + pluginRegistry: PluginRegistry ) { this.$log = $log; this.$q = $q; @@ -101,6 +120,7 @@ export class WorkspaceDetailsService { this.cheNotification = cheNotification; this.ideSvc = ideSvc; this.createWorkspaceSvc = createWorkspaceSvc; + this.pluginRegistry = pluginRegistry; this.workspaceDetailsProjectsService = workspaceDetailsProjectsService; this.observable = new Observable(); @@ -114,6 +134,49 @@ export class WorkspaceDetailsService { this.addPage('Share', '', 'icon-ic_folder_shared_24px'); } }); + + this.cheWorkspace.fetchWorkspaceSettings().then(workspaceSettings => { + this.pluginRegistry.fetchPlugins(workspaceSettings.cheWorkspacePluginRegistryUrl).then(items => { + if (angular.isArray(items)) { + items.filter(item => !!item.deprecate).forEach(item => { + const target = item.type === PluginRegistry.EDITOR_TYPE ? this.deprecatedEditors : this.deprecatedPlugins; + target.push(item.id); + }); + } + }); + }); + this.workspaceDataManager = this.cheWorkspace.getWorkspaceDataManager(); + } + + /** + * Returns selected deprecated editor. + * + * @param {che.IWorkspace} workspace + * @returns {string} + */ + getSelectedDeprecatedEditor(workspace: che.IWorkspace): string { + if (this.workspaceDataManager && workspace) { + const editor = this.workspaceDataManager.getEditor(workspace); + if (this.deprecatedEditors.indexOf(editor) !== -1) { + return editor; + } + } + return ''; + } + + /** + * Returns selected deprecated plugins. + * + * @param {che.IWorkspace} workspace + * @returns {Array} + */ + getSelectedDeprecatedPlugins(workspace: che.IWorkspace): Array { + if (this.workspaceDataManager && workspace) { + return this.workspaceDataManager.getPlugins(workspace).filter(plugin => { + return this.deprecatedPlugins.indexOf(plugin) !== -1 + }); + } + return []; } /** diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.styl b/dashboard/src/app/workspaces/workspace-details/workspace-details.styl index a7b19f7e3fe..6cf2f40e260 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-details.styl +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.styl @@ -74,11 +74,10 @@ div.workspace-details-warning-info button padding-left 40px !important text-align right - span - font-size 12px - font-weight bold - text-transform none - &:before + font-size 12px + font-weight bold + text-transform none + &:before position absolute display inline font-size 35px diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.controller.ts index 4a4778b0a86..d29eac1b418 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.controller.ts +++ b/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.controller.ts @@ -14,10 +14,6 @@ import {IPlugin, PluginRegistry, IPluginRow} from '../../../../components/api/pl import {CheNotification} from '../../../../components/notification/che-notification.factory'; import {CheWorkspace} from '../../../../components/api/workspace/che-workspace.factory'; -const PLUGIN_SEPARATOR = ','; -const PLUGIN_VERSION_SEPARATOR = ':'; -const EDITOR_TYPE = 'Che Editor'; - /** * @ngdoc controller * @name workspaces.details.editors.controller:WorkspaceEditorsController @@ -25,20 +21,25 @@ const EDITOR_TYPE = 'Che Editor'; * @author Ann Shumilova */ export class WorkspaceEditorsController { - static $inject = ['pluginRegistry', 'cheListHelperFactory', '$scope', 'cheNotification', 'cheWorkspace']; + static $inject = ['pluginRegistry', 'cheListHelperFactory', '$scope', 'cheNotification', 'cheWorkspace', '$sce']; workspace: che.IWorkspace; pluginRegistryLocation: string; + pluginRegistry: PluginRegistry; cheNotification: CheNotification; cheWorkspace: CheWorkspace; + $sce: ng.ISCEService; + onChange: Function; isLoading: boolean; editorOrderBy = 'displayName'; editors: Map = new Map(); // the key is publisher/name selectedEditor: string = ''; - editorFilter: any; + deprecatedEditorsInfo: Map = new Map(); + + private editorFilter: {displayName: string}; private cheListHelper: che.widget.ICheListHelper; @@ -46,10 +47,11 @@ export class WorkspaceEditorsController { * Default constructor that is using resource */ constructor(pluginRegistry: PluginRegistry, cheListHelperFactory: che.widget.ICheListHelperFactory, $scope: ng.IScope, - cheNotification: CheNotification, cheWorkspace: CheWorkspace) { + cheNotification: CheNotification, cheWorkspace: CheWorkspace, $sce: ng.ISCEService) { this.pluginRegistry = pluginRegistry; this.cheNotification = cheNotification; this.cheWorkspace = cheWorkspace; + this.$sce = $sce; const helperId = 'workspace-editors'; this.cheListHelper = cheListHelperFactory.getHelper(helperId); @@ -58,7 +60,7 @@ export class WorkspaceEditorsController { cheListHelperFactory.removeHelper(helperId); }); - this.editorFilter = { displayName: '' }; + this.editorFilter = {displayName: ''}; const deRegistrationFn = $scope.$watch(() => { return this.workspace; @@ -75,39 +77,56 @@ export class WorkspaceEditorsController { } $onInit(): void { - this.loadPlugins(); + this.loadEditors(); } /** - * Loads the list of plugins from registry. + * Loads the list of editors from registry. */ - loadPlugins(): void { + loadEditors(): void { this.editors = new Map(); + this.deprecatedEditorsInfo = new Map(); this.isLoading = true; - this.pluginRegistry.fetchPlugins(this.pluginRegistryLocation).then((result: Array) => { + this.pluginRegistry.fetchPlugins(this.pluginRegistryLocation).then((result: Array) => { this.isLoading = false; - result.forEach((item: IPluginRow) => { - if (item.type === EDITOR_TYPE) { - - // since plugin registry returns an array of plugins/editors with a single version we need to unite the editor versions into one - const pluginID = `${item.publisher}/${item.name}`; - - item.selected = 'latest'; // set the default selected to latest + result.filter(item => item.type === PluginRegistry.EDITOR_TYPE).forEach((item: IPlugin) => { + // since editor registry returns an array of editors/editors with a single version we need to unite the editor versions into one + const editorID = `${item.publisher}/${item.name}`; + // set the default selected to latest + const selected = 'latest'; + + if (!this.editors.has(editorID)) { + const {name, displayName, description, publisher} = item; + const versions = []; + const id = `${editorID}/${selected}`; + this.editors.set(editorID, {id, name, displayName, description, publisher, selected, versions}); + } - if (this.editors.has(pluginID)) { - const foundPlugin = this.editors.get(pluginID); - foundPlugin.versions.push(item.version); - } else { - item.versions = [item.version]; - this.editors.set(pluginID, item); + const value = item.version; + const label = !item.deprecate ? item.version : `${item.version} [DEPRECATED]`; + this.editors.get(editorID).versions.push({value, label}); + if (item.deprecate) { + this.deprecatedEditorsInfo.set(item.id, item.deprecate); + if (selected === item.version) { + this.editors.get(editorID).isDeprecated = true; } } }); + const {publisher, name, version} = this.splitEditorId(this.selectedEditor); + const editorID = `${publisher}/${name}`; + + if (this.editors.has(editorID)) { + const foundEditor = this.editors.get(editorID); + foundEditor.id = editorID; + foundEditor.selected = version; + foundEditor.isDeprecated = this.isDeprecatedEditor(editorID); + } + this.updateEditors(); }, (error: any) => { this.isLoading = false; - this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to load plugins.'); + this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to load editors.'); }); } @@ -122,22 +141,21 @@ export class WorkspaceEditorsController { } /** - * Update plugin information based on UI changes. + * Update editor information based on UI changes. * - * @param {IPlugin} plugin + * @param {IPlugin} editor */ - updateEditor(plugin: IPluginRow): void { - if (plugin.type === EDITOR_TYPE) { - const pluginID = `${plugin.publisher}/${plugin.name}`; - const pluginIDWithVersion = `${plugin.publisher}/${plugin.name}/${plugin.selected}`; + updateEditor(editor: IPluginRow): void { + + const editorID = `${editor.publisher}/${editor.name}`; + const editorIDWithVersion = `${editor.publisher}/${editor.name}/${editor.selected}`; - this.selectedEditor = plugin.isEnabled ? pluginIDWithVersion : ''; + this.selectedEditor = editor.isEnabled ? editorIDWithVersion : ''; - this.editors.get(pluginID).selected = plugin.selected; - this.editors.get(pluginID).id = pluginIDWithVersion; + this.editors.get(editorID).selected = editor.selected; + this.editors.get(editorID).id = editorIDWithVersion; this.cheWorkspace.getWorkspaceDataManager().setEditor(this.workspace, this.selectedEditor); - } this.onChange(); } @@ -145,25 +163,74 @@ export class WorkspaceEditorsController { /** * Update the selected editor version when the editor version dropdown is changed * - * @param {IPlugin} plugin + * @param {IPluginRow} editor */ - updateSelectedEditorVersion(plugin: IPluginRow): void { - if (plugin.type === EDITOR_TYPE) { - const pluginID = `${plugin.publisher}/${plugin.name}`; + updateSelectedEditorVersion(editor: IPluginRow): void { + const editorID = `${editor.publisher}/${editor.name}`; - // create a plugin id with the newly selected version - const pluginIDWithVersion = `${pluginID}/${plugin.selected}`; + // create a editor id with the newly selected version + const editorIDWithVersion = `${editorID}/${editor.selected}`; - this.editors.get(pluginID).selected = plugin.selected; - this.editors.get(pluginID).id = pluginIDWithVersion; + this.editors.get(editorID).isDeprecated = this.isDeprecatedEditor(editorIDWithVersion); + this.editors.get(editorID).selected = editor.selected; + this.editors.get(editorID).id = editorIDWithVersion; - this.cheWorkspace.getWorkspaceDataManager().setEditor(this.workspace, pluginIDWithVersion); + this.cheWorkspace.getWorkspaceDataManager().setEditor(this.workspace, editorIDWithVersion); + + this.onChange(); + } + + /** + * Returns a warning message for the editor. + * + * @returns {any} warning message + */ + getWarningMessage(): any { + let warningMessage = 'This editor is deprecated.'; + const deprecatedInfo = this.deprecatedEditorsInfo.get(this.selectedEditor); + if (deprecatedInfo && deprecatedInfo.migrateTo) { + const {publisher, name} = this.splitEditorId(this.selectedEditor); + const pluginToMigrate = this.splitEditorId(deprecatedInfo.migrateTo); + if (pluginToMigrate.publisher === publisher && pluginToMigrate.name === name) { + warningMessage += ' Select a newer version in the dropdown list.'; + } else { + const targetEditor = this.editors.get(`${pluginToMigrate.publisher}/${pluginToMigrate.name}`); + if (targetEditor) { + warningMessage += ` Use ${targetEditor.displayName} (${targetEditor.publisher} publisher).`; + } + } } + + return this.$sce.trustAsHtml(warningMessage); + } + + /** + * Returns true if the editor deprecated. + * + * @param {string} editorId + */ + isDeprecatedEditor(editorId: string): boolean { + return this.deprecatedEditorsInfo.has(editorId); + } + + /** + * Auto-migrate the deprecated editor. + */ + autoMigrateDeprecatedEditor(): void { + if (!this.deprecatedEditorsInfo.has(this.selectedEditor)) { + return; + } + const deprecatedInfo = this.deprecatedEditorsInfo.get(this.selectedEditor); + if (deprecatedInfo && deprecatedInfo.automigrate && deprecatedInfo.migrateTo) { + this.cheWorkspace.getWorkspaceDataManager().setEditor(this.workspace, deprecatedInfo.migrateTo); + this.selectedEditor = deprecatedInfo.migrateTo; + } + this.onChange(); } /** - * Update the state of plugins. + * Update the state of editors. */ private updateEditors(): void { this.selectedEditor = this.cheWorkspace.getWorkspaceDataManager().getEditor(this.workspace); @@ -172,9 +239,8 @@ export class WorkspaceEditorsController { this.editors.forEach((editor: IPluginRow) => { editor.isEnabled = this.isEditorEnabled(editor); if (editor.isEnabled) { - - // this.selectedEditor is in the form publisher/name/pluginVersion - const { publisher, name, version } = this.splitEditorId(this.selectedEditor); + editor.isDeprecated = this.isDeprecatedEditor(this.selectedEditor); + const {version} = this.splitEditorId(this.selectedEditor); editor.selected = version; } }); @@ -184,10 +250,10 @@ export class WorkspaceEditorsController { /** * - * @param {IPlugin} plugin + * @param {IPluginRow} editor * @returns {boolean} the editor's enabled state */ - private isEditorEnabled(editor: IPlugin): boolean { + private isEditorEnabled(editor: IPluginRow): boolean { const partialId = `${editor.publisher}/${editor.name}/`; return this.selectedEditor && this.selectedEditor.indexOf(partialId) !== -1; } diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.html b/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.html index 201625cf6ad..d8588d014eb 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.html +++ b/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.html @@ -1,5 +1,5 @@ -
+
@@ -37,7 +37,11 @@ plugin-item-name="{{editor.displayName}}" plugin-item-version="{{editor.version}}" class="che-list-item-row"> -
+
+ +
@@ -58,7 +62,7 @@
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.styl b/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.styl index 6ba61beffd6..83b552d1602 100755 --- a/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.styl +++ b/dashboard/src/app/workspaces/workspace-details/workspace-editors/workspace-editors.styl @@ -1,32 +1,34 @@ -md-tab-content .plugins-page +md-tab-content .workspace-editors-page margin 0 25px -md-tab-content .plugins-page:first-of-type - margin-top 25px +md-tab-content .workspace-editors-page:first-of-type + margin-top 25px md-tab-content .plugin-page-separator margin-left 25px margin-right 25px -.plugins-page - .che-list-header-additional-parts - padding-left 0 - padding-right 0 - padding-bottom 10px - md-item - border-top none - .che-list-header-content - * - margin-left 0 - margin-right 0 - div.che-list-item-row - min-height 47px - line-height 47px - background-color $cat-gray-color - border 1px solid $very-light-grey-color - .che-list-item-checkbox - min-width 50px - height inherit +.workspace-editors-page + padding-top 55px + .che-list-item-checkbox + font-size 18px + .fa-warning + color $warning-color + cursor pointer + md-item + border-top none + .che-list-header-content + * + margin-left 0 + margin-right 0 + div.che-list-item-row + min-height 47px + line-height 47px + background-color $cat-gray-color + border 1px solid $very-light-grey-color + .che-list-item-checkbox + min-width 50px + height inherit div.che-list-header-column line-height inherit @@ -60,9 +62,13 @@ md-tab-content .plugin-page-separator .che-list-item-checkbox min-width 50px height inherit + & .md-checked + div.md-thumb + background-color rgb(74,144,226) + div.md-bar + background-color rgba(74,144,226,0.5) .che-version-dropdown - width:80%; - border-color: transparent; - background-colur: #e4e4e4; - - + width 80% + white-space nowrap + background #e4e4e4 + border-color transparent diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.controller.ts index 57d9847cc58..29574f57554 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.controller.ts +++ b/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.controller.ts @@ -10,14 +10,10 @@ * Red Hat, Inc. - initial API and implementation */ 'use strict'; -import {IPlugin, PluginRegistry, IPluginRow} from '../../../../components/api/plugin-registry.factory'; +import {IPlugin, IPluginRow, PluginRegistry} from '../../../../components/api/plugin-registry.factory'; import {CheNotification} from '../../../../components/notification/che-notification.factory'; import {CheWorkspace} from '../../../../components/api/workspace/che-workspace.factory'; -const PLUGIN_SEPARATOR = ','; -const PLUGIN_VERSION_SEPARATOR = ':'; -const EDITOR_TYPE = 'Che Editor'; - /** * @ngdoc controller * @name workspaces.details.plugins.controller:WorkspacePluginsController @@ -25,7 +21,7 @@ const EDITOR_TYPE = 'Che Editor'; * @author Ann Shumilova */ export class WorkspacePluginsController { - static $inject = ['pluginRegistry', 'cheListHelperFactory', '$scope', 'cheNotification', 'cheWorkspace']; + static $inject = ['pluginRegistry', 'cheListHelperFactory', '$scope', 'cheNotification', 'cheWorkspace', '$sce']; workspace: che.IWorkspace; pluginRegistryLocation: string; @@ -33,14 +29,17 @@ export class WorkspacePluginsController { pluginRegistry: PluginRegistry; cheNotification: CheNotification; cheWorkspace: CheWorkspace; + $sce: ng.ISCEService; + onChange: Function; isLoading: boolean; pluginOrderBy = 'displayName'; plugins: Map = new Map(); // the key is publisher/name selectedPlugins: Array = []; + deprecatedPluginsInfo: Map = new Map(); - pluginFilter: any; + private pluginFilter: {displayName: string}; private cheListHelper: che.widget.ICheListHelper; @@ -48,10 +47,11 @@ export class WorkspacePluginsController { * Default constructor that is using resource */ constructor(pluginRegistry: PluginRegistry, cheListHelperFactory: che.widget.ICheListHelperFactory, $scope: ng.IScope, - cheNotification: CheNotification, cheWorkspace: CheWorkspace) { + cheNotification: CheNotification, cheWorkspace: CheWorkspace, $sce: ng.ISCEService) { this.pluginRegistry = pluginRegistry; this.cheNotification = cheNotification; this.cheWorkspace = cheWorkspace; + this.$sce = $sce; const helperId = 'workspace-plugins'; this.cheListHelper = cheListHelperFactory.getHelper(helperId); @@ -60,7 +60,7 @@ export class WorkspacePluginsController { cheListHelperFactory.removeHelper(helperId); }); - this.pluginFilter = { displayName: '' }; + this.pluginFilter = {displayName: ''}; const deRegistrationFn = $scope.$watch(() => { return this.workspace; @@ -85,25 +85,31 @@ export class WorkspacePluginsController { */ loadPlugins(): void { this.plugins = new Map(); + this.deprecatedPluginsInfo = new Map(); this.isLoading = true; - this.pluginRegistry.fetchPlugins(this.pluginRegistryLocation).then((result: Array) => { + this.pluginRegistry.fetchPlugins(this.pluginRegistryLocation).then((result: Array) => { this.isLoading = false; - result.forEach((item: IPluginRow) => { - if (item.type !== EDITOR_TYPE) { - - // since plugin registry returns an array of plugins/editors with a single version we need to unite the plugin versions into one - const pluginID = `${item.publisher}/${item.name}`; - - item.selected = 'latest'; // set the default selected to latest + result.filter(item => item.type !== PluginRegistry.EDITOR_TYPE).forEach((item: IPlugin) => { + // since plugin registry returns an array of plugins/editors with a single version we need to unite the plugin versions into one + const pluginID = `${item.publisher}/${item.name}`; + // set the default selected to latest + const selected = 'latest'; + + if (!this.plugins.has(pluginID)) { + const {name, displayName, description, publisher} = item; + const versions = []; + const id = `${pluginID}/${selected}`; + this.plugins.set(pluginID, {id, name, displayName, description, publisher, selected, versions}); + } - if (this.plugins.has(pluginID)) { - const foundPlugin = this.plugins.get(pluginID); - foundPlugin.versions.push(item.version); - } else { - item.versions = [item.version]; - this.plugins.set(pluginID, item); + const value = item.version; + const label = !item.deprecate ? item.version : `${item.version} [DEPRECATED]`; + this.plugins.get(pluginID).versions.push({value, label}); + if (item.deprecate) { + this.deprecatedPluginsInfo.set(item.id, item.deprecate); + if (selected === item.version) { + this.plugins.get(pluginID).isDeprecated = true; } - } }); @@ -117,6 +123,7 @@ export class WorkspacePluginsController { const foundPlugin = this.plugins.get(pluginID); foundPlugin.id = plugin; foundPlugin.selected = version; + foundPlugin.isDeprecated = this.isDeprecatedPlugin(plugin); } }); @@ -143,23 +150,20 @@ export class WorkspacePluginsController { * @param {IPlugin} plugin */ updatePlugin(plugin: IPluginRow): void { - if (plugin.type !== EDITOR_TYPE) { - - const pluginID = `${plugin.publisher}/${plugin.name}`; - const pluginIDWithVersion = `${plugin.publisher}/${plugin.name}/${plugin.selected}`; + const pluginID = `${plugin.publisher}/${plugin.name}`; + const pluginIDWithVersion = `${plugin.publisher}/${plugin.name}/${plugin.selected}`; - this.plugins.get(pluginID).selected = plugin.selected; - this.plugins.get(pluginID).id = pluginIDWithVersion; - - if (plugin.isEnabled) { - this.selectedPlugins.push(pluginIDWithVersion); - } else { - this.selectedPlugins.splice(this.selectedPlugins.indexOf(plugin.id), 1); - } + this.plugins.get(pluginID).selected = plugin.selected; + this.plugins.get(pluginID).id = pluginIDWithVersion; - this.cheWorkspace.getWorkspaceDataManager().setPlugins(this.workspace, this.selectedPlugins); + if (plugin.isEnabled) { + this.selectedPlugins.push(pluginIDWithVersion); + } else { + this.selectedPlugins.splice(this.selectedPlugins.indexOf(plugin.id), 1); } + this.cheWorkspace.getWorkspaceDataManager().setPlugins(this.workspace, this.selectedPlugins); + this.onChange(); } @@ -169,23 +173,80 @@ export class WorkspacePluginsController { * @param {IPlugin} plugin */ updateSelectedPlugin(plugin: IPluginRow): void { - if (plugin.type !== EDITOR_TYPE) { - const pluginID = `${plugin.publisher}/${plugin.name}`; - const pluginIDWithVersion = `${pluginID}/${plugin.selected}`; + const pluginID = `${plugin.publisher}/${plugin.name}`; + const pluginIDWithVersion = `${pluginID}/${plugin.selected}`; + + this.plugins.get(pluginID).isDeprecated = this.isDeprecatedPlugin(pluginIDWithVersion); + this.plugins.get(pluginID).selected = plugin.selected; + this.plugins.get(pluginID).id = pluginIDWithVersion; - this.plugins.get(pluginID).selected = plugin.selected; - this.plugins.get(pluginID).id = pluginIDWithVersion; + const currentlySelectedPlugins = this.cheWorkspace.getWorkspaceDataManager().getPlugins(this.workspace); + + if (plugin.isEnabled) { + currentlySelectedPlugins.splice(this.selectedPlugins.indexOf(plugin.id), 1, pluginIDWithVersion); + } else { + currentlySelectedPlugins.push(pluginIDWithVersion); + } - const currentlySelectedPlugins = this.cheWorkspace.getWorkspaceDataManager().getPlugins(this.workspace); + this.cheWorkspace.getWorkspaceDataManager().setPlugins(this.workspace, currentlySelectedPlugins); + this.selectedPlugins = currentlySelectedPlugins; - if (plugin.isEnabled) { - currentlySelectedPlugins.splice(this.selectedPlugins.indexOf(plugin.id), 1, pluginIDWithVersion); + this.onChange(); + } + + /** + * Returns a warning message for the plugin. + * + * @param {string} pluginId + * @returns {any} warning message + */ + getWarningMessage(pluginId: string): any { + let warningMessage = 'This plugin is deprecated.'; + const deprecatedInfo = this.deprecatedPluginsInfo.get(pluginId); + if (deprecatedInfo && deprecatedInfo.migrateTo) { + const {publisher, name} = this.splitPluginId(pluginId); + const pluginToMigrate = this.splitPluginId(deprecatedInfo.migrateTo); + if (pluginToMigrate.publisher === publisher && pluginToMigrate.name === name) { + warningMessage += ' Select a newer version in the dropdown list.'; } else { - currentlySelectedPlugins.push(pluginIDWithVersion); + const targetPlugin = this.plugins.get(`${pluginToMigrate.publisher}/${pluginToMigrate.name}`); + if (targetPlugin) { + warningMessage += ` Use ${targetPlugin.displayName} (${targetPlugin.publisher} publisher).`; + } } - this.cheWorkspace.getWorkspaceDataManager().setPlugins(this.workspace, currentlySelectedPlugins); - this.selectedPlugins = currentlySelectedPlugins; } + return this.$sce.trustAsHtml(warningMessage); + } + + /** + * Returns true if the plugin deprecated. + * + * @param {string} pluginId + */ + isDeprecatedPlugin(pluginId: string): boolean { + return this.deprecatedPluginsInfo.has(pluginId); + } + + /** + * Auto-migrate plugins. + * + * @param {string[]} plugins + */ + private autoMigratePlugins(plugins: string[]): void { + const currentlySelectedPlugins = this.cheWorkspace.getWorkspaceDataManager().getPlugins(this.workspace); + plugins.forEach((pluginId: string) => { + const deprecatedInfo = this.deprecatedPluginsInfo.get(pluginId); + if (deprecatedInfo && deprecatedInfo.automigrate && deprecatedInfo.migrateTo) { + if (this.selectedPlugins.indexOf(deprecatedInfo.migrateTo) === -1) { + currentlySelectedPlugins.splice(this.selectedPlugins.indexOf(pluginId), 1, deprecatedInfo.migrateTo); + } else { + currentlySelectedPlugins.splice(this.selectedPlugins.indexOf(pluginId), 1); + } + } + }); + this.cheWorkspace.getWorkspaceDataManager().setPlugins(this.workspace, currentlySelectedPlugins); + this.selectedPlugins = currentlySelectedPlugins; + this.onChange(); } @@ -200,7 +261,8 @@ export class WorkspacePluginsController { plugin.isEnabled = !!selectedPluginId; if (selectedPluginId) { plugin.id = selectedPluginId; - const {publisher, name, version} = this.splitPluginId(selectedPluginId); + plugin.isDeprecated = this.isDeprecatedPlugin(plugin.id); + const {version} = this.splitPluginId(selectedPluginId); plugin.selected = version; } }); diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.html b/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.html index c0a364383e9..3dc8d882639 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.html +++ b/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.html @@ -46,7 +46,11 @@ plugin-item-name="{{plugin.displayName}}" plugin-item-version="{{plugin.version}}" class="che-list-item-row"> -
+
+ +
@@ -67,7 +71,7 @@
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.styl b/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.styl index 46c0e47ddaa..e0f1a673c4c 100755 --- a/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.styl +++ b/dashboard/src/app/workspaces/workspace-details/workspace-plugins/workspace-plugins.styl @@ -2,14 +2,22 @@ md-tab-content .plugins-page margin 0 25px md-tab-content .plugins-page:first-of-type - margin-top 25px + margin-top 15px md-tab-content .plugin-page-separator margin-left 25px margin-right 25px .plugins-page + #auto-migrate-plugins-button button + margin 0 10px 0 0 + .che-list-item-checkbox + font-size 18px + .fa-warning + color $warning-color + cursor pointer .che-list-header-additional-parts + padding-top 5px padding-left 0 padding-right 0 padding-bottom 10px @@ -61,9 +69,10 @@ md-tab-content .plugin-page-separator min-width 50px height inherit .che-version-dropdown - width:80%; - border-color: transparent; - background-colur: #e4e4e4; + width 80% + white-space nowrap + background #e4e4e4 + border-color transparent .plugin-page-separator margin-top 40px diff --git a/dashboard/src/app/workspaces/workspaces.service.spec.ts b/dashboard/src/app/workspaces/workspaces.service.spec.ts index aaeec79d334..94eb9344b9c 100644 --- a/dashboard/src/app/workspaces/workspaces.service.spec.ts +++ b/dashboard/src/app/workspaces/workspaces.service.spec.ts @@ -11,12 +11,11 @@ */ 'use strict'; -import {CheWorkspace} from '../../components/api/workspace/che-workspace.factory'; import {WorkspacesService} from './workspaces.service'; /** * WorkspacesService tests. - * + * * @author Oleksii Orel */ describe(`WorkspacesService >`, () => { @@ -25,7 +24,6 @@ describe(`WorkspacesService >`, () => { * Service to test. */ let workspacesService: WorkspacesService; - let cheWorkspace: CheWorkspace; function getCHE6Workspace(recipeType?: string): che.IWorkspace { return { @@ -36,7 +34,7 @@ describe(`WorkspacesService >`, () => { 'environments': { 'default': { 'recipe': { - 'type': recipeType ? recipeType : 'dockerimage', + 'type': recipeType ? recipeType : 'dockerimage', 'content': 'eclipse/ubuntu_jdk8' }, 'machines': { @@ -120,14 +118,30 @@ describe(`WorkspacesService >`, () => { .service('cheWorkspace', function () { this.getSupportedRecipeTypes = (): string[] => { return ['kubernetes','dockerimage','no-environment']; - } + }; + this.fetchWorkspaceSettings = (): any => { + // todo: rework to use Angular promise instead of native one + return Promise.resolve({ + cheWorkspacePluginRegistryUrl: 'cheWorkspacePluginRegistryUrl' + }); + }; + this.getWorkspaceDataManager = () => { + return { + getEditor() { + return ''; + }, + getPlugins() { + return []; + } + }; + }; }); angular.mock.module('workspacesServiceMock'); }); - beforeEach(inject((_workspacesService_: WorkspacesService, - _cheWorkspace_: CheWorkspace) => { - cheWorkspace = _cheWorkspace_; + beforeEach(inject(( + _workspacesService_: WorkspacesService + ) => { workspacesService = _workspacesService_; })); diff --git a/dashboard/src/components/api/plugin-registry.factory.ts b/dashboard/src/components/api/plugin-registry.factory.ts index 27229b2ed6d..df29c36b7e0 100644 --- a/dashboard/src/components/api/plugin-registry.factory.ts +++ b/dashboard/src/components/api/plugin-registry.factory.ts @@ -15,46 +15,79 @@ export interface IPlugin { id: string; name: string; publisher: string; - deprecate: { - autoMigrate: boolean; + deprecate?: { + automigrate: boolean; migrateTo: string; }; displayName: string; type: string; - version: string; + version?: string; description?: string; - isEnabled: boolean; + isEnabled?: boolean; } -export interface IPluginRow extends IPlugin { +export interface IPluginRow { + id: string; + name: string; + displayName: string; + description: string; + publisher: string; + isEnabled?: boolean; + isDeprecated?: boolean; selected: string; - versions: string[]; + versions: { + value: string; + label: string; + }[]; } - /** * This class is handling plugin registry api * @author Ann Shumilova */ export class PluginRegistry { - static $inject = ['$http']; + static $inject = ['$http', '$q']; /** * Angular Http service. */ private $http: ng.IHttpService; + /** + * Angular promise service. + */ + private $q: ng.IQService; + + private plugins = new Map>(); /** * Default constructor that is using resource */ - constructor($http: ng.IHttpService) { + constructor($http: ng.IHttpService, $q: ng.IQService) { this.$http = $http; + this.$q = $q; + } + + static get EDITOR_TYPE(): string { + return 'Che Editor'; } fetchPlugins(location: string): ng.IPromise> { - let promise = this.$http({ 'method': 'GET', 'url': location + '/plugins/' }); - return promise.then((result: any) => { - return result.data; + return this.$http({'method': 'GET', 'url': location + '/plugins/'}).then(result => { + this.plugins.set(location, >result.data); + return this.$q.when(result.data); + }, (error: any) => { + if (error && error.status === 304) { + return this.$q.when(this.plugins.get(location)); + } + return this.$q.reject(error); }); } + + getPlugins(location: string): Array { + return this.plugins.get(location); + } + + hasPlugins(location: string): boolean { + return this.plugins.has(location); + } } diff --git a/dashboard/src/components/api/test/che-http-backend.ts b/dashboard/src/components/api/test/che-http-backend.ts index 9aa3a18257a..cf66330f8e3 100644 --- a/dashboard/src/components/api/test/che-http-backend.ts +++ b/dashboard/src/components/api/test/che-http-backend.ts @@ -11,6 +11,7 @@ */ 'use strict'; import {CheAPIBuilder} from '../builder/che-api-builder.factory'; +import {IPlugin} from '../plugin-registry.factory'; /** * This class is providing helper methods for simulating a fake HTTP backend simulating @@ -49,6 +50,9 @@ export class CheHttpBackend { private installersMap: Map = new Map(); private installersList: Array = []; + private plugins: Array; + private pluginRegistryUrl: string; + /** * Constructor to use */ @@ -86,6 +90,9 @@ export class CheHttpBackend { this.defaultProfile = cheAPIBuilder.getProfileBuilder().withId('idDefaultUser').withEmail('eclipseChe@eclipse.org').withFirstName('FirstName').withLastName('LastName').build(); this.defaultProfilePrefs = {}; this.defaultBranding = {}; + + this.plugins = []; + this.pluginRegistryUrl = 'http://plugin-registry-che/v3'; } @@ -93,6 +100,11 @@ export class CheHttpBackend { * Setup all data that should be retrieved on calls */ setup(): void { + this.$httpBackend.when('GET', '/api/workspace/settings').respond(200, { + cheWorkspacePluginRegistryUrl: this.pluginRegistryUrl + }); + this.$httpBackend.when('GET', this.pluginRegistryUrl + '/plugins/').respond(200, this.plugins); + this.$httpBackend.when('GET', '/api/oauth/').respond(200, []); this.$httpBackend.when('GET', 'https://api.github.com/user/orgs').respond(200, {}); this.$httpBackend.when('GET', 'https://api.github.com/user').respond(200, {}); @@ -119,8 +131,6 @@ export class CheHttpBackend { this.$httpBackend.when('DELETE', '/api/workspace/' + key).respond(200); } - this.$httpBackend.when('GET', '/api/workspace/settings').respond({}); - this.$httpBackend.when('GET', '/api/workspace').respond(workspaceReturn); this.$httpBackend.when('GET', '/api/stack?maxItems=50').respond(this.stacks); @@ -150,7 +160,7 @@ export class CheHttpBackend { } // branding - this.$httpBackend.when('GET', 'assets/branding/product.json').respond(this.defaultBranding); + this.$httpBackend.when('GET', 'assets/branding/product.json').respond(200, this.defaultBranding); this.$httpBackend.when('POST', '/api/analytics/log/session-usage').respond(200, {}); @@ -178,6 +188,22 @@ export class CheHttpBackend { this.$httpBackend.when('GET', /\/_app\/compilation-mappings(\?.*$)?/).respond(200, ''); } + /** + * Sets plugins + * @param plugins + */ + setPlugins(plugins: Array) { + this.plugins = plugins; + } + + /** + * Sets plugin registry URL + * @param pluginRegistryUrl + */ + setPluginRegistryUrl(pluginRegistryUrl: string) { + this.pluginRegistryUrl = pluginRegistryUrl; + } + /** * Add the given workspaces on this backend * @param workspaces an array of workspaces diff --git a/dashboard/src/components/branding/che-branding.factory.ts b/dashboard/src/components/branding/che-branding.factory.ts index 38e552f0b46..64b5bbc2189 100644 --- a/dashboard/src/components/branding/che-branding.factory.ts +++ b/dashboard/src/components/branding/che-branding.factory.ts @@ -81,7 +81,7 @@ export class CheBranding { private $rootScope: che.IRootScopeService; private $http: ng.IHttpService; private cheService: CheService; - private brandingData: IBranding; + private branding: IBranding; private callbacks: Map = new Map(); /** @@ -91,9 +91,27 @@ export class CheBranding { this.$http = $http; this.$rootScope = $rootScope; this.cheService = cheService; - this.brandingData = {}; + this.branding = {}; this.updateData(); this.updateVersion(); + + this.$rootScope.branding = { + title: this.getProductName(), + name: this.getName(), + logoURL: this.getProductLogo(), + logoText: this.getProductLogoText(), + favicon: this.getProductFavicon(), + loaderURL: this.getLoaderUrl(), + websocketContext: this.getWebsocketContext(), + helpPath: this.getProductHelpPath(), + helpTitle: this.getProductHelpTitle(), + footer: this.getFooter(), + supportEmail: this.getProductSupportEmail(), + oauthDocs: this.getOauthDocs(), + cli: this.getCLI(), + docs: this.getDocs(), + workspace: this.getWorkspace() + }; } /** @@ -110,30 +128,9 @@ export class CheBranding { * Update branding data. */ updateData(): void { - this.$http.get(ASSET_PREFIX + 'product.json').then((branding: { data: any }) => { - return branding && branding.data ? branding.data : {}; - }, () => { - return {}; - }).then((brandingData: IBranding) => { - this.brandingData = brandingData; - this.$rootScope.branding = { - title: this.getProductName(), - name: this.getName(), - logoURL: this.getProductLogo(), - logoText: this.getProductLogoText(), - favicon: this.getProductFavicon(), - loaderURL: this.getLoaderUrl(), - websocketContext: this.getWebsocketContext(), - helpPath: this.getProductHelpPath(), - helpTitle: this.getProductHelpTitle(), - footer: this.getFooter(), - supportEmail: this.getProductSupportEmail(), - oauthDocs: this.getOauthDocs(), - cli: this.getCLI(), - docs: this.getDocs(), - workspace: this.getWorkspace() - }; - this.callbacks.forEach((callback: Function) => { + this.$http.get(ASSET_PREFIX + 'product.json').then(res => res.data).then((branding: IBranding) => { + this.branding = branding; + this.callbacks.forEach(callback => { if (angular.isFunction(callback)) { callback(this.$rootScope.branding); } @@ -169,7 +166,7 @@ export class CheBranding { * @returns {string} */ getName(): string { - return this.brandingData.name ? this.brandingData.name : DEFAULT_NAME; + return this.branding.name ? this.branding.name : DEFAULT_NAME; } /** @@ -177,7 +174,7 @@ export class CheBranding { * @returns {string} */ getProductName(): string { - return this.brandingData.title ? this.brandingData.title : DEFAULT_PRODUCT_NAME; + return this.branding.title ? this.branding.title : DEFAULT_PRODUCT_NAME; } /** @@ -185,7 +182,7 @@ export class CheBranding { * @returns {string} */ getProductLogo(): string { - return this.brandingData.logoFile ? ASSET_PREFIX + this.brandingData.logoFile : ASSET_PREFIX + DEFAULT_PRODUCT_LOGO; + return this.branding.logoFile ? ASSET_PREFIX + this.branding.logoFile : ASSET_PREFIX + DEFAULT_PRODUCT_LOGO; } /** @@ -193,7 +190,7 @@ export class CheBranding { * @returns {string} */ getProductFavicon(): string { - return this.brandingData.favicon ? ASSET_PREFIX + this.brandingData.favicon : ASSET_PREFIX + DEFAULT_PRODUCT_FAVICON; + return this.branding.favicon ? ASSET_PREFIX + this.branding.favicon : ASSET_PREFIX + DEFAULT_PRODUCT_FAVICON; } /** @@ -201,7 +198,7 @@ export class CheBranding { * @returns {string} */ getLoaderUrl(): string { - return this.brandingData.loader ? ASSET_PREFIX + this.brandingData.loader : ASSET_PREFIX + DEFAULT_LOADER; + return this.branding.loader ? ASSET_PREFIX + this.branding.loader : ASSET_PREFIX + DEFAULT_LOADER; } /** @@ -209,7 +206,7 @@ export class CheBranding { * @returns {string} */ getWebsocketContext(): string { - return this.brandingData.websocketContext ? this.brandingData.websocketContext : DEFAULT_WEBSOCKET_CONTEXT; + return this.branding.websocketContext ? this.branding.websocketContext : DEFAULT_WEBSOCKET_CONTEXT; } /** @@ -217,7 +214,7 @@ export class CheBranding { * @returns {string} */ getProductHelpPath(): string { - return this.brandingData.helpPath ? this.brandingData.helpPath : null; + return this.branding.helpPath ? this.branding.helpPath : null; } /** @@ -225,7 +222,7 @@ export class CheBranding { * @returns {string} */ getProductHelpTitle(): string { - return this.brandingData.helpTitle ? this.brandingData.helpTitle : null; + return this.branding.helpTitle ? this.branding.helpTitle : null; } /** @@ -233,7 +230,7 @@ export class CheBranding { * @returns {string} */ getProductLogoText(): string { - return this.brandingData.logoTextFile ? ASSET_PREFIX + this.brandingData.logoTextFile : ASSET_PREFIX + DEFAULT_PRODUCT_LOGO_TEXT; + return this.branding.logoTextFile ? ASSET_PREFIX + this.branding.logoTextFile : ASSET_PREFIX + DEFAULT_PRODUCT_LOGO_TEXT; } /** @@ -241,7 +238,7 @@ export class CheBranding { * @returns {string} */ getOauthDocs(): string { - return this.brandingData.oauthDocs ? this.brandingData.oauthDocs : DEFAULT_OAUTH_DOCS; + return this.branding.oauthDocs ? this.branding.oauthDocs : DEFAULT_OAUTH_DOCS; } /** @@ -249,7 +246,7 @@ export class CheBranding { * @returns {string} */ getProductSupportEmail(): string { - return this.brandingData.supportEmail ? this.brandingData.supportEmail : null; + return this.branding.supportEmail ? this.branding.supportEmail : null; } /** @@ -259,9 +256,9 @@ export class CheBranding { */ getFooter(): {content?: string; links?: Array<{title: string, location: string}>; email: {title: string, address: string, subject: string}} { return { - content: this.brandingData.footer && this.brandingData.footer.content ? this.brandingData.footer.content : '', - links: this.brandingData.footer && this.brandingData.footer.links ? this.brandingData.footer.links : [], - email: this.brandingData.footer && this.brandingData.footer.email ? this.brandingData.footer.email : null + content: this.branding.footer && this.branding.footer.content ? this.branding.footer.content : '', + links: this.branding.footer && this.branding.footer.links ? this.branding.footer.links : [], + email: this.branding.footer && this.branding.footer.email ? this.branding.footer.email : null }; } @@ -271,8 +268,8 @@ export class CheBranding { */ getCLI(): { configName: string; name: string } { return { - configName: this.brandingData.cli && this.brandingData.cli.configName ? this.brandingData.cli.configName : DEFAULT_CLI_CONFIG_NAME, - name: this.brandingData.cli && this.brandingData.cli.name ? this.brandingData.cli.name : DEFAULT_CLI_NAME + configName: this.branding.cli && this.branding.cli.configName ? this.branding.cli.configName : DEFAULT_CLI_CONFIG_NAME, + name: this.branding.cli && this.branding.cli.name ? this.branding.cli.name : DEFAULT_CLI_NAME }; } @@ -282,12 +279,12 @@ export class CheBranding { */ getDocs(): { devfile: string; workspace: string; factory: string; organization: string; general: string, converting: string} { return { - devfile: this.brandingData.docs && this.brandingData.docs.devfile ? this.brandingData.docs.devfile : DEFAULT_DOCS_DEVFILE, - workspace: this.brandingData.docs && this.brandingData.docs.workspace ? this.brandingData.docs.workspace : DEFAULT_DOCS_WORKSPACE, - factory: this.brandingData.docs && this.brandingData.docs.factory ? this.brandingData.docs.factory : DEFAULT_DOCS_FACTORY, - organization: this.brandingData.docs && this.brandingData.docs.organization ? this.brandingData.docs.organization : DEFAULT_DOCS_ORGANIZATION, - general: this.brandingData.docs && this.brandingData.docs.general ? this.brandingData.docs.general : DEFAULT_DOCS_GENERAL, - converting: this.brandingData.docs && this.brandingData.docs.converting ? this.brandingData.docs.converting : DEFAULT_DOCS_CONVERTING + devfile: this.branding.docs && this.branding.docs.devfile ? this.branding.docs.devfile : DEFAULT_DOCS_DEVFILE, + workspace: this.branding.docs && this.branding.docs.workspace ? this.branding.docs.workspace : DEFAULT_DOCS_WORKSPACE, + 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 }; } @@ -297,9 +294,9 @@ export class CheBranding { */ getWorkspace(): { priorityStacks: Array; defaultStack: string, creationLink: string} { return { - priorityStacks: this.brandingData.workspace && this.brandingData.workspace.priorityStacks ? this.brandingData.workspace.priorityStacks : DEFAULT_WORKSPACE_PRIORITY_STACKS, - defaultStack: this.brandingData.workspace && this.brandingData.workspace.defaultStack ? this.brandingData.workspace.defaultStack : DEFAULT_WORKSPACE_DEFAULT_STACK, - creationLink: this.brandingData.workspace && this.brandingData.workspace.creationLink ? this.brandingData.workspace.creationLink : DEFAULT_WORKSPACE_CREATION_LINK + priorityStacks: this.branding.workspace && this.branding.workspace.priorityStacks ? this.branding.workspace.priorityStacks : DEFAULT_WORKSPACE_PRIORITY_STACKS, + defaultStack: this.branding.workspace && this.branding.workspace.defaultStack ? this.branding.workspace.defaultStack : DEFAULT_WORKSPACE_DEFAULT_STACK, + creationLink: this.branding.workspace && this.branding.workspace.creationLink ? this.branding.workspace.creationLink : DEFAULT_WORKSPACE_CREATION_LINK }; } }