mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
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:
1 parent
e9d951a65c
commit
4d23ea7b82
4 files changed
+140
-78
No files matched your search
+52
-14
@@ -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"
|
||||
|
||||
+67
-41
@@ -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
|
||||
+18
-19
@@ -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
|
||||
+3
-4
@@ -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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user