From 6d9d8d8fbb0608f7fc96a01eceb4b67d9d404cf0 Mon Sep 17 00:00:00 2001 From: AishDani Date: Wed, 5 Mar 2025 11:04:45 +0530 Subject: [PATCH 1/3] feat:created language-mapper component in UI --- .../Actions/LoadLanguageMapper.tsx | 227 ++++++++++++++++++ .../Actions/singleRowComp.tsx | 77 ++++++ .../DestinationStack/Actions/tableHeader.tsx | 28 +++ .../DestinationStack/DestinationStack.scss | 93 ++++++- ui/src/components/DestinationStack/index.tsx | 36 ++- ui/src/services/api/stacks.service.ts | 23 ++ ui/src/services/api/upload.service.ts | 22 ++ 7 files changed, 503 insertions(+), 3 deletions(-) create mode 100644 ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx create mode 100644 ui/src/components/DestinationStack/Actions/singleRowComp.tsx create mode 100644 ui/src/components/DestinationStack/Actions/tableHeader.tsx diff --git a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx new file mode 100644 index 000000000..a14a34da6 --- /dev/null +++ b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx @@ -0,0 +1,227 @@ +import { Button, CircularLoader, Field, FieldLabel, Help, Icon, Info, MiniScrollableTable, Paragraph, Select, Tooltip } from "@contentstack/venus-components"; +import { useEffect, useState } from "react"; +import TableHeader from "./tableHeader"; +import SingleRowComp from "./singleRowComp"; +import { useSelector } from "react-redux"; +import { RootState } from "../../../store"; +import { getLocales } from "../../../services/api/upload.service"; +import { getStackLocales } from "../../../services/api/stacks.service"; +import { all } from "axios"; + +const Mapper = ({ cmsLocaleOptions, handleLangugeDelete, options, masterLocale }: { cmsLocaleOptions: Array, handleLangugeDelete: any, options:any, masterLocale:string }) => { + + + const handleSelectedLocale = (data: any, index: number)=>{ + return; + + } + + const csValue = null; + return ( + <> + { cmsLocaleOptions?.length > 0 ? + cmsLocaleOptions?.map((locale:any, index:any)=>( +
+ handleSelectedLocale(data, 1)} + options={[]} + placeholder={ + 'select language' + } + //isSearchable + //menuShouldScrollIntoView + multiDisplayLimit={5} + //menuPortalTarget={document.querySelector(".config-wrapper")} + width="270px" + version="v2" + hideSelectedOptions={true} + noOptionsMessage={() => ''} + /> +
+ {locale?.value !== 'master_locale' && + + { + handleLangugeDelete(index, locale)} + } + hover + hoverType="secondary" + shadow="medium" + /> + } +
+ +
+ + )) : + } + //version="v2" + content="No langauges configured" + type="light"/> + } + + + + ); + +} + +const LanguageMapper = () => { + const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); + const [newEntry, setnewEntry] = useState(false); + const [options, setoptions] = useState([{ + label: '1', + value: 'hello' + }]); + const [cmsLocaleOptions, setcmsLocaleOptions] = useState<{ label: string; value: string }[]>([]); + + + const selectedOrganisation = useSelector((state:RootState)=>state?.authentication?.selectedOrganisation); + const [isLoading, setisLoading] = useState(false); + + useEffect(() => { + const fetchData = async () => { + try { + setisLoading(true); + const res = await fetchLocales(); + console.log("res ---> ", res?.data?.locales); + const allLocales:any = Object.keys(res?.data?.locales || {}).map((key) => ({ + label: key, + value: key + })); + + setoptions(allLocales); + setcmsLocaleOptions((prevList: any) => { + + const newLabel = newMigrationData?.destination_stack?.selectedStack?.master_locale; + + const isPresent = prevList.some((item: any) => item.value === 'master_locale'); + + if (!isPresent) { + return [ + ...prevList, + { + label: newLabel, + value: 'master_locale', + }, + ]; + } + + return prevList; + }); + + setisLoading(false); + } catch (error) { + console.error("Error fetching locales:", error); + } + }; + + fetchData(); + }, []); + + const fetchLocales = async () => { + return await getStackLocales( + newMigrationData?.destination_stack?.selectedOrg?.value, + newMigrationData?.destination_stack?.selectedStack?.value + ); + }; + const addRowComp = () =>{ + setnewEntry(true); + setcmsLocaleOptions((prevList: any) => [ + ...prevList, // Keep existing elements + { + label: `${prevList.length + 1}`, // Generate new label + value: 'name' + } + ]); + + + } + + const handleDeleteLocale= (id:number, locale: any) => { + setcmsLocaleOptions((prevList) => { + return prevList.filter((item:any) => item.label !== locale.label)}); + + } + + return( +
+ {isLoading ? + + : + <> + } + rowComponent={ + } + // footerComponent={ + // + + // } + type="Secondary" + /> + + + + } + +
+) + +} + +export default LanguageMapper; \ No newline at end of file diff --git a/ui/src/components/DestinationStack/Actions/singleRowComp.tsx b/ui/src/components/DestinationStack/Actions/singleRowComp.tsx new file mode 100644 index 000000000..7a1bd711c --- /dev/null +++ b/ui/src/components/DestinationStack/Actions/singleRowComp.tsx @@ -0,0 +1,77 @@ +/* Import React modules */ +import React, { useState } from "react"; +/* Import other node modules */ +import { Select, Tooltip, Icon } from "@contentstack/venus-components"; +/* Import our modules */ +//import { TypeMultiSelectObj, TypeSingleRowComp } from "../../common/types"; +//import localeTexts from "../locale/en-us"; +/* Import node module CSS */ +/* Import our CSS */ + +const SingleRowComp: React.FC = function () { + const [contentTypes, setContentTypes] = useState(); + const [metaFields, setMetaFields] = useState(); + + const saveData = () => { + if (contentTypes?.length && metaFields?.length) { + const contentTypesData = contentTypes.map( + (item: any) => item?.label + ); + const metaFieldsData = metaFields.map( + (item: any) => item?.label + ); + // setList([ + // ...list, + // { content_types: contentTypesData, meta_fields: metaFieldsData }, + // ]); + // setContentTypes([]); + // setMetaFields([]); + // removeRowComp(); + } + }; + + return ( +
+ + + + +
+ ); +}; + +export default SingleRowComp; diff --git a/ui/src/components/DestinationStack/Actions/tableHeader.tsx b/ui/src/components/DestinationStack/Actions/tableHeader.tsx new file mode 100644 index 000000000..22280ec07 --- /dev/null +++ b/ui/src/components/DestinationStack/Actions/tableHeader.tsx @@ -0,0 +1,28 @@ +import { FieldLabel } from "@contentstack/venus-components"; + +const TableHeader = ({cms}:{cms:string}) => { + return ( +
+ + Contentstack + + +
+ + {cms} + +
+
+ ); +} +export default TableHeader; \ No newline at end of file diff --git a/ui/src/components/DestinationStack/DestinationStack.scss b/ui/src/components/DestinationStack/DestinationStack.scss index cfe630cec..b56cfdee7 100644 --- a/ui/src/components/DestinationStack/DestinationStack.scss +++ b/ui/src/components/DestinationStack/DestinationStack.scss @@ -45,7 +45,14 @@ border-radius: 4px; } .destination-stack-container { - padding: 20px; + + padding-left: 25px !important; + padding-top: 26px; + padding-right: 30px; + + overflow-y: auto; + overflow-x: hidden; + max-height: 80vh; .migration-vertical-stepper { padding: 24px 30px; border: 1px solid $color-brand-secondary-lightest; @@ -156,6 +163,17 @@ color: $color-brand-warning-medium; font-size: $size-font-small; } + .table-content-section { + .Field { + &.Field--full { + .field-content { + border-left: none !important; + } + } + } + } + + } @@ -183,3 +201,76 @@ align-items: center; padding: $px-10; } +.lang-container{ + //display: flex; + //flex-wrap: wrap; + //padding: $px-20; + //padding-bottom: 50px; +} +.language-mapper{ + //padding-top:20px; + //margin-top: 20px; + //padding: 24px 30px; + // height: 150px; + border: 1px solid $color-brand-secondary-lightest; + border-radius: 4px; + background-color: $color-brand-white-base; + white-space: nowrap; + //z-index: 999 !important; + // min-height: auto; + // max-height: none; + // overflow: visible; + // flex-grow: 1; + height: auto; + margin-bottom: 120px; + //padding: 24px 30px, 24px, 30px; + gap: 24px; + //margin-bottom: 40px; + //padding: 10px 50px; + .table-content-section { + .Field { + &.Field--full { + .field-content { + width: 100% !important; + //border-left: none !important; + } + } + } + } + .span{ + padding: 10px 20px; + } + .field-label{ + padding: 10px 10px; + } +} + +.language-title{ + margin-top: 10px; + //margin-left: 2px; +} +.info-lang{ + display: flex; +} +.help-text{ + margin-top: -5px; + margin-left: 0px; +} +.mini-table{ + margin-left: 40px; + margin-top: 10px; + //width: 120px; +} +.lang-container{ + margin-bottom: 30px; + margin-left: 10px; + margin-top: 10px; + display: flex; +} +.info-tag{ + max-width: 500px; +} +.info-language-mapper{ + margin-bottom: 120px; + +} \ No newline at end of file diff --git a/ui/src/components/DestinationStack/index.tsx b/ui/src/components/DestinationStack/index.tsx index 148971126..dc3e04fb9 100644 --- a/ui/src/components/DestinationStack/index.tsx +++ b/ui/src/components/DestinationStack/index.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import AutoVerticalStepper from '../Stepper/VerticalStepper/AutoVerticalStepper'; import { getDestinationStackSteps } from './StepperSteps'; -import { CircularLoader } from '@contentstack/venus-components'; +import { CircularLoader, FieldLabel, HelpText, Icon, Info, Tooltip } from '@contentstack/venus-components'; import { CS_ENTRIES } from '../../utilities/constants'; import { DEFAULT_DESTINATION_STACK_DATA, @@ -14,6 +14,7 @@ import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; import { RootState } from '../../store'; import { updateMigrationData } from '../../store/slice/migrationDataSlice'; import { AutoVerticalStepperRef } from '../LegacyCms'; +import LanguageMapper from './Actions/LoadLanguageMapper'; type DestinationStackComponentProps = { isCompleted: boolean; @@ -103,7 +104,9 @@ const DestinationStackComponent = ({ ) : (
{migrationData?.destinationStackData?.title}
- +
+ + +
+
+ +
+
Language configuration
+ + + + +
+ Contentstack and {newMigrationData?.legacy_cms?.selectedCms?.parent} Languages Mapping + + {/* + Language configuration + */} + {newMigrationData?.destination_stack?.selectedStack?.value ? + +
+ + +
: + } + //version="v2" + content="Please select the stack to processed with Language mapping" + type="light"/>} + )} diff --git a/ui/src/services/api/stacks.service.ts b/ui/src/services/api/stacks.service.ts index 429d92a1d..0e4b1e443 100644 --- a/ui/src/services/api/stacks.service.ts +++ b/ui/src/services/api/stacks.service.ts @@ -43,3 +43,26 @@ export const getStackStatus = async (orgId: string, data: string) => { } } }; + +export const getStackLocales = async(orgId: string, api_key: string) => { +try { + + const updatedOptions = { + headers: { + app_token: getDataFromLocalStorage('app_token'), + //stack_api_key: api_key + } + }; + + + const res = await getCall(`${API_VERSION}/org/${orgId}/locales`, updatedOptions); + return res; + + } catch (error) { + if (error instanceof Error) { + throw new Error(`${error.message}`); + } else { + throw new Error('Unknown error'); + } + } +} \ No newline at end of file diff --git a/ui/src/services/api/upload.service.ts b/ui/src/services/api/upload.service.ts index 159aa43c3..691d27075 100644 --- a/ui/src/services/api/upload.service.ts +++ b/ui/src/services/api/upload.service.ts @@ -80,3 +80,25 @@ export const getRestrictedKeywords = async () => { } } } + +export const getLocales = async(api_key : string) => { + try { + const options = { + headers: { + 'authtoken': getDataFromLocalStorage('app_token'), + 'api_key': api_key , + //'affix': affix, + }, + + }; + + return await getCall(`https://api.contentstack.io/v3/locales`, options) + + } catch (error) { + if (error instanceof Error) { + throw new Error(`${error.message}`); + } else { + throw new Error('Unknown error'); + } + } +} \ No newline at end of file From 055f5c32d379f871cc333a60694ad5857d563a84 Mon Sep 17 00:00:00 2001 From: AishDani Date: Mon, 17 Mar 2025 00:10:23 +0530 Subject: [PATCH 2/3] refactor:language-mapper --- .../Actions/LoadLanguageMapper.tsx | 661 ++++++++++++------ .../DestinationStack/DestinationStack.scss | 34 +- ui/src/components/DestinationStack/index.tsx | 58 +- ui/src/context/app/app.interface.ts | 7 +- ui/src/pages/Migration/index.tsx | 82 ++- ui/src/services/api/migration.service.ts | 11 +- ui/src/services/api/stacks.service.ts | 2 +- ui/src/utilities/functions.ts | 2 +- 8 files changed, 585 insertions(+), 272 deletions(-) diff --git a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx index a14a34da6..7a2c7eb6e 100644 --- a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx @@ -1,227 +1,474 @@ -import { Button, CircularLoader, Field, FieldLabel, Help, Icon, Info, MiniScrollableTable, Paragraph, Select, Tooltip } from "@contentstack/venus-components"; -import { useEffect, useState } from "react"; -import TableHeader from "./tableHeader"; -import SingleRowComp from "./singleRowComp"; -import { useSelector } from "react-redux"; -import { RootState } from "../../../store"; -import { getLocales } from "../../../services/api/upload.service"; -import { getStackLocales } from "../../../services/api/stacks.service"; -import { all } from "axios"; - -const Mapper = ({ cmsLocaleOptions, handleLangugeDelete, options, masterLocale }: { cmsLocaleOptions: Array, handleLangugeDelete: any, options:any, masterLocale:string }) => { - - - const handleSelectedLocale = (data: any, index: number)=>{ - return; +import { + Button, + CircularLoader, + Icon, + Info, + MiniScrollableTable, + Select, + Tooltip +} from '@contentstack/venus-components'; +import { useEffect, useState } from 'react'; +import TableHeader from './tableHeader'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../../store'; +import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice'; +import { INewMigration } from '../../../context/app/app.interface'; +const Mapper = ({ + cmsLocaleOptions, + handleLangugeDelete, + options, + masterLocale, + sourceOptions +}: { + cmsLocaleOptions: Array; + handleLangugeDelete: any; + options: any; + masterLocale: string; + sourceOptions: any; +}) => { + const [selectedMappings, setSelectedMappings] = useState<{ [key: string]: string }>({}); + const [existingField, setExistingField] = useState({}); + const [existingLocale, setexistingLocale] = useState({}); + const [selectedCsOptions, setselectedCsOption] = useState([]); + const [selectedSourceOption, setselectedSourceOption] = useState([]); + const [csOptions, setcsOptions] = useState(options); + const [sourceoptions, setsourceoptions] = useState(sourceOptions); + const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); + const dispatch = useDispatch(); + const [placeholder] = useState('Select language'); + + useEffect(() => { + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + destination_stack: { + ...newMigrationData.destination_stack, + localeMapping: selectedMappings + } + }; + + dispatch(updateNewMigrationData(newMigrationDataObj)); + }, [selectedMappings]); + + useEffect(() => { + if (selectedCsOptions?.length === 0) { + setcsOptions(options); } - - const csValue = null; - return ( - <> - { cmsLocaleOptions?.length > 0 ? - cmsLocaleOptions?.map((locale:any, index:any)=>( -
- handleSelectedCsLocale(key, index, 'csLocale')} + options={csOptions} + placeholder={placeholder} + isSearchable + maxMenuHeight={150} + multiDisplayLimit={5} + menuPortalTarget={document.querySelector('.language-mapper')} + width="270px" + version="v2" + hideSelectedOptions={true} + isClearable={true} + isDisabled={true} // Ensure it's disabled + className="select-container" + noOptionsMessage={() => ''} + /> +
+ + ) : ( + + handleSelectedSourceLocale(data, index, 'sourceLocale', locale) + } + options={sourceOptions} + placeholder={placeholder} + isSearchable + //menuShouldScrollIntoView + multiDisplayLimit={5} + //menuPortalTarget={document.querySelector(".config-wrapper")} + menuPortalTarget={document.querySelector('.language-mapper')} + width="270px" + maxMenuHeight={150} + version="v2" + hideSelectedOptions={true} + isClearable={true} + className="select-container" + /> */ + handleSelectedLocale(data, 1)} - options={[]} - placeholder={ - 'select language' - } - //isSearchable - //menuShouldScrollIntoView - multiDisplayLimit={5} - //menuPortalTarget={document.querySelector(".config-wrapper")} - width="270px" - version="v2" - hideSelectedOptions={true} - noOptionsMessage={() => ''} - /> -
- {locale?.value !== 'master_locale' && - - { - handleLangugeDelete(index, locale)} - } - hover - hoverType="secondary" - shadow="medium" - /> - } -
+ /> + } +
+ {locale?.value !== 'master_locale' && ( + + { + handleLanguageDeletaion(index, locale); + }} + hover + hoverType="secondary" + shadow="medium" + /> + + )} +
+ + )) + ) : ( + } + //version="v2" + content="No langauges configured" + type="light" + /> + )} + + ); +}; - +const LanguageMapper = () => { + const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); + const [newEntry, setnewEntry] = useState(false); + const [options, setoptions] = useState([]); + const [cmsLocaleOptions, setcmsLocaleOptions] = useState<{ label: string; value: string }[]>([]); + const [sourceLocales, setsourceLocales] = useState([]); - )) : - } - //version="v2" - content="No langauges configured" - type="light"/> - } - - - - ); + const selectedOrganisation = useSelector( + (state: RootState) => state?.authentication?.selectedOrganisation + ); + const [isLoading, setisLoading] = useState(false); -} - -const LanguageMapper = () => { - const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - const [newEntry, setnewEntry] = useState(false); - const [options, setoptions] = useState([{ - label: '1', - value: 'hello' - }]); - const [cmsLocaleOptions, setcmsLocaleOptions] = useState<{ label: string; value: string }[]>([]); - - - const selectedOrganisation = useSelector((state:RootState)=>state?.authentication?.selectedOrganisation); - const [isLoading, setisLoading] = useState(false); - - useEffect(() => { - const fetchData = async () => { - try { - setisLoading(true); - const res = await fetchLocales(); - console.log("res ---> ", res?.data?.locales); - const allLocales:any = Object.keys(res?.data?.locales || {}).map((key) => ({ - label: key, - value: key - })); - - setoptions(allLocales); - setcmsLocaleOptions((prevList: any) => { - - const newLabel = newMigrationData?.destination_stack?.selectedStack?.master_locale; - - const isPresent = prevList.some((item: any) => item.value === 'master_locale'); - - if (!isPresent) { - return [ - ...prevList, - { - label: newLabel, - value: 'master_locale', - }, - ]; - } - - return prevList; - }); - - setisLoading(false); - } catch (error) { - console.error("Error fetching locales:", error); - } - }; - - fetchData(); - }, []); - - const fetchLocales = async () => { - return await getStackLocales( - newMigrationData?.destination_stack?.selectedOrg?.value, - newMigrationData?.destination_stack?.selectedStack?.value - ); + useEffect(() => { + const fetchData = async () => { + try { + setisLoading(true); + const allLocales: any = Object.keys( + newMigrationData?.destination_stack?.csLocale || {} + ).map((key) => ({ + label: key, + value: key + })); + const sourceLocale = newMigrationData?.destination_stack?.sourceLocale?.map((item) => ({ + label: item, + value: item + })); + setsourceLocales(sourceLocale); + + setoptions(allLocales); + setcmsLocaleOptions((prevList: any) => { + const newLabel = newMigrationData?.destination_stack?.selectedStack?.master_locale; + + const isPresent = prevList.some((item: any) => item.value === 'master_locale'); + + if (!isPresent) { + return [ + ...prevList, + { + label: newLabel, + value: 'master_locale' + } + ]; + } + + return prevList; + }); + setisLoading(false); + } catch (error) { + console.error('Error fetching locales:', error); + } }; - const addRowComp = () =>{ - setnewEntry(true); - setcmsLocaleOptions((prevList: any) => [ - ...prevList, // Keep existing elements - { - label: `${prevList.length + 1}`, // Generate new label - value: 'name' - } - ]); - - } + fetchData(); + }, []); - const handleDeleteLocale= (id:number, locale: any) => { - setcmsLocaleOptions((prevList) => { - return prevList.filter((item:any) => item.label !== locale.label)}); + // const fetchLocales = async () => { + // return await getStackLocales(newMigrationData?.destination_stack?.selectedOrg?.value); + // }; + const addRowComp = () => { + setnewEntry(true); + setcmsLocaleOptions((prevList: any) => [ + ...prevList, // Keep existing elements + { + label: `${prevList.length + 1}`, // Generate new label + value: '' + } + ]); + }; - } - - return( + const handleDeleteLocale = (id: number, locale: any) => { + setcmsLocaleOptions((prevList) => { + return prevList.filter((item: any) => item.label !== locale.label); + }); + }; + + return (
- {isLoading ? - - : - <> - } - rowComponent={ - } - // footerComponent={ - // - - // } - type="Secondary" - /> - - - - } - -
-) + {isLoading ? ( + + ) : ( + <> + + } + rowComponent={ + + } + // footerComponent={ + // -} + // } + type="Secondary" + /> + + + )} + + ); +}; -export default LanguageMapper; \ No newline at end of file +export default LanguageMapper; diff --git a/ui/src/components/DestinationStack/DestinationStack.scss b/ui/src/components/DestinationStack/DestinationStack.scss index b56cfdee7..a4650b1a9 100644 --- a/ui/src/components/DestinationStack/DestinationStack.scss +++ b/ui/src/components/DestinationStack/DestinationStack.scss @@ -45,7 +45,6 @@ border-radius: 4px; } .destination-stack-container { - padding-left: 25px !important; padding-top: 26px; padding-right: 30px; @@ -172,10 +171,6 @@ } } } - - - - } } .action-summary-wrapper { @@ -193,7 +188,6 @@ } } - .locale-container { display: flex; background-color: $color-base-white-5; @@ -201,17 +195,17 @@ align-items: center; padding: $px-10; } -.lang-container{ +.lang-container { //display: flex; //flex-wrap: wrap; //padding: $px-20; //padding-bottom: 50px; } -.language-mapper{ +.language-mapper { //padding-top:20px; //margin-top: 20px; //padding: 24px 30px; - // height: 150px; + // height: 150px; border: 1px solid $color-brand-secondary-lightest; border-radius: 4px; background-color: $color-brand-white-base; @@ -237,40 +231,40 @@ } } } - .span{ + .span { padding: 10px 20px; } - .field-label{ + .field-label { padding: 10px 10px; } } -.language-title{ +.language-title { margin-top: 10px; //margin-left: 2px; } -.info-lang{ +.info-lang { display: flex; + gap: 10px; } -.help-text{ +.help-text { margin-top: -5px; margin-left: 0px; } -.mini-table{ +.mini-table { margin-left: 40px; margin-top: 10px; //width: 120px; } -.lang-container{ +.lang-container { margin-bottom: 30px; margin-left: 10px; margin-top: 10px; display: flex; } -.info-tag{ +.info-tag { max-width: 500px; } -.info-language-mapper{ +.info-language-mapper { margin-bottom: 120px; - -} \ No newline at end of file +} diff --git a/ui/src/components/DestinationStack/index.tsx b/ui/src/components/DestinationStack/index.tsx index dc3e04fb9..ec16c76b9 100644 --- a/ui/src/components/DestinationStack/index.tsx +++ b/ui/src/components/DestinationStack/index.tsx @@ -2,11 +2,17 @@ import { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import AutoVerticalStepper from '../Stepper/VerticalStepper/AutoVerticalStepper'; import { getDestinationStackSteps } from './StepperSteps'; -import { CircularLoader, FieldLabel, HelpText, Icon, Info, Tooltip } from '@contentstack/venus-components'; +import { + CircularLoader, + HelpText, + Icon, + Info, + Tooltip +} from '@contentstack/venus-components'; import { CS_ENTRIES } from '../../utilities/constants'; import { DEFAULT_DESTINATION_STACK_DATA, - IDestinationStackComponent, + IDestinationStackComponent } from '../../context/app/app.interface'; import './DestinationStack.scss'; import { MigrationResponse } from '../../services/api/service.interface'; @@ -124,34 +130,36 @@ const DestinationStackComponent = ({
-
Language configuration
- - - - +
Language configuration
+ + +
- Contentstack and {newMigrationData?.legacy_cms?.selectedCms?.parent} Languages Mapping - - {/* - Language configuration - */} - {newMigrationData?.destination_stack?.selectedStack?.value ? - -
- - -
: + + Contentstack and {newMigrationData?.legacy_cms?.selectedCms?.parent} Languages Mapping + + + {newMigrationData?.destination_stack?.selectedStack?.value ? ( +
+ +
+ ) : ( } - //version="v2" - content="Please select the stack to processed with Language mapping" - type="light"/>} - + className="info-language-mapper col-12 info-tag" + icon={} + version="v2" + content="Please select the stack to proceed with language mapping" + type="light" + /> + )} )} ); }; -export default DestinationStackComponent; \ No newline at end of file +export default DestinationStackComponent; diff --git a/ui/src/context/app/app.interface.ts b/ui/src/context/app/app.interface.ts index a65d447a3..583aff8b7 100644 --- a/ui/src/context/app/app.interface.ts +++ b/ui/src/context/app/app.interface.ts @@ -173,7 +173,9 @@ export interface IDestinationStack { selectedStack: IDropDown; stackArray: IDropDown[]; migratedStacks: string[]; - + sourceLocale: string[]; + localeMapping: {}; + csLocale: string[]; } export interface IContentMapper { existingGlobal: ContentTypeList[] | (() => ContentTypeList[]); @@ -337,6 +339,9 @@ export const DEFAULT_DESTINATION_STACK: IDestinationStack = { selectedStack: DEFAULT_DROPDOWN, stackArray: [], migratedStacks: [], + sourceLocale: [], + localeMapping: {}, + csLocale: [] }; export const DEFAULT_CONTENT_MAPPER: IContentMapper = { diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index cb1deffb9..70613b22c 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -9,7 +9,22 @@ import { RootState } from '../../store'; import { updateMigrationData, updateNewMigrationData } from '../../store/slice/migrationDataSlice'; // Services -import { getMigrationData, updateCurrentStepData, updateLegacyCMSData, updateDestinationStack, updateAffixData, fileformatConfirmation, updateFileFormatData, affixConfirmation, updateStackDetails, getExistingContentTypes, getExistingGlobalFields, startMigration, updateMigrationKey } from '../../services/api/migration.service'; +import { + getMigrationData, + updateCurrentStepData, + updateLegacyCMSData, + updateDestinationStack, + updateAffixData, + fileformatConfirmation, + updateFileFormatData, + affixConfirmation, + updateStackDetails, + getExistingContentTypes, + getExistingGlobalFields, + startMigration, + updateMigrationKey, + updateLocaleMapper +} from '../../services/api/migration.service'; import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; // Utilities @@ -23,7 +38,14 @@ import { DEFAULT_IFLOWSTEP, IFlowStep } from '../../components/Stepper/FlowStepper/flowStep.interface'; -import { IDropDown, INewMigration, ICMSType, ILegacyCMSComponent, DEFAULT_CMS_TYPE, TestStacks } from '../../context/app/app.interface'; +import { + IDropDown, + INewMigration, + ICMSType, + ILegacyCMSComponent, + DEFAULT_CMS_TYPE, + TestStacks +} from '../../context/app/app.interface'; import { ContentTypeSaveHandles } from '../../components/ContentMapper/contentMapper.interface'; import { ICardType } from "../../components/Common/Card/card.interface"; import { ModalObj } from '../../components/Modal/modal.interface'; @@ -38,6 +60,7 @@ import TestMigration from '../../components/TestMigration'; import MigrationExecution from '../../components/MigrationExecution'; import SaveChangesModal from '../../components/Common/SaveChangesModal'; import { getMigratedStacks } from '../../services/api/project.service'; +import { getStackLocales } from '../../services/api/stacks.service'; type StepperComponentRef = { handleStepChange: (step: number) => void; @@ -116,7 +139,6 @@ const Migration = () => { } catch (error) { // return error; console.log(error); - } } @@ -163,7 +185,7 @@ const Migration = () => { const data = await getMigrationData(selectedOrganisation?.value, params?.projectId ?? ''); const migratedstacks = await getMigratedStacks(selectedOrganisation?.value, projectId ); - + const csLocales = await getStackLocales(selectedOrganisation?.value); if (data) { setIsLoading(false); setProjectData(data?.data); @@ -233,8 +255,10 @@ const Migration = () => { destination_stack: { selectedOrg: selectedOrganisationData, selectedStack: selectedStackData, - stackArray:[], + stackArray: [], migratedStacks: migratedstacks?.data?.destinationStacks, + sourceLocale: projectData?.source_locales, + csLocale: csLocales?.data?.locales }, content_mapping: { isDropDownChanged: false, @@ -413,40 +437,66 @@ const Migration = () => { const handleOnClickDestinationStack = async (event: MouseEvent) => { setIsLoading(true); - if(isCompleted && !isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value)){ + const hasNonEmptyMapping = + newMigrationData?.destination_stack?.localeMapping && + Object.values(newMigrationData?.destination_stack?.localeMapping)?.some( + (value) => value !== '' || value !== null || value !== undefined + ); + console.log(hasNonEmptyMapping); + + const master_locale:any = {}; + const locales: any= {}; + Object.entries(newMigrationData?.destination_stack?.localeMapping).forEach(([key, value]) => { + if (key.includes("master_locale")) { + master_locale[key.replace("-master_locale", "")] = value; + } else { + locales[key] = value; + } + }); + if ( + isCompleted && + !isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value) && + hasNonEmptyMapping + ) { event?.preventDefault(); //Update Data in backend await updateDestinationStack(selectedOrganisation?.value, projectId, { stack_api_key: newMigrationData?.destination_stack?.selectedStack?.value }); - await updateStackDetails(selectedOrganisation?.value, projectId,{ - label:newMigrationData?.destination_stack?.selectedStack?.label, - value:newMigrationData?.destination_stack?.selectedStack?.value, - master_locale:newMigrationData?.destination_stack?.selectedStack?.master_locale, - created_at:newMigrationData?.destination_stack?.selectedStack?.created_at, + await updateStackDetails(selectedOrganisation?.value, projectId, { + label: newMigrationData?.destination_stack?.selectedStack?.label, + value: newMigrationData?.destination_stack?.selectedStack?.value, + master_locale: newMigrationData?.destination_stack?.selectedStack?.master_locale, + created_at: newMigrationData?.destination_stack?.selectedStack?.created_at, isNewStack: newMigrationData?.destination_stack?.selectedStack?.isNewStack - }) + }); + await updateLocaleMapper(projectId,{'master_locale': master_locale, locales:locales}) const res = await updateCurrentStepData(selectedOrganisation?.value, projectId); if (res?.status === 200) { handleStepChange(2); setIsLoading(false); const url = `/projects/${projectId}/migration/steps/3`; navigate(url, { replace: true }); - } - else{ + } else { setIsLoading(false); Notification({ - notificationContent: { text: res?.data?.error?.message}, + notificationContent: { text: res?.data?.error?.message }, type: 'error' }); } - } else{ + } else if (!isCompleted) { setIsLoading(false); Notification({ notificationContent: { text: 'Please select a stack to proceed further' }, type: 'warning' }); + } else if (! hasNonEmptyMapping) { + setIsLoading(false); + Notification({ + notificationContent: { text: 'Please complete the language mapping to preceed futher' }, + type: 'warning' + }); } }; diff --git a/ui/src/services/api/migration.service.ts b/ui/src/services/api/migration.service.ts index b3d7dd7d9..dba28037b 100644 --- a/ui/src/services/api/migration.service.ts +++ b/ui/src/services/api/migration.service.ts @@ -330,4 +330,13 @@ export const updateMigrationKey = async (orgId: string, projectId: string) => { } catch (error) { return error; } -}; \ No newline at end of file +}; + +export const updateLocaleMapper = async(projectId: string, data: any) => { + try { + return await postCall( + `${API_VERSION}/migration/updateLocales/${projectId}`, data, options); + } catch (error) { + return error; + } +} \ No newline at end of file diff --git a/ui/src/services/api/stacks.service.ts b/ui/src/services/api/stacks.service.ts index 0e4b1e443..dd6b67497 100644 --- a/ui/src/services/api/stacks.service.ts +++ b/ui/src/services/api/stacks.service.ts @@ -44,7 +44,7 @@ export const getStackStatus = async (orgId: string, data: string) => { } }; -export const getStackLocales = async(orgId: string, api_key: string) => { +export const getStackLocales = async(orgId: string) => { try { const updatedOptions = { diff --git a/ui/src/utilities/functions.ts b/ui/src/utilities/functions.ts index d7d9ceabb..5e12ecf1d 100644 --- a/ui/src/utilities/functions.ts +++ b/ui/src/utilities/functions.ts @@ -145,7 +145,7 @@ export const getDays = (day: string | number | Date) => { }; export const isEmptyString = (str: string | undefined) => - str === undefined || str === null || str.trim().length < 1; + str === undefined || str === null || str?.trim()?.length < 1; export const shortName = (name: string) => { if (name && name.length > 25) { From 12a561aa116947e877a7916d48ae8ea9f0fd4932 Mon Sep 17 00:00:00 2001 From: AishDani Date: Mon, 17 Mar 2025 01:01:33 +0530 Subject: [PATCH 3/3] refactor:added optional chaining and comments in code of language-mapper --- .../Actions/LoadLanguageMapper.tsx | 122 +++++++++--------- .../DestinationStack/Actions/tableHeader.tsx | 2 + 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx index 7a2c7eb6e..23c302040 100644 --- a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx @@ -1,3 +1,4 @@ +// Import library import { Button, CircularLoader, @@ -14,24 +15,35 @@ import { RootState } from '../../../store'; import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice'; import { INewMigration } from '../../../context/app/app.interface'; +export type ExistingFieldType = { + [key: string]: { label: string ; value: string }; +}; + +/** + * A functional component that displays selection for language mapping. + * + * @param {Array<{ label: string; value: string }>} cmsLocaleOptions - An array to dispaly number of locales select. + * @param {Function} handleLangugeDelete - a function to delete the mapping. + * @param {Array<{ label: string; value: string }>} options - option array of contentstack locales. + * @param {Array<{ label: string; value: string }>} sourceOptions - option array of source locales. + * @returns {JSX.Element | null} - Returns a JSX element if empty, otherwise null. + */ const Mapper = ({ cmsLocaleOptions, handleLangugeDelete, options, - masterLocale, sourceOptions }: { - cmsLocaleOptions: Array; - handleLangugeDelete: any; - options: any; - masterLocale: string; - sourceOptions: any; + cmsLocaleOptions: Array<{ label: string; value: string }>; + handleLangugeDelete: (index: number, locale: { label: string; value: string }) => void; + options: Array<{ label: string; value: string }>; + sourceOptions: Array<{ label: string; value: string }>; }) => { const [selectedMappings, setSelectedMappings] = useState<{ [key: string]: string }>({}); - const [existingField, setExistingField] = useState({}); - const [existingLocale, setexistingLocale] = useState({}); - const [selectedCsOptions, setselectedCsOption] = useState([]); - const [selectedSourceOption, setselectedSourceOption] = useState([]); + const [existingField, setExistingField] = useState({}); + const [existingLocale, setexistingLocale] = useState({}); + const [selectedCsOptions, setselectedCsOption] = useState([]); + const [selectedSourceOption, setselectedSourceOption] = useState([]); const [csOptions, setcsOptions] = useState(options); const [sourceoptions, setsourceoptions] = useState(sourceOptions); const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); @@ -42,7 +54,7 @@ const Mapper = ({ const newMigrationDataObj: INewMigration = { ...newMigrationData, destination_stack: { - ...newMigrationData.destination_stack, + ...newMigrationData?.destination_stack, localeMapping: selectedMappings } }; @@ -64,26 +76,26 @@ const Mapper = ({ useEffect(() => { const formattedoptions = options?.filter( - (item: any) => !selectedCsOptions?.some((selected: any) => selected?.value === item.value) + (item: { label: string; value: string }) => !selectedCsOptions?.some((selected: string) => selected === item?.value) ); const adjustedOptions = sourceOptions?.filter( - (item: any) => !selectedSourceOption?.some((selected: any) => selected === item?.label) + (item: { label: string; value: string }) => !selectedSourceOption?.some((selected: string) => selected === item?.label) ); setcsOptions(formattedoptions); setsourceoptions(adjustedOptions); }, [selectedCsOptions, selectedSourceOption]); useEffect(() => { - setExistingField((prevExisting: any) => { + setExistingField((prevExisting: ExistingFieldType) => { const updatedExisting = { ...prevExisting }; - cmsLocaleOptions.forEach((locale: any, index: number) => { - if (locale.value === 'master_locale' && !updatedExisting[index]) { + cmsLocaleOptions?.forEach((locale: { label: string; value: string }, index: number) => { + if (locale?.value === 'master_locale' && !updatedExisting?.[index]) { setSelectedMappings((prev) => ({ ...prev, [`${locale?.label}-master_locale`]: '' })); - updatedExisting[index] = { label: locale.label, value: `${locale?.label}-master_locale` }; + updatedExisting[index] = { label: locale?.label, value: `${locale?.label}-master_locale` }; } }); @@ -91,15 +103,16 @@ const Mapper = ({ }); }, [cmsLocaleOptions]); + // function for change select value const handleSelectedCsLocale = ( - selectedValue: any, + selectedValue: { label: string; value: string }, index: number, type: 'csLocale' | 'sourceLocale' ) => { const selectedLocaleKey = selectedValue?.value; if (!selectedLocaleKey) return; - setExistingField((prevOptions: any) => { + setExistingField((prevOptions: ExistingFieldType) => { const updatedOptions = { ...prevOptions, [index]: { label: selectedValue?.label || null, value: selectedValue?.label } @@ -113,8 +126,8 @@ const Mapper = ({ return updatedOptions; }); setselectedCsOption((prevSelected) => { - const newSelectedOptions: any = prevSelected?.filter((item) => item !== selectedValue?.label); - const newValue: any = selectedValue?.label; + const newSelectedOptions: string[] = prevSelected?.filter((item) => item !== selectedValue?.label); + const newValue: string = selectedValue?.label; if (!newSelectedOptions?.includes(newValue)) { newSelectedOptions.push(newValue); } @@ -134,30 +147,30 @@ const Mapper = ({ }); }; const handleSelectedSourceLocale = ( - selectedValue: any, + selectedValue: { label: string; value: string }, index: number, type: 'csLocale' | 'sourceLocale', label: any ) => { - const csLocaleKey = existingField[index]?.value; + const csLocaleKey = existingField?.[index]?.value; //const selectedLocaleKey = selectedMappings[index]; if (!selectedValue?.label) { setselectedSourceOption((prevSelected) => - prevSelected.filter((item) => item !== existingField[index]?.label) + prevSelected?.filter((item) => item !== existingField?.[index]?.label) ); } - setexistingLocale((prevOptions: any) => { - const updatedOptions = { + setexistingLocale((prevOptions: ExistingFieldType) => { + const updatedOptions: ExistingFieldType = { ...prevOptions, [index]: { label: selectedValue?.label || null, value: selectedValue?.label } }; // Ensure selectedOption only contains values that exist in updatedOptions setselectedSourceOption((prevSelected) => - prevSelected.filter((item) => - Object.values(updatedOptions).some((opt: any) => opt.label === item) + prevSelected?.filter((item) => + Object.values(updatedOptions)?.some((opt: {label: string, value: string}) => opt?.label === item) ) ); @@ -165,10 +178,10 @@ const Mapper = ({ }); setselectedSourceOption((prevSelected) => { - const newSelectedOptions: any = prevSelected?.filter((item) => item !== selectedValue?.label); - const newValue: any = selectedValue?.label; + const newSelectedOptions = prevSelected?.filter((item) => item !== selectedValue?.label); + const newValue: string = selectedValue?.label; if (!newSelectedOptions?.includes(newValue)) { - newSelectedOptions.push(newValue); + newSelectedOptions?.push(newValue); } return newSelectedOptions; }); @@ -179,7 +192,8 @@ const Mapper = ({ [csLocaleKey]: selectedValue?.label || '' })); }; - const handleLanguageDeletaion = (index: number, locale: any) => { + const handleLanguageDeletaion = (index: number, locale: {label: string, value: string}) => { + // Remove item at index from existingField let csLocale = ''; @@ -196,7 +210,7 @@ const Mapper = ({ ); // Remove item at index from existingLocale - setexistingLocale((prevLocales: any) => { + setexistingLocale((prevLocales: ExistingFieldType) => { if (!prevLocales) return {}; const updatedOptions = { ...prevLocales }; // Create a shallow copy delete updatedOptions[index]; // Remove the key @@ -213,18 +227,11 @@ const Mapper = ({ handleLangugeDelete(index, locale); }; - console.log( - 'Updated Mappings:', - existingField, - existingLocale, - selectedMappings, - selectedCsOptions - ); return ( <> {cmsLocaleOptions?.length > 0 ? ( - cmsLocaleOptions?.map((locale: any, index: any) => ( + cmsLocaleOptions?.map((locale: any, index: number) => (
{locale?.value === 'master_locale' ? ( { + onChange={(key: { label: string; value: string }) => { handleSelectedCsLocale(key, index, 'csLocale'); }} options={csOptions} @@ -293,7 +300,7 @@ const Mapper = ({ /> */