From 90c4a97566e58bd6243b49ca14f35e273fe365ba Mon Sep 17 00:00:00 2001 From: Elsa Mary Date: Mon, 25 Aug 2025 16:09:22 +0530 Subject: [PATCH 01/25] Converts service dialogs from angular to react --- .../common/inline-flash-message/index.jsx | 36 ++ .../components/date-time-picker/index.jsx | 102 ++++ .../embedded-automate-entry-point/index.jsx | 13 +- .../service-dialog-form/component-types.js | 7 + .../components/service-dialog-form/data.js | 70 +++ .../dynamic-component-chooser.jsx | 34 ++ .../dynamic-field-actions.jsx | 113 ++++ .../service-dialog-form/dynamic-field.jsx | 51 ++ .../dynamic-fields/dynamic-checkbox.jsx | 122 ++++ .../dynamic-fields/dynamic-date-picker.jsx | 131 ++++ .../dynamic-fields/dynamic-dropdown.jsx | 186 ++++++ .../dynamic-field-configuration.js | 107 ++++ .../dynamic-fields/dynamic-radio-button.jsx | 159 +++++ .../dynamic-fields/dynamic-tag-control.jsx | 181 ++++++ .../dynamic-fields/dynamic-text-area.jsx | 135 +++++ .../dynamic-fields/dynamic-text-input.jsx | 132 ++++ .../dynamic-fields/dynamic-time-picker.jsx | 225 +++++++ .../service-dialog-form/dynamic-section.jsx | 144 +++++ .../edit-field-modal.schema.js | 59 ++ .../edit-field-modal/fields.schema.js | 254 ++++++++ .../edit-field-modal/index.jsx | 146 +++++ .../edit-section-modal/index.jsx | 49 ++ .../edit-section-modal/section.schema.js | 21 + .../edit-tab-modal/index.jsx | 49 ++ .../edit-tab-modal/tab.schema.js | 21 + .../components/service-dialog-form/helper.js | 511 ++++++++++++++++ .../components/service-dialog-form/index.jsx | 515 ++++++++++++++++ .../components/service-dialog-form/style.scss | 187 ++++++ .../service-dialog-form/tab-options-menu.jsx | 41 ++ .../forms/mappers/componentMapper.jsx | 2 + .../packs/component-definitions-common.js | 8 +- app/stylesheet/ddf_override.scss | 15 + .../miq_ae_customization/editor.html.haml | 2 + .../service-dialogs/add/checkbox.cy.js | 411 +++++++++++++ .../service-dialogs/add/datepicker.cy.js | 415 +++++++++++++ .../service-dialogs/add/dropdown.cy.js | 562 ++++++++++++++++++ .../service-dialogs/add/radiobutton.cy.js | 469 +++++++++++++++ .../add/tabs-and-sections.cy.js | 146 +++++ .../service-dialogs/add/tagControl.cy.js | 547 +++++++++++++++++ .../service-dialogs/add/textarea.cy.js | 425 +++++++++++++ .../service-dialogs/add/textbox.cy.js | 439 ++++++++++++++ .../service-dialogs/add/timepicker.cy.js | 502 ++++++++++++++++ .../service-dialogs/form-submission.cy.js | 138 +++++ cypress/support/commands/common.js | 37 ++ .../commands/customization/service-dialogs.js | 182 ++++++ cypress/support/e2e.js | 2 + 46 files changed, 8098 insertions(+), 5 deletions(-) create mode 100644 app/javascript/components/common/inline-flash-message/index.jsx create mode 100644 app/javascript/components/date-time-picker/index.jsx create mode 100644 app/javascript/components/service-dialog-form/component-types.js create mode 100644 app/javascript/components/service-dialog-form/data.js create mode 100644 app/javascript/components/service-dialog-form/dynamic-component-chooser.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-field-actions.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-field.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-checkbox.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-date-picker.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-dropdown.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-field-configuration.js create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-radio-button.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-tag-control.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-text-area.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-text-input.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-fields/dynamic-time-picker.jsx create mode 100644 app/javascript/components/service-dialog-form/dynamic-section.jsx create mode 100644 app/javascript/components/service-dialog-form/edit-field-modal/edit-field-modal.schema.js create mode 100644 app/javascript/components/service-dialog-form/edit-field-modal/fields.schema.js create mode 100644 app/javascript/components/service-dialog-form/edit-field-modal/index.jsx create mode 100644 app/javascript/components/service-dialog-form/edit-section-modal/index.jsx create mode 100644 app/javascript/components/service-dialog-form/edit-section-modal/section.schema.js create mode 100644 app/javascript/components/service-dialog-form/edit-tab-modal/index.jsx create mode 100644 app/javascript/components/service-dialog-form/edit-tab-modal/tab.schema.js create mode 100644 app/javascript/components/service-dialog-form/helper.js create mode 100644 app/javascript/components/service-dialog-form/index.jsx create mode 100644 app/javascript/components/service-dialog-form/style.scss create mode 100644 app/javascript/components/service-dialog-form/tab-options-menu.jsx create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/checkbox.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/datepicker.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/dropdown.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/radiobutton.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/tabs-and-sections.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/tagControl.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/textarea.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/textbox.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/add/timepicker.cy.js create mode 100644 cypress/e2e/ui/Automation/Embedded-Automate/customization/service-dialogs/form-submission.cy.js create mode 100644 cypress/support/commands/common.js create mode 100644 cypress/support/commands/customization/service-dialogs.js diff --git a/app/javascript/components/common/inline-flash-message/index.jsx b/app/javascript/components/common/inline-flash-message/index.jsx new file mode 100644 index 00000000000..16eafcd91ce --- /dev/null +++ b/app/javascript/components/common/inline-flash-message/index.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { InlineNotification } from 'carbon-components-react'; + +const InlineFlashMessage = ({ message, setMessage }) => { + if (!message) return null; + + return ( + setMessage(null) : undefined + } + /> + ); +}; + +InlineFlashMessage.propTypes = { + message: PropTypes.shape({ + kind: PropTypes.oneOf(['success', 'error', 'info', 'warning']), + title: PropTypes.string, + subtitle: PropTypes.string, + }), + setMessage: PropTypes.func, +}; + +InlineFlashMessage.defaultProps = { + message: null, + setMessage: undefined, +}; + +export default InlineFlashMessage; diff --git a/app/javascript/components/date-time-picker/index.jsx b/app/javascript/components/date-time-picker/index.jsx new file mode 100644 index 00000000000..9b956bc25ad --- /dev/null +++ b/app/javascript/components/date-time-picker/index.jsx @@ -0,0 +1,102 @@ +import React, { useState } from 'react'; +import { + DatePicker, + DatePickerInput, + TimePicker, + TimePickerSelect, + SelectItem, + FormLabel, +} from 'carbon-components-react'; +import { getCurrentDate, getCurrentTimeAndPeriod } from '../service-dialog-form/helper'; + +const CustomDateTimePicker = (field) => { + const { initialData, onChange } = field; + + const [date, setDate] = useState(initialData.date || getCurrentDate); + const [time, setTime] = useState(() => initialData.time || getCurrentTimeAndPeriod().time); + const [isValid, setIsValid] = useState(true); + const [period, setPeriod] = useState(() => initialData.period || getCurrentTimeAndPeriod().period); + + const combinedDateTime = () => { + const dateTime = `${date} ${time} ${period}`; + // return new Date(dateTime).toISOString(); + return dateTime; + }; + + const handleDateChange = (newDate) => { + if (newDate.length > 0) { + const formattedDate = new Intl.DateTimeFormat('en-US', { + month: '2-digit', + day: '2-digit', + year: 'numeric', + }).format(newDate[0]); + setDate(formattedDate); + // onChange({ date: newDate, time, period }); // Call the onChange function passed as prop, passing the updated values + onChange({ value: combinedDateTime(), initialData }); + } + }; + + // Function to validate the time input + const validateTime = (value) => { + const timeRegex = /^(0[1-9]|1[0-2]):[0-5][0-9]$/; // Matches 12-hour format hh:mm + setIsValid(timeRegex.test(value)); + }; + + const handleTimeChange = (event) => { + const newTime = event.target.value; + setTime(newTime); + validateTime(newTime); + if (isValid) onChange({ value: combinedDateTime(), initialData }); + }; + + const handlePeriodChange = (event) => { + setPeriod(event.target.value); + onChange({ value: combinedDateTime(), initialData }); + }; + + + + return ( +
+ {field.label} + + + + + + + + + +
+ ); +}; + +export default CustomDateTimePicker; diff --git a/app/javascript/components/embedded-automate-entry-point/index.jsx b/app/javascript/components/embedded-automate-entry-point/index.jsx index d7e70c88c28..f0859921b00 100644 --- a/app/javascript/components/embedded-automate-entry-point/index.jsx +++ b/app/javascript/components/embedded-automate-entry-point/index.jsx @@ -38,9 +38,14 @@ const EmbeddedAutomateEntryPoint = (props) => { useEffect(() => { if (selectedValue && selectedValue.name && selectedValue.name.text) { selectedValue.name.text = textValue; + input.onChange(selectedValue); + } else if (!selectedValue || Object.keys(selectedValue).length === 0) { + // When selectedValue is empty or undefined, pass null to trigger validation + input.onChange(null); + } else { + input.onChange(selectedValue); } - input.onChange(selectedValue); - }, [textValue]); + }, [textValue, selectedValue]); return (
@@ -56,7 +61,7 @@ const EmbeddedAutomateEntryPoint = (props) => { />
- setTextValue(value.target.value)} value={textValue} /> + setTextValue(value.target.value)} value={textValue} readOnly />
@@ -75,6 +80,8 @@ const EmbeddedAutomateEntryPoint = (props) => { onClick={() => { setSelectedValue({}); setTextValue(''); + // Ensure the input change is triggered to update form state + input.onChange(null); }} />
diff --git a/app/javascript/components/service-dialog-form/component-types.js b/app/javascript/components/service-dialog-form/component-types.js new file mode 100644 index 00000000000..286b239a59a --- /dev/null +++ b/app/javascript/components/service-dialog-form/component-types.js @@ -0,0 +1,7 @@ +// // import { componentTypes as defaultComponentTypes } from '@data-driven-forms/react-form-renderer'; +// import { componentTypes as defaultComponentTypes } from '@@ddf'; + +// export const componentTypes = { +// ...defaultComponentTypes, +// DATE_TIME_PICKER: 'date-time-picker', +// }; diff --git a/app/javascript/components/service-dialog-form/data.js b/app/javascript/components/service-dialog-form/data.js new file mode 100644 index 00000000000..b8b9e0caf4b --- /dev/null +++ b/app/javascript/components/service-dialog-form/data.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { + CheckboxChecked32, RadioButtonChecked32, Time32, StringText32, TextSmallCaps32, CaretDown32, Tag32, Calendar32, +} from '@carbon/icons-react'; +import { formattedCatalogPayload } from './helper'; + +export const dragItems = { + COMPONENT: 'component', + SECTION: 'section', + FIELD: 'field', + TAB: 'tab', +}; + +/** Data needed to render the dynamic components on the left hand side of the form. */ +export const dynamicComponents = [ + { id: 1, title: 'Text Box', icon: }, + { id: 2, title: 'Text Area', icon: }, + { id: 3, title: 'Check Box', icon: }, + { id: 4, title: 'Dropdown', icon: }, + { id: 5, title: 'Radio Button', icon: }, + { id: 6, title: 'Datepicker', icon: }, + { id: 7, title: 'Timepicker', icon: }, + { id: 8, title: 'Tag Control', icon: }, +]; + +/** Function which returens the default data for a section under a tab. */ +export const defaultSectionContents = (tabId, sectionId) => ({ + tabId, + sectionId, + title: 'New Section', + fields: [], + order: 0, +}); + +/** Function which returns the default data for a tab with default section. */ +export const defaultTabContents = (tabId) => ({ + tabId, + name: tabId === 0 ? __('New Tab') : __(`New Tab ${tabId}`), + sections: [defaultSectionContents(tabId, 0)], +}); + +/** Function to create a dummy tab for creating new tabs. */ +export const createNewTab = () => ({ + tabId: 'new', + name: 'Create Tab', + sections: [], +}); + +export const tagControlCategories = async() => { + try { + const { resources } = await API.get('/api/categories?expand=resources&attributes=id,name,description,single_value,children'); + console.log("Resources: ", resources); + + return resources; + } catch (error) { + console.error('Error fetching categories:', error); + return []; + } +}; + +// data has formfields and list (as of now); no dialog related general info - this is needed +export const saveServiceDialog = (data) => { + const payload = formattedCatalogPayload(data); + // const payload = sample_create_payload(); + + const { result } = API.post('/api/service_dialogs', payload, { + skipErrors: [400], + }); + return result; +}; diff --git a/app/javascript/components/service-dialog-form/dynamic-component-chooser.jsx b/app/javascript/components/service-dialog-form/dynamic-component-chooser.jsx new file mode 100644 index 00000000000..39deb0a7d55 --- /dev/null +++ b/app/javascript/components/service-dialog-form/dynamic-component-chooser.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { dragItems } from './data'; + +/** Component to render the components list vertically on left side. + * Components can be used to drag and drop into the tab Contents */ +const DynamicComponentChooser = ({ list, onDragStartComponent }) => ( +
+ { + list.map((item, index) => ( +
onDragStartComponent(event, dragItems.COMPONENT)} + key={index.toString()} + > +
+ {item.icon} + {item.title} +
+
+ )) + } +
+); + +DynamicComponentChooser.propTypes = { + list: PropTypes.arrayOf(PropTypes.any).isRequired, + onDragStartComponent: PropTypes.func.isRequired, +}; + +export default DynamicComponentChooser; diff --git a/app/javascript/components/service-dialog-form/dynamic-field-actions.jsx b/app/javascript/components/service-dialog-form/dynamic-field-actions.jsx new file mode 100644 index 00000000000..63c6e6945e3 --- /dev/null +++ b/app/javascript/components/service-dialog-form/dynamic-field-actions.jsx @@ -0,0 +1,113 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'carbon-components-react'; +import { Close16, Edit16 } from '@carbon/icons-react'; +import { SD_ACTIONS, SD_PROP_SHAPES } from './helper'; +import EditFieldModal from './edit-field-modal'; + +/** Component to render a Field. */ +const DynamicFieldActions = ({ + componentId, fieldProps, updateFieldProps, dynamicFieldAction, fieldConfiguration, dynamicToggleAction, setCategoryData, onValueChange, +}) => { + const [{ showModal, ...editedFields }, setState] = useState({ showModal: false }); + + const onModalHide = () => setState((state) => ({ ...state, showModal: false })); + const onModalShow = () => setState((state) => ({ ...state, showModal: true })); + // const onModalApply = () => setState((state) => ({ ...state, showModal: false })); + const onModalApply = (formValues, event) => { + setState((prevState) => ({ ...prevState, showModal: false, ...formValues })); + dynamicFieldAction(event, formValues); + }; + + const onDynamicSwitchToggle = (isDynamic) => { + setState((prevState) => ({ ...prevState, dynamic: isDynamic })); + dynamicToggleAction(isDynamic); + }; + + const onCategorySelect = (cat, subCat) => { + setCategoryData(cat, subCat); + }; + + const onTimePickerChange = (dateTime) => { + setState((prevState) => ({ ...prevState, value: dateTime })); + onValueChange(dateTime); + }; + + const renderEditButton = () => ( +