Hide is unique option for column that's a primary key (#43974)

## Context

Table Editor - hide "Is unique" configuration for a column that's a
primary key - unnecessary to configure a column as unique if it's
already a primary key (redundant index is created)

## Changes involved

- TableEditor side panel: No longer shows is unique option under
configuration popover if column is a PK
- ColumnEditor side panel: Disable is unique + is nullable option if
column is a PK (Added tooltip)
<img width="810" height="151" alt="image"
src="https://github.com/user-attachments/assets/0f877706-1a56-44b7-864e-ba30efbb67f7"
/>


Also chucked in some minor refactors:
- Refactored some `any` types
- Refactored Column to use the latest Checkbox component
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-03-20 17:12:53 +08:00
1 parent e9d951a65c
commit 4d23ea7b82
4 files changed
+140 -78

No files matched your search

@@ -19,7 +19,16 @@ import { ExternalLink, Plus } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import type { Dictionary } from 'types'
import { Button, Checkbox, Input, SidePanel, Toggle } from 'ui'
import {
Button,
Checkbox,
Input,
SidePanel,
Toggle,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { ActionBar } from '../ActionBar'
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
@@ -378,20 +387,49 @@ export const ColumnEditor = ({
label="Is Primary Key"
descriptionText="A primary key indicates that a column or group of columns can be used as a unique identifier for rows in the table"
checked={columnFields?.isPrimaryKey ?? false}
onChange={() => onUpdateField({ isPrimaryKey: !columnFields?.isPrimaryKey })}
/>
<Toggle
label="Allow Nullable"
descriptionText="Allow the column to assume a NULL value if no value is provided"
checked={columnFields.isNullable}
onChange={() => onUpdateField({ isNullable: !columnFields.isNullable })}
/>
<Toggle
label="Is Unique"
descriptionText="Enforce values in the column to be unique across rows"
checked={columnFields.isUnique}
onChange={() => onUpdateField({ isUnique: !columnFields.isUnique })}
onChange={() =>
onUpdateField({
isPrimaryKey: !columnFields?.isPrimaryKey,
isUnique: false,
isNullable: false,
})
}
/>
<Tooltip>
<TooltipTrigger asChild>
<div>
<Toggle
label="Allow Nullable"
disabled={columnFields.isPrimaryKey}
descriptionText="Allow the column to assume a NULL value if no value is provided"
checked={columnFields.isNullable}
onChange={() => onUpdateField({ isNullable: !columnFields.isNullable })}
/>
</div>
</TooltipTrigger>
<TooltipContent side="left" align="start">
Column is a primary key and hence cannot be NULL
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<div>
<Toggle
label="Is Unique"
disabled={columnFields.isPrimaryKey}
descriptionText="Enforce values in the column to be unique across rows"
checked={columnFields.isUnique}
onChange={() => onUpdateField({ isUnique: !columnFields.isUnique })}
/>
</div>
</TooltipTrigger>
<TooltipContent side="left" align="start">
Column is a primary key and hence already unique
</TooltipContent>
</Tooltip>
<Input
label="CHECK Constraint"
labelOptional="Optional"
@@ -4,10 +4,12 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { EMPTY_ARR, EMPTY_OBJ } from 'lib/void'
import { Link, Menu, Plus, Settings, X } from 'lucide-react'
import { useState } from 'react'
import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd'
import {
Badge,
Button,
Checkbox,
Checkbox_Shadcn_,
cn,
Command_Shadcn_,
CommandGroup_Shadcn_,
@@ -19,6 +21,7 @@ import {
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants'
import type { Suggestion } from '../ColumnEditor/ColumnEditor.types'
@@ -53,20 +56,20 @@ interface ColumnProps {
isNewRecord: boolean
hasForeignKeys: boolean
hasImportContent: boolean
dragHandleProps?: any
dragHandleProps?: DraggableProvidedDragHandleProps | null
onUpdateColumn: (changes: Partial<ColumnField>) => void
onRemoveColumn: () => void
onEditForeignKey: (relation?: ForeignKey) => void
}
const Column = ({
export const Column = ({
column = EMPTY_OBJ as ColumnField,
relations = EMPTY_ARR as ForeignKey[],
enumTypes = EMPTY_ARR as EnumeratedType[],
isNewRecord = false,
hasForeignKeys = false,
hasImportContent = false,
dragHandleProps = EMPTY_OBJ,
dragHandleProps,
onUpdateColumn,
onRemoveColumn,
onEditForeignKey,
@@ -125,7 +128,7 @@ const Column = ({
'[&>div>div>div>input]:py-1.5 [&>div>div>div>input]:border-r-transparent [&>div>div>div>input]:rounded-r-none',
hasImportContent ? 'opacity-50' : ''
)}
onChange={(event: any) => onUpdateColumn({ name: event.target.value })}
onChange={(event) => onUpdateColumn({ name: event.target.value })}
/>
{relations.filter((r) => !r.toRemove).length === 0 ? (
<Button
@@ -258,7 +261,7 @@ const Column = ({
suggestions={suggestions}
suggestionsHeader="Suggested expressions"
suggestionsTooltip="Suggested expressions"
onChange={(event: any) => onUpdateColumn({ defaultValue: event.target.value })}
onChange={(event) => onUpdateColumn({ defaultValue: event.target.value })}
onSelectSuggestion={(suggestion: Suggestion) =>
onUpdateColumn({ defaultValue: suggestion.value })
}
@@ -295,54 +298,79 @@ const Column = ({
<Settings size={16} strokeWidth={1} />
</div>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ align="end" className="w-96 p-0">
<div className="flex items-center justify-center bg-surface-200 space-y-1 py-1.5 px-3 border-b border-overlay">
<PopoverContent_Shadcn_ align="end" className="w-80 p-0">
<div className="flex items-center justify-center bg-surface-200 gap-y-1 py-1.5 px-3 border-b border-overlay">
<h5 className="text-foreground">Extra options</h5>
</div>
<div className="flex flex-col space-y-1" key={`${column.id}_configuration`}>
<div className="flex flex-col gap-y-4 p-4" key={`${column.id}_configuration`}>
{!column.isPrimaryKey && (
<Checkbox
<FormItemLayout
isReactForm={false}
layout="flex"
id="isNullable"
label="Is Nullable"
description="Specify if the column can assume a NULL value if no value is provided"
checked={column.isNullable}
className="p-4"
onChange={() => onUpdateColumn({ isNullable: !column.isNullable })}
/>
>
<Checkbox_Shadcn_
id="isNullable"
checked={column.isNullable}
onCheckedChange={() => onUpdateColumn({ isNullable: !column.isNullable })}
/>
</FormItemLayout>
)}
{!column.isPrimaryKey && (
<FormItemLayout
isReactForm={false}
layout="flex"
id="isUnique"
label="Is Unique"
description="Enforce if values in the column should be unique across rows"
>
<Checkbox_Shadcn_
id="isUnique"
checked={column.isUnique}
onCheckedChange={() => onUpdateColumn({ isUnique: !column.isUnique })}
/>
</FormItemLayout>
)}
<Checkbox
label="Is Unique"
description="Enforce if values in the column should be unique across rows"
checked={column.isUnique}
className="p-4"
onChange={() => onUpdateColumn({ isUnique: !column.isUnique })}
/>
{column.format.includes('int') && (
<Checkbox
<FormItemLayout
isReactForm={false}
layout="flex"
id="isIdentity"
label="Is Identity"
description="Automatically assign a sequential unique number to the column"
checked={column.isIdentity}
className="p-4"
onChange={() => {
const isIdentity = !column.isIdentity
const isArray = isIdentity ? false : column.isArray
onUpdateColumn({ isIdentity, isArray })
}}
/>
>
<Checkbox_Shadcn_
id="isIdentity"
checked={column.isIdentity}
onCheckedChange={() => {
const isIdentity = !column.isIdentity
const isArray = isIdentity ? false : column.isArray
onUpdateColumn({ isIdentity, isArray })
}}
/>
</FormItemLayout>
)}
{!column.isPrimaryKey && (
<Checkbox
<FormItemLayout
isReactForm={false}
layout="flex"
id="defineAsArray"
label="Define as Array"
description="Define your column as a variable-length multidimensional array"
checked={column.isArray}
className="p-4"
onChange={() => {
const isArray = !column.isArray
const isIdentity = isArray ? false : column.isIdentity
onUpdateColumn({ isArray, isIdentity })
}}
/>
>
<Checkbox_Shadcn_
id="defineAsArray"
checked={column.isArray}
onCheckedChange={() => {
const isArray = !column.isArray
const isIdentity = isArray ? false : column.isIdentity
onUpdateColumn({ isArray, isIdentity })
}}
/>
</FormItemLayout>
)}
</div>
</PopoverContent_Shadcn_>
@@ -359,5 +387,3 @@ const Column = ({
</div>
)
}
export default Column
@@ -1,36 +1,37 @@
import { isEmpty, noop, partition } from 'lodash'
import { Edit, ExternalLink, HelpCircle, Key, Trash } from 'lucide-react'
import { useState } from 'react'
import {
DragDropContext,
Draggable,
DraggableProvided,
Droppable,
DroppableProvided,
} from 'react-beautiful-dnd'
import { useParams } from 'common'
import InformationBox from 'components/ui/InformationBox'
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { DOCS_URL } from 'lib/constants'
import { isEmpty, noop, partition } from 'lodash'
import { Edit, ExternalLink, HelpCircle, Key, Trash } from 'lucide-react'
import { useState } from 'react'
import {
DragDropContext,
Draggable,
Droppable,
type DraggableProvided,
type DroppableProvided,
type DropResult,
} from 'react-beautiful-dnd'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
Tooltip,
TooltipContent,
TooltipTrigger,
WarningIcon,
} from 'ui'
import { generateColumnField } from '../ColumnEditor/ColumnEditor.utils'
import { ForeignKeySelector } from '../ForeignKeySelector/ForeignKeySelector'
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
import { TEXT_TYPES } from '../SidePanelEditor.constants'
import type { ColumnField, ExtendedPostgresRelationship } from '../SidePanelEditor.types'
import Column from './Column'
import { Column } from './Column'
import type { ImportContent, TableField } from './TableEditor.types'
interface ColumnManagementProps {
@@ -46,7 +47,7 @@ interface ColumnManagementProps {
onUpdateFkRelations: (relations: ForeignKey[]) => void
}
const ColumnManagement = ({
export const ColumnManagement = ({
table,
columns = [],
relations,
@@ -114,7 +115,7 @@ const ColumnManagement = ({
onColumnsUpdated(updatedColumns)
}
const onSortColumns = (result: any, type: 'pks' | 'others') => {
const onSortColumns = (result: DropResult, type: 'pks' | 'others') => {
// Dropped outside of the list
if (!result.destination) {
return
@@ -258,7 +259,7 @@ const ColumnManagement = ({
</div>
{primaryKeyColumns.length > 0 && (
<DragDropContext onDragEnd={(result: any) => onSortColumns(result, 'pks')}>
<DragDropContext onDragEnd={(result) => onSortColumns(result, 'pks')}>
<Droppable droppableId="pk_columns_droppable">
{(droppableProvided: DroppableProvided) => (
<div
@@ -303,7 +304,7 @@ const ColumnManagement = ({
</DragDropContext>
)}
<DragDropContext onDragEnd={(result: any) => onSortColumns(result, 'others')}>
<DragDropContext onDragEnd={(result) => onSortColumns(result, 'others')}>
<Droppable droppableId="other_columns_droppable">
{(droppableProvided: DroppableProvided) => (
<div
@@ -379,5 +380,3 @@ const ColumnManagement = ({
</>
)
}
export default ColumnManagement
@@ -29,7 +29,7 @@ import type { SaveTableParams } from '../SidePanelEditor'
import type { ColumnField } from '../SidePanelEditor.types'
import { SpreadsheetImport } from '../SpreadsheetImport/SpreadsheetImport'
import { ApiAccessToggle, type TableApiAccessHandlerWithHistoryReturn } from './ApiAccessToggle'
import ColumnManagement from './ColumnManagement'
import { ColumnManagement } from './ColumnManagement'
import { ForeignKeysManagement } from './ForeignKeysManagement/ForeignKeysManagement'
import { HeaderTitle } from './HeaderTitle'
import { RLSDisableModalContent } from './RLSDisableModal'
@@ -42,7 +42,6 @@ import {
validateFields,
} from './TableEditor.utils'
import { useDataApiGrantTogglesEnabled } from '@/hooks/misc/useDataApiGrantTogglesEnabled'
import { checkDataApiPrivilegesNonEmpty } from '@/lib/data-api-types'
type SaveTableParamsFor<Action extends SaveTableParams['action']> = Extract<
SaveTableParams,
@@ -357,7 +356,7 @@ export const TableEditor = ({
type="text"
error={errors.name ? String(errors.name) : undefined}
value={tableFields?.name}
onChange={(event: any) => onUpdateField({ name: event.target.value })}
onChange={(event) => onUpdateField({ name: event.target.value })}
/>
<Input
label="Description"
@@ -366,7 +365,7 @@ export const TableEditor = ({
layout="horizontal"
type="text"
value={tableFields?.comment ?? ''}
onChange={(event: any) => onUpdateField({ comment: event.target.value })}
onChange={(event) => onUpdateField({ comment: event.target.value })}
/>
</SidePanel.Content>