Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Component that triggers Provisioning Request #58

Merged
merged 3 commits into from
May 11, 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
31 changes: 31 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"presets": [
[
"env",
{
"targets": {
"browsers": [
"> 1%",
"last 2 versions",
"Firefox ESR",
"IE 10",
"IE 11"
]
}
}
],
"react",
"es2015",
],
"plugins": [
"transform-class-properties",
"transform-export-extensions",
"transform-object-rest-spread",
"transform-object-assign"
],
"env": {
"test": {
"plugins": ["transform-es2015-modules-commonjs"]
}
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
/config/environments/*.local.yml

/spec/manageiq

node_modules/
yarn.lock
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# using yarn
package-lock = false
4 changes: 4 additions & 0 deletions .postcssrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
plugins:
postcss-smart-import: {}
precss: {}
autoprefixer: {}
16 changes: 14 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,25 @@ notifications:
on_success: change
on_failure: always
on_start: never
cache: bundler
cache:
bundler: true
yarn: true
env:
global:
- RUBY_GC_HEAP_GROWTH_MAX_SLOTS=300000
- RUBY_GC_HEAP_INIT_SLOTS=600000
- RUBY_GC_HEAP_GROWTH_FACTOR=1.25
matrix:
- TEST_SUITE=spec
- TEST_SUITE=yarn:test
matrix:
exclude:
- rvm: 2.4.5
env: TEST_SUITE=yarn:test
addons:
postgresql: '10'
install: bin/setup
install:
- bin/setup
- "bundle exec rake yarn:install"
script: bundle exec rake $TEST_SUITE
after_script: bundle exec codeclimate-test-reporter
14 changes: 14 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,18 @@ require 'bundler/gem_tasks'

FileList['lib/tasks_private/**/*.rake'].each { |r| load r }

namespace :yarn do
desc "install yarn dependencies"
task :install do
system('yarn install')
exit $CHILD_STATUS.exitstatus
end

desc "run jest tests"
task :test do
system('yarn test')
exit $CHILD_STATUS.exitstatus
end
end

task :default => :spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module ManageIQ::Providers::Redfish
class ToolbarOverrides::PhysicalServerCenter \
< ::ApplicationHelper::Toolbar::Override
button_group(
"physical_server_policy",
[
select(
:physical_server_lifecycle_choice,
"fa fa-recycle fa-lg",
t = N_("Lifecycle"),
t,
:enabled => true,
:items => [
button(
:physical_server_provision,
"pficon pficon-add-circle-o fa-lg",
t = N_("Provision Physical Server"),
t,
:klass => ApplicationHelper::Button::ButtonWithoutRbacCheck,
:data => {
"function" => "sendDataWithRx",
"function-data" => {
:controller => "provider_dialogs",
:button => :physical_server_provision,
:modal_title => N_("Provision Physical Server"),
:component_name => "RedfishServerProvisionDialog",
}.to_json,
}
),
]
),
]
)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module ManageIQ::Providers::Redfish
class ToolbarOverrides::PhysicalServersCenter \
< ::ApplicationHelper::Toolbar::Override
button_group(
"physical_server_policy",
[
select(
:physical_server_lifecycle_choice,
"fa fa-recycle fa-lg",
t = N_("Lifecycle"),
t,
:enabled => true,
:items => [
button(
:physical_server_provision,
"pficon pficon-add-circle-o fa-lg",
t = N_("Provision Selected Physical Servers"),
t,
:klass => ApplicationHelper::Button::ButtonWithoutRbacCheck,
:data => {
"function" => "sendDataWithRx",
"function-data" => {
:controller => "provider_dialogs",
:button => :physical_server_provision,
:modal_title => N_("Provision Selected Physical Servers"),
:component_name => "RedfishServerProvisionDialog",
}.to_json,
},
:enabled => false,
:onwhen => "1+"
),
]
),
]
)
end
end
99 changes: 99 additions & 0 deletions app/javascript/components/forms/pxe-details.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { Component } from "react";
import { Form, Field, FormSpy } from "react-final-form";
import { Form as PfForm, Grid, Button, Col, Row, Spinner } from "patternfly-react";
import PropTypes from "prop-types";
import { required } from "redux-form-validators";

import { FinalFormField, FinalFormSelect } from "@manageiq/react-ui-components/dist/forms";
import '@manageiq/react-ui-components/dist/forms.css';

const PxeDetails = ({loading, updateFormState, physicalServerIds, initialValues, pxeServers, pxeImages,
customizationTemplates}) => {
if(loading){
return (
<Spinner loading size="lg" />
);
}

return (
<Form
onSubmit={() => {}} // handled by modal
initialValues={initialValues}
render={({ handleSubmit }) => (
<PfForm horizontal>
<FormSpy onChange={state => updateFormState({ ...state, values: state.values })} />
<Grid fluid>
<Row>
<Col xs={12}>
<h2>{__(`Number of servers to be provisioned: ${physicalServerIds.length}`)}</h2>
</Col>
</Row>
<hr />
<Row>
<Col xs={12}>
<Field
name="pxeServer"
component={FinalFormSelect}
placeholder={__('Select a PXE Server')}
options={pxeServers}
label={__('PXE Server')}
validateOnMount={false}
validate={required({ msg: 'PXE Server is required' })}
labelColumnSize={3}
inputColumnSize={8}
searchable
/>
</Col>
<Col xs={12}>
<Field
name="pxeImage"
component={FinalFormSelect}
placeholder={__('Select a PXE Image')}
options={pxeImages}
disabled={pxeImages.length === 0}
label={__('PXE Image')}
validateOnMount={false}
validate={required({ msg: 'PXE Image is required' })}
labelColumnSize={3}
inputColumnSize={8}
searchable
/>
</Col>
<Col xs={12}>
<Field
name="customizationTemplate"
component={FinalFormSelect}
placeholder={__('Select Customization Template')}
options={customizationTemplates}
disabled={customizationTemplates.length === 0}
label={__('Customization Template')}
validateOnMount={false}
validate={required({ msg: 'Customization Template is required' })}
labelColumnSize={3}
inputColumnSize={8}
searchable
/>
</Col>
<hr />
</Row>
</Grid>
</PfForm>
)}
/>
);
};

PxeDetails.propTypes = {
updateFormState: PropTypes.func.isRequired,
physicalServerIds: PropTypes.array.isRequired,
pxeServers: PropTypes.array.isRequired,
pxeImages: PropTypes.array.isRequired,
customizationTemplates: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired
};

PxeDetails.defaultProps = {
loading: false,
};

export default PxeDetails;
112 changes: 112 additions & 0 deletions app/javascript/components/redfish-server-provision-dialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import PxeDetails from "./forms/pxe-details"
import { handleApiError, fetchPxeServers, fetchPxeImagesForServer, fetchTemplatesForPxeImage,
createProvisionRequest
} from "../utils/api";

class RedfishServerProvisionDialog extends React.Component {
constructor(props) {
super(props);
this.handleFormStateUpdate = this.handleFormStateUpdate.bind(this);
this.state = {
loading: true,
physicalServerIds: [],
pxeServers: [],
pxeImages: [],
customizationTemplates: [],
}
}

selectedPhysicalServers = () => {
if(ManageIQ.record.recordId){ // Single-record page
this.setState({physicalServerIds: [ManageIQ.record.recordId]});
} else if(ManageIQ.gridChecks.length > 0){ // Multi-record page
this.setState({physicalServerIds: ManageIQ.gridChecks});
} else{
this.setState({physicalServerIds: [], error: __('Please select at lest one physical server to provision.')});
}
};

pxeServerToSelectOption = pxeServer => { return { value: pxeServer.id, label: `${pxeServer.name} (${pxeServer.uri})` } };
pxeImageToSelectOption = pxeImage => { return { value: pxeImage.id, label: pxeImage.name } };
templateToSelectOption = template => { return { value: template.id, label: template.name } };

initializeData = () => fetchPxeServers().then((pxeServers) => {
this.setState({
pxeServers: pxeServers.resources.map(this.pxeServerToSelectOption),
loading: false
});
}, handleApiError(this));

onChange = (formState) => {
if(formState.modified.pxeServer){
this.setState({customizationTemplates: []});
fetchPxeImagesForServer(formState.values.pxeServer).then(images => {
this.setState({pxeImages: images.resources.map(this.pxeImageToSelectOption)});
}, handleApiError(this));
} else if(formState.modified.pxeImage){
fetchTemplatesForPxeImage(formState.values.pxeImage).then(templates => {
this.setState({customizationTemplates: templates.resources.map(this.templateToSelectOption)});
}, handleApiError(this));
}
};

componentDidMount() {
this.props.dispatch({
type: "FormButtons.init",
payload: {
newRecord: true,
pristine: true,
addClicked: () => createProvisionRequest(
this.state.physicalServerIds, this.state.values.pxeImage, this.state.values.customizationTemplate
)
}
});
this.props.dispatch({
type: "FormButtons.customLabel",
payload: __('Provision')
});
this.selectedPhysicalServers();
this.initializeData()
}

handleFormStateUpdate(formState) {
this.props.dispatch({
type: "FormButtons.saveable",
payload: formState.valid
});
this.props.dispatch({
type: "FormButtons.pristine",
payload: formState.pristine
});
this.setState({
values: formState.values
});
this.onChange(formState);
}

render() {
if(this.state.error) {
return <p>{this.state.error}</p>
}
return (
<PxeDetails
updateFormState={this.handleFormStateUpdate}
physicalServerIds={this.state.physicalServerIds}
pxeServers={this.state.pxeServers}
pxeImages={this.state.pxeImages}
customizationTemplates={this.state.customizationTemplates}
loading={this.state.loading}
initialValues={this.state.values}
/>
);
}
}

RedfishServerProvisionDialog.propTypes = {
dispatch: PropTypes.func.isRequired,
};

export default connect()(RedfishServerProvisionDialog);
6 changes: 6 additions & 0 deletions app/javascript/packs/component-definition-common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import RedfishServerProvisionDialog
from "../components/redfish-server-provision-dialog";

ManageIQ.component.addReact(
"RedfishServerProvisionDialog", RedfishServerProvisionDialog
);
Loading