From 409cc34a338ca066a756eb38237bd1a315029d80 Mon Sep 17 00:00:00 2001 From: Amal-Vijayan Date: Wed, 28 Feb 2024 14:48:39 +0530 Subject: [PATCH] API integration and code mirror implementation --- app/controllers/miq_ae_class_controller.rb | 19 +++ .../automate-method-code-mirror/index.jsx | 85 ++++++++----- .../automate-method-input-parameter-form.jsx | 2 +- .../automate-method-input-parameter/index.jsx | 50 ++++---- .../automate-method-input-parameter/schema.js | 6 - .../components/automate-method-form/index.jsx | 76 +++++++++--- .../automate-method-form/schema.config.js | 43 ++++--- .../components/automate-method-form/schema.js | 114 ++++++++++-------- .../automate-method-form/style.scss | 28 +++++ app/stylesheet/miq-data-table.scss | 2 +- app/views/miq_ae_class/_method_form.html.haml | 5 +- config/routes.rb | 1 + 12 files changed, 281 insertions(+), 150 deletions(-) create mode 100644 app/javascript/components/automate-method-form/style.scss diff --git a/app/controllers/miq_ae_class_controller.rb b/app/controllers/miq_ae_class_controller.rb index d13ce6bb0096..b5cae74d3cf5 100644 --- a/app/controllers/miq_ae_class_controller.rb +++ b/app/controllers/miq_ae_class_controller.rb @@ -804,6 +804,25 @@ def ae_class_for_instance_or_method(record) record.id ? record.ae_class : MiqAeClass.find(x_node.split("-").last) end + def validate_automate_method_data + assert_privileges("miq_ae_method_edit") + @edit[:new][:data] = params[:cls_method_data] if params[:cls_method_data] + @edit[:new][:data] = params[:method_data] if params[:method_data] + res = MiqAeMethod.validate_syntax(@edit[:new][:data]) + line = 0 + if !res + render :json => {:status => true, :message => _("Data validated successfully")} + else + res.each do |err| + line = err[0] if line.zero? + render :json => { + :status => false, + :message => (_("Error on line %{line_num}: %{err_txt}") % {:line_num => err[0], :err_txt => err[1]}) + } + end + end + end + def validate_method_data assert_privileges("miq_ae_method_edit") return unless load_edit("aemethod_edit__#{params[:id]}", "replace_cell__explorer") diff --git a/app/javascript/components/automate-method-form/automate-method-code-mirror/index.jsx b/app/javascript/components/automate-method-form/automate-method-code-mirror/index.jsx index 38633ac3658f..4c87a43077a9 100644 --- a/app/javascript/components/automate-method-form/automate-method-code-mirror/index.jsx +++ b/app/javascript/components/automate-method-form/automate-method-code-mirror/index.jsx @@ -1,45 +1,66 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import { Button } from 'carbon-components-react'; -import CodeMirror from 'codemirror'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/mode/javascript/javascript'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import { http } from '../../../http_api'; +import NotificationMessage from '../../notification-message'; +import AutomateMethodContext from '../automate-method-context'; const AutomateMethodCodeMirror = () => { - const codeMirrorRef = useRef(null); + const { updateCodeEditor } = useContext(AutomateMethodContext); + + const defaultEditorContents = `#\n# Description: \n#\n`; + + const [data, setData] = useState({ + editorContents: defaultEditorContents, + enableValidationButton: false, + validation: undefined, + }); useEffect(() => { - const editor = CodeMirror.fromTextArea(codeMirrorRef.current, { - mode: 'javascript', - theme: 'material', - lineNumbers: true, - }); + updateCodeEditor(data.editorContents); + }, [data.validation]); - editor.on('change', (instance) => { - console.log(instance); + const validate = () => { + const formData = { cls_method_data: data.editorContents }; + http.post('/miq_ae_class/validate_automate_method_data/new?button=validate', formData).then((response) => { + setData({ + ...data, + validation: response, + }); }); + }; - editor.setValue('test'); - - return () => { - editor.toTextArea(); - }; - }, []); + const renderValidateButton = () => ( +
+ +
+ ); return ( -
- - +
+
+
{__('Data')}
+
+
+ { + data.validation && + } + setData({ ...data, validation: undefined, editorContents: value })} + value={data.editorContents} + /> + {renderValidateButton()} +
+
); }; diff --git a/app/javascript/components/automate-method-form/automate-method-input-parameter/automate-method-input-parameter-form.jsx b/app/javascript/components/automate-method-form/automate-method-input-parameter/automate-method-input-parameter-form.jsx index c9b9dddde467..36b061068272 100644 --- a/app/javascript/components/automate-method-form/automate-method-input-parameter/automate-method-input-parameter-form.jsx +++ b/app/javascript/components/automate-method-form/automate-method-input-parameter/automate-method-input-parameter-form.jsx @@ -36,7 +36,7 @@ const AutomateMethodInputParameterForm = ({ modalStatus }) => { passiveModal > addOrUpdateInputParameter(values)} /> diff --git a/app/javascript/components/automate-method-form/automate-method-input-parameter/index.jsx b/app/javascript/components/automate-method-form/automate-method-input-parameter/index.jsx index 2834824de98c..7cdbff68d6b4 100644 --- a/app/javascript/components/automate-method-form/automate-method-input-parameter/index.jsx +++ b/app/javascript/components/automate-method-form/automate-method-input-parameter/index.jsx @@ -25,35 +25,39 @@ const AutomateMethodInputParameter = () => { }; const renderAddButton = () => ( - +
+ +
); return ( -
-
-

{__('Input Parameters')}

+
+
+
{__('Input Parameters')}
+
+
{renderAddButton()} + { + formData.inputParameter.items.length > 0 + ? ( + onSelect(selectedRow)} + mode="button-group-list" + /> + ) + : ( + <> +
+ + + ) + }
- { - formData.inputParameter.items.length > 0 - ? ( - onSelect(selectedRow)} - mode="button-group-list" - /> - ) - : ( - <> -
- - - ) - }
); }; diff --git a/app/javascript/components/automate-method-form/automate-method-input-parameter/schema.js b/app/javascript/components/automate-method-form/automate-method-input-parameter/schema.js index c05e18b841b1..e61d4b3fe65e 100644 --- a/app/javascript/components/automate-method-form/automate-method-input-parameter/schema.js +++ b/app/javascript/components/automate-method-form/automate-method-input-parameter/schema.js @@ -1,12 +1,6 @@ /* eslint-disable camelcase */ import { componentTypes, validatorTypes } from '@@ddf'; -// const options = [ -// { label: 'Option 1', value: 'option1' }, -// { label: 'Option 2', value: 'option2' }, -// { label: 'Option 3', value: 'option3' }, -// ]; - /** Schema for input parameter form */ export const inputParameterSchema = ({ available_datatypes }) => ({ fields: [ diff --git a/app/javascript/components/automate-method-form/index.jsx b/app/javascript/components/automate-method-form/index.jsx index f1cdeb4d3100..52c37a7b7199 100644 --- a/app/javascript/components/automate-method-form/index.jsx +++ b/app/javascript/components/automate-method-form/index.jsx @@ -10,14 +10,23 @@ import AutomateMethodCodeMirror from './automate-method-code-mirror'; import AutomateMethodInputParameterForm from './automate-method-input-parameter/automate-method-input-parameter-form'; import AutomateMethodContext from './automate-method-context'; import { http } from '../../http_api'; +import './style.scss'; -const AutomateMethodForm = ({ availableLocations }) => { - const options = availableLocations.map((item) => ({ id: item[1], label: item[0] })); +const AutomateMethodForm = ({ availableLocations, levels }) => { + const mapper = { + ...componentMapper, + 'automate-method-code-mirror': AutomateMethodCodeMirror, + 'automate-method-input-parameter': AutomateMethodInputParameter, + }; const [formData, setFormData] = useState({ - loading: true, - ansibleJobTemplate: undefined, + loading: false, + apiResponse: undefined, selectedType: undefined, + providerId: undefined, + workflowTemplates: undefined, + levels, + codeEditor: undefined, inputParameter: { modal: false, selectedId: undefined, @@ -26,14 +35,38 @@ const AutomateMethodForm = ({ availableLocations }) => { }); useEffect(() => { - http.get('/miq_ae_class/method_form_fields/new?location=ansible_job_template').then((ansibleJobTemplateResponse) => { + if (formData.selectedType && formData.selectedType.id) { + http.get(`/miq_ae_class/method_form_fields/new?location=${formData.selectedType.id}`).then((apiResponse) => { + setFormData({ + ...formData, + loading: false, + apiResponse, + }); + }); + } + }, [formData.selectedType]); + + useEffect(() => { + if (formData.providerId) { + const collectionClass = 'collection_class=ManageIQ::Providers::AnsibleTower::AutomationManager::ConfigurationScript'; + const filter = `filter[]=manager_id=${formData.providerId}`; + const sort = `sort_by=name&sort_order=asc`; + const url = `/api/configuration_scripts?expand=resources&${collectionClass}&${filter}&${sort}`; + API.get(url).then((response) => { + miqSparkleOn(); + setFormData({ + ...formData, + workflowTemplates: response, + }); + miqSparkleOff(); + }); + } else { setFormData({ ...formData, - loading: false, - ansibleJobTemplate: ansibleJobTemplateResponse, + workflowTemplates: undefined, }); - }); - }, []); + } + }, [formData.providerId]); const updateInputParameter = (actionType, data) => { setFormData({ @@ -42,22 +75,24 @@ const AutomateMethodForm = ({ availableLocations }) => { }); }; - const mapper = { - ...componentMapper, - 'automate-method-inline': AutomateMethodCodeMirror, - 'automate-method-input-parameter': AutomateMethodInputParameter, + const updateCodeEditor = (data) => { + setFormData({ + ...formData, + codeEditor: data, + }); }; - const chooseAutomateType = () => ( + const renderAutomateTypes = () => (

