Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,210 changes: 395 additions & 815 deletions api/package-lock.json

Large diffs are not rendered by default.

66 changes: 33 additions & 33 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 20 additions & 3 deletions ui/src/components/ContentMapper/entryMapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { ItemStatusMapProp } from '@contentstack/venus-components/build/componen
// Styles and Assets
import './index.scss';

const EntryMapper = ({selectedContentTypeId, tableHeight}: {selectedContentTypeId: ContentType | null, tableHeight: number}) => {
const EntryMapper = ({selectedContentTypeId, tableHeight, onEntrySelectionChange}: {selectedContentTypeId: ContentType | null, tableHeight: number, onEntrySelectionChange?: (contentTypeId: string, hasSelection: boolean) => void}) => {
// Redux State
const dispatch = useDispatch();

Expand Down Expand Up @@ -173,6 +173,12 @@ const EntryMapper = ({selectedContentTypeId, tableHeight}: {selectedContentTypeI
setPersistedRowIds(initialSelected);
setTotalCounts(validTableData?.length);
setInitialRowSelectedData(validTableData?.filter((item: EntryMapperType) => !item?.isUpdate))

// Reflect any pre-existing entry selections on the content type icon (green when present)
const ctId = contentTypeId || selectedContentTypeId?.id;
if (ctId) {
onEntrySelectionChange?.(ctId, Object.keys(initialSelected ?? {}).length > 0);
}

} catch (error) {
console.error('fetchData -> error', error);
Expand Down Expand Up @@ -272,6 +278,15 @@ const EntryMapper = ({selectedContentTypeId, tableHeight}: {selectedContentTypeI
if (status === 200) {
setPersistedRowIds({ ...(rowIds ?? {}) });
setLoading(false);

// Reflect the saved update on the content type icon: green (Updated) when entries
// remain selected after save, blue (Mapped) when all selections were cleared.
const ctId = selectedContentTypeId?.id || contentTypeUid;
if (ctId) {
const hasSelection = Object.values(rowIds ?? {}).some(Boolean);
onEntrySelectionChange?.(ctId, hasSelection);
}

return Notification({
notificationContent: { text: 'Entries saved successfully' },
notificationProps: {
Expand Down Expand Up @@ -404,7 +419,8 @@ const EntryMapper = ({selectedContentTypeId, tableHeight}: {selectedContentTypeI
}}

/>
<div className="mapper-footer">
{totalCounts > 0 && (
<div className="mapper-footer">
<div>Total Entries: <strong>{totalCounts}</strong></div>
<Button
className="saveButton"
Expand All @@ -415,7 +431,8 @@ const EntryMapper = ({selectedContentTypeId, tableHeight}: {selectedContentTypeI
>
Save
</Button>
</div>
</div>
)}


</div>
Expand Down
16 changes: 16 additions & 0 deletions ui/src/components/ContentMapper/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,22 @@ div .table-row {
}

.entry-mapper-container {
// Empty state ("No Records Found"): venus renders a plain .Table__body with no height,
// so it collapses to just the text. Stretch the table chrome and the empty message to
// fill the available viewport space instead of leaving a grey gap below.
.TableWrapper,
.Table {
min-height: calc(100vh - 300px);
}
.no-table-data {
min-height: calc(100vh - 300px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}

.Table {
// Force row layout alignment
.Table__head,
Expand Down
39 changes: 27 additions & 12 deletions ui/src/components/ContentMapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,18 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
};

// Method to change the content type
/**
* Reflect entry-update selection on the content type's status icon:
* has selected entries → 'Updated' (status '2', green); none → 'Mapped' (status '1', blue).
*/
const handleEntrySelectionStatusChange = (contentTypeId: string, hasSelection: boolean) => {
const nextStatus = hasSelection ? '2' : '1';
const applyStatus = (list: ContentType[]) =>
list?.map?.((ct) => (ct?.id === contentTypeId ? { ...ct, status: nextStatus } : ct));
setContentTypes((prev) => applyStatus(prev));
setFilteredContentTypes((prev) => applyStatus(prev));
};

const handleOpenContentType = (i = 0) => {
if (isDropDownChanged) {
setIsModalOpen(true);
Expand Down Expand Up @@ -3401,6 +3413,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
<EntryMapper
tableHeight={tableHeight}
selectedContentTypeId={selectedContentType ?? null}
onEntrySelectionChange={handleEntrySelectionStatusChange}
/>
</div>
): (
Expand Down Expand Up @@ -3469,18 +3482,20 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
plural: `${totalCounts === 0 ? 'Count' : ''}`
}}
/>
<div className="mapper-footer">
<div>Total Fields: <strong>{totalCounts}</strong></div>
<Button
className="saveButton"
onClick={handleSaveContentType}
version="v2"
disabled={newMigrationData?.project_current_step > 4}
isLoading={isLoadingSaveButton}
>
Save
</Button>
</div>
{totalCounts > 0 && (
<div className="mapper-footer">
<div>Total Fields: <strong>{totalCounts}</strong></div>
<Button
className="saveButton"
onClick={handleSaveContentType}
version="v2"
disabled={newMigrationData?.project_current_step > 4}
isLoading={isLoadingSaveButton}
>
Save
</Button>
</div>
)}
</div>
)}
</div>
Expand Down
113 changes: 105 additions & 8 deletions ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,25 @@ const FileComponent = ( { fileDetails, fileFormatId }: Props ) =>
const [localPath, setLocalPath] = useState(fileDetails?.localPath || '');
const dispatch = useDispatch();
const currentPath = newMigrationData?.legacy_cms?.uploadedFile?.file_details?.localPath || fileDetails?.localPath || '';

// SQL editing state — mirrors the local-path edit flow but for the 3 MySQL fields.
// Prefer the most up-to-date MySQL values from Redux over the (potentially stale) prop,
// so the edit inputs (which can start open when iteration > 1) don't seed/overwrite with stale data.
const currentMysql = newMigrationData?.legacy_cms?.uploadedFile?.file_details?.mysql || fileDetails?.mysql;
const [isEditingSql, setIsEditingSql] = useState((newMigrationData?.iteration > 1 && !newMigrationData?.legacy_cms?.uploadedFile?.isValidated) ? true : false);
const [sqlDetails, setSqlDetails] = useState({
host: currentMysql?.host || '',
database: currentMysql?.database || '',
user: currentMysql?.user || ''
});

const handleEditFile = async () => {
// Once the file is validated, editing the path is disabled
if (isValidated) return;
setIsEditing(true);
setLocalPath(currentPath);
};

const handleBlur = async () => {
setIsEditing(false);

Expand All @@ -84,20 +96,97 @@ const FileComponent = ( { fileDetails, fileFormatId }: Props ) =>
}
}
}
};
};
dispatch(updateNewMigrationData(updatedMigrationData));
};


const handleEditSql = async () => {
// Once the connection is validated, editing the details is disabled
if (isValidated) return;
setIsEditingSql(true);
setSqlDetails({
host: currentMysql?.host || '',
database: currentMysql?.database || '',
user: currentMysql?.user || ''
});
};

const handleSqlBlur = async (e: React.FocusEvent<HTMLDivElement>) => {
// Only exit edit mode when focus leaves the whole group, not when moving
// between the host/database/user inputs.
if (e.currentTarget.contains(e.relatedTarget as Node)) return;

setIsEditingSql(false);

// Update Redux state with new MySQL details, preserving other mysql fields (e.g. port)
const updatedMigrationData = {
...newMigrationData,
legacy_cms: {
...newMigrationData?.legacy_cms,
uploadedFile: {
...newMigrationData?.legacy_cms?.uploadedFile,
file_details: {
...newMigrationData?.legacy_cms?.uploadedFile?.file_details,
mysql: {
...newMigrationData?.legacy_cms?.uploadedFile?.file_details?.mysql,
host: sqlDetails.host,
database: sqlDetails.database,
user: sqlDetails.user
}
}
}
}
};
dispatch(updateNewMigrationData(updatedMigrationData));
};


return (
<div>
{ isSQL ? (
// ✅ SQL format (from legacyCms.json allowed_file_formats): show MySQL details
fileDetails?.mysql && (
<div>
<p className="pb-2">Host: { fileDetails?.mysql?.host }</p>
<p className="pb-2">Database: { fileDetails?.mysql?.database }</p>
<p className="pb-2">User: { fileDetails?.mysql?.user }</p>
<div className="file-container">
<div className="file-path-text">
{isEditingSql ? (
<div className="sql-edit-fields" onBlur={handleSqlBlur}>
<TextInput
value={sqlDetails.host}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSqlDetails((prev) => ({ ...prev, host: e.target.value }))}
width="full"
version="v2"
placeholder="Enter host"
aria-label="MySQL host"
autoFocus
/>
<TextInput
value={sqlDetails.database}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSqlDetails((prev) => ({ ...prev, database: e.target.value }))}
width="full"
version="v2"
placeholder="Enter database"
aria-label="MySQL database"
/>
<TextInput
value={sqlDetails.user}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSqlDetails((prev) => ({ ...prev, user: e.target.value }))}
width="full"
version="v2"
placeholder="Enter user"
aria-label="MySQL user"
/>
Comment thread
yashin4112 marked this conversation as resolved.
</div>
) : (
<div>
<p className="pb-2">Host: { currentMysql?.host }</p>
<p className="pb-2">Database: { currentMysql?.database }</p>
<p className="pb-2">User: { currentMysql?.user }</p>
</div>
)}
</div>
<div className={`edit-icon${isValidated ? ' edit-icon--disabled' : ''}`}>
<Icon icon="EditSmallActive" size="small" onClick={handleEditSql} />
</div>
</div>
)
) : fileDetails?.isLocalPath ? (
Expand Down Expand Up @@ -210,10 +299,18 @@ const LoadUploadFile = ( props: LoadUploadFileProps ) =>
}
}

const validationMysql = newMigrationData?.legacy_cms?.uploadedFile?.file_details?.mysql;
const { data, status } = await fileValidation({
projectId,
affix: newMigrationData?.legacy_cms?.affix,
localPath: resolvedPath
localPath: resolvedPath,
mysql: validationMysql
? {
host: validationMysql.host,
database: validationMysql.database,
user: validationMysql.user
}
: undefined
});

setProgressPercentage( 70 );
Expand Down
Loading
Loading