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

Commit

Permalink
Add edit functionality to Vm Details overview
Browse files Browse the repository at this point in the history
  • Loading branch information
rawagner committed Jan 4, 2019
1 parent c6fc293 commit d592fba
Show file tree
Hide file tree
Showing 37 changed files with 1,441 additions and 222 deletions.
49 changes: 49 additions & 0 deletions src/components/Details/Description/Description.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';

import { getDescription } from '../../../utils';
import { InlineEdit } from '../../InlineEdit';
import { Loading } from '../../Loading/Loading';

const descriptionFormFields = {
description: {
id: 'description-textarea',
type: 'textarea',
},
};

export const Description = ({ formValues, vm, editing, updating, LoadingComponent, onFormChange }) => {
if (!(formValues && formValues.description)) {
onFormChange({ value: getDescription(vm) }, 'description', true);
}

return (
<InlineEdit
formFields={descriptionFormFields}
fieldsValues={formValues}
editing={editing}
updating={updating}
LoadingComponent={LoadingComponent}
onFormChange={(newValue, key) => onFormChange(newValue, key, true)}
>
{getDescription(vm) || 'VM has no description'}
</InlineEdit>
);
};

Description.propTypes = {
formValues: PropTypes.object,
vm: PropTypes.object.isRequired,
editing: PropTypes.bool,
updating: PropTypes.bool,
LoadingComponent: PropTypes.func,
onFormChange: PropTypes.func,
};

Description.defaultProps = {
formValues: undefined,
editing: false,
updating: false,
LoadingComponent: Loading,
onFormChange: () => {},
};
46 changes: 46 additions & 0 deletions src/components/Details/Description/fixtures/Description.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Description } from '..';
import { cloudInitTestVm } from '../../../../k8s/mock_vm/cloudInitTestVm.mock';

export default [
{
component: Description,
name: 'Description',
props: {
vm: cloudInitTestVm,
onFormChange: () => {},
formValues: {
description: {
value: 'vm description',
},
},
},
},
{
component: Description,
name: 'Description edit',
props: {
vm: cloudInitTestVm,
editing: true,
onFormChange: () => {},
formValues: {
description: {
value: 'vm description',
},
},
},
},
{
component: Description,
name: 'Description updating',
props: {
vm: cloudInitTestVm,
updating: true,
onFormChange: () => {},
formValues: {
description: {
value: 'vm description',
},
},
},
},
];
1 change: 1 addition & 0 deletions src/components/Details/Description/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Description';
14 changes: 14 additions & 0 deletions src/components/Details/Description/tests/Description.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';

import { Description } from '..';
import { default as DescriptionFixture } from '../fixtures/Description.fixture';

const testDescription = () => <Description {...DescriptionFixture[0].props} />;