{__('Main Info')}

({ id: item[1], label: item[0] }))} itemToString={(item) => (item ? item.label : '')} onChange={({ selectedItem }) => setFormData({ ...formData, + loading: true, selectedType: selectedItem, })} titleText={formData.selectedType ? formData.selectedType.label : ''} @@ -80,14 +115,11 @@ const AutomateMethodForm = ({ availableLocations }) => { const renderFormContents = () => ( <> - { - chooseAutomateType() - } { formData.selectedType && ( - + { @@ -101,6 +133,9 @@ const AutomateMethodForm = ({ availableLocations }) => { return (
+ { + renderAutomateTypes() + } { formData.loading ? renderLoader() @@ -114,4 +149,5 @@ export default AutomateMethodForm; AutomateMethodForm.propTypes = { availableLocations: PropTypes.arrayOf(PropTypes.any).isRequired, + levels: PropTypes.shape({}).isRequired, }; diff --git a/app/javascript/components/automate-method-form/schema.config.js b/app/javascript/components/automate-method-form/schema.config.js index 1c27400d9017..8153e0fdaa5a 100644 --- a/app/javascript/components/automate-method-form/schema.config.js +++ b/app/javascript/components/automate-method-form/schema.config.js @@ -1,23 +1,35 @@ import { componentTypes } from '@@ddf'; -export const ansibleFields = [ +const dropdownOptions = (items) => items.map((item) => ({ label: item.name, value: item.id })); + +export const ansibleFields = (formData, setFormData) => ([ { component: componentTypes.SELECT, id: 'provider', name: 'provider', label: __('Provider'), - // options: options, + placeholder: __(''), + includeEmpty: true, + options: formData.apiResponse && formData.apiResponse.managers ? dropdownOptions(formData.apiResponse.managers) : [], + onChange: (providerId) => setFormData({ + ...formData, + providerId, + }), }, { component: componentTypes.SELECT, id: 'workflowTemplate', name: 'workflowTemplate', label: __('Workflow Template'), - // options: options, + placeholder: __(''), + includeEmpty: true, + options: (formData.workflowTemplates && formData.workflowTemplates.resources) + ? dropdownOptions(formData.workflowTemplates.resources) + : [], }, -]; +]); -const ansibleFieldsCommon = [ +const ansibleFieldsCommon = ({ levels }) => ([ { component: componentTypes.TEXT_FIELD, id: 'maxttl', @@ -29,8 +41,9 @@ const ansibleFieldsCommon = [ id: 'loggingOutput', name: 'loggingOutput', label: __('Logging Output'), + options: Object.entries(levels.output).map(([id, name]) => ({ label: name, value: id })), }, -]; +]); const additionalFields = [ { @@ -136,20 +149,22 @@ const playBookFields = [ }, ]; -const verbosityField = [ +const verbosityField = ({ levels }) => ([ { component: componentTypes.SELECT, id: 'verbosity', name: 'verbosity', label: __('Verbosity'), - options: [], + placeholder: __(''), + includeEmpty: true, + options: Object.entries(levels.verbosity).map(([value, label]) => ({ label, value })), }, -]; +]); -export const schemaConfig = { - ansibleJobTemplate: [...ansibleFields, ...additionalFields, ...ansibleFieldsCommon], - ansibleWorkflowTemplate: [...ansibleFields, ...ansibleFieldsCommon], +export const schemaConfig = (formData, setFormData) => ({ + ansibleJobTemplate: [...ansibleFields(formData, setFormData), ...additionalFields, ...(ansibleFieldsCommon(formData))], + ansibleWorkflowTemplate: [...ansibleFields(setFormData), ...ansibleFieldsCommon(formData)], builtIn: [...builtInFields], expression: [...expressionFields], - playbook: [...playBookFields, ...additionalFields, ...ansibleFieldsCommon, ...verbosityField], -}; + playbook: [...playBookFields, ...additionalFields, ...ansibleFieldsCommon(formData), ...verbosityField(formData)], +}); diff --git a/app/javascript/components/automate-method-form/schema.js b/app/javascript/components/automate-method-form/schema.js index f7016cfad9a9..dee05ec56597 100644 --- a/app/javascript/components/automate-method-form/schema.js +++ b/app/javascript/components/automate-method-form/schema.js @@ -1,73 +1,83 @@ import { componentTypes } from '@@ddf'; import { schemaConfig } from './schema.config'; -export const createSchema = (selectedOption) => { - let selectedFields = []; +const commonFields = [ + { + component: componentTypes.TEXT_FIELD, + id: 'type', + name: 'type', + label: __('Type'), + }, + { + component: componentTypes.TEXT_FIELD, + id: 'fully-qualified-name', + name: 'fully-qualified-name', + label: __('Fully Qualified Name'), + initialValue: '', + }, + { + component: componentTypes.TEXT_FIELD, + id: 'name', + name: 'name', + label: __('Name'), + initialValue: '', + }, + { + component: componentTypes.TEXT_FIELD, + id: 'displayName', + name: 'displayname', + label: __('Display Name'), + initialValue: '', + }, +]; + +const inputParametersField = [ + { + component: 'automate-method-input-parameter', + id: 'inputParameter', + name: 'inputParameter', + label: __('Input Parameter'), + }, +]; - const commonFields = [ - { - component: componentTypes.TEXT_FIELD, - id: 'type', - name: 'type', - label: __('Type'), - }, - { - component: componentTypes.TEXT_FIELD, - id: 'fully-qualified-name', - name: 'fully-qualified-name', - label: __('Fully Qualified Name'), - initialValue: '', - }, - { - component: componentTypes.TEXT_FIELD, - id: 'name', - name: 'name', - label: __('Name'), - initialValue: '', - }, - { - component: componentTypes.TEXT_FIELD, - id: 'displayName', - name: 'displayname', - label: __('Display Name'), - initialValue: '', - }, - ]; +const codeMirrorField = [ + { + component: 'automate-method-code-mirror', + id: 'codeMirror', + name: 'codeMirror', + label: __('Data'), + }, +]; - const inputParametersField = [ - { - component: 'automate-method-input-parameter', - id: 'inputParameter', - name: 'inputParameter', - label: __('Input Parameter'), - initialValue: 'test', - }, - ]; +const automateFields = (conditionalFields) => [ + ...commonFields, + ...conditionalFields, + ...inputParametersField, +]; - const automateFields = (conditionalFields) => [ - ...commonFields, - ...conditionalFields, - ...inputParametersField, - ]; +export const createSchema = (formData, setFormData) => { + let selectedFields = []; + const { selectedType } = formData; + const config = schemaConfig(formData, setFormData); - switch (selectedOption.id) { + switch (selectedType.id) { case 'ansible_job_template': - selectedFields = automateFields(schemaConfig.ansibleJobTemplate); + selectedFields = automateFields(config.ansibleJobTemplate); break; case 'ansible_workflow_template': - selectedFields = automateFields(schemaConfig.ansibleWorkflowTemplate); + selectedFields = automateFields(config.ansibleWorkflowTemplate); break; case 'builtin': - selectedFields = automateFields(schemaConfig.builtIn); + selectedFields = automateFields(config.builtIn); break; case 'expression': - selectedFields = automateFields(schemaConfig.expression); + selectedFields = automateFields(config.expression); break; case 'inline': - selectedFields = [...commonFields, ...inputParametersField]; + selectedFields = [...commonFields, ...codeMirrorField, ...inputParametersField]; break; case 'playbook': - selectedFields = automateFields(schemaConfig.playbook); + selectedFields = automateFields(config.playbook); break; default: selectedFields = []; diff --git a/app/javascript/components/automate-method-form/style.scss b/app/javascript/components/automate-method-form/style.scss new file mode 100644 index 000000000000..8979bcce8a6e --- /dev/null +++ b/app/javascript/components/automate-method-form/style.scss @@ -0,0 +1,28 @@ +.custom-form-wrapper { + border: 1px solid lightgray; + margin-bottom: 20px; + display: flex; + flex-direction: column; + + .custom-form-title-wrapper { + background: lightgray; + display: flex; + flex-direction: row; + + .custom-form-title { + display: flex; + flex-grow: 1; + font-size: 16px; + padding: 8px 10px; + } + } + + .custom-form-component-wrapper { + padding: 10px; + + .custom-form-buttons { + display: flex; + justify-content: flex-end; + } + } +} diff --git a/app/stylesheet/miq-data-table.scss b/app/stylesheet/miq-data-table.scss index be306c729497..530a201a3688 100644 --- a/app/stylesheet/miq-data-table.scss +++ b/app/stylesheet/miq-data-table.scss @@ -304,7 +304,7 @@ table.miq_preview { display: flex; gap: 20px; } - .reconfigure-form-table, .input-parameter-form-table { + .reconfigure-form-table { margin-bottom: 2rem; .form-section-title { diff --git a/app/views/miq_ae_class/_method_form.html.haml b/app/views/miq_ae_class/_method_form.html.haml index ae9eed2599a7..7c2953dc5888 100644 --- a/app/views/miq_ae_class/_method_form.html.haml +++ b/app/views/miq_ae_class/_method_form.html.haml @@ -1,7 +1,10 @@ - if @sb[:active_tab] == "methods" - url = url_for_only_path(:action => 'form_method_field_changed', :id => (@ae_method.id || 'new')) - obs = {:interval => '.5', :url => url}.to_json -= react('AutomateMethodForm', availableLocations: available_locations_with_labels) + +- levels = {:output => ViewHelper::LOG_OUTPUT_LEVELS, :verbosity => ViewHelper::VERBOSITY_LEVELS} += react('AutomateMethodForm', availableLocations: available_locations_with_labels, levels: levels) + %h3 = _('Main Info') .form-horizontal diff --git a/config/routes.rb b/config/routes.rb index 01db17d40095..1bb2966d6ddc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1948,6 +1948,7 @@ update_method update_namespace validate_method_data + validate_automate_method_data x_button x_history x_show