diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index 757db739..775ced39 100644 --- a/api/src/constants/index.ts +++ b/api/src/constants/index.ts @@ -70,8 +70,8 @@ export const HTTP_TEXTS = { "Reseting the content mapping is restricted. Please verify the status and review preceding actions.", CONTENTMAPPER_NOT_FOUND: "Sorry, the requested content mapper id does not exists.", - ADMIN_LOGIN_ERROR: - "Sorry, You Don't have admin access in any of the Organisation" + ADMIN_LOGIN_ERROR: + "Sorry, You Don't have admin access in any of the Organisation", }; export const HTTP_RESPONSE_HEADERS = { @@ -127,11 +127,11 @@ export const PREDEFINED_STATUS = [ export const PREDEFINED_STEPS = [1, 2, 3, 4, 5]; export const NEW_PROJECT_STATUS = { - 0: 0, //DRAFT - 1: 1, //READY_TO_TEST - 2: 2, //TESTING_IN_PROGRESS - 3: 3, //READY_FOR_MIGRATION - 4: 4, //MIGRATION_IN_PROGRESS - 5: 5, //MIGRATION_SUCCESSFUL - 6: 6 //MIGRATION_TERMINATED -}; \ No newline at end of file + 0: 0, //DRAFT + 1: 1, //READY_TO_TEST + 2: 2, //TESTING_IN_PROGRESS + 3: 3, //READY_FOR_MIGRATION + 4: 4, //MIGRATION_IN_PROGRESS + 5: 5, //MIGRATION_SUCCESSFUL + 6: 6, //MIGRATION_TERMINATED +}; diff --git a/api/src/controllers/projects.contentMapper.controller.ts b/api/src/controllers/projects.contentMapper.controller.ts index eb0c6ea2..797d5a9b 100644 --- a/api/src/controllers/projects.contentMapper.controller.ts +++ b/api/src/controllers/projects.contentMapper.controller.ts @@ -37,6 +37,14 @@ const resetContentType = async (req: Request, res: Response): Promise => { // res.status(200).json(resp); // }; +const getSingleContentTypes = async ( + req: Request, + res: Response +): Promise => { + const resp = await contentMapperService.getSingleContentTypes(req); + res.status(201).json(resp); +}; + export const contentMapperController = { getContentTypes, getFieldMapping, @@ -45,4 +53,5 @@ export const contentMapperController = { putContentTypeFields, resetContentType, // removeMapping, + getSingleContentTypes }; diff --git a/api/src/database.ts b/api/src/database.ts index 0e7d331a..0f4d162c 100644 --- a/api/src/database.ts +++ b/api/src/database.ts @@ -8,7 +8,6 @@ const connectToDatabase = async () => { fs.mkdirSync("./database"); } logger.info("successfully connecting to Low DB"); - } catch (error) { logger.error("Error while connecting to Low DB:", error); process.exit(1); diff --git a/api/src/models/FieldMapper.ts b/api/src/models/FieldMapper.ts index b4662188..f37f6bff 100644 --- a/api/src/models/FieldMapper.ts +++ b/api/src/models/FieldMapper.ts @@ -2,11 +2,11 @@ import { JSONFile } from "lowdb/node"; import LowWithLodash from "../utils/lowdb-lodash.utils.js"; interface Advanced { - validationRegex: string, - Mandatory:boolean, - Multiple:boolean, - Unique:boolean, - NonLocalizable:boolean + validationRegex: string; + Mandatory: boolean; + Multiple: boolean; + Unique: boolean; + NonLocalizable: boolean; } interface FieldMapper { @@ -21,7 +21,7 @@ interface FieldMapper { isDeleted: boolean; backupFieldType: string; refrenceTo: { uid: string; title: string }; - advanced:Advanced + advanced: Advanced; }[]; } diff --git a/api/src/routes/contentMapper.routes.ts b/api/src/routes/contentMapper.routes.ts index 57d90bbc..1fc8ba35 100644 --- a/api/src/routes/contentMapper.routes.ts +++ b/api/src/routes/contentMapper.routes.ts @@ -36,5 +36,11 @@ router.put( "/resetFields/:orgId/:projectId/:contentTypeId", asyncRouter(contentMapperController.resetContentType) ); +//get Single contenttype data +router.get( + "/:projectId/:contentTypeUid", + asyncRouter(contentMapperController.getSingleContentTypes) +); + export default router; diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index 749390f1..6076dd4f 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -8,7 +8,7 @@ import { HTTP_TEXTS, HTTP_CODES, STEPPER_STEPS, - NEW_PROJECT_STATUS + NEW_PROJECT_STATUS, } from "../constants/index.js"; import logger from "../utils/logger.js"; import { config } from "../config/index.js"; @@ -256,8 +256,7 @@ const updateContentType = async (req: Request) => { NEW_PROJECT_STATUS[4], ].includes(project.status) || project.current_step < STEPPER_STEPS.CONTENT_MAPPING - ) - { + ) { logger.error( getLogMessage( srcFun, @@ -635,6 +634,47 @@ const removeMapping = async (projectId: string) => { ); } }; +const getSingleContentTypes = async (req: Request) => { + const projectId = req?.params?.projectId; + const contentTypeUID = req?.params?.contentTypeUid; + const { token_payload } = req.body; + + const authtoken = await getAuthtoken( + token_payload?.region, + token_payload?.user_id + ); + await ProjectModelLowdb.read(); + const project = ProjectModelLowdb.chain + .get("projects") + .find({ id: projectId }) + .value(); + const stackId = project?.destination_stack_id; + + const [err, res] = await safePromise( + https({ + method: "GET", + url: `${config.CS_API[ + token_payload?.region as keyof typeof config.CS_API + ]!}/content_types/${contentTypeUID}`, + headers: { + api_key: stackId, + authtoken: authtoken, + }, + }) + ); + + if (err) + return { + data: err.response.data, + status: err.response.status, + }; + + return { + title: res?.data?.content_type?.title, + uid: res?.data?.content_type?.uid, + schema: res?.data?.content_type?.schema, + }; +}; export const contentMapperService = { putTestData, @@ -645,4 +685,5 @@ export const contentMapperService = { resetToInitialMapping, resetAllContentTypesMapping, removeMapping, + getSingleContentTypes }; diff --git a/api/src/services/org.service.ts b/api/src/services/org.service.ts index 0eed96e9..e71dbfc5 100644 --- a/api/src/services/org.service.ts +++ b/api/src/services/org.service.ts @@ -312,19 +312,19 @@ const getStackLocal = async (token_payload: any, data: any) => { status: err.response.status, }; } - let localesArr:any =[] - res?.data?.locales.map((lang:any)=>{ + let localesArr: any = []; + res?.data?.locales.map((lang: any) => { return localesArr.push({ - code:lang.code, - name:lang.name - }) - }) + code: lang.code, + name: lang.name, + }); + }); let obj = { name: stack.name, api_key: stack.api_key, master_locale: stack.master_locale, - locales: localesArr + locales: localesArr, }; stacks.push(obj); } diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index 81ddbe54..e091c690 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -9,7 +9,7 @@ import { HTTP_TEXTS, HTTP_CODES, STEPPER_STEPS, - NEW_PROJECT_STATUS + NEW_PROJECT_STATUS, } from "../constants/index.js"; import { config } from "../config/index.js"; import { getLogMessage, safePromise } from "../utils/index.js"; @@ -479,8 +479,8 @@ const updateDestinationStack = async (req: Request) => { ); if ( - project.status === NEW_PROJECT_STATUS[4] || - project.status === NEW_PROJECT_STATUS[5] || + project.status === NEW_PROJECT_STATUS[4] || + project.status === NEW_PROJECT_STATUS[5] || project.current_step < STEPPER_STEPS.DESTINATION_STACK ) { logger.error( @@ -531,7 +531,7 @@ const updateDestinationStack = async (req: Request) => { data.projects[projectIndex].destination_stack_id = stack_api_key; data.projects[projectIndex].current_step = STEPPER_STEPS.DESTINATION_STACK; - data.projects[projectIndex].status = NEW_PROJECT_STATUS[0]; + data.projects[projectIndex].status = NEW_PROJECT_STATUS[0]; data.projects[projectIndex].updated_at = new Date().toISOString(); }); @@ -590,7 +590,7 @@ const updateCurrentStep = async (req: Request) => { switch (project.current_step) { case STEPPER_STEPS.LEGACY_CMS: { - if (project.status !== NEW_PROJECT_STATUS[0] || !isStepCompleted) { + if (project.status !== NEW_PROJECT_STATUS[0] || !isStepCompleted) { logger.error( getLogMessage( srcFunc, diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 297e6c98..40823eac 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -1,4 +1,3 @@ - export const throwError = (message: string, statusCode: number) => { throw Object.assign(new Error(message), { statusCode }); }; @@ -26,4 +25,3 @@ export const getLogMessage = ( ...(error && { error }), }; }; - diff --git a/ui/src/components/ContentMapper/contentMapper.interface.ts b/ui/src/components/ContentMapper/contentMapper.interface.ts index 80a7662c..8d307299 100644 --- a/ui/src/components/ContentMapper/contentMapper.interface.ts +++ b/ui/src/components/ContentMapper/contentMapper.interface.ts @@ -32,6 +32,7 @@ export interface ContentstackFields { export interface FieldTypes { label: string; value: any; + id?: string; } export interface TableTypes { sortBy: any; @@ -76,16 +77,16 @@ export interface FieldMetadata { allow_rich_text?: boolean; markdown?: boolean; } -export interface ContentTypeField { +export interface ContentTypesSchema { uid?: string; display_name?: string; data_type?: 'text' | 'number' | 'isodate' | 'json' | 'file'; field_metadata?: FieldMetadata; enum?: any; } -export interface ContentTypesSchema { - [key: string]: ContentTypeField; -} +// export interface ContentTypesSchema { +// [key: string]: ContentTypeField; +// } export type ExistingFieldType = { [key: string]: FieldTypes | undefined; @@ -96,7 +97,8 @@ export interface Mapping { } export interface ContentTypeList { title: string; - schema: []; + schema: ContentTypesSchema[]; + uid: string; } export interface optionsType { diff --git a/ui/src/components/ContentMapper/index.scss b/ui/src/components/ContentMapper/index.scss index 05a8e229..6a19fc3e 100644 --- a/ui/src/components/ContentMapper/index.scss +++ b/ui/src/components/ContentMapper/index.scss @@ -2,7 +2,9 @@ @import '../../scss/variables'; .content-types-list-wrapper { + max-height: calc(100vh - 7.25rem); max-width: 27%; + overflow: hidden; width: 100%; } .content-types-list-header { @@ -17,8 +19,11 @@ } } .ct-list { + height: 100%; list-style-type: none; - margin: 5px 15px 0; + margin: 15px 0; + overflow-y: auto; + overflow-x: hidden; li { align-items: center; border: 1px solid transparent; @@ -41,17 +46,15 @@ } .content-types-fields-wrapper { border-left: 1px solid $color-brand-secondary-lightest; + display: flex; + flex-direction: column; max-width: 73%; width: 100%; padding: 0px; } .cta-wrapper { border-top: 1px solid $color-base-gray-40; - flex: 1 0 auto; - // bottom: 0; padding: $space-12 $space-24 $space-12 $space-12; - // position: fixed; - // right: 0; text-align: right; width: 100%; z-index: 1; @@ -61,6 +64,7 @@ flex: 1 0 auto; } .table-wrapper { + flex: 1; .Table { min-height: inherit; .Table__body__column { @@ -83,12 +87,15 @@ justify-content: space-between; padding: 12px; } +.action-component-body { + display: flex; +} .step-container { display: flex; flex-direction: column; margin-top: 0; margin-left: 0px; - margin-bottom: 20px; + // margin-bottom: 20px; width: 100%; } .saveButton { diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index e1f96e42..cb6d47d7 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -28,7 +28,8 @@ import { getExistingContentTypes, updateContentType, resetToInitialMapping, - createTestStack + createTestStack, + fetchExistingContentType } from '../../services/api/migration.service'; import { getStackStatus } from '../../services/api/stacks.service'; @@ -158,6 +159,7 @@ const ContentMapper = () => { const [rowIds, setRowIds] = useState({}); const [selectedEntries, setSelectedEntries] = useState([]); + const [contentTypeSchema, setContentTypeSchema] = useState([]); /** ALL HOOKS Here */ const { projectId = '' } = useParams(); @@ -222,6 +224,7 @@ const ContentMapper = () => { acc[item?.id] = true; return acc; }, {}); + setRowIds(selectedId); }, [tableData]); @@ -487,15 +490,37 @@ const ContentMapper = () => { }; // Function to handle selected fields - const handleSelectedEntries = (singleSelectedRowIds: any, selectedData: any) => { + const handleSelectedEntries = (singleSelectedRowIds: UidMap[], selectedData: FieldMapType[]) => { const selectedObj: any = {}; + singleSelectedRowIds.forEach((uid: any) => { selectedObj[uid] = true; }); + + const uncheckedElements = findUncheckedElement(selectedData, tableData); + uncheckedElements && validateArray(uncheckedElements) && uncheckedElements?.forEach((field) => { + if (field?.otherCmsType === "Group") { + const newEle = selectedData?.filter((entry) => entry?.uid?.startsWith(field?.uid + '.')) + + newEle && validateArray(newEle) && newEle.forEach((child) => { + selectedObj[child?.id || ''] = false; + selectedData?.splice(selectedData?.indexOf(child), 1); + }) + } + }) + setRowIds(selectedObj); setSelectedEntries(selectedData); + }; + // Function to find unchecked field + const findUncheckedElement = (selectedData: FieldMapType[], tableData: FieldMapType[]) => { + return tableData.filter((mainField: FieldMapType) => + !selectedData.some((selectedField:FieldMapType) => selectedField?.otherCmsField === mainField?.otherCmsField) + ); + } + // Method for change select value const handleValueChange = (value: FieldTypes, rowIndex: string) => { setisDropDownCHanged(true); @@ -642,7 +667,10 @@ const ContentMapper = () => { const SelectAccessorOfColumn = (data: FieldMapType) => { const fieldsOfContentstack: Mapping = { 'Single Line Textbox': 'text', + 'Single-Line Text': 'text', + 'text': 'text', 'Multi Line Textbox': 'multiline', + 'multiline': 'multiline', 'HTML Rich text Editor': 'allow_rich_text', 'JSON Rich Text Editor': 'json', URL: 'url', @@ -657,18 +685,20 @@ const ContentMapper = () => { CheckBox: 'enum' }; const OptionsForRow: optionsType[] = []; - let ContentTypeSchema: ContentTypesSchema | undefined; + // let ContentTypeSchema: ContentTypesSchema | undefined; if (OtherContentType?.label && contentTypesList) { const ContentType: any = contentTypesList?.find( ({ title }) => title === OtherContentType?.label ); - - ContentTypeSchema = ContentType?.schema; + setContentTypeSchema(ContentType?.schema) } - if (ContentTypeSchema && typeof ContentTypeSchema === 'object') { - const fieldTypeToMatch = fieldsOfContentstack[data?.backupFieldType as keyof Mapping]; - Object.entries(ContentTypeSchema).forEach(([key, value]) => { + + if (contentTypeSchema && validateArray(contentTypeSchema)) { + const fieldTypeToMatch = fieldsOfContentstack[data?.otherCmsType as keyof Mapping]; + // console.log("fieldTypeToMatch", contentTypeSchema, fieldsOfContentstack, data?.backupFieldType); + + contentTypeSchema.forEach((value) => { switch (fieldTypeToMatch) { case 'text': if ( @@ -721,6 +751,11 @@ const ContentMapper = () => { 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; default: OptionsForRow.push({ label: 'No matches found', @@ -887,6 +922,23 @@ const ContentMapper = () => { } }; + // Function to fetch single content type + const handleFetchContentType = async () => { + if (OtherContentType?.label === "Select Content Type") { + Notification({ + notificationContent: { text: "Please Select a Content Type to fetch." }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'error' + }); + } else { + const { data } = await fetchExistingContentType(projectId, OtherContentType?.id || ''); + setContentTypeSchema(data?.schema) + } + } + const columns = [ { disableSortBy: true, @@ -924,6 +976,7 @@ const ContentMapper = () => { const options = contentTypesList?.map((item) => ({ label: item?.title, value: item?.title, + id: item?.uid, isDisabled: false })); @@ -1010,7 +1063,7 @@ const ContentMapper = () => { searchPlaceholder={searchPlaceholder} fetchTableData={fetchData} loadMoreItems={loadMoreItems} - tableHeight={485} + tableHeight={465} equalWidthColumns={true} columnSelector={false} initialRowSelectedData={tableData} @@ -1030,7 +1083,7 @@ const ContentMapper = () => { {!IsEmptyStack && ( - + )} diff --git a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx index 53803041..ed8c6dae 100644 --- a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx @@ -1,5 +1,5 @@ -import { useContext, useEffect, useState } from 'react'; -import { Icon, Select, cbModal } from '@contentstack/venus-components'; +import { useContext, useState } from 'react'; +import { AsyncSelect, Icon, cbModal } from '@contentstack/venus-components'; import { AppContext } from '../../../context/app/app.context'; import { DEFAULT_DROPDOWN, IDropDown, INewMigration } from '../../../context/app/app.interface'; @@ -43,10 +43,11 @@ const LoadStacks = (props: LoadFileFormatProps) => { created_at: '' } ]; - const [allStack, setAllStack] = useState(loadingOption); + const [allStack, setAllStack] = useState([]); const [allLocales, setAllLocales] = useState([]); const [isSaving, setIsSaving] = useState(false); + const [isLoading, setisLoading] = useState(false); const { projectId = '' }: Params = useParams(); @@ -113,31 +114,31 @@ const LoadStacks = (props: LoadFileFormatProps) => { //Handle Legacy cms selection const handleDropdownChange = (name: string) => (data: IDropDown) => { const stackChanged = selectedStack?.value !== data?.value; - const stackCleared = data?.value === ''; + const stackCleared = data?.value === '' || data?.value === null || data === null; + if (name === 'stacks') { - if (stackChanged || stackCleared) { - setSelectedStack(() => ({ ...data })); + setSelectedStack(() => ({ ...data })); - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - destination_stack: { - ...newMigrationData.destination_stack, - selectedStack: stackCleared ? DEFAULT_DROPDOWN : { ...data } - } - }; + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + destination_stack: { + ...newMigrationData?.destination_stack, + selectedStack: stackCleared ? DEFAULT_DROPDOWN : { ...data } + } + }; - updateNewMigrationData(newMigrationDataObj); + updateNewMigrationData(newMigrationDataObj); - //call for Step Change + if (!stackCleared) { if (props?.handleStepChange) { - props.handleStepChange(props?.currentStep, true); + props?.handleStepChange(props?.currentStep, true); } - //.handleStepChange(props?.currentStep, true); } } }; const fetchData = async () => { + setisLoading(true); const stackData = await getAllStacksInOrg( newMigrationData?.destination_stack?.selectedOrg?.value ); //org id will always be there @@ -179,12 +180,13 @@ const LoadStacks = (props: LoadFileFormatProps) => { }; updateNewMigrationData(newMigrationDataObj); + setisLoading(false); }; /**** ALL USEEffects HERE ****/ - useEffect(() => { - fetchData(); - }, []); + // useEffect(() => { + // fetchData(); + // }, []); const handleCreateNewStack = () => { cbModal({ component: (props: LoadFileFormatProps) => ( @@ -209,25 +211,88 @@ const LoadStacks = (props: LoadFileFormatProps) => { } }); }; + + const loadMoreOptions: any = async ({ + search, + skip, + limit + }: { + search: string; + skip: number; + limit: number; + }) => { + const stackData = await getAllStacksInOrg( + newMigrationData?.destination_stack?.selectedOrg?.value + ); //org id will always be there + + const stackArray = validateArray(stackData?.data?.stacks) + ? stackData?.data?.stacks?.map((stack: StackResponse) => ({ + label: stack?.name, + value: stack?.api_key, + uid: stack?.api_key, + master_locale: stack?.master_locale, + locales: stack?.locales, + created_at: stack?.created_at + })) + : []; + + stackArray.sort((a: IDropDown, b: IDropDown) => { + new Date(b?.created_at)?.getTime() - new Date(a?.created_at)?.getTime(); + }); + + setAllStack(stackArray); + + //Set selected Stack + const selectedStackData = validateArray(stackArray) + ? stackArray.find( + (stack: IDropDown) => + stack?.value === newMigrationData?.destination_stack?.selectedStack?.value + ) + : DEFAULT_DROPDOWN; + + setSelectedStack(selectedStackData); + + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + destination_stack: { + ...newMigrationData?.destination_stack, + selectedStack: selectedStackData + } + }; + + updateNewMigrationData(newMigrationDataObj); + + return { options: stackArray }; + }; + const onBlurDropdown = () => { + if (!isEmptyString(selectedStack?.value)) { + if (props?.handleStepChange) { + props?.handleStepChange(props?.currentStep, true); + } + } + }; + return (
-
-