From 772f2cf2f00bdbf649f9dce3ae9875abc4856b89 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Fri, 19 Jul 2024 13:16:27 +0530 Subject: [PATCH 1/9] [CMG-258] --- ui/src/components/ContentMapper/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 291427249..ea37fadd2 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -635,15 +635,17 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { isClearable={false} options={option} isDisabled={ - data?.ContentstackFieldType === 'group' || + data?.otherCmsType === "Group" || data?.otherCmsField === 'title' || - data?.otherCmsField === 'url' + data?.otherCmsField === 'url' || + data?.otherCmsType === "reference" } /> - {data?.ContentstackFieldType !== 'group' && + {data?.otherCmsType !== 'Group' && data?.otherCmsField !== 'title' && data?.otherCmsField !== 'url' && + data?.otherCmsType !== 'reference' && Date: Tue, 23 Jul 2024 18:32:38 +0530 Subject: [PATCH 2/9] refactor:added group nesting options --- .../ContentMapper/contentMapper.interface.ts | 1 + ui/src/components/ContentMapper/index.tsx | 420 +++++++++++------- 2 files changed, 259 insertions(+), 162 deletions(-) diff --git a/ui/src/components/ContentMapper/contentMapper.interface.ts b/ui/src/components/ContentMapper/contentMapper.interface.ts index cdc6ba0af..723e63474 100644 --- a/ui/src/components/ContentMapper/contentMapper.interface.ts +++ b/ui/src/components/ContentMapper/contentMapper.interface.ts @@ -91,6 +91,7 @@ export interface ContentTypesSchema { data_type?: 'text' | 'number' | 'isodate' | 'json' | 'file' | 'reference' | 'group' | 'boolean' | 'link'; field_metadata?: FieldMetadata; enum?: any; + schema?: ContentTypesSchema[] } // export interface ContentTypesSchema { // [key: string]: ContentTypeField; diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 9f60fdd52..01d7617b7 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -32,7 +32,7 @@ import { updateMigrationData, updateNewMigrationData } from '../../store/slice/m // Utilities import { CS_ENTRIES, CONTENT_MAPPING_STATUS, STATUS_ICON_Mapping } from '../../utilities/constants'; -import { validateArray } from '../../utilities/functions'; +import { isEmptyString, validateArray } from '../../utilities/functions'; // Interface import { DEFAULT_CONTENT_MAPPING_DATA, INewMigration } from '../../context/app/app.interface'; @@ -62,6 +62,7 @@ import SaveChangesModal from '../Common/SaveChangesModal'; // Styles import './index.scss'; import { MigrationResponse } from '../../services/api/service.interface'; +import { schemaType } from '../SchemaModal/schemaModal.interface'; const dummy_obj:any = { 'single_line_text':{ label : 'Single Line Textbox', @@ -266,6 +267,8 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { const [showFilter, setShowFilter] = useState(false); const [filteredContentTypes, setFilteredContentTypes] = useState([]) const [count, setCount] = useState(0); + const [nestedList, setNestedList] = useState([]); + const [disabledOptions, setDisabledOptions] = useState>(new Set()); /** ALL HOOKS Here */ const { projectId = '' } = useParams(); @@ -408,6 +411,8 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { setTableData(newTableData || []); setTotalCounts(data?.count); + + generateSourceGroupSchema(data?.fieldMapping); } catch (error) { console.error('fetchData -> error', error); } @@ -685,10 +690,13 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { const handleFieldChange = (selectedValue: FieldTypes, rowIndex: string) => { setisDropDownCHanged(true); + const previousSelectedValue = exstingField[rowIndex]?.label; + setexsitingField((prevOptions) => ({ ...prevOptions, [rowIndex]: { label: selectedValue?.label, value: selectedValue?.value } })); + setadvancePropertise({ validationRegex: selectedValue?.value?.format, Mandatory: selectedValue?.value?.mandatory, @@ -697,12 +705,24 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { NonLocalizable: selectedValue?.value?.non_localizable }); - if (isDropDownChanged && isContentTypeSaved) { - setSelectedOptions((prevSelected) => { - const newValue = selectedValue?.label; - return prevSelected?.includes(newValue) ? prevSelected : [...prevSelected, newValue]; - }); - } + setDisabledOptions((prevDisabledOptions) => { + const newDisabledOptions = new Set(prevDisabledOptions); + newDisabledOptions.add(selectedValue?.label); + return newDisabledOptions; + }); + + //add selected option to array if it is not mapped to any other field + setSelectedOptions((prevSelected) => { + const newSelectedOptions = prevSelected.filter( + (item) => item !== previousSelectedValue + ); + const newValue = selectedValue?.label; + if (!newSelectedOptions.includes(newValue)) { + newSelectedOptions.push(newValue); + } + return newSelectedOptions; + }); + const updatedRows = tableData.map((row) => { if (row?.uid === rowIndex) { @@ -725,11 +745,156 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { setSelectedEntries(updatedRows as FieldMapType[]); }; + //function to generate group schema structure of source cms + const generateSourceGroupSchema = ( schema:any) =>{ + + let groupId = ''; + const data: any = []; + schema?.forEach((field:any) => { + if (field?.ContentstackFieldType === 'group') { + groupId = field?.uid; + data?.push({ ...field, child: [] }); + } else { + if (field?.uid?.startsWith(groupId + '.')) { + const obj = data[data?.length - 1]; + if (Object.prototype.hasOwnProperty.call(obj, 'child')) { + obj?.child?.push(field); + } else { + obj.child = [field]; + } + } else { + data.push({ ...field, child: [] }); + } + } + }); + setNestedList(data); + } + + //utility function to create option object + function getMatchingOption(value:any, matchFound:boolean, label:any) { + return matchFound ? { label, value, isDisabled: selectedOptions.includes(label) } : null + } + + //utility function to map the source cms field type to content type field type + function checkConditions(fieldTypeToMatch:any, value:any, data:any) { + + const fieldTypes = new Set(['number', 'isodate', 'file', 'reference', 'boolean', 'group', 'link','global_field']); + switch (fieldTypeToMatch) { + case 'text': + return ( + (value?.uid !== 'title' && + data?.uid !== 'title') && + (value?.uid !== 'url' && + data?.uid !== 'url') && + !fieldTypes.has(value?.data_type || '') && + !value?.field_metadata?.multiline && + !value?.enum && + !value?.field_metadata?.allow_rich_text && + !value?.field_metadata?.allow_json_rte && + !value?.field_metadata?.markdown + ); + case 'multiline': + return value?.field_metadata?.multiline === true; + case 'url': + return value?.uid === 'url'; + case 'file': + return value?.data_type === 'file'; + case 'number': + return value?.data_type === 'number' && !value?.enum; + case 'isodate': + return value?.data_type === 'isodate'; + case 'json': + return value?.data_type === 'json'; + case 'enum': + return 'enum' in value; + case 'allow_rich_text': + return value?.field_metadata?.allow_rich_text === true; + case 'Group': + return value?.data_type === 'group'; + case 'reference': + return value?.data_type === 'reference'; + case 'boolean': + return value?.data_type === 'boolean'; + default: + return false; + } + } + + //function to process the nested group structure present in contentstack content type + const processSchema = ( + value: any, + data: any, + array: any, + OptionsForRow: any[], + fieldsOfContentstack: any, + currentDisplayName = '' + ) => { + // Update the current display name with the current value's display name + const updatedDisplayName = currentDisplayName ? `${currentDisplayName} > ${value?.display_name}` : value?.display_name; + + if (value?.data_type === 'group') { + + // Check and process the group itself + if (data?.otherCmsType === 'Group' && checkConditions('Group', value, data)) { + + OptionsForRow.push(getMatchingOption(value, true, updatedDisplayName)); + + // Process nested groups within this group + for (const key of value.schema || []) { + if (key?.data_type === 'group') { + processSchema(key, data, array, OptionsForRow, fieldsOfContentstack, updatedDisplayName); + } + } + } + // Process nested schemas within the current group + for (const item of array) { + const fieldTypeToMatch = fieldsOfContentstack[item?.otherCmsType as keyof Mapping]; + if (item.id === data?.id) { + for (const key of value.schema || []) { + if (checkConditions(fieldTypeToMatch, key, item)) { + OptionsForRow.push(getMatchingOption(key, true, `${updatedDisplayName} > ${key.display_name}` || '')); + } + + // Recursively process nested groups + if (key?.data_type === 'group') { + processSchema(key, data, array, OptionsForRow, fieldsOfContentstack, updatedDisplayName); + } + } + } + } + } else { + + const fieldTypeToMatch = fieldsOfContentstack[data?.otherCmsType as keyof Mapping]; + if (!array.some((item :any)=> item.id === data?.id) && checkConditions(fieldTypeToMatch, value, data)) { + OptionsForRow.push(getMatchingOption(value, true, updatedDisplayName || '')); + } + + // Process nested schemas if value is not a group + for (const item of array) { + if (item.id === data?.id) { + for (const key of value.schema || []) { + if (checkConditions(fieldTypeToMatch, key, item)) { + OptionsForRow.push(getMatchingOption(key, true, `${updatedDisplayName} > ${key.display_name}` || '')); + } + + // Recursively process nested groups + if (key?.data_type === 'group') { + processSchema(key, data, array, OptionsForRow, fieldsOfContentstack, updatedDisplayName); + } + } + } + } + } + + return OptionsForRow; + }; + + + const SelectAccessorOfColumn = (data: FieldMapType) => { - // object for storing select options according to mapped field + // Fetch options for the current row from dummy_obj based on backupFieldType( empty stack options) const OptionsForEachRow = dummy_obj?.[data?.backupFieldType]?.options; - - // Mapping of field types + const fieldsOfContentstack: Mapping = { 'Single Line Textbox': 'text', 'Single-Line Text': 'text', @@ -738,136 +903,70 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { 'multiline': 'multiline', 'HTML Rich text Editor': 'allow_rich_text', 'JSON Rich Text Editor': 'json', - 'Rich Text':'json', + 'Rich Text': 'json', 'Group': 'Group', 'URL': 'url', 'file': 'file', - 'Image':'file', + 'Image': 'file', 'number': 'number', - 'Integer':'number', + 'Integer': 'number', 'Date': 'isodate', 'boolean': 'boolean', - 'Checkbox':'boolean', + 'Checkbox': 'boolean', 'link': 'link', 'reference': 'reference', 'dropdown': 'enum', - 'Droplist':'enum', - 'radio': 'enum', - //'CheckBox': 'enum' + 'Droplist': 'enum', + 'radio': 'enum' }; - - //array of options if exsting content type has selected - const OptionsForRow: optionsType[] = []; - + + const OptionsForRow: any[] = []; + + // If OtherContentType label and contentTypesList are present, set the contentTypeSchema if (OtherContentType?.label && contentTypesList) { - const ContentType: any = contentTypesList?.find( + const ContentType:any = contentTypesList?.find( ({ title }) => title === OtherContentType?.label ); - setContentTypeSchema(ContentType?.schema) + setContentTypeSchema(ContentType?.schema); } - - // If content type schema is available and valid + if (contentTypeSchema && validateArray(contentTypeSchema)) { const fieldTypeToMatch = fieldsOfContentstack[data?.otherCmsType as keyof Mapping]; - - //check if UID of source cms field is matching with contentstack content type fields + + //check if UID of souce field is matching to exsting content type field UID for (const value of contentTypeSchema) { - if (data?.uid === value?.uid) { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); + if (data?.uid === value?.uid || (data?.uid === value?.uid && data?.otherCmsType === value?.data_type)) { + OptionsForRow.push({ label: value?.display_name, value, isDisabled: false }); break; } } - // If UID does not match then check for field type - if(OptionsForRow.length === 0){ - for (const value of contentTypeSchema) { - const fieldTypes = new Set(['number', 'isodate', 'file', 'reference', 'boolean', 'group', 'link']); - - switch (fieldTypeToMatch) { - case 'text': - if ( - value?.uid !== 'title' && - value?.uid !=='url' && - !fieldTypes.has(value?.data_type || '') && - !value?.field_metadata?.multiline && - !value?.enum && - !value?.field_metadata?.allow_rich_text && - !value?.field_metadata?.allow_json_rte && - !value?.field_metadata?.markdown - ) { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'multiline': - if (value?.field_metadata?.multiline === true) { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'url': - if (value?.uid === 'url') { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'file': - if (value?.data_type === 'file') { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'number': - if (value?.data_type === 'number' && !value?.enum) { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'isodate': - if (value?.data_type === 'isodate') { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'json': - if (value?.data_type === 'json') { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'enum': - if ('enum' in value) { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'allow_rich_text': - if (value?.field_metadata?.allow_rich_text === true) { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'Group': - if (value?.data_type === 'group') { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; - case 'reference': - if (value?.data_type === 'reference') { - OptionsForRow.push({ label: value?.display_name, value: value, isDisabled: false }); - } - break; + + if (OptionsForRow.length === 0) { + for (const value of contentTypeSchema) { - default: - OptionsForRow.push({ - label: 'No matches found', - value: { 'No matches found': '' }, - isDisabled: false - }); - break; + const groupArray = nestedList.filter(item => + item.child && item.child.some(e => e.id === data?.id) + ); + + const array = groupArray[0]?.child || [] + if(value.data_type === 'group'){ + processSchema(value, data, array, OptionsForRow, fieldsOfContentstack) + } + else if (!array.some(item => item.id === data?.id) && checkConditions(fieldTypeToMatch, value, data)) { + + OptionsForRow.push(getMatchingOption(value, true, value?.display_name || '')); + + } } - - }} + } } - - // Variable to store length of options - const selectedOption = OptionsForRow?.length; - - //variable to store the options if exsting contentstack content type is not selected - let option:any; + + const selectedOption = OptionsForRow.length; + + let option: any; if (Array.isArray(OptionsForEachRow)) { - option = OptionsForEachRow.map((option) => ({ + option = OptionsForEachRow.map((option) => ({ label: option, value: option, })); @@ -876,60 +975,59 @@ const ContentMapper = ({projectData}:ContentMapperComponentProps) => { label, value, })); - }else{ - option = [{ label: OptionsForEachRow, value: OptionsForEachRow }] + } else { + option = [{ label: OptionsForEachRow, value: OptionsForEachRow }]; } - - const OptionValue: any = - OptionsForRow?.length === 1 && - //disable url, title and group fields - (OptionsForRow?.[0]?.value?.uid === 'url' || OptionsForRow?.[0]?.value?.uid === 'title' ||OptionsForRow?.[0]?.value?.uid === 'group') - ? { - label: OptionsForRow?.[0]?.value?.display_name, - value: OptionsForRow?.[0]?.value, - isDisabled: true - } - : OptionsForRow?.length === 0 - ? { - label: dummy_obj[data?.ContentstackFieldType]?.label, - value: dummy_obj[data?.ContentstackFieldType]?.label, - isDisabled: data?.ContentstackFieldType === 'text' || - data?.ContentstackFieldType === 'group' || - data?.ContentstackFieldType === 'url' - } - : { - label: `${selectedOption} matches`, - value: `${selectedOption} matches`, - isDisabled: false - }; - // Adjust the options based on whether existing contentstack content type is selected - const adjustedOptions = OptionsForRow.length === 0 ? option - : OptionsForRow.map((option: optionsType) => ({ - ...option, - isDisabled: selectedOptions?.includes(option?.label ?? '') - })); + const OptionValue: any = + OptionsForRow.length === 1 && + (OptionsForRow[0]?.value?.uid === 'url' || OptionsForRow[0]?.value?.uid === 'title' || OptionsForRow[0]?.value?.data_type === 'group') + ? { + label: OptionsForRow[0]?.value?.display_name, + value: OptionsForRow[0]?.value, + isDisabled: true + } + : (OptionsForRow.length === 0 || (OptionsForRow.length > 0 && OptionsForRow.every((item)=>item.isDisabled) + && ! exstingField[data?.uid] )) + ? { + label: dummy_obj[data?.ContentstackFieldType]?.label, + value: dummy_obj[data?.ContentstackFieldType]?.label, + isDisabled: data?.ContentstackFieldType === 'text' || + data?.ContentstackFieldType === 'group' || + data?.ContentstackFieldType === 'url' + } + : { + label: `${selectedOption} matches`, + value: `${selectedOption} matches`, + isDisabled: false + }; + const adjustedOptions = (OptionsForRow.length === 0 && !contentTypeSchema) ? option : + (OptionsForRow.length > 0 && OptionsForRow.every((item)=>item.isDisabled) && OptionValue.label === dummy_obj[data?.ContentstackFieldType]?.label) ? [] + : OptionsForRow.map((option: optionsType) => ({ + ...option, + isDisabled: selectedOptions.includes(option?.label ?? '') + })); + + return (