diff --git a/src/components/Dialog/CloneDialog/CloneDialog.js b/src/components/Dialog/CloneDialog/CloneDialog.js index 690a476d1..dd4c8fcc1 100644 --- a/src/components/Dialog/CloneDialog/CloneDialog.js +++ b/src/components/Dialog/CloneDialog/CloneDialog.js @@ -11,11 +11,13 @@ import { START_VM_KEY, VIRTUAL_MACHINES_KEY, } from '../../Wizard/CreateVmWizard/constants'; -import { getDescription, getNamespace, getName, isVmRunning } from '../../../selectors'; +import { getDescription, getNamespace, getName, isVmRunning, getVolumes } from '../../../selectors'; import { validateVmName, vmAlreadyExists } from '../../../utils/validations'; import { settingsValue } from '../../../k8s/selectors'; import { clone } from '../../../k8s/clone'; +import { getResource } from '../../../utils/utils'; import { Loading } from '../../Loading'; +import { DataVolumeModel, NamespaceModel, PersistentVolumeClaimModel, VirtualMachineModel } from '../../../models'; const getFormFields = (namespaces, vm, persistentVolumeClaims, dataVolumes, virtualMachines) => ({ [NAME_KEY]: { @@ -54,11 +56,18 @@ const getFormFields = (namespaces, vm, persistentVolumeClaims, dataVolumes, virt }, }); +const getLoadedData = (result, defaultValue) => + result && result.loaded && !result.loadError ? result.data : defaultValue; + export class CloneDialog extends React.Component { constructor(props) { super(props); const initVmName = `${getName(props.vm)}-clone`; - const initVmNameValidation = vmAlreadyExists(initVmName, getNamespace(props.vm), props.virtualMachines); + const initVmNameValidation = vmAlreadyExists( + initVmName, + getNamespace(props.vm), + getLoadedData(props.virtualMachines, []) + ); if (initVmNameValidation && initVmNameValidation.message) { initVmNameValidation.message = `Name ${initVmNameValidation.message}`; } @@ -114,10 +123,10 @@ export class CloneDialog extends React.Component { settingsValue(this.state, NAMESPACE_KEY), settingsValue(this.state, DESCRIPTION_KEY), settingsValue(this.state, START_VM_KEY), - this.props.persistentVolumeClaims, - this.props.dataVolumes + getLoadedData(this.props.persistentVolumeClaims, []), + getLoadedData(this.props.dataVolumes, []) ) - .then(() => this.props.onClose()) + .then(() => this.props.close()) .catch(error => this.setState({ cloning: false, @@ -129,22 +138,43 @@ export class CloneDialog extends React.Component { onErrorDismissed = () => this.setState({ error: null }); render() { + const { + LoadingComponent, + vm, + virtualMachines, + namespaces, + persistentVolumeClaims, + dataVolumes, + requestsDatavolumes, + requestsPVCs, + loadError, + } = this.props; + const formFields = getFormFields( - this.props.namespaces, - this.props.vm, - this.props.persistentVolumeClaims, - this.props.dataVolumes, - this.props.virtualMachines + getLoadedData(namespaces, []), + vm, + getLoadedData(persistentVolumeClaims, []), + getLoadedData(dataVolumes, []), + getLoadedData(virtualMachines, []) ); - const { LoadingComponent } = this.props; + + const dataVolumesValid = requestsDatavolumes ? dataVolumes && dataVolumes.loaded && !dataVolumes.loadError : true; + const pvcsValid = requestsPVCs + ? persistentVolumeClaims && persistentVolumeClaims.loaded && !persistentVolumeClaims.loadError + : true; + const footer = this.state.cloning ? ( ) : ( - - @@ -152,13 +182,14 @@ export class CloneDialog extends React.Component { return ( - Clone Virtual Machine
+ {loadError && {loadError.message}} {isVmRunning(this.props.vm) && ( The VM {getName(this.props.vm)} is still running. It will be powered off while cloning. @@ -182,16 +213,83 @@ export class CloneDialog extends React.Component { CloneDialog.propTypes = { vm: PropTypes.object.isRequired, - namespaces: PropTypes.array.isRequired, - persistentVolumeClaims: PropTypes.array.isRequired, - LoadingComponent: PropTypes.func, - virtualMachines: PropTypes.array.isRequired, k8sCreate: PropTypes.func.isRequired, k8sPatch: PropTypes.func.isRequired, - dataVolumes: PropTypes.array.isRequired, - onClose: PropTypes.func.isRequired, + namespaces: PropTypes.object, + persistentVolumeClaims: PropTypes.object, + virtualMachines: PropTypes.object, + dataVolumes: PropTypes.object, + requestsDatavolumes: PropTypes.bool, + requestsPVCs: PropTypes.bool, + loadError: PropTypes.object, + LoadingComponent: PropTypes.func, + close: PropTypes.func.isRequired, + cancel: PropTypes.func.isRequired, }; CloneDialog.defaultProps = { LoadingComponent: Loading, + namespaces: null, + persistentVolumeClaims: null, + virtualMachines: null, + dataVolumes: null, + requestsDatavolumes: false, + requestsPVCs: false, + loadError: null, +}; + +// eslint-disable-next-line react/no-multi-comp +export class CloneVMModalFirehose extends React.Component { + constructor(props) { + super(props); + this.state = { + namespace: getNamespace(props.vm), + }; + } + + render() { + const { vm, Firehose } = this.props; + const { namespace } = this.state; + + const requestsDatavolumes = !!getVolumes(vm).find(v => v.dataVolume && v.dataVolume.name); + const requestsPVCs = !!getVolumes(vm).find(v => v.persistentVolumeClaim && v.persistentVolumeClaim.claimName); + + const resources = [ + getResource(NamespaceModel, { prop: 'namespaces' }), + getResource(VirtualMachineModel, { namespace, prop: 'virtualMachines' }), + ]; + + if (requestsPVCs) { + resources.push(getResource(PersistentVolumeClaimModel, { namespace, prop: 'persistentVolumeClaims' })); + } + + if (requestsDatavolumes) { + resources.push(getResource(DataVolumeModel, { namespace, prop: 'dataVolumes' })); + } + + return ( + + this.setState({ namespace: n })} + requestsDatavolumes={requestsDatavolumes} + requestsPVCs={requestsPVCs} + /> + + ); + } +} + +CloneVMModalFirehose.propTypes = { + vm: PropTypes.object.isRequired, + Firehose: PropTypes.object.isRequired, + LoadingComponent: PropTypes.func, + k8sCreate: PropTypes.func.isRequired, + k8sPatch: PropTypes.func.isRequired, + close: PropTypes.func.isRequired, + cancel: PropTypes.func.isRequired, +}; + +CloneVMModalFirehose.defaultProps = { + LoadingComponent: Loading, }; diff --git a/src/components/Dialog/CloneDialog/fixtures/CloneDialog.fixture.js b/src/components/Dialog/CloneDialog/fixtures/CloneDialog.fixture.js index 7ccea4433..b4c37239d 100644 --- a/src/components/Dialog/CloneDialog/fixtures/CloneDialog.fixture.js +++ b/src/components/Dialog/CloneDialog/fixtures/CloneDialog.fixture.js @@ -10,13 +10,13 @@ export default { component: CloneDialog, props: { vm: cloudInitTestVm, - namespaces, - persistentVolumeClaims: [], - dataVolumes: [], + namespaces: { data: namespaces, loaded: true }, + persistentVolumeClaims: { data: [], loaded: true }, + dataVolumes: { data: [], loaded: true }, k8sCreate, k8sPatch, units, onClose: noop, - virtualMachines: [], + virtualMachines: { data: [], loaded: true }, }, }; diff --git a/src/components/Dialog/CloneDialog/index.js b/src/components/Dialog/CloneDialog/index.js index fd3ae997e..6c86d5230 100644 --- a/src/components/Dialog/CloneDialog/index.js +++ b/src/components/Dialog/CloneDialog/index.js @@ -1 +1 @@ -export { CloneDialog } from './CloneDialog'; +export { CloneDialog, CloneVMModalFirehose } from './CloneDialog'; diff --git a/src/components/Dialog/CloneDialog/tests/CloneDialog.test.js b/src/components/Dialog/CloneDialog/tests/CloneDialog.test.js index 03345a5d3..5229a3e06 100644 --- a/src/components/Dialog/CloneDialog/tests/CloneDialog.test.js +++ b/src/components/Dialog/CloneDialog/tests/CloneDialog.test.js @@ -19,7 +19,13 @@ import { flushPromises, setCheckbox, setInput, clickButton, selectDropdownItem } jest.mock('../../../../k8s/clone'); const testCloneDialog = (vms = [], onClose = noop, vm = cloudInitTestVm) => ( - + ); const setVmName = (component, value) => setInput(component.find('#vm-name').find(Text), value); diff --git a/src/components/Dialog/CloneDialog/tests/__snapshots__/CloneDialog.test.js.snap b/src/components/Dialog/CloneDialog/tests/__snapshots__/CloneDialog.test.js.snap index 216974d04..571ab3f8c 100644 --- a/src/components/Dialog/CloneDialog/tests/__snapshots__/CloneDialog.test.js.snap +++ b/src/components/Dialog/CloneDialog/tests/__snapshots__/CloneDialog.test.js.snap @@ -39,7 +39,6 @@ exports[` renders correctly 1`] = ` bsStyle="default" className="close" disabled={false} - onClick={[Function]} > renders correctly 1`] = ` bsStyle="default" className="btn-cancel" disabled={false} - onClick={[Function]} > Cancel