diff --git a/.gitignore b/.gitignore index e082e3ff0..07d1ff8d3 100644 --- a/.gitignore +++ b/.gitignore @@ -360,6 +360,9 @@ upload-api/extracted_files* *copy* .qodo .vscode +app.json +# Snyk Security Extension - AI Rules (auto-generated) +.cursor/rules/snyk_rules.mdc *extracted_files* *MigrationData* *.zip diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index 26f28cdd4..56eb67515 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -331,8 +331,6 @@ const AdvancePropertise = (props: SchemaProps) => { }); }; - - const handleDrop = (index: number) => { if (draggedIndex === null) return; @@ -588,7 +586,7 @@ const AdvancePropertise = (props: SchemaProps) => { props?.data?.contentstackFieldUid ); }} - options={option} + options={option ?? []} placeholder="Add Content Type(s)" version="v2" isSearchable={true} @@ -673,6 +671,7 @@ const AdvancePropertise = (props: SchemaProps) => { true )) } + disabled={props?.fieldtype === 'Modular Blocks' || props?.fieldtype === 'Block'} /> )} diff --git a/ui/src/components/Common/AddStack/addStack.tsx b/ui/src/components/Common/AddStack/addStack.tsx index 2d0c78417..5d460602a 100644 --- a/ui/src/components/Common/AddStack/addStack.tsx +++ b/ui/src/components/Common/AddStack/addStack.tsx @@ -24,7 +24,7 @@ import { getCMSDataFromFile } from '../../../cmsData/cmsSelector'; import { CS_ENTRIES } from '../../../utilities/constants'; // Interface -import { AddStackCMSData, defaultAddStackCMSData } from './addStack.interface'; +import { AddStackCMSData, defaultAddStackCMSData, Errors, Stack } from './addStack.interface'; // Styles import './addStack.scss'; @@ -129,8 +129,12 @@ const AddStack = (props: any): JSX.Element => { { - const errors: any = {}; + validate={(values: Stack) => { + const errors: Errors = { + name: '', + locale: '', + description: '' + }; if (!values?.name || values?.name?.trim().length < 1) { errors.name = 'Stack name required'; } @@ -263,7 +267,7 @@ const AddStack = (props: any): JSX.Element => { version={'v2'} placeholder={addStackCMSData?.stack_locale_description} /> -
Important: The master language cannot be changed after the stack has been created.
+
Important: The default language cannot be changed after the stack has been created.
{meta?.error && meta?.touched && ( (false); const [isLoadingSaveButton, setisLoadingSaveButton] = useState(false); const [activeFilter, setActiveFilter] = useState(''); - const [isAllCheck, setIsAllCheck] = useState(false); + const [isAllCheck, setIsAllCheck] = useState(false); + const [isResetFetch, setIsResetFetch] = useState(false); /** ALL HOOKS Here */ @@ -496,6 +497,20 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: }; }, []); + /** + * Debounces a function call by delaying its execution until after the specified delay has elapsed since the last invocation. + * @param fn - The function to debounce + * @param delay - The delay in milliseconds to wait before executing the function + * @returns A debounced version of the function + */ + const debounce = (fn: (...args: any[]) => any, delay: number | undefined) => { + let timeoutId: string | number | NodeJS.Timeout | undefined; + return (...args: any[]) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn(...args), delay); + }; + }; + const checkAndUpdateField = ( item: any, value: any, @@ -1342,14 +1357,14 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: @@ -1998,11 +2013,14 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: handleDropdownState })); - const handleResetContentType = async () => { + const handleResetContentType = debounce(async () => { + // Prevent duplicate clicks + if (isResetFetch) return; + const orgId = selectedOrganisation?.value; const projectID = projectId; setIsDropDownChanged(false); - + setIsResetFetch(true); const updatedRows: FieldMapType[] = tableData?.map?.((row) => { return { ...row, @@ -2056,6 +2074,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: }); if (status === 200) { + setIsResetFetch(false); const updatedContentMapping = { ...newMigrationData?.content_mapping?.content_type_mapping }; delete updatedContentMapping[selectedContentType?.contentstackUid]; @@ -2094,7 +2113,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: notificationContent: { text: data?.message }, notificationProps: { position: 'bottom-center', - hideProgressBar: false + hideProgressBar: true }, type: 'success' }); @@ -2111,112 +2130,30 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } catch (error) { console.error(error); return error; + } finally { + // Re-enable icon after API completes + setIsResetFetch(false); } } - }; - - const handleCTDeleted = async (isContentType: boolean, contentTypes: ContentTypeList[]) => { - const updatedContentTypeMapping = Object.fromEntries( - Object.entries(newMigrationData?.content_mapping?.content_type_mapping || {})?.filter( - ([key]) => !selectedContentType?.contentstackUid?.includes?.(key) - ) - ); - - const orgId = selectedOrganisation?.value; - const projectID = projectId; - setIsDropDownChanged(false); - - const updatedRows: FieldMapType[] = tableData.map((row) => { - return { ...row, contentstackFieldType: row?.backupFieldType }; - }); - setTableData(updatedRows); - setSelectedEntries(updatedRows); - - const dataCs = { - contentTypeData: { - status: selectedContentType?.status, - id: selectedContentType?.id, - projectId: projectId, - otherCmsTitle: otherCmsTitle, - otherCmsUid: selectedContentType?.otherCmsUid, - isUpdated: true, - updateAt: new Date(), - contentstackTitle: selectedContentType?.contentstackTitle, - contentstackUid: selectedContentType?.contentstackUid, - fieldMapping: updatedRows - } - }; - let newstate = {}; - setContentTypeMapped((prevState: ContentTypeMap) => { - const newState = { ...prevState }; - - delete newState[selectedContentType?.contentstackUid ?? '']; - newstate = newState; - - return newstate; - }); - - if (orgId && selectedContentType) { - try { - const { data, status } = await resetToInitialMapping( - orgId, - projectID, - selectedContentType?.id ?? '', - dataCs - ); - - setExistingField({}); - setContentTypeSchema([]); - setOtherContentType({ - label: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack`, - value: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack` - }); - - if (status === 200) { - const resetCT = filteredContentTypes?.map?.(ct => - ct?.id === selectedContentType?.id ? { ...ct, status: data?.data?.status } : ct - ); - setFilteredContentTypes(resetCT); - setContentTypes(resetCT); - - try { - await updateContentMapper(orgId, projectID, { ...newstate }); - } catch (err) { - console.error(err); - return err; - } - - } - } catch (error) { - console.error(error); - return error; - } - } - - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - content_mapping: { - ...newMigrationData?.content_mapping, - [isContentType ? 'existingCT' : 'existingGlobal']: contentTypes, - content_type_mapping: updatedContentTypeMapping - - } - - } - dispatch(updateNewMigrationData(newMigrationDataObj)); - - } + }, 1500); +/** + * Retrieves existing content types for a given project. + * @returns An array containing the retrieved content types or global fields based on condition if itContentType true and if existing content type or global field id is passed then returns an object containing title, uid and schema of that particular content type or global field. + */ + const handleFetchContentType = debounce(async () => { + // Prevent duplicate clicks + if (isResetFetch) return; - /** - * Retrieves existing content types for a given project. - * @returns An array containing the retrieved content types or global fields based on condition if itContentType true and if existing content type or global field id is passed then returns an object containing title, uid and schema of that particular content type or global field. - */ - const handleFetchContentType = async () => { + setIsResetFetch(true); + if (isContentType) { try { + + const { data, status } = await getExistingContentTypes(projectId, otherContentType?.id ?? ''); if (status == 201 && data?.contentTypes?.length > 0) { (otherContentType?.id === data?.selectedContentType?.uid) && setsCsCTypeUpdated(false); + setIsResetFetch(false); (otherContentType?.id && otherContentType?.label !== data?.selectedContentType?.title && data?.selectedContentType?.title) && setOtherContentType({ @@ -2238,7 +2175,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: notificationContent: { text: 'Content Types fetched successfully' }, notificationProps: { position: 'bottom-center', - hideProgressBar: false + hideProgressBar: true }, type: 'success' }); @@ -2261,6 +2198,9 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } catch (error) { console.error(error); return error; + } finally { + // Re-enable icon after API completes + setIsResetFetch(false); } } else { try { @@ -2268,6 +2208,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: if (status == 201 && data?.globalFields?.length > 0) { (otherContentType?.id === data?.selectedGlobalField?.uid) && setsCsCTypeUpdated(false); + setIsResetFetch(false); (otherContentType?.id && otherContentType?.label !== data?.selectedGlobalField?.title && data?.selectedGlobalField?.title) && setOtherContentType({ @@ -2316,6 +2257,9 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } catch (error) { console.error(error); return error; + } finally { + // Re-enable icon after API completes + setIsResetFetch(false); } } @@ -2345,7 +2289,103 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: }); } - } + }, 1500); + + /** + * Handles the deletion of a content type or global field. + * @param isContentType - Whether the content type is a content type or global field. + * @param contentTypes - The content types to delete. + */ + const handleCTDeleted = async (isContentType: boolean, contentTypes: ContentTypeList[]) => { + // Prevent duplicate clicks + if (isResetFetch) return; + + const updatedContentTypeMapping = Object.fromEntries( + Object.entries(newMigrationData?.content_mapping?.content_type_mapping || {})?.filter( + ([key]) => !selectedContentType?.contentstackUid?.includes?.(key) + ) + ); + + const orgId = selectedOrganisation?.value; + const projectID = projectId; + setIsDropDownChanged(false); + + const updatedRows: FieldMapType[] = tableData.map((row) => { + return { ...row, contentstackFieldType: row?.backupFieldType }; + }); + setTableData(updatedRows); + setSelectedEntries(updatedRows); + + const dataCs = { + contentTypeData: { + status: selectedContentType?.status, + id: selectedContentType?.id, + projectId: projectId, + otherCmsTitle: otherCmsTitle, + otherCmsUid: selectedContentType?.otherCmsUid, + isUpdated: true, + updateAt: new Date(), + contentstackTitle: selectedContentType?.contentstackTitle, + contentstackUid: selectedContentType?.contentstackUid, + fieldMapping: updatedRows + } + }; + let newstate = {}; + setContentTypeMapped((prevState: ContentTypeMap) => { + const newState = { ...prevState }; + + delete newState[selectedContentType?.contentstackUid ?? '']; + newstate = newState; + + return newstate; + }); + + if (orgId && selectedContentType) { + try { + const { data, status } = await resetToInitialMapping( + orgId, + projectID, + selectedContentType?.id ?? '', + dataCs + ); + + setExistingField({}); + setContentTypeSchema([]); + setOtherContentType({ + label: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack`, + value: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack` + }); + + if (status === 200) { + const resetCT = filteredContentTypes?.map?.(ct => + ct?.id === selectedContentType?.id ? { ...ct, status: data?.data?.status } : ct + ); + setFilteredContentTypes(resetCT); + setContentTypes(resetCT); + + try { + await updateContentMapper(orgId, projectID, { ...newstate }); + } catch (err) { + console.error(err); + return err; + } + } + } catch (error) { + console.error(error); + return error; + } + } + + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + content_mapping: { + ...newMigrationData?.content_mapping, + [isContentType ? 'existingCT' : 'existingGlobal']: contentTypes, + content_type_mapping: updatedContentTypeMapping + } + } + dispatch(updateNewMigrationData(newMigrationDataObj)); + }; const columns = [ { @@ -2641,7 +2681,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: @@ -2650,7 +2690,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: + size='small' onClick={handleResetContentType} disabled={isResetFetch}> ), diff --git a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx index 361e63d3d..0c49145ed 100644 --- a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx @@ -30,8 +30,6 @@ export type ExistingFieldType = { * @returns {JSX.Element | null} - Returns a JSX element if empty, otherwise null. */ const Mapper = ({ - key, - uid, cmsLocaleOptions, handleLangugeDelete, options, @@ -252,7 +250,6 @@ const Mapper = ({ selectedValue: { label: string; value: string }, index: number, ) => { - const csLocaleKey = existingField?.[index]?.value; const selectedLocaleKey = selectedValue?.value; const existingLabel = existingField?.[index]; //const selectedLocaleKey = selectedMappings[index]; @@ -392,7 +389,7 @@ const Mapper = ({ {locale?.value === 'master_locale' ? (
@@ -531,7 +528,6 @@ const LanguageMapper = ({stack, uid} :{ stack : IDropDown, uid : string}) => { const [currentStack, setCurrentStack] = useState(stack); const [previousStack, setPreviousStack] = useState(); const [isStackChanged, setisStackChanged] = useState(false); - const [stackValue, setStackValue] = useState(stack?.value) const prevStackRef:any = useRef(null); diff --git a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx index c5dfa427b..1f6dab984 100644 --- a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx @@ -84,7 +84,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { const [isError, setIsError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [placeholder] = useState('Select a stack'); - const [localePlaceholder, setlocalePlaceholder ] = useState('Master Locale will be set after stack selection'); + const [localePlaceholder, setlocalePlaceholder ] = useState('Default Locale will be set after stack selection'); const newMigrationDataRef = useRef(newMigrationData); const [isStackLoading, setIsStackLoading] = useState(true); @@ -97,7 +97,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { setlocalePlaceholder('') } else{ - setlocalePlaceholder('Master Locale will be set after stack selection'); + setlocalePlaceholder('Default Locale will be set after stack selection'); } },[selectedStack]) @@ -334,10 +334,10 @@ const LoadStacks = (props: LoadFileFormatProps) => {
-