-
Notifications
You must be signed in to change notification settings - Fork 163
Feat: Nullable Column UI #3731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat: Nullable Column UI #3731
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a nullable checkbox feature for ClickHouse destinations, allowing users to configure whether columns can contain null values. The changes introduce UI components and state management logic to support nullable column configuration.
Key Changes:
- Added nullable checkbox UI control in the column configuration interface
- Implemented state preservation logic for nullable settings across column updates
- Refactored column display layout to support the new nullable control
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
ui/app/mirrors/create/cdc/columnbox.tsx |
Major refactor to add nullable checkbox column with grid layout and new state management handlers |
ui/app/mirrors/create/cdc/sortingkey.tsx |
Preserves nullable settings when creating new column configurations |
ui/app/mirrors/create/cdc/schemabox.tsx |
Ensures existing column settings are preserved during row updates |
ui/app/mirrors/create/cdc/customColumnType.tsx |
Minor refactor changing some() to find() for consistency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!columns.find((setting) => setting.sourceName === colName)) { | ||
| // Check if there's an existing ColumnSetting to preserve nullableEnabled |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existingSetting will always be undefined because line 53 already verifies that no column with sourceName === colName exists. This logic attempts to find a setting that was just confirmed to not exist, making existingSetting always undefined and the preservation attempt ineffective.
| if (!columns.find((setting) => setting.sourceName === colName)) { | |
| // Check if there's an existing ColumnSetting to preserve nullableEnabled | |
| // If the column with colName does not exist after update, add it and preserve nullableEnabled if possible | |
| if (!columns.some((setting) => setting.sourceName === colName)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Br0wnHammer the existingSetting code can be removed, the result of it is only used when the setting doesn't exist :)
| if (row.source !== tableRow.source) return row; | ||
|
|
||
| const columnExistsInRow = row.columns.some( | ||
| const existingColumn = row.columns.find( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Find helps in getting the column object. Instead of a boolean. Which we can use for future proofing. If needed I can revert to as it was before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now it is just putting more work on the runtime while we only need a boolean. Let's leave this change for when the future actually comes.
| ); | ||
|
|
||
| const isIncluded = !tableRow.exclude.has(column.name); | ||
| const nullableEnabled = getNullableEnabled(column.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is O(N^2) in the number of columns, could that be improved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I can use a map for O(1) lookups.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still O(N^2) :)
| <div style={{ fontSize: 12, fontWeight: 500 }}>Name</div> | ||
| <div style={{ fontSize: 12, fontWeight: 500 }}>Type</div> | ||
| {showOrdering && | ||
| <div style={{ fontSize: 12, fontWeight: 500 }}>Nullable</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div style={{ fontSize: 12, fontWeight: 500 }}>Nullable</div> | |
| <div style={{ fontSize: 12, fontWeight: 500, display: 'flex', alignItems: 'center', gap: '0.25rem' }}> | |
| Nullable | |
| <InfoPopover tips="Enabling nullable columns wraps types in Nullable() which may impact query performance and storage in ClickHouse." /> | |
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, didn't realize the popover would land right on top of checkboxes and would make things look off. Not seeing a way to make it all work together, let's just remove it.
ilidemi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Figured out an ok design (not my specialty 😅)
| import { Checkbox } from '@/lib/Checkbox'; | ||
| import { Label } from '@/lib/Label'; | ||
| import { RowWithCheckbox } from '@/lib/Layout'; | ||
| import { Dispatch, SetStateAction } from 'react'; | ||
| import { Dispatch, Fragment, SetStateAction, useMemo } from 'react'; | ||
| import InfoPopover from '@/components/InfoPopover'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| import { Checkbox } from '@/lib/Checkbox'; | |
| import { Label } from '@/lib/Label'; | |
| import { RowWithCheckbox } from '@/lib/Layout'; | |
| import { Dispatch, SetStateAction } from 'react'; | |
| import { Dispatch, Fragment, SetStateAction, useMemo } from 'react'; | |
| import InfoPopover from '@/components/InfoPopover'; | |
| import { appTheme } from '@/lib/AppTheme/appTheme'; | |
| import { Checkbox } from '@/lib/Checkbox'; | |
| import { Label } from '@/lib/Label'; | |
| import { RowWithCheckbox } from '@/lib/Layout'; | |
| import { Dispatch, Fragment, SetStateAction, useMemo } from 'react'; |
| <div | ||
| style={{ | ||
| display: 'grid', | ||
| gridTemplateColumns: showNullable | ||
| ? 'minmax(0, 2fr) minmax(0, 1fr) auto' | ||
| : 'minmax(0, 2fr) minmax(0, 1fr)', | ||
| alignItems: 'center', | ||
| columnGap: '2.5rem', | ||
| width: '80%', | ||
| }} | ||
| > | ||
| <div style={{ fontSize: 12, fontWeight: 500, textAlign: 'left' }}>Name</div> | ||
| <div style={{ fontSize: 12, fontWeight: 500 }}>Type</div> | ||
| {showNullable && ( | ||
| <div style={{ fontSize: 12, fontWeight: 500, display: 'flex', alignItems: 'center', gap: '0.25rem', justifyContent: 'flex-end' }}> | ||
| Nullable | ||
| <InfoPopover | ||
| tips="Enabling nullable columns wraps types in Nullable() which may impact query performance and storage in Clickhouse." | ||
| /> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div | |
| style={{ | |
| display: 'grid', | |
| gridTemplateColumns: showNullable | |
| ? 'minmax(0, 2fr) minmax(0, 1fr) auto' | |
| : 'minmax(0, 2fr) minmax(0, 1fr)', | |
| alignItems: 'center', | |
| columnGap: '2.5rem', | |
| width: '80%', | |
| }} | |
| > | |
| <div style={{ fontSize: 12, fontWeight: 500, textAlign: 'left' }}>Name</div> | |
| <div style={{ fontSize: 12, fontWeight: 500 }}>Type</div> | |
| {showNullable && ( | |
| <div style={{ fontSize: 12, fontWeight: 500, display: 'flex', alignItems: 'center', gap: '0.25rem', justifyContent: 'flex-end' }}> | |
| Nullable | |
| <InfoPopover | |
| tips="Enabling nullable columns wraps types in Nullable() which may impact query performance and storage in Clickhouse." | |
| /> | |
| </div> | |
| )} | |
| <div | |
| style={{ | |
| display: 'grid', | |
| gridTemplateColumns: showNullable | |
| ? 'auto auto auto' | |
| : 'auto auto', | |
| alignItems: 'center', | |
| columnGap: '3rem', | |
| width: 'fit-content', | |
| }} | |
| > | |
| <div | |
| style={{ | |
| fontSize: 12, | |
| fontWeight: 500, | |
| textAlign: 'left', | |
| paddingLeft: `calc(${appTheme.spacing.xxSmall} + ${appTheme.spacing.medium})`, | |
| }} | |
| > | |
| Name | |
| </div> | |
| <div style={{ fontSize: 12, fontWeight: 500 }}>Type</div> | |
| {showNullable && ( | |
| <div style={{ fontSize: 12, fontWeight: 500 }}>Nullable</div> | |
| )} |


This PR introduces the nullable checkbox for clickhouse destinations.
Fixes #3486