From 2810710fdd1104583e90a68624851db778b3de46 Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Sat, 22 Nov 2025 12:54:54 +0530 Subject: [PATCH 1/2] Feat: Nullable Column UI --- ui/app/mirrors/create/cdc/columnbox.tsx | 184 +++++++++++++----- .../mirrors/create/cdc/customColumnType.tsx | 4 +- ui/app/mirrors/create/cdc/schemabox.tsx | 1 + ui/app/mirrors/create/cdc/sortingkey.tsx | 6 +- 4 files changed, 141 insertions(+), 54 deletions(-) diff --git a/ui/app/mirrors/create/cdc/columnbox.tsx b/ui/app/mirrors/create/cdc/columnbox.tsx index d444b96877..2fcc2fb80b 100644 --- a/ui/app/mirrors/create/cdc/columnbox.tsx +++ b/ui/app/mirrors/create/cdc/columnbox.tsx @@ -1,10 +1,11 @@ 'use client'; import { TableMapRow } from '@/app/dto/MirrorsDTO'; +import { ColumnSetting } from '@/grpc_generated/flow'; import { ColumnsItem } from '@/grpc_generated/route'; import { Checkbox } from '@/lib/Checkbox'; import { Label } from '@/lib/Label'; import { RowWithCheckbox } from '@/lib/Layout'; -import { Dispatch, SetStateAction } from 'react'; +import { Dispatch, Fragment, SetStateAction } from 'react'; interface ColumnProps { columns: ColumnsItem[]; @@ -20,69 +21,150 @@ export default function ColumnBox({ rows, setRows, disabled, + showOrdering, }: ColumnProps) { - const handleColumnExclusion = (column: string, include: boolean) => { - const source = tableRow.source; - const currRows = [...rows]; - const rowIndex = currRows.findIndex((row) => row.source === source); + // Helper to update a specific row + const updateRow = (updater: (row: TableMapRow) => TableMapRow) => { + const rowIndex = rows.findIndex((row) => row.source === tableRow.source); if (rowIndex !== -1) { - const sourceRow = currRows[rowIndex], - newExclude = new Set(sourceRow.exclude); + const updatedRows = [...rows]; + updatedRows[rowIndex] = updater(updatedRows[rowIndex]); + setRows(updatedRows); + } + }; + + const handleColumnExclusion = (column: string, include: boolean) => { + updateRow((row) => { + const newExclude = new Set(row.exclude); if (include) { newExclude.delete(column); } else { newExclude.add(column); } - currRows[rowIndex] = { - ...sourceRow, - exclude: newExclude, - }; - setRows(currRows); - } + return { ...row, exclude: newExclude }; + }); }; - return columns.map((column) => { - const partOfOrderingKey = rows - .find((row) => row.source == tableRow.source) - ?.columns.some( - (col) => - col.sourceName === column.name && - (col.ordering > 0 || col.partitioning > 0) + const handleNullableEnabledChange = (columnName: string, enabled: boolean) => { + updateRow((row) => { + const existingColumn = row.columns.find( + (col) => col.sourceName === columnName ); + + const updatedColumns: ColumnSetting[] = existingColumn + ? // Update existing ColumnSetting + row.columns.map((col) => + col.sourceName === columnName + ? { ...col, nullableEnabled: enabled } + : col + ) + : // Create new ColumnSetting + [ + ...row.columns, + { + sourceName: columnName, + destinationName: '', + destinationType: '', + ordering: 0, + partitioning: 0, + nullableEnabled: enabled, + }, + ]; + + return { ...row, columns: updatedColumns }; + }); + }; + + const getNullableEnabled = (columnName: string): boolean => { return ( - - {column.name} -

col.sourceName === columnName) + ?.nullableEnabled ?? false + ); + }; + + return ( +

+
Name
+
Type
+ {showOrdering && +
Nullable
+ } + + {columns.map((column) => { + const partOfOrderingKey = rows + .find((row) => row.source == tableRow.source) + ?.columns.some( + (col) => + col.sourceName === column.name && + (col.ordering > 0 || col.partitioning > 0) + ); + + const isIncluded = !tableRow.exclude.has(column.name); + const nullableEnabled = getNullableEnabled(column.name); + + return ( + + + {column.name} + + } + action={ + + handleColumnExclusion(column.name, state) + } + /> + } + /> + +
{column.type} -

- - } - action={ - - handleColumnExclusion(column.name, state) - } - /> - } - /> - ); - }); -} +
+ + {showOrdering && ( + + handleNullableEnabledChange(column.name, state) + } + /> + )} +
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/ui/app/mirrors/create/cdc/customColumnType.tsx b/ui/app/mirrors/create/cdc/customColumnType.tsx index b0aea24549..c33c2d5d93 100644 --- a/ui/app/mirrors/create/cdc/customColumnType.tsx +++ b/ui/app/mirrors/create/cdc/customColumnType.tsx @@ -141,10 +141,10 @@ export default function CustomColumnType({ prev.map((row) => { if (row.source !== tableRow.source) return row; - const columnExistsInRow = row.columns.some( + const existingColumn = row.columns.find( (col) => col.sourceName === selectedColumnName ); - if (!columnExistsInRow) { + if (!existingColumn) { return { ...row, columns: [ diff --git a/ui/app/mirrors/create/cdc/schemabox.tsx b/ui/app/mirrors/create/cdc/schemabox.tsx index 2bfcca3901..da18c6ad59 100644 --- a/ui/app/mirrors/create/cdc/schemabox.tsx +++ b/ui/app/mirrors/create/cdc/schemabox.tsx @@ -231,6 +231,7 @@ export default function SchemaBox({ row.partitionByExpr = existingRow.partitionByExpr; row.exclude = new Set(existingRow.exclude ?? []); row.destination = existingRow.destinationTableIdentifier; + row.columns = existingRow.columns ?? []; addTableColumns(row.source); } } diff --git a/ui/app/mirrors/create/cdc/sortingkey.tsx b/ui/app/mirrors/create/cdc/sortingkey.tsx index cf631e9123..9f472127e0 100644 --- a/ui/app/mirrors/create/cdc/sortingkey.tsx +++ b/ui/app/mirrors/create/cdc/sortingkey.tsx @@ -51,6 +51,10 @@ function UpdatedRows( const newRows = [...rows]; const columns = rows[rowIndex].columns.map(update); if (!columns.find((setting) => setting.sourceName === colName)) { + // Check if there's an existing ColumnSetting to preserve nullableEnabled + const existingSetting = rows[rowIndex].columns.find( + (setting) => setting.sourceName === colName + ); columns.push( update({ sourceName: colName, @@ -58,7 +62,7 @@ function UpdatedRows( destinationType: '', ordering: 0, partitioning: 0, - nullableEnabled: false, + nullableEnabled: existingSetting?.nullableEnabled ?? false, }) ); } From b8a2dc21850299ee81c08551a18191946df4940d Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Thu, 27 Nov 2025 23:32:06 +0530 Subject: [PATCH 2/2] Fix: Suggested Changes --- ui/app/mirrors/create/cdc/columnbox.tsx | 43 +++++++++++++++--------- ui/app/mirrors/create/cdc/schemabox.tsx | 2 +- ui/app/mirrors/create/cdc/sortingkey.tsx | 8 ++--- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/ui/app/mirrors/create/cdc/columnbox.tsx b/ui/app/mirrors/create/cdc/columnbox.tsx index 2fcc2fb80b..aca0be7bca 100644 --- a/ui/app/mirrors/create/cdc/columnbox.tsx +++ b/ui/app/mirrors/create/cdc/columnbox.tsx @@ -5,7 +5,8 @@ import { ColumnsItem } from '@/grpc_generated/route'; import { Checkbox } from '@/lib/Checkbox'; import { Label } from '@/lib/Label'; import { RowWithCheckbox } from '@/lib/Layout'; -import { Dispatch, Fragment, SetStateAction } from 'react'; +import { Dispatch, Fragment, SetStateAction, useMemo } from 'react'; +import InfoPopover from '@/components/InfoPopover'; interface ColumnProps { columns: ColumnsItem[]; @@ -13,7 +14,7 @@ interface ColumnProps { rows: TableMapRow[]; setRows: Dispatch>; disabled?: boolean; - showOrdering: boolean; + showNullable: boolean; } export default function ColumnBox({ columns, @@ -21,7 +22,7 @@ export default function ColumnBox({ rows, setRows, disabled, - showOrdering, + showNullable, }: ColumnProps) { // Helper to update a specific row const updateRow = (updater: (row: TableMapRow) => TableMapRow) => { @@ -75,30 +76,40 @@ export default function ColumnBox({ }); }; + const nullableEnabledMap = useMemo(() => { + const map = new Map(); + tableRow.columns.forEach((col) => { + map.set(col.sourceName, col.nullableEnabled ?? false); + }); + return map; + }, [tableRow.columns]); + const getNullableEnabled = (columnName: string): boolean => { - return ( - tableRow.columns.find((col) => col.sourceName === columnName) - ?.nullableEnabled ?? false - ); + return nullableEnabledMap.get(columnName) ?? false; }; return (
-
Name
+
Name
Type
- {showOrdering && -
Nullable
- } + {showNullable && ( +
+ Nullable + +
+ )} {columns.map((column) => { const partOfOrderingKey = rows @@ -149,11 +160,11 @@ export default function ColumnBox({ {column.type}
- {showOrdering && ( + {showNullable && ( ); -} \ No newline at end of file +} diff --git a/ui/app/mirrors/create/cdc/schemabox.tsx b/ui/app/mirrors/create/cdc/schemabox.tsx index da18c6ad59..d4bb23c5aa 100644 --- a/ui/app/mirrors/create/cdc/schemabox.tsx +++ b/ui/app/mirrors/create/cdc/schemabox.tsx @@ -530,7 +530,7 @@ export default function SchemaBox({ rows={rows} setRows={setRows} disabled={row.editingDisabled} - showOrdering={ + showNullable={ peerType?.toString() === DBType[DBType.CLICKHOUSE].toString() } diff --git a/ui/app/mirrors/create/cdc/sortingkey.tsx b/ui/app/mirrors/create/cdc/sortingkey.tsx index 9f472127e0..4d4f00525c 100644 --- a/ui/app/mirrors/create/cdc/sortingkey.tsx +++ b/ui/app/mirrors/create/cdc/sortingkey.tsx @@ -49,12 +49,12 @@ function UpdatedRows( const rowIndex = rows.findIndex((row) => row.source === tableRow.source); if (rowIndex !== -1) { const newRows = [...rows]; + // Check if there's an existing ColumnSetting to preserve nullableEnabled + const existingSetting = rows[rowIndex].columns.find( + (setting) => setting.sourceName === colName + ); const columns = rows[rowIndex].columns.map(update); if (!columns.find((setting) => setting.sourceName === colName)) { - // Check if there's an existing ColumnSetting to preserve nullableEnabled - const existingSetting = rows[rowIndex].columns.find( - (setting) => setting.sourceName === colName - ); columns.push( update({ sourceName: colName,