diff --git a/packages/dashboard-frontend/src/components/BasicViewer/index.tsx b/packages/dashboard-frontend/src/components/BasicViewer/index.tsx index de8104dd7..e692448d1 100644 --- a/packages/dashboard-frontend/src/components/BasicViewer/index.tsx +++ b/packages/dashboard-frontend/src/components/BasicViewer/index.tsx @@ -35,7 +35,6 @@ export class BasicViewer extends React.PureComponent { }); editor.setSize(`100%`, `100%`); editor.setValue(this.props.value); - editor.focus(); this.editor = editor; } @@ -44,7 +43,6 @@ export class BasicViewer extends React.PureComponent { componentDidUpdate(prevProps: Readonly): void { if (this.editor && this.props.value !== prevProps.value) { this.editor.setValue(this.props.value); - this.editor.focus(); } } diff --git a/packages/dashboard-frontend/src/components/DevfileViewer/index.tsx b/packages/dashboard-frontend/src/components/DevfileViewer/index.tsx index 7876a719d..37cd4f734 100644 --- a/packages/dashboard-frontend/src/components/DevfileViewer/index.tsx +++ b/packages/dashboard-frontend/src/components/DevfileViewer/index.tsx @@ -43,12 +43,9 @@ export class DevfileViewer extends React.PureComponent { lineWrapping: true, readOnly: true, autoRefresh: true, - autofocus: true, - gooters: true, }); editor.setSize(`100%`, `100%`); editor.setValue(this.props.value); - editor.focus(); this.editor = editor; } @@ -56,7 +53,6 @@ export class DevfileViewer extends React.PureComponent { componentDidUpdate(): void { this.editor.setValue(this.props.value); - this.editor.focus(); } public render(): React.ReactElement { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx index 00c2fcc9c..8ed8ada76 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/index.spec.tsx @@ -78,6 +78,9 @@ const devfile = { }, } as devfileApi.Devfile; +// mute console.error +console.error = jest.fn(); + describe('Creating steps, applying a devfile', () => { let searchParams: URLSearchParams; let factoryId: string; @@ -245,6 +248,7 @@ describe('Creating steps, applying a devfile', () => { factoryId, undefined, false, + undefined, ), ); await waitFor(() => expect(mockCreateWorkspaceFromDevfile).toHaveBeenCalled()); @@ -334,6 +338,7 @@ describe('Creating steps, applying a devfile', () => { factoryId, undefined, false, + undefined, ), ); await waitFor(() => expect(mockCreateWorkspaceFromDevfile).toHaveBeenCalled()); @@ -410,6 +415,7 @@ describe('Creating steps, applying a devfile', () => { factoryId, undefined, false, + undefined, ), ); await waitFor(() => expect(mockCreateWorkspaceFromDevfile).toHaveBeenCalled()); @@ -488,6 +494,7 @@ describe('Creating steps, applying a devfile', () => { factoryId, undefined, false, + undefined, ), ); await waitFor(() => expect(mockCreateWorkspaceFromDevfile).toHaveBeenCalled()); @@ -511,7 +518,7 @@ describe('Creating steps, applying a devfile', () => { await jest.advanceTimersByTimeAsync(MIN_STEP_DURATION_MS); await waitFor(() => - expect(prepareDevfile).toHaveBeenCalledWith(devfile, factoryId, undefined, true), + expect(prepareDevfile).toHaveBeenCalledWith(devfile, factoryId, undefined, true, undefined), ); }); @@ -534,7 +541,7 @@ describe('Creating steps, applying a devfile', () => { await jest.advanceTimersByTimeAsync(MIN_STEP_DURATION_MS); await waitFor(() => - expect(prepareDevfile).toHaveBeenCalledWith(devfile, factoryId, undefined, true), + expect(prepareDevfile).toHaveBeenCalledWith(devfile, factoryId, undefined, true, undefined), ); }); @@ -554,7 +561,13 @@ describe('Creating steps, applying a devfile', () => { await jest.advanceTimersByTimeAsync(MIN_STEP_DURATION_MS); await waitFor(() => - expect(prepareDevfile).toHaveBeenCalledWith(devfile, factoryId, undefined, false), + expect(prepareDevfile).toHaveBeenCalledWith( + devfile, + factoryId, + undefined, + false, + undefined, + ), ); }); }); @@ -679,7 +692,7 @@ describe('Creating steps, applying a devfile', () => { expect(mockOnNextStep).not.toHaveBeenCalled(); expect(mockOnError).not.toHaveBeenCalled(); - expect(mockCreateWorkspaceFromDevfile).toHaveBeenCalledTimes(1); + await waitFor(() => expect(mockCreateWorkspaceFromDevfile).toHaveBeenCalledTimes(1)); }); test('action callback to continue with default devfile', async () => { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/prepareDevfile.spec.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/prepareDevfile.spec.ts index acdeaec56..a0eebe6c2 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/prepareDevfile.spec.ts +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/__tests__/prepareDevfile.spec.ts @@ -226,5 +226,112 @@ describe('FactoryLoaderContainer/prepareDevfile', () => { expect(newDevfile.metadata.attributes?.[DEVWORKSPACE_STORAGE_TYPE_ATTR]).toBeUndefined(); expect(newDevfile.attributes?.[DEVWORKSPACE_STORAGE_TYPE_ATTR]).toEqual('ephemeral'); }); + + describe('has parent', () => { + describe('with registryUrl', () => { + it('with storage-type attribute', () => { + // mute console logs + console.warn = jest.fn(); + const devfile = { + schemaVersion: '2.2.0', + metadata: { + name: 'wksp-test', + }, + parent: { + id: 'nodejs', + registryUrl: 'https://registry.devfile.io/', + }, + } as devfileApi.Devfile; + + const newDevfile = prepareDevfile(devfile, factoryId, 'ephemeral', false, { + schemaVersion: '2.2.2', + metadata: { + generateName: 'nodejs', + }, + attributes: { + 'controller.devfile.io/storage-type': 'ephemeral', + }, + } as devfileApi.Devfile); + + expect(console.warn).toHaveBeenCalledWith( + 'Unable to apply controller.devfile.io/storage-type attribute.', + ); + expect(newDevfile.attributes?.[DEVWORKSPACE_STORAGE_TYPE_ATTR]).toBeUndefined(); + }); + + it('without storage-type attribute', () => { + const devfile = { + schemaVersion: '2.2.0', + metadata: { + name: 'wksp-test', + }, + parent: { + id: 'nodejs', + registryUrl: 'https://registry.devfile.io/', + }, + } as devfileApi.Devfile; + + const newDevfile = prepareDevfile(devfile, factoryId, 'ephemeral', false, { + schemaVersion: '2.2.2', + metadata: { + generateName: 'nodejs', + }, + } as devfileApi.Devfile); + + expect(newDevfile.attributes?.[DEVWORKSPACE_STORAGE_TYPE_ATTR]).toEqual('ephemeral'); + }); + }); + describe('with uri', () => { + it('with storage-type attribute', () => { + // mute console logs + console.warn = jest.fn(); + const devfile = { + schemaVersion: '2.2.0', + metadata: { + name: 'wksp-test', + }, + parent: { + uri: 'https://raw.githubusercontent.com/test/devfile.yaml', + }, + } as devfileApi.Devfile; + + const newDevfile = prepareDevfile(devfile, factoryId, 'ephemeral', false, { + schemaVersion: '2.2.2', + metadata: { + generateName: 'nodejs', + }, + attributes: { + 'controller.devfile.io/storage-type': 'ephemeral', + }, + } as devfileApi.Devfile); + + expect(console.warn).toHaveBeenCalledWith( + 'Unable to apply controller.devfile.io/storage-type attribute.', + ); + expect(newDevfile.attributes?.[DEVWORKSPACE_STORAGE_TYPE_ATTR]).toBeUndefined(); + }); + + it('without storage-type attribute', () => { + const devfile = { + schemaVersion: '2.2.0', + metadata: { + name: 'wksp-test', + }, + parent: { + uri: 'https://raw.githubusercontent.com/test/devfile.yaml', + }, + } as devfileApi.Devfile; + + const newDevfile = prepareDevfile(devfile, factoryId, 'ephemeral', false, { + schemaVersion: '2.2.2', + metadata: { + generateName: 'nodejs', + }, + } as devfileApi.Devfile); + + expect(newDevfile.attributes?.[DEVWORKSPACE_STORAGE_TYPE_ATTR]).toEqual('ephemeral'); + }); + }); + }); }); }); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx index 01135cf94..145bb8b02 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx @@ -45,6 +45,7 @@ import { RootState } from '@/store'; import { selectDefaultDevfile } from '@/store/DevfileRegistries/selectors'; import { selectFactoryResolver } from '@/store/FactoryResolver/selectors'; import { selectDefaultNamespace } from '@/store/InfrastructureNamespaces/selectors'; +import { selectPvcStrategy } from '@/store/ServerConfig'; import { workspacesActionCreators } from '@/store/Workspaces'; import { selectDevWorkspaceWarnings } from '@/store/Workspaces/devWorkspaces/selectors'; import { selectAllWorkspaces } from '@/store/Workspaces/selectors'; @@ -176,7 +177,7 @@ class CreatingStepApplyDevfile extends ProgressStep { private updateCurrentDevfile(devfile: devfileApi.Devfile): void { const { factoryResolver, allWorkspaces, defaultDevfile } = this.props; const { factoryParams } = this.state; - const { factoryId, policiesCreate, sourceUrl, storageType, remotes } = factoryParams; + const { factoryId, policiesCreate, sourceUrl, remotes } = factoryParams; // when using the default devfile instead of a user devfile if (factoryResolver === undefined && isEqual(devfile, defaultDevfile)) { @@ -214,8 +215,16 @@ class CreatingStepApplyDevfile extends ProgressStep { // test the devfile name to decide if we need to append a suffix to is const nameConflict = allWorkspaces.some(w => devfile.metadata.name === w.name); + const storageType = factoryParams.storageType || this.props.preferredStorageType || undefined; const appendSuffix = policiesCreate === 'perclick' || nameConflict; - const updatedDevfile = prepareDevfile(devfile, factoryId, storageType, appendSuffix); + const parentDevfile = factoryResolver?.parentDevfile; + const updatedDevfile = prepareDevfile( + devfile, + factoryId, + storageType, + appendSuffix, + parentDevfile, + ); this.setState({ devfile: updatedDevfile, @@ -465,6 +474,7 @@ const mapStateToProps = (state: RootState) => ({ factoryResolver: selectFactoryResolver(state), defaultDevfile: selectDefaultDevfile(state), devWorkspaceWarnings: selectDevWorkspaceWarnings(state), + preferredStorageType: selectPvcStrategy(state), }); const connector = connect(mapStateToProps, workspacesActionCreators, null, { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/prepareDevfile.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/prepareDevfile.ts index fcb2791b8..6b607cdcb 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/prepareDevfile.ts +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/prepareDevfile.ts @@ -31,6 +31,7 @@ export function prepareDevfile( factoryId: string, storageType: che.WorkspaceStorageType | undefined, appendSuffix: boolean, + parentDevfile?: devfileApi.Devfile | undefined, ): devfileApi.Devfile { const devfile = cloneDeep(_devfile); const attributes = DevfileAdapter.getAttributes(devfile); @@ -60,8 +61,15 @@ export function prepareDevfile( devfile.metadata.name = sanitizeName(devfile.metadata.name); // propagate storage type - if (storageType === 'ephemeral') { - attributes[DEVWORKSPACE_STORAGE_TYPE_ATTR] = 'ephemeral'; + if (storageType) { + attributes[DEVWORKSPACE_STORAGE_TYPE_ATTR] = storageType; + } + if (parentDevfile && attributes[DEVWORKSPACE_STORAGE_TYPE_ATTR]) { + const parentDevfileAttributes = DevfileAdapter.getAttributes(parentDevfile); + if (parentDevfileAttributes[DEVWORKSPACE_STORAGE_TYPE_ATTR]) { + delete attributes[DEVWORKSPACE_STORAGE_TYPE_ATTR]; + console.warn(`Unable to apply ${DEVWORKSPACE_STORAGE_TYPE_ATTR} attribute.`); + } } return devfile; diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx index 87ceda7cc..f7ab74f06 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx @@ -41,6 +41,7 @@ import { selectDevWorkspaceResources } from '@/store/DevfileRegistries/selectors import { factoryResolverActionCreators } from '@/store/FactoryResolver'; import { selectFactoryResolver } from '@/store/FactoryResolver/selectors'; import { selectDefaultNamespace } from '@/store/InfrastructureNamespaces/selectors'; +import { selectPvcStrategy } from '@/store/ServerConfig'; import { workspacesActionCreators } from '@/store/Workspaces'; import { selectDevWorkspaceWarnings } from '@/store/Workspaces/devWorkspaces/selectors'; import { selectAllWorkspaces } from '@/store/Workspaces/selectors'; @@ -174,7 +175,7 @@ class CreatingStepApplyResources extends ProgressStep { protected async runStep(): Promise { const { devWorkspaceResources } = this.props; const { factoryParams, shouldCreate, resources, warning } = this.state; - const { cheEditor, factoryId, sourceUrl, storageType, policiesCreate } = factoryParams; + const { cheEditor, factoryId, sourceUrl, policiesCreate } = factoryParams; if (warning) { const newName = `Warning: ${warning}`; @@ -202,7 +203,7 @@ class CreatingStepApplyResources extends ProgressStep { return true; } - if (shouldCreate === false) { + if (!shouldCreate) { throw new Error('The workspace creation unexpectedly failed.'); } @@ -218,6 +219,7 @@ class CreatingStepApplyResources extends ProgressStep { ); const appendSuffix = policiesCreate === 'perclick' || nameConflict; + const storageType = factoryParams.storageType || this.props.preferredStorageType || undefined; // create a workspace using pre-generated resources const [devWorkspace, devWorkspaceTemplate] = prepareResources( _resources, @@ -297,6 +299,7 @@ const mapStateToProps = (state: RootState) => ({ factoryResolver: selectFactoryResolver(state), devWorkspaceResources: selectDevWorkspaceResources(state), devWorkspaceWarnings: selectDevWorkspaceWarnings(state), + preferredStorageType: selectPvcStrategy(state), }); const connector = connect( diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/prepareResources.ts b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/prepareResources.ts index 8c7b15e98..84ecf6a6d 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/prepareResources.ts +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/prepareResources.ts @@ -45,7 +45,7 @@ export default function prepareResources( } // set storage type attribute - if (storageType === 'ephemeral') { + if (storageType) { if (!devWorkspace.spec.template.attributes) { devWorkspace.spec.template.attributes = {}; } diff --git a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__mocks__/index.tsx b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__mocks__/index.tsx index 23a686e66..ecde98746 100644 --- a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__mocks__/index.tsx +++ b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__mocks__/index.tsx @@ -16,11 +16,24 @@ import { Props } from '@/pages/WorkspaceDetails/OverviewTab/StorageType'; export default class StorageTypeFormGroup extends React.PureComponent { render() { - return ( -
- Mock Storage Type Form - -
- ); + if (this.props.parentStorageType) { + return ( +
+ Mock Storage Type Form + + StorageType: {this.props.storageType} +
+ ); + } else { + return ( +
+ Mock Storage Type Form + + StorageType: {this.props.storageType} +
+ ); + } } } diff --git a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__tests__/index.spec.tsx index 340a9f5e5..6b5f7f817 100644 --- a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/__tests__/index.spec.tsx @@ -102,52 +102,141 @@ describe('StorageTypeFormGroup', () => { }); describe('change storage type modal dialog', () => { - test('show modal', async () => { - renderComponent(store, { readonly }); + describe('with parent', () => { + test('show modal', async () => { + renderComponent(store, { readonly, parentStorageType: 'ephemeral' }); + + const button = screen.queryByRole('button', { name: 'Change Storage Type' }); + expect(button).not.toBeNull(); + + await userEvent.click(button!); + + const modal = screen.queryByRole('dialog', { name: 'Change Storage Type' }); + const buttonSave = screen.queryByRole('button', { name: 'Save' }); + const buttonClose = screen.queryByRole('button', { name: 'Close' }); + const buttonCancel = screen.queryByRole('button', { name: 'Cancel' }); + + expect(modal).not.toBeNull(); + expect(buttonSave).not.toBeNull(); + expect(buttonClose).not.toBeNull(); + expect(buttonCancel).not.toBeNull(); + }); + + test('close modal dialog', async () => { + renderComponent( + store, + { readonly, parentStorageType: 'ephemeral' }, + { isSelectorOpen: true }, + ); + + // modal is opened + expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).not.toBeNull(); + + const buttonClose = screen.getByRole('button', { name: 'Close' }); + + await userEvent.click(buttonClose!); + + // modal is closed + expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).toBeNull(); + }); + + test('check the warning message', () => { + renderComponent( + store, + { readonly, parentStorageType: 'ephemeral' }, + { isSelectorOpen: true }, + ); + + let warninrMessage = screen.queryByText( + 'Note that after changing the storage type you may lose workspace data.', + ); + expect(warninrMessage).toBeNull(); + + warninrMessage = screen.queryByText( + 'Storage type is already defined in parent, you cannot change it.', + ); + expect(warninrMessage).not.toBeNull(); + }); + + test('change storage type disabled', () => { + renderComponent( + store, + { readonly, parentStorageType: 'ephemeral' }, + { isSelectorOpen: true }, + ); + + const radioPerWorkspace = screen.getByRole('radio', { name: 'Per-workspace' }); + const radioPerUser = screen.getByRole('radio', { name: 'Per-user' }); + const radioEphemeral = screen.getByRole('radio', { name: 'Ephemeral' }); + + expect(radioPerWorkspace).toBeDisabled(); + expect(radioPerUser).toBeDisabled(); + expect(radioEphemeral).toBeDisabled(); + }); + }); - const button = screen.queryByRole('button', { name: 'Change Storage Type' }); - expect(button).not.toBeNull(); + describe('without parent', () => { + test('show modal', async () => { + renderComponent(store, { readonly }); - await userEvent.click(button!); + const button = screen.queryByRole('button', { name: 'Change Storage Type' }); + expect(button).not.toBeNull(); - const modal = screen.queryByRole('dialog', { name: 'Change Storage Type' }); - const buttonSave = screen.queryByRole('button', { name: 'Save' }); - const buttonClose = screen.queryByRole('button', { name: 'Close' }); - const buttonCancel = screen.queryByRole('button', { name: 'Cancel' }); + await userEvent.click(button!); - expect(modal).not.toBeNull(); - expect(buttonSave).not.toBeNull(); - expect(buttonClose).not.toBeNull(); - expect(buttonCancel).not.toBeNull(); - }); + const modal = screen.queryByRole('dialog', { name: 'Change Storage Type' }); + const buttonSave = screen.queryByRole('button', { name: 'Save' }); + const buttonClose = screen.queryByRole('button', { name: 'Close' }); + const buttonCancel = screen.queryByRole('button', { name: 'Cancel' }); - test('close modal dialog', async () => { - renderComponent(store, { readonly }, { isSelectorOpen: true }); + expect(modal).not.toBeNull(); + expect(buttonSave).not.toBeNull(); + expect(buttonClose).not.toBeNull(); + expect(buttonCancel).not.toBeNull(); + }); - // modal is opened - expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).not.toBeNull(); + test('close modal dialog', async () => { + renderComponent(store, { readonly }, { isSelectorOpen: true }); - const buttonClose = screen.getByRole('button', { name: 'Close' }); + // modal is opened + expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).not.toBeNull(); - await userEvent.click(buttonClose!); + const buttonClose = screen.getByRole('button', { name: 'Close' }); - // modal is closed - expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).toBeNull(); - }); + await userEvent.click(buttonClose!); - test('change storage type', async () => { - renderComponent(store, { readonly, storageType: 'ephemeral' }, { isSelectorOpen: true }); + // modal is closed + expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).toBeNull(); + }); - const radioPerWorkspace = screen.getByRole('radio', { name: 'Per-workspace' }); - const buttonSave = screen.getByRole('button', { name: 'Save' }); + test('check the warning message', () => { + renderComponent(store, { readonly }, { isSelectorOpen: true }); - await userEvent.click(radioPerWorkspace); - await userEvent.click(buttonSave); + let warninrMessage = screen.queryByText( + 'Note that after changing the storage type you may lose workspace data.', + ); + expect(warninrMessage).not.toBeNull(); - // modal is closed - expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).toBeNull(); + warninrMessage = screen.queryByText( + 'Storage type is already defined in parent, you cannot change it.', + ); + expect(warninrMessage).toBeNull(); + }); + + test('change storage type', async () => { + renderComponent(store, { readonly, storageType: 'ephemeral' }, { isSelectorOpen: true }); + + const radioPerWorkspace = screen.getByRole('radio', { name: 'Per-workspace' }); + const buttonSave = screen.getByRole('button', { name: 'Save' }); + + await userEvent.click(radioPerWorkspace); + await userEvent.click(buttonSave); + + // modal is closed + expect(screen.queryByRole('dialog', { name: 'Change Storage Type' })).toBeNull(); - expect(mockOnSave).toHaveBeenCalledWith('per-workspace'); + expect(mockOnSave).toHaveBeenCalledWith('per-workspace'); + }); }); }); }); @@ -155,7 +244,11 @@ describe('StorageTypeFormGroup', () => { function getComponent( store: Store, - props: { readonly: boolean; storageType?: che.WorkspaceStorageType }, + props: { + readonly: boolean; + storageType?: che.WorkspaceStorageType; + parentStorageType?: che.WorkspaceStorageType; + }, state?: Partial, ) { if (state) { @@ -166,6 +259,7 @@ function getComponent( onSave={mockOnSave} readonly={props.readonly} storageType={props.storageType} + parentStorageType={props.parentStorageType} /> @@ -177,6 +271,7 @@ function getComponent( onSave={mockOnSave} readonly={props.readonly} storageType={props.storageType} + parentStorageType={props.parentStorageType} /> ); diff --git a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/index.tsx b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/index.tsx index 8011e25b6..d9cee0c93 100644 --- a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/index.tsx +++ b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/StorageType/index.tsx @@ -37,6 +37,7 @@ import { selectPvcStrategy } from '@/store/ServerConfig/selectors'; export type Props = MappedProps & { readonly: boolean; storageType?: che.WorkspaceStorageType; + parentStorageType?: che.WorkspaceStorageType; onSave: (storageType: che.WorkspaceStorageType) => void; }; export type State = { @@ -78,7 +79,7 @@ class StorageTypeFormGroup extends React.PureComponent { } public componentDidMount(): void { - const selected = this.props.storageType ? this.props.storageType : this.preferredType; + const selected = this.getSelection(); this.setState({ selected }); } @@ -93,43 +94,20 @@ class StorageTypeFormGroup extends React.PureComponent { } private getExistingTypes(): { - hasAsync: boolean; - hasPersistent: boolean; hasEphemeral: boolean; hasPerUser: boolean; hasPerWorkspace: boolean; } { - const hasAsync = this.storageTypes.some(type => type === 'async'); - const hasPersistent = this.storageTypes.some(type => type === 'persistent'); const hasEphemeral = this.storageTypes.some(type => type === 'ephemeral'); const hasPerUser = this.storageTypes.some(type => type === 'per-user'); const hasPerWorkspace = this.storageTypes.some(type => type === 'per-workspace'); - return { hasAsync, hasPersistent, hasEphemeral, hasPerUser, hasPerWorkspace }; + return { hasEphemeral, hasPerUser, hasPerWorkspace }; } private getInfoModalContent(): React.ReactNode { - const { hasAsync, hasPersistent, hasEphemeral, hasPerUser, hasPerWorkspace } = - this.getExistingTypes(); + const { hasEphemeral, hasPerUser, hasPerWorkspace } = this.getExistingTypes(); - const asyncTypeDescr = hasAsync ? ( - - Experimental feature -
- Asynchronous Storage - is combination of Ephemeral and Persistent storages. It allows for faster I / O and keeps - your changes, it does backup the workspace on stop and restores it on start. -
- ) : ( - '' - ); - const persistentTypeDescr = hasPersistent ? ( - - Persistent Storage is slow I/O but persistent. - - ) : ( - '' - ); const ephemeralTypeDescr = hasEphemeral ? ( Ephemeral Storage allows for faster I/O but may have limited storage and is not @@ -160,11 +138,9 @@ class StorageTypeFormGroup extends React.PureComponent { return ( - {persistentTypeDescr} {perUserTypeDescr} {perWorkspaceTypeDescr} {ephemeralTypeDescr} - {asyncTypeDescr} Open documentation page @@ -175,41 +151,12 @@ class StorageTypeFormGroup extends React.PureComponent { } private getSelectorModal(): React.ReactNode { - const { hasAsync, hasPersistent, hasEphemeral, hasPerUser, hasPerWorkspace } = - this.getExistingTypes(); + const { hasEphemeral, hasPerUser, hasPerWorkspace } = this.getExistingTypes(); + const isDisabled = this.props.parentStorageType !== undefined; const { isSelectorOpen, selected } = this.state; - const originSelection = this.props.storageType ? this.props.storageType : this.preferredType; + const originSelection = this.getSelection(); - const asyncTypeDescr = hasAsync ? ( - - this.setState({ selected: 'async' })} - /> - - ) : ( - '' - ); - const persistentTypeDescr = hasPersistent ? ( - - this.setState({ selected: 'persistent' })} - /> - - ) : ( - '' - ); const ephemeralTypeDescr = hasEphemeral ? ( { id="ephemeral-type-radio" description="Ephemeral Storage allows for faster I/O but may have limited storage and is not persistent." isChecked={selected === 'ephemeral'} + isDisabled={isDisabled} onChange={() => this.setState({ selected: 'ephemeral' })} /> @@ -232,6 +180,7 @@ class StorageTypeFormGroup extends React.PureComponent { id="per-user-type-radio" description="Per-user Storage. One PVC is provisioned per user namespace and used by all workspaces of a given user" isChecked={selected === 'per-user'} + isDisabled={isDisabled} onChange={() => this.setState({ selected: 'per-user' })} /> @@ -246,6 +195,7 @@ class StorageTypeFormGroup extends React.PureComponent { id="per-workspace-type-radio" description="Per-workspace Storage. One PVC is provisioned for each workspace within the namespace." isChecked={selected === 'per-workspace'} + isDisabled={isDisabled} onChange={() => this.setState({ selected: 'per-workspace' })} /> @@ -264,7 +214,7 @@ class StorageTypeFormGroup extends React.PureComponent { + + StorageType: + + +
+ Mock Projects Form +
+ + +`; + +exports[`OverviewTab Without parent screenshot 1`] = ` +
+
+
+ Mock Workspace Name Form +
+
+ Mock Infrastructure Namespace Form +
+
+ Mock Storage Type Form + + + StorageType: +
Mock Projects Form diff --git a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/__tests__/index.spec.tsx index ce55fa48a..282caa46f 100644 --- a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/__tests__/index.spec.tsx @@ -27,36 +27,88 @@ jest.mock('@/pages/WorkspaceDetails/OverviewTab/WorkspaceName'); const { createSnapshot, renderComponent } = getComponentRenderer(getComponent); +const mockFetchParentDevfile = jest.fn(); +jest.mock('@/services/backend-client/parentDevfileApi', () => { + return { + getParentDevfile: async (href: string) => { + return mockFetchParentDevfile(href); + }, + }; +}); + const mockOnSave = jest.fn(); describe('OverviewTab', () => { let workspace: Workspace; - beforeEach(() => { - const devWorkspace = new DevWorkspaceBuilder().withName('my-project').build(); - workspace = constructWorkspace(devWorkspace); - }); - afterEach(() => { jest.clearAllMocks(); }); - test('screenshot', () => { - const snapshot = createSnapshot(workspace); - expect(snapshot.toJSON()).toMatchSnapshot(); + describe('With parent', () => { + // mute the outputs + console.error = jest.fn(); + beforeEach(() => { + const devWorkspace = new DevWorkspaceBuilder() + .withName('my-project') + .withTemplate({ + parent: { + uri: 'https://dummy-registry/devfile.yaml', + }, + }) + .build(); + workspace = constructWorkspace(devWorkspace); + + mockFetchParentDevfile.mockResolvedValueOnce({ + schemaVersion: '2.2.2', + attributes: { + 'controller.devfile.io/storage-type': 'ephemeral', + }, + }); + }); + + test('screenshot', () => { + const snapshot = createSnapshot(workspace); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + test('change storage type', async () => { + renderComponent(workspace); + expect(mockFetchParentDevfile).toHaveBeenCalled(); + expect(mockOnSave).not.toHaveBeenCalled(); + + const changeStorageType = screen.getByRole('button', { name: 'Change storage type' }); + + expect(changeStorageType).not.toBeDisabled(); + }); }); - test('change storage type', async () => { - renderComponent(workspace); - expect(mockOnSave).not.toHaveBeenCalled(); + describe('Without parent', () => { + beforeEach(() => { + const devWorkspace = new DevWorkspaceBuilder().withName('my-project').build(); + workspace = constructWorkspace(devWorkspace); + }); + + test('screenshot', () => { + const snapshot = createSnapshot(workspace); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + test('change storage type', async () => { + renderComponent(workspace); + expect(mockFetchParentDevfile).not.toHaveBeenCalled(); + expect(mockOnSave).not.toHaveBeenCalled(); + + const changeStorageType = screen.getByRole('button', { name: 'Change storage type' }); - const changeStorageType = screen.getByRole('button', { name: 'Change storage type' }); + expect(changeStorageType).not.toBeDisabled(); - await userEvent.click(changeStorageType); + await userEvent.click(changeStorageType); - expect(mockOnSave).toHaveBeenCalledWith( - expect.objectContaining({ storageType: 'per-workspace' }), - ); + expect(mockOnSave).toHaveBeenCalledWith( + expect.objectContaining({ storageType: 'per-workspace' }), + ); + }); }); }); diff --git a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/index.tsx b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/index.tsx index fe773c40c..7dc9b1193 100644 --- a/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/index.tsx +++ b/packages/dashboard-frontend/src/pages/WorkspaceDetails/OverviewTab/index.tsx @@ -18,6 +18,9 @@ import { InfrastructureNamespaceFormGroup } from '@/pages/WorkspaceDetails/Overv import { ProjectsFormGroup } from '@/pages/WorkspaceDetails/OverviewTab/Projects'; import StorageTypeFormGroup from '@/pages/WorkspaceDetails/OverviewTab/StorageType'; import WorkspaceNameFormGroup from '@/pages/WorkspaceDetails/OverviewTab/WorkspaceName'; +import { getParentDevfile } from '@/services/backend-client/parentDevfileApi'; +import { DevfileAdapter } from '@/services/devfile/adapter'; +import { DEVWORKSPACE_STORAGE_TYPE_ATTR } from '@/services/devfileApi/devWorkspace/spec/template'; import { DevWorkspaceStatus } from '@/services/helpers/types'; import { che } from '@/services/models'; import { constructWorkspace, Workspace } from '@/services/workspace-adapter'; @@ -29,6 +32,7 @@ export type Props = { export type State = { storageType: che.WorkspaceStorageType; + parentStorageType: che.WorkspaceStorageType | undefined; }; export class OverviewTab extends React.Component { @@ -39,9 +43,28 @@ export class OverviewTab extends React.Component { this.state = { storageType: workspace.storageType, + parentStorageType: undefined, }; } + async componentDidMount(): Promise { + const { workspace } = this.props; + const parent = workspace.ref.spec.template.parent; + if (!parent) { + return; + } + const parentDevfile = await getParentDevfile({ schemaVersion: '2.2.2', parent }); + if (parentDevfile) { + const parentDevfileAttributes = DevfileAdapter.getAttributes(parentDevfile); + const parentStorageType = parentDevfileAttributes[DEVWORKSPACE_STORAGE_TYPE_ATTR]; + if (parentStorageType) { + this.setState({ + parentStorageType, + }); + } + } + } + public componentDidUpdate(): void { const { storageType } = this.state; const workspace = this.props.workspace; @@ -61,7 +84,7 @@ export class OverviewTab extends React.Component { } public render(): React.ReactElement { - const { storageType } = this.state; + const { storageType, parentStorageType } = this.state; const { workspace } = this.props; const namespace = workspace.namespace; const projects = workspace.projects; @@ -76,6 +99,7 @@ export class OverviewTab extends React.Component { this.handleStorageSave(storageType)} /> diff --git a/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.ts b/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.ts index 955289661..abcc5a72f 100644 --- a/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.ts +++ b/packages/dashboard-frontend/src/services/backend-client/__tests__/factoryApi.spec.ts @@ -16,6 +16,15 @@ import { getFactoryResolver, refreshFactoryOauthToken } from '@/services/backend import devfileApi from '@/services/devfileApi'; import { FactoryResolver } from '@/services/helpers/types'; +const mockFetchParentDevfile = jest.fn(); +jest.mock('@/services/backend-client/parentDevfileApi', () => { + return { + getParentDevfile: async (href: string) => { + return mockFetchParentDevfile(href); + }, + }; +}); + describe('Factory API', () => { const mockPost = mockAxios.post as jest.Mock; @@ -42,6 +51,9 @@ describe('Factory API', () => { }); describe('resolve factory', () => { + beforeEach(() => { + mockFetchParentDevfile.mockResolvedValueOnce(expect.anything()); + }); it('should call "/factory/resolver"', async () => { mockPost.mockResolvedValueOnce({ data: expect.anything(), @@ -54,6 +66,7 @@ describe('Factory API', () => { expect(mockPost).toHaveBeenCalledWith('/api/factory/resolver', { url: 'https://test.azure.com/_git/public-repo?version=GBtest/branch', }); + expect(mockFetchParentDevfile).toHaveBeenCalled(); }); it('should return a factory resolver', async () => { @@ -63,6 +76,7 @@ describe('Factory API', () => { const res = await getFactoryResolver(location, {}); + expect(mockFetchParentDevfile).toHaveBeenCalled(); expect(res).toEqual(factoryResolver); }); }); diff --git a/packages/dashboard-frontend/src/services/backend-client/__tests__/parentDevfileApi.spec.ts b/packages/dashboard-frontend/src/services/backend-client/__tests__/parentDevfileApi.spec.ts new file mode 100644 index 000000000..c9c01a87f --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/__tests__/parentDevfileApi.spec.ts @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2024 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 { dump } from 'js-yaml'; + +import { getParentDevfile } from '@/services/backend-client/parentDevfileApi'; +import devfileApi from '@/services/devfileApi'; + +const mockFetchRemoteData = jest.fn(); +jest.mock('@/services/backend-client/dataResolverApi', () => { + return { + getDataResolver: async (href: string) => { + return mockFetchRemoteData(href); + }, + }; +}); + +const _parentDevfile = { + schemaVersion: '2.2.0', + metadata: { + name: 'nodejs', + }, +}; + +describe('Parent Devfile API', () => { + let devfile: devfileApi.Devfile; + + beforeEach(() => { + devfile = { + schemaVersion: '2.2.0', + metadata: { + name: 'wksp-test', + }, + } as devfileApi.Devfile; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('devfile without a parent', () => { + it('should return undefined', async () => { + const parentDevfile = await getParentDevfile(devfile); + + expect(mockFetchRemoteData).not.toHaveBeenCalled(); + expect(parentDevfile).toBeUndefined(); + }); + }); + describe('devfile with a parent', () => { + describe('with registryUrl', () => { + beforeEach(() => { + devfile.parent = { + id: 'nodejs', + registryUrl: 'https://registry.devfile.io/', + }; + }); + + it('should fetch parent devfile', async () => { + mockFetchRemoteData.mockResolvedValueOnce(dump(_parentDevfile)); + + const parentDevfile = await getParentDevfile(devfile); + + expect(mockFetchRemoteData).toHaveBeenCalledWith( + 'https://registry.devfile.io//devfiles/nodejs', + ); + expect(parentDevfile).toEqual(_parentDevfile); + }); + }); + describe('with uri', () => { + beforeEach(() => { + devfile.parent = { + uri: 'https://raw.githubusercontent.com/test/devfile.yaml', + }; + }); + + it('should fetch parent devfile', async () => { + mockFetchRemoteData.mockResolvedValueOnce(dump(_parentDevfile)); + + const parentDevfile = await getParentDevfile(devfile); + + expect(mockFetchRemoteData).toHaveBeenCalledWith( + 'https://raw.githubusercontent.com/test/devfile.yaml', + ); + expect(parentDevfile).toEqual(_parentDevfile); + }); + + it('should return undefined on error happens', async () => { + const error = new Error('Not Found'); + // mute the outputs + console.error = jest.fn(); + mockFetchRemoteData.mockRejectedValue(error); + + const parentDevfile = await getParentDevfile(devfile); + + expect(mockFetchRemoteData).toHaveBeenCalledWith( + 'https://raw.githubusercontent.com/test/devfile.yaml', + ); + + expect(console.error).toHaveBeenCalledWith('Failed to fetch parent devfile', error); + expect(parentDevfile).toBeUndefined(); + }); + }); + }); +}); diff --git a/packages/dashboard-frontend/src/services/backend-client/factoryApi.ts b/packages/dashboard-frontend/src/services/backend-client/factoryApi.ts index 48883a967..1fc317fb0 100644 --- a/packages/dashboard-frontend/src/services/backend-client/factoryApi.ts +++ b/packages/dashboard-frontend/src/services/backend-client/factoryApi.ts @@ -13,6 +13,7 @@ import axios from 'axios'; import { cheServerPrefix } from '@/services/backend-client/const'; +import { getParentDevfile } from '@/services/backend-client/parentDevfileApi'; import { FactoryResolver } from '@/services/helpers/types'; export async function getFactoryResolver( @@ -32,7 +33,13 @@ export async function getFactoryResolver( Object.assign({}, overrideParams, { url }), ); - return response.data; + const factoryResolver: FactoryResolver = response.data; + + if (factoryResolver) { + factoryResolver.parentDevfile = await getParentDevfile(factoryResolver.devfile); + } + + return factoryResolver; } export async function refreshFactoryOauthToken(url: string): Promise { diff --git a/packages/dashboard-frontend/src/services/backend-client/parentDevfileApi.ts b/packages/dashboard-frontend/src/services/backend-client/parentDevfileApi.ts new file mode 100644 index 000000000..a5642387d --- /dev/null +++ b/packages/dashboard-frontend/src/services/backend-client/parentDevfileApi.ts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2024 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 { load } from 'js-yaml'; + +import { getDataResolver } from '@/services/backend-client/dataResolverApi'; +import devfileApi, { isDevfileV2 } from '@/services/devfileApi'; + +export async function getParentDevfile(devfile: unknown): Promise { + if (isDevfileV2(devfile) && devfile.parent) { + let uri: string | undefined; + if (devfile.parent.uri) { + uri = devfile.parent.uri; + } else if (devfile.parent.id && devfile.parent.registryUrl) { + uri = `${devfile.parent.registryUrl}/devfiles/${devfile.parent.id}`; + } + if (uri) { + try { + const data = await getDataResolver(uri); + if (typeof data === 'string') { + const parentDevfile = load(data); + if (isDevfileV2(parentDevfile)) { + return parentDevfile; + } + } + } catch (e) { + console.error('Failed to fetch parent devfile', e); + } + } + } + + return undefined; +} diff --git a/packages/dashboard-frontend/src/services/helpers/types.ts b/packages/dashboard-frontend/src/services/helpers/types.ts index 9fd39affe..667bd463e 100644 --- a/packages/dashboard-frontend/src/services/helpers/types.ts +++ b/packages/dashboard-frontend/src/services/helpers/types.ts @@ -37,6 +37,7 @@ export interface AlertItem { // - any other - devfile is found in repository as filename from the value export interface FactoryResolver extends Omit { devfile?: che.api.workspace.devfile.Devfile | devfileApi.Devfile; + parentDevfile?: devfileApi.Devfile; location?: string; scm_info?: FactoryResolverScmInfo; } diff --git a/packages/dashboard-frontend/src/services/storageTypes.ts b/packages/dashboard-frontend/src/services/storageTypes.ts index 93fabe0f8..f40cb5d46 100644 --- a/packages/dashboard-frontend/src/services/storageTypes.ts +++ b/packages/dashboard-frontend/src/services/storageTypes.ts @@ -31,15 +31,3 @@ export function toTitle(type: che.WorkspaceStorageType): string { export function getAvailable(): che.WorkspaceStorageType[] { return ['per-user', 'per-workspace', 'ephemeral']; } - -export function attributesToType( - attrs: che.WorkspaceDevfileAttributes | undefined, -): che.WorkspaceStorageType { - if (attrs?.persistVolumes === 'false') { - if (attrs.asyncPersist === 'true') { - return 'async'; - } - return 'ephemeral'; - } - return 'persistent'; -} diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts index c72cdeaf8..3c770e483 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/DevWorkspaceDefaultPluginsHandler.ts @@ -191,12 +191,14 @@ export class DevWorkspaceDefaultPluginsHandler { return true; } - private async patchWorkspaceComponents(workspace: devfileApi.DevWorkspace) { + private async patchWorkspaceComponents( + workspace: devfileApi.DevWorkspace, + ): Promise<{ headers: DwApi.Headers; devWorkspace: devfileApi.DevWorkspace }> { const patch: api.IPatch[] = [ { op: 'replace', path: '/spec/template/components', - value: workspace.spec.template.components, + value: workspace.spec.template.components || [], }, ]; return DwApi.patchWorkspace(workspace.metadata.namespace, workspace.metadata.name, patch); diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.managePvcStrategy.spec.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.managePvcStrategy.spec.ts index 8dcc66372..587b5edaa 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.managePvcStrategy.spec.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/__tests__/devWorkspaceClient.managePvcStrategy.spec.ts @@ -89,34 +89,6 @@ describe('DevWorkspace client, managePvcStrategy', () => { ]); }); - it('should update devWorkspace storage type', async () => { - const devWorkspace = devWorkspaceBuilder - .withSpec({ - template: { - attributes: { - [DEVWORKSPACE_CONFIG_ATTR]: 'custom-config', - }, - }, - }) - .build(); - const config = { - defaults: { - pvcStrategy: 'custom-pvc-strategy', - }, - cheNamespace: namespace, - } as api.IServerConfig; - - await client.managePvcStrategy(devWorkspace, config); - - expect(spyPatchWorkspace).toHaveBeenCalledWith(namespace, name, [ - { - op: 'add', - path: '/spec/template/attributes/controller.devfile.io~1storage-type', - value: 'custom-pvc-strategy', - }, - ]); - }); - it('should update devWorkspace template components', async () => { const devWorkspace = devWorkspaceBuilder .withSpec({ diff --git a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts index 51b4fce07..ba951d65e 100644 --- a/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts +++ b/packages/dashboard-frontend/src/services/workspace-client/devworkspace/devWorkspaceClient.ts @@ -32,7 +32,6 @@ import { import { DEVWORKSPACE_CONFIG_ATTR, DEVWORKSPACE_CONTAINER_BUILD_ATTR, - DEVWORKSPACE_STORAGE_TYPE_ATTR, } from '@/services/devfileApi/devWorkspace/spec/template'; import { delay } from '@/services/helpers/delay'; import { isWebTerminal } from '@/services/helpers/devworkspace'; @@ -212,7 +211,7 @@ export class DevWorkspaceClient { { op: 'replace', path: '/spec/template/components', - value: devWorkspace.spec.template.components, + value: devWorkspace.spec.template.components || [], }, ]); } @@ -460,26 +459,6 @@ export class DevWorkspaceClient { } } - const currentPvcStrategy = config.defaults.pvcStrategy; - if (currentPvcStrategy) { - const devworkspaceStorageTypePath = `/spec/template/attributes/${this.escape( - DEVWORKSPACE_STORAGE_TYPE_ATTR, - )}`; - - if (attributes) { - if (!attributes[DEVWORKSPACE_STORAGE_TYPE_ATTR]) { - patch.push({ op: 'add', path: devworkspaceStorageTypePath, value: currentPvcStrategy }); - } - } else { - patch.push({ - op: 'add', - path: '/spec/template/attributes', - value: { [DEVWORKSPACE_STORAGE_TYPE_ATTR]: currentPvcStrategy }, - }); - attributes = {}; - } - } - const openVSXURL = config.pluginRegistry?.openVSXURL || ''; const components = cloneDeep(workspace.spec.template.components); if (components) { diff --git a/scripts/yarn/old_version/.deps/EXCLUDED/prod.md b/scripts/yarn/old_version/.deps/EXCLUDED/prod.md index b495ecdd7..d48068740 100644 --- a/scripts/yarn/old_version/.deps/EXCLUDED/prod.md +++ b/scripts/yarn/old_version/.deps/EXCLUDED/prod.md @@ -10,6 +10,7 @@ This file lists dependencies that do not need CQs or auto-detection does not wor | `@patternfly/react-icons@4.93.7` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-icons/4.93.7) | | `@patternfly/react-table@4.113.6` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/@patternfly/react-table/4.113.6) | | `blueimp-md5@2.19.0` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/blueimp-md5/2.19.0) | +| `asn1.js@4.10.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/asn1.js/4.10.1) | | `codemirror@5.65.18` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/codemirror/5.65.18) | | `cookie-signature@1.2.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/cookie-signature/1.2.1) | | `elliptic@6.6.1` | [clearlydefined](https://clearlydefined.io/definitions/npm/npmjs/-/elliptic/6.6.1) |