Skip to content

Commit

Permalink
Merge pull request #5139 from rvsia/catalog-form-to-react-forms
Browse files Browse the repository at this point in the history
Converted Catalog form
  • Loading branch information
martinpovolny authored Feb 25, 2019
2 parents 075ac68 + 98b9917 commit c7641cc
Show file tree
Hide file tree
Showing 11 changed files with 717 additions and 177 deletions.
70 changes: 8 additions & 62 deletions app/controllers/catalog_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -578,54 +578,19 @@ def st_catalog_edit
@in_a_form = false
replace_right_cell
when "save", "add"
assert_privileges("st_catalog_#{params[:id] ? "edit" : "new"}")
return unless load_edit("st_catalog_edit__#{params[:id] || "new"}", "replace_cell__explorer")

@stc = @edit[:rec_id] ? ServiceTemplateCatalog.find(@edit[:rec_id]) : ServiceTemplateCatalog.new
st_catalog_set_record_vars(@stc)
begin
@stc.save
rescue => bang
add_flash(_("Error during 'Catalog Edit': %{error_message}") % {:error_message => bang.message}, :error)
else
if @stc.errors.empty?
add_flash(_("Catalog \"%{name}\" was saved") % {:name => @edit[:new][:name]})
else
@stc.errors.each do |field, msg|
add_flash("#{field.to_s.capitalize} #{msg}", :error)
end
javascript_flash
return
end
end
add_flash(_("Catalog was saved"))
@changed = session[:changed] = false
@in_a_form = false
@edit = session[:edit] = nil
replace_right_cell(:replace_trees => trees_to_replace(%i(sandt svccat stcat)))
when "reset", nil # Reset or first time in
when nil # First time in
st_catalog_set_form_vars
if params[:button] == "reset"
add_flash(_("All changes have been reset"), :warning)
end
@changed = session[:changed] = false
replace_right_cell(:action => "st_catalog_edit")
return
end
end

def st_catalog_form_field_changed
id = session[:edit][:rec_id] || "new"
return unless load_edit("st_catalog_edit__#{id}", "replace_cell__explorer")
st_catalog_get_form_vars
changed = (@edit[:new] != @edit[:current])
render :update do |page|
page << javascript_prologue
page.replace(@refresh_div, :partial => @refresh_partial) if @refresh_div
page << javascript_for_miq_button_visibility(changed)
page << "miqSparkle(false);"
end
end

def process_sts(sts, task, _display_name = nil)
ServiceTemplate.where(:id => sts).order("lower(name)").each do |st|
id = st.id
Expand Down Expand Up @@ -1201,14 +1166,6 @@ def service_dialog_from_ot_submit_save
end
end

def st_catalog_get_form_vars
case params[:button]
when 'right' then move_cols_left_right('right')
when 'left' then move_cols_left_right('left')
else copy_params_if_set(@edit[:new], params, %i(name description))
end
end

def st_catalog_set_form_vars
checked = find_checked_items
checked[0] = params[:id] if checked.blank? && params[:id]
Expand All @@ -1220,28 +1177,13 @@ def st_catalog_set_form_vars
_("Adding a new Catalog")
end
@edit = {}
@edit[:key] = "st_catalog_edit__#{@record.id || "new"}"
@edit[:new] = {}
@edit[:current] = {}
@edit[:rec_id] = @record.id
@edit[:new][:name] = @record.name
@edit[:new][:description] = @record.description
@edit[:new][:fields] = @record.service_templates.collect { |st| [st.name, st.id] }.sort

@edit[:new][:available_fields] = Rbac.filtered(ServiceTemplate, :named_scope => %i(displayed public_service_templates without_service_template_catalog_id))
.collect { |st| [st.name, st.id] }
.sort

@edit[:current] = copy_hash(@edit[:new])
@edit[:current] = {} # because of locking tree in replace_right_cell method
@in_a_form = true
end

def st_catalog_set_record_vars(stc)
stc.name = @edit[:new][:name]
stc.description = @edit[:new][:description]
stc.service_templates = @edit[:new][:fields].collect { |sf| ServiceTemplate.find(sf[1]) }
end

