diff --git a/packages/dashboard-backend/src/api/serverConfigApi.ts b/packages/dashboard-backend/src/api/serverConfigApi.ts index 84db2bf89..41954cb11 100644 --- a/packages/dashboard-backend/src/api/serverConfigApi.ts +++ b/packages/dashboard-backend/src/api/serverConfigApi.ts @@ -18,15 +18,21 @@ import { getSchema } from '../services/helpers'; const tags = ['Server Config']; export function registerServerConfigApi(server: FastifyInstance) { - server.get( - `${baseApiPath}/server-config/default-plugins`, - getSchema({ tags }), - async function () { - const token = await getServiceAccountToken(); - const { serverConfigApi } = await getDevWorkspaceClient(token); - const cheCustomResource = await serverConfigApi.getCheCustomResource(); + server.get(`${baseApiPath}/server-config`, getSchema({ tags }), async function () { + const token = await getServiceAccountToken(); + const { serverConfigApi } = await getDevWorkspaceClient(token); + const cheCustomResource = await serverConfigApi.getCheCustomResource(); - return serverConfigApi.getDefaultPlugins(cheCustomResource); - }, - ); + const plugins = serverConfigApi.getDefaultPlugins(cheCustomResource); + const editor = serverConfigApi.getDefaultEditor(cheCustomResource); + const components = serverConfigApi.getDefaultComponents(cheCustomResource); + + return { + defaults: { + editor, + plugins, + components, + }, + }; + }); } diff --git a/packages/dashboard-backend/src/devworkspace-client/services/api/serverConfigApi.ts b/packages/dashboard-backend/src/devworkspace-client/services/api/serverConfigApi.ts index b4faec77e..bdc2b7132 100644 --- a/packages/dashboard-backend/src/devworkspace-client/services/api/serverConfigApi.ts +++ b/packages/dashboard-backend/src/devworkspace-client/services/api/serverConfigApi.ts @@ -14,6 +14,7 @@ import * as k8s from '@kubernetes/client-node'; import { IServerConfigApi } from '../../types'; import { createError } from '../helpers'; import { api } from '@eclipse-che/common'; +import { V220DevfileComponents } from '@devfile/api'; const CUSTOM_RESOURCE_DEFINITIONS_API_ERROR_LABEL = 'CUSTOM_RESOURCE_DEFINITIONS_API_ERROR'; @@ -61,6 +62,14 @@ export class ServerConfigApi implements IServerConfigApi { return cheCustomResource.spec.server.workspacesDefaultPlugins || []; } + getDefaultEditor(cheCustomResource: { [key: string]: any }): string | undefined { + return cheCustomResource.spec.server.workspaceDefaultEditor; + } + + getDefaultComponents(cheCustomResource: { [key: string]: any }): V220DevfileComponents[] { + return cheCustomResource.spec.server.workspaceDefaultComponents || []; + } + getDashboardWarning(cheCustomResource: { [key: string]: any }): string | undefined { return cheCustomResource.spec.dashboard?.warning; } diff --git a/packages/dashboard-backend/src/devworkspace-client/types/index.ts b/packages/dashboard-backend/src/devworkspace-client/types/index.ts index 637a7e1e0..621bf54b3 100644 --- a/packages/dashboard-backend/src/devworkspace-client/types/index.ts +++ b/packages/dashboard-backend/src/devworkspace-client/types/index.ts @@ -12,6 +12,7 @@ import { V1alpha2DevWorkspace, V1alpha2DevWorkspaceTemplate } from '@devfile/api'; import { api } from '@eclipse-che/common'; +import { V220DevfileComponents } from '@devfile/api'; /** * Holds the methods for working with dockerconfig for devworkspace @@ -94,7 +95,15 @@ export interface IServerConfigApi { * Returns default plugins */ getDefaultPlugins(cheCustomResource: { [key: string]: any }): api.IWorkspacesDefaultPlugins[]; - + /** + * Returns the default editor to workspace create with. It could be a plugin ID or a URI. + */ + getDefaultEditor(cheCustomResource: { [key: string]: any }): string | undefined; + /** + * Returns the default components applied to DevWorkspaces. + * These default components are meant to be used when a Devfile does not contain any components. + */ + getDefaultComponents(cheCustomResource: { [key: string]: any }): V220DevfileComponents[]; /** * Returns a maintenance warning */ diff --git a/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx b/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx index 6f70c6cc9..4cd302f33 100644 --- a/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx +++ b/packages/dashboard-frontend/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx @@ -120,7 +120,7 @@ export class SamplesListGallery extends React.PureComponent { : `&storageType=${storageType}`; } // use factory workflow to load the getting started samples - const factoryUrl = `${window.location.origin}/#/load-factory?url=${link}${devWorkspace}`; + const factoryUrl = `${window.location.origin}${window.location.pathname}#/load-factory?url=${link}${devWorkspace}`; // open a new page to handle that window.open(factoryUrl, '_blank'); this.isLoading = false; diff --git a/packages/dashboard-frontend/src/services/bootstrap/index.ts b/packages/dashboard-frontend/src/services/bootstrap/index.ts index 854d04042..b067c5565 100644 --- a/packages/dashboard-frontend/src/services/bootstrap/index.ts +++ b/packages/dashboard-frontend/src/services/bootstrap/index.ts @@ -18,6 +18,7 @@ import * as BannerAlertStore from '../../store/BannerAlert'; import * as BrandingStore from '../../store/Branding'; import * as ClusterConfigStore from '../../store/ClusterConfig'; import * as ClusterInfoStore from '../../store/ClusterInfo'; +import * as ServerConfigStore from '../../store/ServerConfig'; import * as DevfileRegistriesStore from '../../store/DevfileRegistries'; import * as InfrastructureNamespacesStore from '../../store/InfrastructureNamespaces'; import * as PluginsStore from '../../store/Plugins/chePlugins'; @@ -77,9 +78,9 @@ export default class Bootstrap { const results = await Promise.allSettled([ this.fetchCurrentUser(), this.fetchUserProfile(), + this.fetchServerConfig().then(() => this.fetchDefaultDwPlugins(settings)), this.fetchPlugins(settings).then(() => this.fetchDevfileSchema()), this.fetchDwPlugins(settings), - this.fetchDefaultDwPlugins(settings), this.fetchRegistriesMetadata(settings), this.watchNamespaces(), this.updateDevWorkspaceTemplates(settings), @@ -253,6 +254,15 @@ export default class Bootstrap { return this.store.getState().workspacesSettings.settings; } + private async fetchServerConfig(): Promise { + const { requestServerConfig } = ServerConfigStore.actionCreators; + try { + await requestServerConfig()(this.store.dispatch, this.store.getState, undefined); + } catch (e) { + console.warn('Unable to fetch server config.'); + } + } + private async fetchRegistriesMetadata(settings: che.WorkspaceSettings): Promise { const { requestRegistriesMetadata } = DevfileRegistriesStore.actionCreators; await requestRegistriesMetadata(settings.cheWorkspaceDevfileRegistryUrl || '')( diff --git a/packages/dashboard-frontend/src/services/dashboard-backend-client/serverConfigApi.ts b/packages/dashboard-frontend/src/services/dashboard-backend-client/serverConfigApi.ts index abfe7c853..d9803b246 100644 --- a/packages/dashboard-frontend/src/services/dashboard-backend-client/serverConfigApi.ts +++ b/packages/dashboard-frontend/src/services/dashboard-backend-client/serverConfigApi.ts @@ -14,14 +14,23 @@ import axios from 'axios'; import common from '@eclipse-che/common'; import { prefix } from './const'; import { api } from '@eclipse-che/common'; +import { V220DevfileComponents } from '@devfile/api'; /** * Returns an array of default plug-ins per editor * - * @returns Promise resolving with the array of default plug-ins for the specified editor + * @returns Promise resolving with the object with includes + * default plug-ins for the specified editor, + * default editor and default components */ -export async function getDefaultPlugins(): Promise { - const url = `${prefix}/server-config/default-plugins`; +export async function getServerConfig(): Promise<{ + defaults: { + plugins: api.IWorkspacesDefaultPlugins[]; + components: V220DevfileComponents[]; + editor: string | undefined; + }; +}> { + const url = `${prefix}/server-config`; try { const response = await axios.get(url); return response.data ? response.data : []; diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts index 35da21df7..4ef553116 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.onStart.spec.ts @@ -14,7 +14,6 @@ import { container } from '../../../../inversify.config'; import { DevWorkspaceBuilder } from '../../../../store/__mocks__/devWorkspaceBuilder'; import { DevWorkspaceClient } from '../devWorkspaceClient'; import * as DwApi from '../../../dashboard-backend-client/devWorkspaceApi'; -import * as ServerConfigApi from '../../../dashboard-backend-client/serverConfigApi'; import mockAxios from 'axios'; /* eslint-disable @typescript-eslint/no-non-null-assertion */ @@ -222,23 +221,4 @@ describe('DevWorkspace client, start', () => { expect(testWorkspace.spec.template.components![0].plugin!.uri!).toBe(uri); expect(patchWorkspace).toHaveBeenCalledTimes(0); }); - - it('should not call ServerConfigApi.getDefaultPlugins', async () => { - const namespace = 'che'; - const name = 'wksp-test'; - const testWorkspace = new DevWorkspaceBuilder() - .withMetadata({ - name, - namespace, - }) - .build(); - - const getDefaultPlugins = jest.spyOn(ServerConfigApi, 'getDefaultPlugins'); - const defaults = {}; - const editor = 'eclipse/theia/next'; - - await client.onStart(testWorkspace, defaults, editor); - - expect(getDefaultPlugins).toHaveBeenCalledTimes(0); - }); }); diff --git a/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts index 1a50d7418..a1876ca27 100644 --- a/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/FactoryResolver/__tests__/index.spec.ts @@ -217,8 +217,8 @@ describe('FactoryResolver store', () => { it('should create REQUEST_FACTORY_RESOLVER and RECEIVE_FACTORY_RESOLVER', async () => { const resolver = { devfile: { - apiVersion: '1.0.0', - } as che.WorkspaceDevfile, + schemaVersion: '2.0.0', + }, } as factoryResolverStore.ResolverState; getFactoryResolverSpy.mockResolvedValueOnce(resolver); @@ -244,7 +244,7 @@ describe('FactoryResolver store', () => { { type: 'RECEIVE_FACTORY_RESOLVER', resolver: expect.objectContaining(resolver), - converted: expect.objectContaining({ isConverted: false }), + converted: expect.objectContaining({ isConverted: true }), }, ]; expect(actions).toEqual(expectedActions); diff --git a/packages/dashboard-frontend/src/store/FactoryResolver/index.ts b/packages/dashboard-frontend/src/store/FactoryResolver/index.ts index 081e33729..6da1c4d12 100644 --- a/packages/dashboard-frontend/src/store/FactoryResolver/index.ts +++ b/packages/dashboard-frontend/src/store/FactoryResolver/index.ts @@ -22,12 +22,10 @@ import { selectDevworkspacesEnabled, selectPreferredStorageType, } from '../Workspaces/Settings/selectors'; +import { selectDefaultComponents } from '../ServerConfig/selectors'; import { Devfile } from '../../services/workspace-adapter'; import devfileApi, { isDevfileV2 } from '../../services/devfileApi'; -import { - convertDevfileV1toDevfileV2, - convertDevfileV2toDevfileV1, -} from '../../services/devfile/converters'; +import { convertDevfileV1toDevfileV2 } from '../../services/devfile/converters'; import normalizeDevfileV2 from './normalizeDevfileV2'; import normalizeDevfileV1 from './normalizeDevfileV1'; @@ -60,7 +58,6 @@ export interface ResolverState extends FactoryResolver { export interface ConvertedState { resolvedDevfile: Devfile; - devfileV1: che.WorkspaceDevfile; devfileV2: devfileApi.Devfile; isConverted: boolean; } @@ -174,20 +171,17 @@ export const actionCreators: ActionCreators = { const preferredStorageType = selectPreferredStorageType(state); const resolvedDevfile = data.devfile; const isResolvedDevfileV2 = isDevfileV2(resolvedDevfile); - let devfileV1: che.WorkspaceDevfile; let devfileV2: devfileApi.Devfile; + const defaultComponents = selectDefaultComponents(state); if (isResolvedDevfileV2) { - devfileV2 = normalizeDevfileV2(resolvedDevfile, data, location); - devfileV1 = normalizeDevfileV1( - await convertDevfileV2toDevfileV1(devfileV2, optionalFilesContent), - preferredStorageType, - ); + devfileV2 = normalizeDevfileV2(resolvedDevfile, data, location, defaultComponents); } else { - devfileV1 = normalizeDevfileV1(resolvedDevfile, preferredStorageType); + const devfileV1 = normalizeDevfileV1(resolvedDevfile, preferredStorageType); devfileV2 = normalizeDevfileV2( await convertDevfileV1toDevfileV2(devfileV1), data, location, + defaultComponents, ); } const converted: ConvertedState = { @@ -195,18 +189,15 @@ export const actionCreators: ActionCreators = { isConverted: (isDevworkspacesEnabled && isResolvedDevfileV2 === false) || (isDevworkspacesEnabled === false && isResolvedDevfileV2), - devfileV1, devfileV2, }; - const devfile: Devfile = isDevworkspacesEnabled ? devfileV2 : devfileV1; - dispatch({ type: 'RECEIVE_FACTORY_RESOLVER', resolver: { ...data, location, - devfile, + devfile: devfileV2, optionalFilesContent, }, converted, diff --git a/packages/dashboard-frontend/src/store/FactoryResolver/normalizeDevfileV2.ts b/packages/dashboard-frontend/src/store/FactoryResolver/normalizeDevfileV2.ts index b4d3afa4d..1b4c2c548 100644 --- a/packages/dashboard-frontend/src/store/FactoryResolver/normalizeDevfileV2.ts +++ b/packages/dashboard-frontend/src/store/FactoryResolver/normalizeDevfileV2.ts @@ -18,23 +18,27 @@ import { DEVWORKSPACE_DEVFILE_SOURCE, DEVWORKSPACE_METADATA_ANNOTATION, } from '../../services/workspace-client/devworkspace/devWorkspaceClient'; +import { V220DevfileComponents } from '@devfile/api'; /** * Returns a devfile from the FactoryResolver object. + * @param devfile a Devfile. * @param data a FactoryResolver object. * @param location a source location. - * @param isDevworkspacesEnabled indicates if devworkspace engine is enabled. + * @param defaultComponents Default components. These default components + * are meant to be used when a Devfile does not contain any components. */ export default function normalizeDevfileV2( devfile: devfileApi.Devfile, data: FactoryResolver, location: string, + defaultComponents: V220DevfileComponents[], ): devfileApi.Devfile { const scmInfo = data['scm_info']; devfile = devfile as devfileApi.Devfile; - if (!devfile.components) { - devfile.components = []; + if (!devfile.components || devfile.components.length === 0) { + devfile.components = defaultComponents; } // temporary solution for fix che-server serialization bug with empty volume diff --git a/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/__tests__/index.spec.ts index e4c95df5c..983e9d56d 100644 --- a/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/__tests__/index.spec.ts +++ b/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/__tests__/index.spec.ts @@ -298,11 +298,27 @@ describe('dwPlugins store', () => { }); it('should create REQUEST_DW_DEFAULT_EDITOR and RECEIVE_DW_DEFAULT_EDITOR when fetching default plugins', async () => { - (mockAxios.get as jest.Mock).mockResolvedValueOnce({ - data: [{ editor: 'eclipse/theia/next', plugins: ['https://test.com/devfile.yaml'] }], - }); - - const store = new FakeStoreBuilder().build() as MockStoreEnhanced< + const store = new FakeStoreBuilder() + .withDwServerConfig({ + defaults: { + editor: 'eclipse/theia/next', + components: [ + { + name: 'universal-developer-image', + container: { + image: 'quay.io/devfile/universal-developer-image:ubi8-latest', + }, + }, + ], + plugins: [ + { + editor: 'eclipse/theia/next', + plugins: ['https://test.com/devfile.yaml'], + }, + ], + }, + }) + .build() as MockStoreEnhanced< AppState, ThunkDispatch >; diff --git a/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts b/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts index 2df7850df..0b459f1cd 100644 --- a/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts +++ b/packages/dashboard-frontend/src/store/Plugins/devWorkspacePlugins/index.ts @@ -17,7 +17,6 @@ import devfileApi from '../../../services/devfileApi'; import { AppThunk } from '../..'; import { fetchDevfile, fetchData } from '../../../services/registry/devfiles'; import { createObject } from '../../helpers'; -import * as ServerConfigApi from '../../../services/dashboard-backend-client/serverConfigApi'; export interface PluginDefinition { plugin?: devfileApi.Devfile; @@ -205,9 +204,11 @@ export const actionCreators: ActionCreators = { requestDwDefaultEditor: (settings: che.WorkspaceSettings): AppThunk> => - async (dispatch): Promise => { - const defaultEditor = settings['che.factory.default_editor']; - + async (dispatch, getState): Promise => { + const config = getState().dwServerConfig.config; + const defaultEditor = config.defaults.editor + ? config.defaults.editor + : settings['che.factory.default_editor']; dispatch({ type: 'REQUEST_DW_DEFAULT_EDITOR', }); @@ -238,14 +239,14 @@ export const actionCreators: ActionCreators = { requestDwDefaultPlugins: (): AppThunk> => - async (dispatch): Promise => { + async (dispatch, getState): Promise => { dispatch({ type: 'REQUEST_DW_DEFAULT_PLUGINS', }); const defaultPlugins = {}; - const defaults = await ServerConfigApi.getDefaultPlugins(); - defaults.forEach(item => { + const defaults = getState().dwServerConfig.config.defaults; + defaults.plugins.forEach(item => { if (!defaultPlugins[item.editor]) { defaultPlugins[item.editor] = []; } diff --git a/packages/dashboard-frontend/src/store/ServerConfig/__tests__/index.spec.ts b/packages/dashboard-frontend/src/store/ServerConfig/__tests__/index.spec.ts new file mode 100644 index 000000000..dd17026c7 --- /dev/null +++ b/packages/dashboard-frontend/src/store/ServerConfig/__tests__/index.spec.ts @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018-2021 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 + */ + +import mockAxios from 'axios'; +import { MockStoreEnhanced } from 'redux-mock-store'; +import { ThunkDispatch } from 'redux-thunk'; +import { FakeStoreBuilder } from '../../__mocks__/storeBuilder'; +import * as dwServerConfigStore from '../../ServerConfig'; +import { AppState } from '../..'; + +// mute the outputs +console.error = jest.fn(); + +describe('dwPlugins store', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('actions', () => { + it('should create RECEIVE_DW_SERVER_CONFIG when fetching server config', async () => { + (mockAxios.get as jest.Mock).mockResolvedValueOnce({ + data: { + defaults: { + editor: 'eclipse/theia/next', + components: [ + { + name: 'universal-developer-image', + container: { + image: 'quay.io/devfile/universal-developer-image:ubi8-latest', + }, + }, + ], + plugins: [ + { + editor: 'eclipse/theia/next', + plugins: ['https://test.com/devfile.yaml'], + }, + ], + }, + }, + }); + + const store = new FakeStoreBuilder().build() as MockStoreEnhanced< + AppState, + ThunkDispatch + >; + + await store.dispatch(dwServerConfigStore.actionCreators.requestServerConfig()); + + const actions = store.getActions(); + + const expectedActions: dwServerConfigStore.KnownAction[] = [ + { + type: 'REQUEST_DW_SERVER_CONFIG', + }, + { + type: 'RECEIVE_DW_SERVER_CONFIG', + config: { + defaults: { + editor: 'eclipse/theia/next', + components: [ + { + name: 'universal-developer-image', + container: { + image: 'quay.io/devfile/universal-developer-image:ubi8-latest', + }, + }, + ], + plugins: [ + { + editor: 'eclipse/theia/next', + plugins: ['https://test.com/devfile.yaml'], + }, + ], + }, + }, + }, + ]; + expect(actions).toEqual(expectedActions); + }); + }); + + it('should create RECEIVE_DW_SERVER_CONFIG_ERROR when fetching server and got an error', async () => { + (mockAxios.get as jest.Mock).mockRejectedValueOnce('Test error'); + + const store = new FakeStoreBuilder().build() as MockStoreEnhanced< + AppState, + ThunkDispatch + >; + + try { + await store.dispatch(dwServerConfigStore.actionCreators.requestServerConfig()); + } catch (e) { + // noop + } + + const actions = store.getActions(); + + const expectedActions: dwServerConfigStore.KnownAction[] = [ + { + type: 'REQUEST_DW_SERVER_CONFIG', + }, + { + type: 'RECEIVE_DW_SERVER_CONFIG_ERROR', + error: 'Failed to fetch default plugins. Test error', + }, + ]; + expect(actions).toEqual(expectedActions); + }); +}); diff --git a/packages/dashboard-frontend/src/store/ServerConfig/index.ts b/packages/dashboard-frontend/src/store/ServerConfig/index.ts new file mode 100644 index 000000000..ebcae76f8 --- /dev/null +++ b/packages/dashboard-frontend/src/store/ServerConfig/index.ts @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018-2021 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 + */ + +import { Action, Reducer } from 'redux'; +import common, { api } from '@eclipse-che/common'; +import { AppThunk } from '../'; +import { createObject } from '../helpers'; +import * as ServerConfigApi from '../../services/dashboard-backend-client/serverConfigApi'; +import { V220DevfileComponents } from '@devfile/api'; + +export interface ServerConfig { + defaults: { + editor: string | undefined; + components: V220DevfileComponents[]; + plugins: api.IWorkspacesDefaultPlugins[]; + }; +} + +export interface State { + isLoading: boolean; + config: ServerConfig; + error?: string; +} + +export interface RequestDwServerConfigAction { + type: 'REQUEST_DW_SERVER_CONFIG'; +} + +export interface ReceiveDwServerConfigAction { + type: 'RECEIVE_DW_SERVER_CONFIG'; + config: ServerConfig; +} + +export interface ReceiveDwServerConfigErrorAction { + type: 'RECEIVE_DW_SERVER_CONFIG_ERROR'; + error: string; +} + +export type KnownAction = + | ReceiveDwServerConfigAction + | ReceiveDwServerConfigErrorAction + | RequestDwServerConfigAction; + +export type ActionCreators = { + requestServerConfig: () => AppThunk>; +}; +export const actionCreators: ActionCreators = { + requestServerConfig: + (): AppThunk> => + async (dispatch): Promise => { + dispatch({ + type: 'REQUEST_DW_SERVER_CONFIG', + }); + try { + const config = await ServerConfigApi.getServerConfig(); + dispatch({ + type: 'RECEIVE_DW_SERVER_CONFIG', + config, + }); + } catch (e) { + const error = common.helpers.errors.getMessage(e); + dispatch({ + type: 'RECEIVE_DW_SERVER_CONFIG_ERROR', + error, + }); + throw new Error(error); + } + }, +}; + +const unloadedState: State = { + isLoading: false, + config: { + defaults: { + editor: undefined, + components: [], + plugins: [], + }, + }, + error: undefined, +}; + +export const reducer: Reducer = ( + state: State | undefined, + incomingAction: Action, +): State => { + if (state === undefined) { + return unloadedState; + } + + const action = incomingAction as KnownAction; + switch (action.type) { + case 'REQUEST_DW_SERVER_CONFIG': + return createObject(state, { + isLoading: true, + }); + case 'RECEIVE_DW_SERVER_CONFIG': + return createObject(state, { + isLoading: false, + config: action.config, + error: undefined, + }); + case 'RECEIVE_DW_SERVER_CONFIG_ERROR': + return createObject(state, { + isLoading: false, + error: action.error, + }); + default: + return state; + } +}; diff --git a/packages/dashboard-frontend/src/store/ServerConfig/selectors.ts b/packages/dashboard-frontend/src/store/ServerConfig/selectors.ts new file mode 100644 index 000000000..eafcc0b42 --- /dev/null +++ b/packages/dashboard-frontend/src/store/ServerConfig/selectors.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2021 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 + */ + +import { createSelector } from 'reselect'; +import { AppState } from '..'; + +const selectState = (state: AppState) => state.dwServerConfig; +export const selectServerConfigState = selectState; + +export const selectDefaultComponents = createSelector( + selectState, + state => state.config.defaults?.components || [], +); + +export const selectDefaultPlugins = createSelector( + selectState, + state => state.config.defaults?.plugins || [], +); + +export const selectServerConfigError = createSelector(selectState, state => state.error); diff --git a/packages/dashboard-frontend/src/store/Workspaces/cheWorkspaces/index.ts b/packages/dashboard-frontend/src/store/Workspaces/cheWorkspaces/index.ts index 18a6ba671..152482add 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/cheWorkspaces/index.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/cheWorkspaces/index.ts @@ -273,34 +273,15 @@ export const actionCreators: ActionCreators = { } }, + /* eslint-disable @typescript-eslint/no-unused-vars */ + /* eslint-enable @typescript-eslint/no-unused-vars */ startWorkspace: ( workspace: che.Workspace, params?: ResourceQueryParams, ): AppThunk> => - async (dispatch): Promise => { - try { - const update = await cheWorkspaceClient.restApiClient.start( - workspace.id, - params, - ); - dispatch({ type: 'CHE_DELETE_WORKSPACE_LOGS', workspaceId: update.id }); - subscribeToEnvironmentOutput(workspace.id, dispatch); - - dispatch({ - type: 'CHE_UPDATE_WORKSPACE', - workspace: update, - }); - } catch (e) { - const errorMessage = - `Failed to start the workspace ${workspace.devfile.metadata.name}, reason: ` + - common.helpers.errors.getMessage(e); - dispatch({ - type: 'CHE_RECEIVE_ERROR', - error: errorMessage, - }); - throw errorMessage; - } + async (): Promise => { + throw new Error('Running Che7 workspaces is not supported.'); }, stopWorkspace: diff --git a/packages/dashboard-frontend/src/store/__mocks__/storeBuilder.ts b/packages/dashboard-frontend/src/store/__mocks__/storeBuilder.ts index e0207d544..9da72007c 100644 --- a/packages/dashboard-frontend/src/store/__mocks__/storeBuilder.ts +++ b/packages/dashboard-frontend/src/store/__mocks__/storeBuilder.ts @@ -27,6 +27,7 @@ import mockThunk from './thunk'; import devfileApi from '../../services/devfileApi'; import { ClusterConfig, ClusterInfo } from '@eclipse-che/common'; import { WorkspacesLogs } from '../../services/helpers/types'; +import { ServerConfig } from '../ServerConfig'; export class FakeStoreBuilder { private state: AppState = { @@ -39,6 +40,16 @@ export class FakeStoreBuilder { runningWorkspacesLimit: 1, }, }, + dwServerConfig: { + isLoading: false, + config: { + defaults: { + editor: undefined, + components: [], + plugins: [], + }, + } as ServerConfig, + }, clusterInfo: { isLoading: false, clusterInfo: { @@ -119,6 +130,14 @@ export class FakeStoreBuilder { }, }; + public withDwServerConfig(config: ServerConfig): FakeStoreBuilder { + this.state.dwServerConfig = { + isLoading: false, + config, + }; + return this; + } + public withBannerAlert(messages: string[]): FakeStoreBuilder { this.state.bannerAlert.messages = [...messages]; return this; diff --git a/packages/dashboard-frontend/src/store/index.ts b/packages/dashboard-frontend/src/store/index.ts index be54a26a3..422b6c48e 100644 --- a/packages/dashboard-frontend/src/store/index.ts +++ b/packages/dashboard-frontend/src/store/index.ts @@ -30,6 +30,7 @@ import * as DwPluginsStore from './Plugins/devWorkspacePlugins'; import * as WorkspacesSettingsStore from './Workspaces/Settings'; import * as CheDockerConfigStore from './DockerConfig/che'; import * as DwDockerConfigStore from './DockerConfig/dw'; +import * as DwServerConfigStore from './ServerConfig'; // the top-level state object export interface AppState { @@ -51,6 +52,7 @@ export interface AppState { workspacesSettings: WorkspacesSettingsStore.State; cheDockerConfig: CheDockerConfigStore.State; dwDockerConfig: DwDockerConfigStore.State; + dwServerConfig: DwServerConfigStore.State; } export const reducers = { @@ -72,6 +74,7 @@ export const reducers = { workspacesSettings: WorkspacesSettingsStore.reducer, cheDockerConfig: CheDockerConfigStore.reducer, dwDockerConfig: DwDockerConfigStore.reducer, + dwServerConfig: DwServerConfigStore.reducer, }; export type AppThunk = ThunkAction<