Skip to content

Commit

Permalink
feat: add workspaces default editor and component
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 committed Jun 24, 2022
1 parent 08db3bc commit e3087ad
Show file tree
Hide file tree
Showing 16 changed files with 395 additions and 67 deletions.
26 changes: 16 additions & 10 deletions packages/dashboard-backend/src/api/serverConfigApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand Down
11 changes: 11 additions & 0 deletions packages/dashboard-frontend/src/services/bootstrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -77,6 +78,7 @@ export default class Bootstrap {
const results = await Promise.allSettled([
this.fetchCurrentUser(),
this.fetchUserProfile(),
this.fetchServerConfig(),
this.fetchPlugins(settings).then(() => this.fetchDevfileSchema()),
this.fetchDwPlugins(settings),
this.fetchDefaultDwPlugins(settings),
Expand Down Expand Up @@ -253,6 +255,15 @@ export default class Bootstrap {
return this.store.getState().workspacesSettings.settings;
}

private async fetchServerConfig(): Promise<void> {
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<void> {
const { requestRegistriesMetadata } = DevfileRegistriesStore.actionCreators;
await requestRegistriesMetadata(settings.cheWorkspaceDevfileRegistryUrl || '')(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<api.IWorkspacesDefaultPlugins[]> {
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 : [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,23 +222,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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
23 changes: 7 additions & 16 deletions packages/dashboard-frontend/src/store/FactoryResolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -60,7 +58,6 @@ export interface ResolverState extends FactoryResolver {

export interface ConvertedState {
resolvedDevfile: Devfile;
devfileV1: che.WorkspaceDevfile;
devfileV2: devfileApi.Devfile;
isConverted: boolean;
}
Expand Down Expand Up @@ -174,39 +171,33 @@ 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 = {
resolvedDevfile,
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ThunkDispatch } from 'redux-thunk';
import devfileApi from '../../../../services/devfileApi';
import { FakeStoreBuilder } from '../../../__mocks__/storeBuilder';
import * as dwPluginsStore from '..';
import * as dwServerConfigStore from '../../../ServerConfig';
import { AppState } from '../../..';
import axios from 'axios';

Expand Down Expand Up @@ -298,11 +299,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<AppState, undefined, dwPluginsStore.KnownAction>
>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -205,9 +204,11 @@ export const actionCreators: ActionCreators = {

requestDwDefaultEditor:
(settings: che.WorkspaceSettings): AppThunk<KnownAction, Promise<void>> =>
async (dispatch): Promise<void> => {
const defaultEditor = settings['che.factory.default_editor'];

async (dispatch, getState): Promise<void> => {
const config = getState().dwServerConfig.config;
const defaultEditor = config.defaults.editor
? config.defaults.editor
: settings['che.factory.default_editor'];
dispatch({
type: 'REQUEST_DW_DEFAULT_EDITOR',
});
Expand Down Expand Up @@ -238,14 +239,14 @@ export const actionCreators: ActionCreators = {

requestDwDefaultPlugins:
(): AppThunk<KnownAction, Promise<void>> =>
async (dispatch): Promise<void> => {
async (dispatch, getState): Promise<void> => {
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] = [];
}
Expand Down
Loading

0 comments on commit e3087ad

Please sign in to comment.