diff --git a/frontend/src/__mocks__/mockPVCK8sResource.ts b/frontend/src/__mocks__/mockPVCK8sResource.ts index aeb905b018..9092688c36 100644 --- a/frontend/src/__mocks__/mockPVCK8sResource.ts +++ b/frontend/src/__mocks__/mockPVCK8sResource.ts @@ -8,6 +8,7 @@ type MockResourceConfigType = { storageClassName?: string; displayName?: string; uid?: string; + status?: PersistentVolumeClaimKind['status']; }; export const mockPVCK8sResource = ({ @@ -17,6 +18,13 @@ export const mockPVCK8sResource = ({ storageClassName = 'gp3', displayName = 'Test Storage', uid = genUID('pvc'), + status = { + phase: 'Bound', + accessModes: ['ReadWriteOnce'], + capacity: { + storage, + }, + }, }: MockResourceConfigType): PersistentVolumeClaimKind => ({ kind: 'PersistentVolumeClaim', apiVersion: 'v1', @@ -43,11 +51,5 @@ export const mockPVCK8sResource = ({ storageClassName, volumeMode: 'Filesystem', }, - status: { - phase: 'Bound', - accessModes: ['ReadWriteOnce'], - capacity: { - storage, - }, - }, + status, }); diff --git a/frontend/src/__tests__/cypress/cypress/pages/clusterStorage.ts b/frontend/src/__tests__/cypress/cypress/pages/clusterStorage.ts index a8bbdcfa9c..9e8139e7ab 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/clusterStorage.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/clusterStorage.ts @@ -84,7 +84,7 @@ class ClusterStorageModal extends Modal { shouldHavePVSizeSelectValue(name: string) { this.findPVSizeSelectButton().contains(name).should('exist'); - return this; + return this.findPVSizeSelectButton(); } private findPVSizeField() { @@ -95,8 +95,12 @@ class ClusterStorageModal extends Modal { return this.findPVSizeField().findByRole('button', { name: 'Minus' }); } - findPersistentStorageWarning() { - return this.find().findByTestId('persistent-storage-warning'); + findPersistentStorageWarningCanNotEdit() { + return this.find().findByTestId('persistent-storage-warning-can-not-edit'); + } + + findPersistentStorageWarningCanOnlyIncrease() { + return this.find().findByTestId('persistent-storage-warning-can-only-increase'); } findPVSizeInput() { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/clusterStorage.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/clusterStorage.cy.ts index af794ec5ff..df0b2e7031 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/clusterStorage.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/clusterStorage.cy.ts @@ -51,6 +51,11 @@ const initInterceptors = ({ isEmpty = false, storageClassName }: HandlersProps) : [ mockPVCK8sResource({ uid: 'test-id', storageClassName }), mockPVCK8sResource({ displayName: 'Another Cluster Storage', storageClassName }), + mockPVCK8sResource({ + displayName: 'Unbound storage', + storageClassName, + status: { phase: 'Pending' }, + }), ], ), ); @@ -412,7 +417,7 @@ describe('ClusterStorage', () => { updateClusterStorageModal.findNameInput().should('have.value', 'Test Storage'); updateClusterStorageModal.findPVSizeInput().should('have.value', '5'); updateClusterStorageModal.shouldHavePVSizeSelectValue('GiB'); - updateClusterStorageModal.findPersistentStorageWarning().should('exist'); + updateClusterStorageModal.findPersistentStorageWarningCanOnlyIncrease().should('exist'); updateClusterStorageModal.findSubmitButton().should('be.enabled'); updateClusterStorageModal.findNameInput().fill('test-updated'); @@ -446,6 +451,17 @@ describe('ClusterStorage', () => { }); }); + it('should disable size field when editing unbound storage', () => { + initInterceptors({}); + clusterStorage.visit('test-project'); + const clusterStorageRow = clusterStorage.getClusterStorageRow('Unbound storage'); + clusterStorageRow.findKebabAction('Edit storage').click(); + updateClusterStorageModal.findNameInput().should('have.value', 'Unbound storage'); + updateClusterStorageModal.findPVSizeInput().should('have.value', '5').should('be.disabled'); + updateClusterStorageModal.shouldHavePVSizeSelectValue('GiB').should('be.disabled'); + updateClusterStorageModal.findPersistentStorageWarningCanNotEdit().should('exist'); + }); + it('Delete cluster storage', () => { initInterceptors({}); clusterStorage.visit('test-project'); diff --git a/frontend/src/components/ValueUnitField.tsx b/frontend/src/components/ValueUnitField.tsx index fb7e783b0d..c19645fe4f 100644 --- a/frontend/src/components/ValueUnitField.tsx +++ b/frontend/src/components/ValueUnitField.tsx @@ -30,6 +30,7 @@ type ValueUnitFieldProps = { value: ValueUnitString; validated?: 'default' | 'error' | 'warning' | 'success' | ValidatedOptions | undefined; menuAppendTo?: HTMLElement; + isDisabled?: boolean; }; const ValueUnitField: React.FC = ({ @@ -41,6 +42,7 @@ const ValueUnitField: React.FC = ({ menuAppendTo, value: fullValue, validated, + isDisabled, }) => { const [open, setOpen] = React.useState(false); const [currentValue, currentUnitOption] = splitValueUnit(fullValue, options); @@ -64,6 +66,7 @@ const ValueUnitField: React.FC = ({ onChange={(value) => { onChange(`${value || minAsNumber}${currentUnitOption.unit}`); }} + isDisabled={isDisabled} /> @@ -80,6 +83,7 @@ const ValueUnitField: React.FC = ({ setOpen(!open); }} isExpanded={open} + isDisabled={isDisabled} > {currentUnitOption.name} diff --git a/frontend/src/pages/projects/components/PVSizeField.tsx b/frontend/src/pages/projects/components/PVSizeField.tsx index 34c348fb2b..62ca708697 100644 --- a/frontend/src/pages/projects/components/PVSizeField.tsx +++ b/frontend/src/pages/projects/components/PVSizeField.tsx @@ -3,13 +3,14 @@ import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternf import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import ValueUnitField from '~/components/ValueUnitField'; import { MEMORY_UNITS_FOR_SELECTION, UnitOption } from '~/utilities/valueUnits'; +import { PersistentVolumeClaimKind } from '~/k8sTypes'; type PVSizeFieldProps = { fieldID: string; size: string; menuAppendTo?: HTMLElement; setSize: (size: string) => void; - currentSize?: string; + currentStatus?: PersistentVolumeClaimKind['status']; label?: string; options?: UnitOption[]; }; @@ -19,35 +20,55 @@ const PVSizeField: React.FC = ({ size, menuAppendTo, setSize, - currentSize, + currentStatus, label = 'Persistent storage size', options = MEMORY_UNITS_FOR_SELECTION, -}) => ( - - setSize(value)} - menuAppendTo={menuAppendTo} - onChange={(value) => setSize(value)} - validated={currentSize ? 'warning' : 'default'} - options={options} - value={size} - /> - {currentSize && ( - - - } - > - Storage size can only be increased. If you do so, the workbench will restart and be - unavailable for a period of time that is usually proportional to the size change. - - - - )} - -); +}) => { + const currentSize = currentStatus?.capacity?.storage; + const isUnbound = currentStatus && currentStatus.phase !== 'Bound'; + + return ( + + setSize(value)} + menuAppendTo={menuAppendTo} + onChange={(value) => setSize(value)} + validated={currentSize ? 'warning' : 'default'} + options={options} + value={size} + isDisabled={isUnbound} + /> + + {(currentSize || isUnbound) && ( + + + {isUnbound && ( + } + > + To edit the size, you must first attach this cluster storage to a workbench, then + start the workbench. If the workbench is already running, it will restart + automatically. + + )} + {currentSize && ( + } + > + Storage size can only be increased. If you do so, the workbench will restart and be + unavailable for a period of time that is usually proportional to the size change. + + )} + + + )} + + ); +}; export default PVSizeField; diff --git a/frontend/src/pages/projects/screens/detail/storage/BaseStorageModal.tsx b/frontend/src/pages/projects/screens/detail/storage/BaseStorageModal.tsx index ffa5ca397a..e3fb46026f 100644 --- a/frontend/src/pages/projects/screens/detail/storage/BaseStorageModal.tsx +++ b/frontend/src/pages/projects/screens/detail/storage/BaseStorageModal.tsx @@ -103,7 +103,7 @@ const BaseStorageModal: React.FC = ({ diff --git a/frontend/src/pages/projects/screens/detail/storage/ManageStorageModal.tsx b/frontend/src/pages/projects/screens/detail/storage/ManageStorageModal.tsx index c0f8475dd2..cd57ea5533 100644 --- a/frontend/src/pages/projects/screens/detail/storage/ManageStorageModal.tsx +++ b/frontend/src/pages/projects/screens/detail/storage/ManageStorageModal.tsx @@ -173,7 +173,7 @@ const ManageStorageModal: React.FC = ({ existingData, onCl diff --git a/frontend/src/pages/projects/screens/spawner/storage/CreateNewStorageSection.tsx b/frontend/src/pages/projects/screens/spawner/storage/CreateNewStorageSection.tsx index ca59d342be..fdab0cab31 100644 --- a/frontend/src/pages/projects/screens/spawner/storage/CreateNewStorageSection.tsx +++ b/frontend/src/pages/projects/screens/spawner/storage/CreateNewStorageSection.tsx @@ -4,12 +4,13 @@ import { CreatingStorageObject, UpdateObjectAtPropAndValue } from '~/pages/proje import PVSizeField from '~/pages/projects/components/PVSizeField'; import NameDescriptionField from '~/concepts/k8s/NameDescriptionField'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; +import { PersistentVolumeClaimKind } from '~/k8sTypes'; import StorageClassSelect from './StorageClassSelect'; type CreateNewStorageSectionProps = { data: D; setData: UpdateObjectAtPropAndValue; - currentSize?: string; + currentStatus?: PersistentVolumeClaimKind['status']; autoFocusName?: boolean; menuAppendTo?: HTMLElement; disableStorageClassSelect?: boolean; @@ -18,7 +19,7 @@ type CreateNewStorageSectionProps = { const CreateNewStorageSection = ({ data, setData, - currentSize, + currentStatus, menuAppendTo, autoFocusName, disableStorageClassSelect, @@ -50,7 +51,7 @@ const CreateNewStorageSection = ({ setData('size', size)} />