Skip to content
This repository has been archived by the owner on Apr 28, 2020. It is now read-only.

CloneDialog: support non-admin users #521

Merged
merged 2 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 118 additions & 20 deletions src/components/Dialog/CloneDialog/CloneDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: {
Expand Down Expand Up @@ -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}`;
}
Expand Down Expand Up @@ -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,
Expand All @@ -129,36 +138,58 @@ 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 ? (
<LoadingComponent />
) : (
<React.Fragment>
<Button bsStyle="default" className="btn-cancel" onClick={this.props.onClose}>
<Button bsStyle="default" className="btn-cancel" onClick={this.props.cancel}>
Cancel
</Button>
<Button bsStyle="primary" onClick={this.cloneVm} disabled={!this.state.valid}>
<Button
bsStyle="primary"
onClick={this.cloneVm}
disabled={!(this.state.valid && dataVolumesValid && pvcsValid)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should handle error state more explicitly - the user should be informed about transition from loading to error state.
I believe rendering info about just the most serious issue (as filtered by Firehose) is enough.

>
Clone Virtual Machine
</Button>
</React.Fragment>
);
return (
<Modal show dialogClassName="kubevirt-clone-dialog">
<Modal.Header>
<Button className="close" onClick={this.props.onClose}>
<Button className="close" onClick={this.props.close}>
<Icon type="pf" name="close" />
</Button>
<Modal.Title>Clone Virtual Machine</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="kubevirt-clone-dialog__content">
{loadError && <Alert type="error">{loadError.message}</Alert>}
{isVmRunning(this.props.vm) && (
<Alert type="warning">
The VM {getName(this.props.vm)} is still running. It will be powered off while cloning.
Expand All @@ -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 (
<Firehose resources={resources}>
<CloneDialog
{...this.props}
onNamespaceChanged={n => this.setState({ namespace: n })}
requestsDatavolumes={requestsDatavolumes}
requestsPVCs={requestsPVCs}
/>
</Firehose>
);
}
}

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,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
},
};
2 changes: 1 addition & 1 deletion src/components/Dialog/CloneDialog/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { CloneDialog } from './CloneDialog';
export { CloneDialog, CloneVMModalFirehose } from './CloneDialog';
8 changes: 7 additions & 1 deletion src/components/Dialog/CloneDialog/tests/CloneDialog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import { flushPromises, setCheckbox, setInput, clickButton, selectDropdownItem }
jest.mock('../../../../k8s/clone');

const testCloneDialog = (vms = [], onClose = noop, vm = cloudInitTestVm) => (
<CloneDialog {...CloneDialogFixture.props} virtualMachines={vms} onClose={onClose} vm={vm} k8sCreate={k8sCreate} />
<CloneDialog
{...CloneDialogFixture.props}
virtualMachines={{ data: vms, loaded: true }}
onClose={onClose}
vm={vm}
k8sCreate={k8sCreate}
/>
);

const setVmName = (component, value) => setInput(component.find('#vm-name').find(Text), value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ exports[`<CloneDialog /> renders correctly 1`] = `
bsStyle="default"
className="close"
disabled={false}
onClick={[Function]}
>
<Icon
name="close"
Expand Down Expand Up @@ -138,7 +137,6 @@ exports[`<CloneDialog /> renders correctly 1`] = `
bsStyle="default"
className="btn-cancel"
disabled={false}
onClick={[Function]}
>
Cancel
</Button>
Expand Down