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

Transforms haml forms to react for Add and Edit feature of Automate Class #9301

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
65 changes: 65 additions & 0 deletions app/controllers/miq_ae_class_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ def replace_right_cell(options = {})
:serialize => @sb[:active_tab] == 'methods',
}
])
if @hide_bottom_bar
presenter.hide(:paging_div, :form_buttons_div)
end
else
# incase it was hidden for summary screen, and incase there were no records on show_list
presenter.hide(:paging_div, :form_buttons_div)
Expand Down Expand Up @@ -459,6 +462,7 @@ def edit_class
@ae_class = find_record_with_rbac(MiqAeClass, params[:id])
end
set_form_vars
@hide_bottom_bar = true
# have to get name and set node info, to load multiple tabs correctly
# rec_name = get_rec_name(@ae_class)
# get_node_info("aec-#{@ae_class.id}")
Expand All @@ -468,6 +472,24 @@ def edit_class
replace_right_cell
end

def edit_class_record
assert_privileges("miq_ae_class_edit")
unless params[:id]
obj = find_checked_items
@_params[:id] = obj[0]
end
@hide_bottom_bar = true

class_rec = MiqAeClass.find(params[:id])

render :json => {
:fqname => class_rec.fqname,
:name => class_rec.name,
:display_name => class_rec.display_name,
:description => class_rec.description
}
end

def edit_fields
assert_privileges("miq_ae_field_edit")
if params[:pressed] == "miq_ae_item_edit" # came from Namespace details screen
Expand Down Expand Up @@ -1291,6 +1313,7 @@ def new
assert_privileges("miq_ae_class_new")
@ae_class = MiqAeClass.new
set_form_vars
@hide_bottom_bar = true
@in_a_form = true
replace_right_cell
end
Expand Down Expand Up @@ -1841,8 +1864,50 @@ def ae_method_operations
end
end

def class_update
assert_privileges(params[:id].present? ? 'miq_ae_class_edit' : 'miq_ae_class_new')
@hide_bottom_bar = true
class_update_create
end

private

def class_update_create
case params[:button]
when "add", "save"
class_rec = params[:id].blank? ? MiqAeClass.new : MiqAeClass.find(params[:id]) # Get new or existing record
add_flash(_("Name is required"), :error) if params[:name].blank?
class_rec.name = params[:name]
class_rec.display_name = params[:display_name]
class_rec.description = params[:description]
class_rec.namespace_id = x_node.split('-')[1] if params[:id].blank?
begin
class_rec.save!
rescue StandardError
errors = []
class_rec.errors.each do |error|
errors.push("#{error.attribute.to_s.capitalize} #{error.message}")
end
@changed = true
render :json => {:error => errors, :status => 500}
else
edit_hash = {}
edit_hash[:new] = {:name => params[:name],
:display_name => params[:display_name], :description => params[:description]}
edit_hash[:current] = if params[:old_data]
{:name => params[:old_data][:name],
:display_name => params[:old_data][:display_name],
:description => params[:old_data][:description]}
else
{:name => nil, :display_name => nil, :description => nil}
end
AuditEvent.success(build_saved_audit(class_rec, edit_hash))
@edit = session[:edit] = nil # clean out the saved info
render :json => {:status => 200}
end
end
end

def get_template_class(location)
if location == "ansible_workflow_template"
ManageIQ::Providers::ExternalAutomationManager::ConfigurationWorkflow
Expand Down
38 changes: 38 additions & 0 deletions app/javascript/components/miq-ae-class/class-form.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { componentTypes } from '@@ddf';

const createSchema = (fqname) => ({
fields: [
{
component: componentTypes.TEXT_FIELD,
name: 'fqname',
label: 'Fully Qualified Name',
value: `${fqname}`,
disabled: true,
},
{
component: componentTypes.TEXT_FIELD,
id: 'name',
name: 'name',
label: __('Name'),
maxLength: 128,
validate: [{ type: 'customValidatorForNameField' }],
isRequired: true,
},
{
component: componentTypes.TEXT_FIELD,
id: 'display_name',
name: 'display_name',
label: __('Display Name'),
maxLength: 128,
},
{
component: componentTypes.TEXT_FIELD,
id: 'description',
name: 'description',
label: __('Description'),
maxLength: 255,
},
],
});

export default createSchema;
193 changes: 193 additions & 0 deletions app/javascript/components/miq-ae-class/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { useState, useEffect } from 'react';
import { FormSpy } from '@data-driven-forms/react-form-renderer';
import { Button } from 'carbon-components-react';
import MiqFormRenderer, { useFormApi } from '@@ddf';
import PropTypes from 'prop-types';
import createSchema from './class-form.schema';
import miqRedirectBack from '../../helpers/miq-redirect-back';
import miqFlash from '../../helpers/miq-flash';