def st_catalog_delete
assert_privileges("st_catalog_delete")
elements = []
Expand Down Expand Up @@ -2099,7 +2041,11 @@ def replace_right_cell(options = {})
presenter.hide(:toolbar).show(:paging_div)
# incase it was hidden for summary screen, and incase there were no records on show_list
presenter.remove_paging
action == 'at_st_new' && ansible_playbook? ? presenter.hide(:form_buttons_div) : presenter.show(:form_buttons_div)
if (action == 'at_st_new' && ansible_playbook?) || (action == 'st_catalog_new' || action == 'st_catalog_edit')
presenter.hide(:form_buttons_div)
else
presenter.show(:form_buttons_div)
end
locals = {:record_id => @edit[:rec_id]}
case action
when 'group_edit'
Expand Down
143 changes: 143 additions & 0 deletions app/javascript/components/catalog-form/catalog-form.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Grid } from 'patternfly-react';
import MiqFormRenderer from '../../forms/data-driven-form';
import createSchema from './catalog-form.schema';
import { filterOptions, filterValues } from '../dual-list-select/helpers';
import { API } from '../../http_api';
import { cleanVirtualDom } from '../../miq-component/helpers';

class CatalogForm extends Component {
constructor(props) {
super(props);
this.state = {
isLoaded: false,
};
cleanVirtualDom();
}

componentDidMount() {
miqSparkleOn();
const { catalogId } = this.props;
if (catalogId) {
Promise.all([
API.get('/api/service_templates?expand=resources&filter[]=service_template_catalog_id=null'),
API.get(`/api/service_catalogs/${catalogId}?expand=service_templates`)])
.then(([{ resources }, { name, description, service_templates }]) => {
const rightValues = service_templates.resources.map(({ href, name }) => ({ key: href, label: name }));
const options = resources.map(({ href, name }) => ({ key: href, label: name })).concat(rightValues);
this.setState(() => ({
schema: createSchema(options),
initialValues: {
name,
description: description === null ? undefined : description,
service_templates: rightValues.map(({ key }) => key),
},
originalRightValues: rightValues,
isLoaded: true,
}), miqSparkleOff);
});
} else {
API.get('/api/service_templates?expand=resources&filter[]=service_template_catalog_id=null').then(
({ resources }) => this.setState({
schema: createSchema(resources.map(({ href, name }) => ({ key: href, label: name }))),
isLoaded: true,
}, miqSparkleOff),
);
}
}

handleError = (error) => {
const { data: { error: { message } } } = error;
return message.includes('Name has already been taken') ? __('Name has already been taken') : message;
}

submitValues = (values) => {
const { catalogId } = this.props;
const { originalRightValues } = this.state;
const { service_templates = [] } = values;
const apiBase = `/api/service_catalogs${catalogId ? `/${catalogId}` : ''}`;

if (!catalogId) {
return API.post(apiBase, {
action: 'create',
resource: {
...values,
service_templates: service_templates.map(key => ({ href: `${key}` })),
},
}, {
skipErrors: [400],
})
.then(() => miqAjaxButton('/catalog/st_catalog_edit?button=add'))
.catch(error => add_flash(this.handleError(error), 'error'));
}

const unassignedRightValues = filterValues(values.service_templates, originalRightValues.map(({ key }) => key));
const unassignedLeftValues = filterOptions(originalRightValues, values.service_templates);
const promises = [
API.post(apiBase, {
action: 'edit',
resource: {
name: values.name,
description: values.description,
},
}, {
skipErrors: [500],
}),
];

if (unassignedRightValues.length > 0) {
promises.push(
API.post(`${apiBase}/service_templates`, {
action: 'assign',
resources: unassignedRightValues.map(key => ({ href: key })),
}),
);
}
if (unassignedLeftValues.length > 0) {
promises.push(
API.post(`${apiBase}/service_templates`, {
action: 'unassign',
resources: unassignedLeftValues.map(({ key }) => ({ href: key })),
}),
);
}

return Promise.all(promises)
.then(([{ id }]) => miqAjaxButton(`/catalog/st_catalog_edit/${id}?button=save`))
.catch(error => add_flash(this.handleError(error), 'error'));
};

render() {
const { catalogId } = this.props;
const { isLoaded, initialValues, schema } = this.state;
const cancelUrl = `/catalog/st_catalog_edit/${catalogId}?button=cancel`;
if (!isLoaded) return null;

return (
<Grid fluid>
<MiqFormRenderer
initialValues={initialValues}
schema={schema}
onSubmit={this.submitValues}
onCancel={() => miqAjaxButton(cancelUrl)}
onReset={() => add_flash(__('All changes have been reset'), 'warn')}
canReset={!!catalogId}
buttonsLabels={{
submitLabel: catalogId ? __('Save') : __('Add'),
}}
/>
</Grid>
);
}
}

