From 667f9c5703e8bf25dfc357014335bb2a478c42d6 Mon Sep 17 00:00:00 2001 From: yashin4112 Date: Tue, 30 Jun 2026 13:43:08 +0530 Subject: [PATCH 1/2] feat(sitecore): extract media-library assets for asset mapper + entry mapper fixes - Add Sitecore extractAssets lib and wire it into createSitecoreMapper so media-library assets reach putTestData and show on the AssetMapper - Fix asset/path cells overflowing into adjacent columns (wrap long text) - Entry mapper: stop search from wiping content-type status icon and in-progress unsaved selections - Add server-side pagination to entry mapper table - Fix locale dropdown overlap and pagination/footer layout --- .../components/ContentMapper/assetMapper.tsx | 4 +- .../components/ContentMapper/entryMapper.tsx | 99 +++++++++---------- ui/src/components/ContentMapper/index.scss | 56 +++++++++-- upload-api/migration-sitecore/index.js | 4 +- upload-api/src/config/index.json | 4 +- upload-api/src/controllers/sitecore/index.ts | 6 +- 6 files changed, 105 insertions(+), 68 deletions(-) diff --git a/ui/src/components/ContentMapper/assetMapper.tsx b/ui/src/components/ContentMapper/assetMapper.tsx index abf2a8577..fb8f04df5 100644 --- a/ui/src/components/ContentMapper/assetMapper.tsx +++ b/ui/src/components/ContentMapper/assetMapper.tsx @@ -209,7 +209,7 @@ const AssetMapper = ({ return (
-
+
{data?.filename || data?.title || '-'}
@@ -221,7 +221,7 @@ const AssetMapper = ({ return (
-
+
{data?.assetPath || '-'}
diff --git a/ui/src/components/ContentMapper/entryMapper.tsx b/ui/src/components/ContentMapper/entryMapper.tsx index f287022a5..fc8ba2cdc 100644 --- a/ui/src/components/ContentMapper/entryMapper.tsx +++ b/ui/src/components/ContentMapper/entryMapper.tsx @@ -42,7 +42,6 @@ import { EntryMapperType, MouseOrKeyboardEvent, } from './contentMapper.interface'; -import { ItemStatusMapProp } from '@contentstack/venus-components/build/components/Table/types'; import { ModalObj } from '../Modal/modal.interface'; // Components @@ -93,7 +92,6 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { const [loading, setLoading] = useState(false); const [isLoading, setIsLoading] = useState(newMigrationData?.isprojectMapped); const [totalCounts, setTotalCounts] = useState(0); - const [itemStatusMap, setItemStatusMap] = useState({}); const [searchText, setSearchText] = useState(''); const [searchContentType, setSearchContentType] = useState(''); @@ -180,7 +178,7 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { // Refetch the entry list when the user switches locale so isUpdate reflects the per-locale flag. useEffect(() => { if (contentTypeUid && selectedLocale?.value) { - fetchEntries(contentTypeUid, searchText || ''); + fetchEntries(contentTypeUid, searchText || '', { seedSelection: true }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedLocale?.value]); @@ -202,7 +200,7 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { setContentTypeUid(data?.contentTypes?.[0]?.id ?? ''); setOtherCmsUid(data?.contentTypes?.[0]?.otherCmsUid ?? ''); if (data?.contentTypes?.[0]?.id) { - fetchEntries(data?.contentTypes?.[0]?.id, searchVal ?? ''); + fetchEntries(data?.contentTypes?.[0]?.id, searchVal ?? '', { seedSelection: true }); } } catch (error) { setIsLoading(false); @@ -258,7 +256,7 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { setContentTypeUid(ct?.id ?? ''); setOtherCmsUid(ct?.otherCmsUid ?? ''); if (ct?.id) { - fetchEntries(ct.id, searchText || ''); + fetchEntries(ct.id, searchText || '', { seedSelection: true }); } }; @@ -319,69 +317,56 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { }; /********** ENTRY TABLE (right panel) *************/ - const fetchEntries = async (ctId: string, searchVal: string) => { + // Single server-paginated fetch for the entry table. The Venus table drives + // paging by calling fetchData with { skip, limit, searchText }; we ask the API + // for just that page and use the returned `count` as the grand total. + // + // seedSelection: only on the initial content-type open (page 0, no search) do we + // seed rowIds/persistedRowIds from the server's persisted isUpdate and refresh the + // content-type status icon. On search / clear-search / page change we instead + // re-apply the user's current (possibly unsaved) selection so nothing gets wiped. + const fetchEntries = async ( + ctId: string, + searchVal: string, + { skip = 0, limit = 30, seedSelection = false }: { skip?: number; limit?: number; seedSelection?: boolean } = {}, + ) => { try { - const itemStatusMapLocal: ItemStatusMapProp = {}; - for (let index = 0; index <= 1000; index++) { - itemStatusMapLocal[index] = 'loading'; - } - setItemStatusMap(itemStatusMapLocal); setLoading(true); - const { data } = await getEntryMapping(ctId || '', 0, 1000, searchVal, projectId, selectedLocale?.value); + const { data } = await getEntryMapping(ctId || '', skip, limit, searchVal, projectId, selectedLocale?.value); - for (let index = 0; index <= 1000; index++) { - itemStatusMapLocal[index] = 'loaded'; - } - setItemStatusMap({ ...itemStatusMapLocal }); setLoading(false); const validTableData: EntryMapperType[] = mapEntriesToRows(data?.entryMapping); + const total = data?.count ?? validTableData?.length ?? 0; + + setTotalCounts(total); + setInitialRowSelectedData(selectableInitialRows(validTableData)); + + if (!seedSelection) { + // Re-apply the user's current selection onto the freshly fetched page; + // don't touch rowIds/persistedRowIds so nothing gets deselected. + setTableData(applySelectionToEntries(validTableData ?? [], rowIds)); + return; + } const initialSelected = buildSelectedEntryRowIds(validTableData ?? []); setTableData(validTableData ?? []); setRowIds(initialSelected); setPersistedRowIds(initialSelected); - setTotalCounts(validTableData?.length); - setInitialRowSelectedData(selectableInitialRows(validTableData)); // Reflect any pre-existing entry selections on the content type icon (green when present). updateContentTypeStatus(ctId, Object.keys(initialSelected ?? {}).length > 0); } catch (error) { console.error('fetchEntries -> error', error); + setLoading(false); } }; - // Fetch table data on search - const fetchData = async ({ searchText: search }: TableTypes) => { - setSearchText(search); - contentTypeUid && fetchEntries(contentTypeUid, search); - }; - - // Load more table data - const loadMoreItems = async ({ searchText: search, skip, limit, startIndex, stopIndex }: TableTypes) => { - try { - const itemStatusMapCopy: ItemStatusMapProp = { ...itemStatusMap }; - for (let index = startIndex; index <= stopIndex; index++) { - itemStatusMapCopy[index] = 'loading'; - } - setItemStatusMap({ ...itemStatusMapCopy }); - setLoading(true); - - const { data } = await getEntryMapping(contentTypeUid || '', skip, limit, search || '', projectId, selectedLocale?.value); - - const updated: ItemStatusMapProp = { ...itemStatusMap }; - for (let index = startIndex; index <= stopIndex; index++) { - updated[index] = 'loaded'; - } - setItemStatusMap({ ...updated }); - setLoading(false); - - const validTableData: EntryMapperType[] = mapEntriesToRows(data?.entryMapping); - - setTableData(applySelectionToEntries(validTableData ?? [], rowIds)); - } catch (error) { - console.error('loadMoreItems -> error', error); - } + // Driven by the table: page change, rows-per-page change, and search all land here. + const fetchData = async ({ searchText: search, skip, limit }: TableTypes) => { + setSearchText(search ?? ''); + // Searching / paging must not drop the user's in-progress selection. + contentTypeUid && fetchEntries(contentTypeUid, search ?? '', { skip, limit }); }; /** @@ -494,7 +479,10 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { } ]; - const calcHeight = () => window.innerHeight - 361; + // Leave room below the scroll body for the taller search row (locale dropdown), + // the venus pagination bar (~56px) and our Total/Save footer (~64px) so none of + // them get clipped off the bottom. + const calcHeight = () => window.innerHeight - 361 - 160; const tableHeight = calcHeight(); return ( @@ -638,18 +626,19 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { key={contentTypeUid || 'entry-mapper-table'} loading={loading} canSearch={true} - totalCounts={Math.max(0, tableData?.length)} + totalCounts={totalCounts ?? 0} data={[...tableData]} columns={columns} uniqueKey={'id'} isRowSelect={true} fullRowSelect={true} - itemStatusMap={itemStatusMap} fetchTableData={fetchData} - loadMoreItems={loadMoreItems} tableHeight={tableHeight} equalWidthColumns={true} columnSelector={false} + v2Features={{ pagination: true, isNewEmptyState: true }} + rowPerPageOptions={[10, 30, 50, 100]} + minBatchSizeToFetch={30} initialRowSelectedData={initialRowSelectedData} initialSelectedRowIds={rowIds} itemSize={80} @@ -662,7 +651,9 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { /> {totalCounts > 0 && (
-
Total Entries: {totalCounts}
+
+ {/* Total Entries: {totalCounts} */} +