describe('<Description />', () => {
it('renders correctly', () => {
const component = shallow(testDescription());
expect(component).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Description /> renders correctly 1`] = `
<InlineEdit
LoadingComponent={[Function]}
editing={false}
fieldsValues={
Object {
"description": Object {
"value": "vm description",
},
}
}
formFields={
Object {
"description": Object {
"id": "description-textarea",
"type": "textarea",
},
}
}
onFormChange={[Function]}
updating={false}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lacus nibh, convallis vel nunc id,tempus vulputate augue. Proin eget nisl vel ante tincidunt accumsan vel at elit. Fusce eget tincidunt sem. Fusce cursus orci vitae nisl hendrerit mollis. Nullam at nulla ut ipsum malesuada laoreet a sit amet est.
</InlineEdit>
`;
150 changes: 150 additions & 0 deletions src/components/Details/Flavor/Flavor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React from 'react';
import PropTypes from 'prop-types';

import { getCpu, getFlavor, getMemory, getVmTemplate } from '../../../utils';
import { InlineEdit } from '../../InlineEdit/InlineEdit';
import { TemplateModel } from '../../../models';
import { CUSTOM_FLAVOR, VALIDATION_ERROR_TYPE } from '../../../constants';
import { getTemplateFlavors, settingsValue } from '../../../k8s/selectors';
import { Loading } from '../../Loading/Loading';

export class Flavor extends React.Component {
constructor(props) {
super(props);
this.state = {
loadingTemplate: false,
template: null,
};
this.resolveInitialValues();
}

resolveInitialValues = () => {
const flavor = getFlavor(this.props.vm) || CUSTOM_FLAVOR;
const cpu = getCpu(this.props.vm);
const memory = getMemory(this.props.vm);
const memoryInt = memory ? parseInt(memory, 10) : undefined;
this.props.onFormChange({ value: flavor }, 'flavor');
if (flavor === CUSTOM_FLAVOR) {
this.props.onFormChange({ value: cpu }, 'cpu', !!cpu);
this.props.onFormChange({ value: memoryInt }, 'memory', !!memoryInt);
}
};

componentDidMount() {
const template = getVmTemplate(this.props.vm);
if (template) {
this.setState({
loadingTemplate: true,
});
const getTemplatePromise = this.props.k8sGet(TemplateModel, template.name, template.namespace);
getTemplatePromise
.then(result => {
this.props.onFormChange(result, 'template');
return this.setState({
loadingTemplate: false,
template: result,
});
})
.catch(error => {
this.props.onLoadError(error.message || 'An error occurred while loading vm flavors. Please try again.');
this.setState({
loadingTemplate: false,
template: null,
});
});
}
}

getFlavorDescription = () => {
const cpu = settingsValue(this.props.formValues, 'cpu');
const memory = settingsValue(this.props.formValues, 'memory');
const cpuStr = cpu ? `${cpu} CPU` : '';
const memoryStr = memory ? `${memory} Memory` : '';
const resourceStr = cpuStr && memoryStr ? `${cpuStr}, ${memoryStr}` : `${cpuStr}${memoryStr}`;
return resourceStr ? <div>{resourceStr}</div> : undefined;
};

getFlavorChoices = () => {
const flavors = [];
if (this.state.template) {
flavors.push(...getTemplateFlavors([this.state.template]));
}
if (!flavors.some(flavor => flavor === CUSTOM_FLAVOR)) {
flavors.push(CUSTOM_FLAVOR);
}
return flavors;
};

flavorFormFields = () => ({
flavor: {
id: 'flavor-dropdown',
type: 'dropdown',
choices: this.getFlavorChoices(),
},
cpu: {
id: 'flavor-cpu',
title: 'CPU',
type: 'positive-number',
required: true,
isVisible: formFields => settingsValue(formFields, 'flavor') === CUSTOM_FLAVOR,
},
memory: {
id: 'flavor-memory',
title: 'Memory (GB)',
type: 'positive-number',
required: true,
isVisible: formFields => settingsValue(formFields, 'flavor') === CUSTOM_FLAVOR,
},
});

onFormChange = (newValue, key) => {
let valid = true;
if (this.props.formValues) {
valid =
valid &&
!Object.keys(this.props.formValues)
.filter(formValueKey => formValueKey !== key)
.some(formValueKey => formValueKey.validation && formValueKey.validation.type === VALIDATION_ERROR_TYPE);
}
valid = valid && newValue.validation ? newValue.validation.type !== VALIDATION_ERROR_TYPE : true;
this.props.onFormChange(newValue, key, valid);
};

render() {
const { editing, updating, LoadingComponent } = this.props;
const formFields = this.flavorFormFields();

return (
<InlineEdit
formFields={formFields}
editing={editing}
updating={updating || (editing && this.state.loadingTemplate)}
LoadingComponent={LoadingComponent}
onFormChange={this.onFormChange}
fieldsValues={this.props.formValues}
>
<div>{settingsValue(this.props.formValues, 'flavor')}</div>
{this.getFlavorDescription()}
</InlineEdit>
);
}
}

Flavor.propTypes = {
vm: PropTypes.object.isRequired,
onFormChange: PropTypes.func.isRequired,
updating: PropTypes.bool,
editing: PropTypes.bool,
k8sGet: PropTypes.func.isRequired,
LoadingComponent: PropTypes.func,
formValues: PropTypes.object,
onLoadError: PropTypes.func,
};

Flavor.defaultProps = {
updating: false,
editing: false,
LoadingComponent: Loading,
formValues: undefined,
onLoadError: () => {},
};
55 changes: 55 additions & 0 deletions src/components/Details/Flavor/fixtures/Flavor.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Flavor } from '..';
import { cloudInitTestVm } from '../../../../k8s/mock_vm/cloudInitTestVm.mock';
import { fedora28 } from '../../../../k8s/mock_templates/fedora28.mock';

export default [
{
component: Flavor,
name: 'Flavor',
props: {
vm: cloudInitTestVm,
onFormChange: () => {},
k8sGet: () =>
new Promise(resolve => {
resolve(fedora28);
}),
},
},
{
component: Flavor,
name: 'Flavor editing',
props: {
vm: cloudInitTestVm,
formValues: {
cpu: {
value: '2',
},
memory: {
value: '2',
},
flavor: {
value: 'Custom',
},
},
editing: true,
onFormChange: () => {},
k8sGet: () =>
new Promise(resolve => {
resolve(fedora28);
}),
},
},
{
component: Flavor,
name: 'Flavor updating',
props: {
vm: cloudInitTestVm,
updating: true,
onFormChange: () => {},
k8sGet: () =>
new Promise(resolve => {
resolve(fedora28);
}),
},
},
];
1 change: 1 addition & 0 deletions src/components/Details/Flavor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Flavor';
14 changes: 14 additions & 0 deletions src/components/Details/Flavor/tests/Flavor.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';

import { Flavor } from '..';
import { default as FlavorFixture } from '../fixtures/Flavor.fixture';

const testFlavor = () => <Flavor {...FlavorFixture[0].props} />;

describe('<Flavor />', () => {
it('renders correctly', () => {
const component = shallow(testFlavor());
expect(component).toMatchSnapshot();
});
});
Loading

0 comments on commit d592fba

Please sign in to comment.