CatalogForm.propTypes = {
catalogId: PropTypes.number,
};

CatalogForm.defaultProps = {
catalogId: undefined,
};

export default CatalogForm;
50 changes: 50 additions & 0 deletions app/javascript/components/catalog-form/catalog-form.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { componentTypes, validatorTypes } from '@data-driven-forms/react-form-renderer';

function createSchema(options) {
const fields = [{
component: componentTypes.SUB_FORM,
title: __('Basic Info'),
fields: [{
component: componentTypes.TEXT_FIELD,
name: 'name',
validate: [{
type: validatorTypes.REQUIRED,
message: __("Name can't be blank"),
}],
label: __('Name'),
maxLength: 40,
autoFocus: true,
validateOnMount: true,
}, {
component: componentTypes.TEXT_FIELD,
name: 'description',
label: __('Description'),
maxLength: 60,
}],
}, {
component: 'hr',
name: 'hr',
}, {
component: componentTypes.SUB_FORM,
title: __('Assign Catalog Items'),
fields: [
{
component: 'dual-list-select',
leftTitle: __('Unassigned:'),
rightTitle: __('Selected:'),
leftId: 'available_fields',
rightId: 'selected_fields',
allToRight: false,
moveLeftTitle: __('Move Selected buttons left'),
moveRightTitle: __('Move Selected buttons right'),
size: 8,
assignFieldProvider: true,
options,
name: 'service_templates',
},
],
}];
return { fields };
}

export default createSchema;
8 changes: 3 additions & 5 deletions app/javascript/components/dual-list-select/helpers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
export const getKeys = value => (Array.isArray(value) ? value.map(({ key }) => key) : []);

/* Returns array of options excluding value options */
export const filterOptions = (options, value) => options.filter(({ key }) => !getKeys(value).includes(key));
export const filterOptions = (options, value) => options.filter(({ key }) => !value.includes(key));

/* Returns array of newly added options from left select */
export const addedLeftValues = (originalOptions, value, originalLeftValues) => (
filterOptions(filterOptions(originalOptions, value), originalLeftValues)
);
/* Returns items from newValue which are not in value */
export const filterValues = (newValue, value) => newValue.filter(item => !value.includes(item));
2 changes: 2 additions & 0 deletions app/javascript/forms/mappers/formsFieldsMapper.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import { formFieldsMapper } from '@data-driven-forms/pf3-component-mapper';
import DualListSelect from '../../components/dual-list-select';

const fieldsMapper = {
...formFieldsMapper,
'dual-list-select': DualListSelect,
hr: () => <hr />,
};

export default fieldsMapper;
2 changes: 2 additions & 0 deletions app/javascript/packs/component-definitions-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SetServiceOwnershipForm from '../components/set-service-ownership-form';
import FlavorForm from '../components/flavor-form/flavor-form';
import ImportDatastoreViaGit from '../components/automate-import-export-form/import-datastore-via-git';
import VmServerRelationshipForm from '../components/vm-server-relationship-form';
import CatalogForm from '../components/catalog-form/catalog-form';

/**
* Add component definitions to this file.
Expand All @@ -34,3 +35,4 @@ ManageIQ.component.addReact('SetServiceOwnershipForm', SetServiceOwnershipForm);
ManageIQ.component.addReact('FlavorForm', FlavorForm);
ManageIQ.component.addReact('ImportDatastoreViaGit', ImportDatastoreViaGit);
ManageIQ.component.addReact('VmServerRelationshipForm', VmServerRelationshipForm);
ManageIQ.component.addReact('CatalogForm', CatalogForm);
Loading

0 comments on commit c7641cc

Please sign in to comment.