const MiqAeClass = ({ classRecord, fqname }) => {
const formattedFqname = fqname.replace(/\s+/g, '');
Copy link
Member

@Fryguy Fryguy Dec 4, 2024

Choose a reason for hiding this comment

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

Is this implying that the fqname comes back from the API with spaces?

I was actually expecting any client-side formatting code to just be deleted, so this is surprising.

EDIT: Here's what the backend returns, which is why I'm surprised:

irb(main):001> MiqAeClass.first.fqname
=> "/ManageIQ/AutomationManagement/AnsibleTower/Operations/JobTemplate"

Copy link
Contributor Author

@elsamaryv elsamaryv Dec 5, 2024

Choose a reason for hiding this comment

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

I was going through the MiqAeClassController and saw this line of code which is creating those spaces.

Not sure if this line was added intentionally. If it's removed, it may affect the Fully Qualified Name displayed during the creation/editing of Domain, Namespace, Class (and potentially other areas). Could you please confirm if it's fine to remove it? @Fryguy

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we can remove that.

const [data, setData] = useState({
isLoading: true,
initialValues: undefined,
});

const isEdit = !!(classRecord && classRecord.id);

useEffect(() => {
if (isEdit) {
http.get(`/miq_ae_class/edit_class_record/${classRecord.id}/`).then((recordValues) => {
if (recordValues) {
setData({ ...data, isLoading: false, initialValues: recordValues });
}
});
} else {
const initialValues = {
formattedFqname,
name: classRecord && classRecord.name,
display_name: classRecord && classRecord.display_name,
description: classRecord && classRecord.description,
};
setData({ ...data, isLoading: false, initialValues });
}
}, [classRecord]);

const onSubmit = (values) => {
miqSparkleOn();

const params = {
action: isEdit ? 'edit' : 'create',
name: values.name,
display_name: values.display_name,
description: values.description,
old_data: data.initialValues,
button: classRecord.id ? 'save' : 'add',
};

const request = isEdit
? http.post(`/miq_ae_class/class_update/${classRecord.id}`, params)
: http.post(`/miq_ae_class/class_update/`, params);

request
.then((response) => {
if (response.status === 200) {
const confirmation = isEdit ? __(`Class "%s" was saved`) : __(`Class "%s" was added`);
const message = sprintf(confirmation, values.name);
miqRedirectBack(message, 'success', '/miq_ae_class/explorer');
} else {
miqSparkleOff();
miqFlash('error', response.error);
}
})
.catch(miqSparkleOff);
};

const onCancel = () => {
const confirmation = classRecord.id ? __(`Edit of Class "%s" cancelled by the user`)
: __(`Add of new Class was cancelled by the user`);
const message = sprintf(confirmation, classRecord.name);
miqRedirectBack(message, 'warning', '/miq_ae_class/explorer');
};

const customValidatorMapper = {
customValidatorForNameField: () => (value) => {
if (!value) {
return __('Required');
}
if (!value.match('^[a-zA-Z0-9_.-]*$')) {
return __('Name may contain only alphanumeric and _ . - characters');
}
return false;
},
};

return (!data.isLoading
? (
<div className="dialog-provision-form">
<MiqFormRenderer
schema={createSchema(formattedFqname)}
initialValues={data.initialValues}
validatorMapper={customValidatorMapper}
onSubmit={onSubmit}
onCancel={onCancel}
canReset={!!classRecord.id}
validate={() => {}}
FormTemplate={(props) => <FormTemplate {...props} recId={classRecord.id} />}
/>
</div>
) : null
);
};

const FormTemplate = ({
formFields, recId,
}) => {
const {
handleSubmit, onReset, onCancel, getState,
} = useFormApi();
const { valid, pristine } = getState();
const submitLabel = !!recId ? __('Save') : __('Add');
return (
<form onSubmit={handleSubmit}>
{formFields}
<FormSpy>
{() => (
<div className="custom-button-wrapper">
{ !recId
? (
<Button
disabled={!valid}
kind="primary"
className="btnRight"
type="submit"
variant="contained"
>
{submitLabel}
</Button>
) : (
<Button
disabled={!valid || pristine}
kind="primary"
className="btnRight"
type="submit"
variant="contained"
>
{submitLabel}
</Button>
)}
{!!recId
? (
<Button
disabled={pristine}
kind="secondary"
className="btnRight"
variant="contained"
onClick={onReset}
type="button"
>
{ __('Reset')}
</Button>
) : null}

<Button variant="contained" type="button" onClick={onCancel} kind="secondary">
{ __('Cancel')}
</Button>
</div>
)}
</FormSpy>
</form>
);
};

MiqAeClass.propTypes = {
classRecord: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
display_name: PropTypes.string,
description: PropTypes.string,
}),
fqname: PropTypes.string.isRequired,
};

MiqAeClass.defaultProps = {
classRecord: undefined,
};

FormTemplate.propTypes = {
formFields: PropTypes.arrayOf(
PropTypes.shape({ id: PropTypes.number }),
PropTypes.shape({ name: PropTypes.string }),
PropTypes.shape({ display_name: PropTypes.string }),
PropTypes.shape({ description: PropTypes.string }),
),
recId: PropTypes.number,
};

FormTemplate.defaultProps = {
formFields: undefined,
recId: undefined,
};

export default MiqAeClass;
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 @@ -177,6 +177,7 @@ import WorkflowPayload from '../components/workflows/workflow_payload';
import WorkflowRepositoryForm from '../components/workflow-repository-form';
import XmlHolder from '../components/XmlHolder';
import ZoneForm from '../components/zone-form';
import MiqAeClass from '../components/miq-ae-class';

/**
* Add component definitions to this file.
Expand Down Expand Up @@ -363,3 +364,4 @@ ManageIQ.component.addReact('WorkflowPayload', WorkflowPayload);
ManageIQ.component.addReact('WorkflowRepositoryForm', WorkflowRepositoryForm);
ManageIQ.component.addReact('XmlHolder', XmlHolder);
ManageIQ.component.addReact('ZoneForm', ZoneForm);
ManageIQ.component.addReact('MiqAeClass', MiqAeClass);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MiqAeClass Form Component should render add class form correctly 1`] = `""`;

exports[`MiqAeClass Form Component should render edit class form correctly 1`] = `""`;
Loading
Loading