Skip to content

Commit 44b3853

Browse files
authored
Reinstate link button for foreign keys in table editor side panel (supabase#23186)
* Reinstate link button for foreign keys in table editor side panel * Skip assigning name when creating foreign key
1 parent 58aa0ef commit 44b3853

File tree

9 files changed

+260
-74
lines changed

9 files changed

+260
-74
lines changed

apps/studio/components/interfaces/Realtime/Inspector/useRealtimeMessages.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export const useRealtimeMessages = (
161161
}
162162

163163
// Finally, subscribe to the Channel we just setup
164-
newChannel.subscribe(async (status, error) => {
164+
newChannel.subscribe(async (status) => {
165165
if (status === 'SUBSCRIBED') {
166166
// Let LiveView know we connected so we can update the button text
167167
// pushMessageTo('#conn_info', 'broadcast_subscribed', { host: host })
@@ -174,8 +174,6 @@ export const useRealtimeMessages = (
174174
payload: { name: name, t: performance.now() },
175175
})
176176
}
177-
} else if (status === 'CLOSED') {
178-
// console.log(`Realtime Channel status: ${status}`)
179177
} else if (status === 'CHANNEL_ERROR') {
180178
toast.error(
181179
'Failed to connect to the channel: This may be due to restrictive RLS policies. Check your role and try again.'
@@ -184,9 +182,6 @@ export const useRealtimeMessages = (
184182
newChannel.unsubscribe()
185183
setChannel(undefined)
186184
setRealtimeConfig({ ...config, enabled: false })
187-
} else {
188-
// console.error(`Realtime Channel error status: ${status}`)
189-
// console.error(`Realtime Channel error: ${error}`)
190185
}
191186
})
192187

apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,7 @@ export function ChartConfig({ results = { rows: [] }, config, onConfigChange }:
164164
id="cumulative"
165165
name="cumulative"
166166
checked={config.cumulative}
167-
onClick={(e) => {
168-
console.log(e)
169-
onConfigChange({ ...config, cumulative: !config.cumulative })
170-
}}
167+
onClick={() => onConfigChange({ ...config, cumulative: !config.cumulative })}
171168
/>
172169
<Label_Shadcn_ className="text-foreground-light p-1.5" htmlFor="cumulative">
173170
Cumulative

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import {
99
Alert_Shadcn_,
1010
Button,
1111
Checkbox,
12-
IconAlertCircle,
13-
IconAlertTriangle,
1412
IconExternalLink,
1513
IconPlus,
1614
Input,
@@ -32,6 +30,7 @@ import {
3230
import { useEnumeratedTypesQuery } from 'data/enumerated-types/enumerated-types-query'
3331
import { EXCLUDED_SCHEMAS_WITHOUT_EXTENSIONS } from 'lib/constants/schemas'
3432
import type { Dictionary } from 'types'
33+
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
3534
import ActionBar from '../ActionBar'
3635
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
3736
import { formatForeignKeys } from '../ForeignKeySelector/ForeignKeySelector.utils'
@@ -52,7 +51,6 @@ import {
5251
import ColumnForeignKey from './ColumnForeignKey'
5352
import ColumnType from './ColumnType'
5453
import HeaderTitle from './HeaderTitle'
55-
import toast from 'react-hot-toast'
5654

5755
export interface ColumnEditorProps {
5856
column?: PostgresColumn
@@ -361,7 +359,7 @@ const ColumnEditor = ({
361359
>
362360
<FormSectionContent loading={false} className="lg:!col-span-8">
363361
<Alert_Shadcn_>
364-
<IconAlertCircle />
362+
<WarningIcon />
365363
<AlertTitle_Shadcn_>
366364
Column encryption has been removed from the GUI
367365
</AlertTitle_Shadcn_>
@@ -400,7 +398,7 @@ const ColumnEditor = ({
400398
>
401399
<FormSectionContent loading={false} className="lg:!col-span-8">
402400
<Alert_Shadcn_ variant="warning">
403-
<IconAlertTriangle strokeWidth={2} />
401+
<WarningIcon strokeWidth={2} />
404402
<AlertTitle_Shadcn_>This table uses column-privileges</AlertTitle_Shadcn_>
405403
<AlertDescription_Shadcn_>
406404
<p>

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,6 @@ const SidePanelEditor = ({
351351
})
352352
}
353353
} catch (error: any) {
354-
console.log({ error })
355354
toast.error(`Failed to update realtime for ${table.name}: ${error.message}`)
356355
}
357356
}

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,7 @@ export const getAddForeignKeySQL = ({
166166
const onUpdateSql = getOnUpdateSql(updateAction)
167167
return `
168168
ALTER TABLE "${table.schema}"."${table.name}"
169-
ADD CONSTRAINT "${table.schema}_${table.name}_${relation.columns.map((x) => x.source).join('_')}_fkey"
170-
FOREIGN KEY (${relation.columns.map((column) => `"${column.source}"`).join(',')})
169+
ADD FOREIGN KEY (${relation.columns.map((column) => `"${column.source}"`).join(',')})
171170
REFERENCES "${relation.schema}"."${relation.table}" (${relation.columns.map((column) => `"${column.target}"`).join(',')})
172171
${onUpdateSql}
173172
${onDeleteSql}

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx

Lines changed: 153 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
1-
import { noop } from 'lodash'
2-
import { Checkbox, IconMenu, IconSettings, IconX, Input, Popover } from 'ui'
1+
import { Link, Menu, Plus, Settings, X } from 'lucide-react'
2+
import {
3+
Badge,
4+
Button,
5+
Checkbox,
6+
CommandGroup_Shadcn_,
7+
CommandItem_Shadcn_,
8+
CommandList_Shadcn_,
9+
CommandSeparator_Shadcn_,
10+
Command_Shadcn_,
11+
Input,
12+
Popover,
13+
PopoverContent_Shadcn_,
14+
PopoverTrigger_Shadcn_,
15+
Popover_Shadcn_,
16+
cn,
17+
} from 'ui'
318

19+
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
20+
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
421
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
522
import { EMPTY_ARR, EMPTY_OBJ } from 'lib/void'
23+
import { useState } from 'react'
624
import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants'
725
import type { Suggestion } from '../ColumnEditor/ColumnEditor.types'
826
import ColumnType from '../ColumnEditor/ColumnType'
927
import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions'
28+
import { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
1029
import type { ColumnField } from '../SidePanelEditor.types'
30+
import { checkIfRelationChanged } from './ForeignKeysManagement/ForeignKeysManagement.utils'
1131

1232
/**
1333
* [Joshen] For context:
@@ -29,25 +49,31 @@ import type { ColumnField } from '../SidePanelEditor.types'
2949

3050
interface ColumnProps {
3151
column: ColumnField
52+
relations: ForeignKey[]
3253
enumTypes: EnumeratedType[]
3354
isNewRecord: boolean
3455
hasForeignKeys: boolean
3556
hasImportContent: boolean
3657
dragHandleProps?: any
3758
onUpdateColumn: (changes: Partial<ColumnField>) => void
3859
onRemoveColumn: () => void
60+
onEditForeignKey: (relation?: ForeignKey) => void
3961
}
4062

4163
const Column = ({
4264
column = EMPTY_OBJ as ColumnField,
65+
relations = EMPTY_ARR as ForeignKey[],
4366
enumTypes = EMPTY_ARR as EnumeratedType[],
4467
isNewRecord = false,
4568
hasForeignKeys = false,
4669
hasImportContent = false,
4770
dragHandleProps = EMPTY_OBJ,
48-
onUpdateColumn = noop,
49-
onRemoveColumn = noop,
71+
onUpdateColumn,
72+
onRemoveColumn,
73+
onEditForeignKey,
5074
}: ColumnProps) => {
75+
const { project } = useProjectContext()
76+
const [open, setOpen] = useState(false)
5177
const suggestions: Suggestion[] = typeExpressionSuggestions?.[column.format] ?? []
5278

5379
const settingsCount = [
@@ -57,26 +83,142 @@ const Column = ({
5783
column.isArray ? 1 : 0,
5884
].reduce((a, b) => a + b, 0)
5985

86+
const { data } = useForeignKeyConstraintsQuery({
87+
projectRef: project?.ref,
88+
connectionString: project?.connectionString,
89+
schema: column.schema,
90+
})
91+
92+
const getRelationStatus = (fk: ForeignKey) => {
93+
const existingRelation = (data ?? []).find((x) => x.id === fk.id)
94+
const stateRelation = relations.find((x) => x.id === fk.id)
95+
96+
if (stateRelation?.toRemove) return 'REMOVE'
97+
if (existingRelation === undefined && stateRelation !== undefined) return 'ADD'
98+
if (existingRelation !== undefined && stateRelation !== undefined) {
99+
const hasUpdated = checkIfRelationChanged(existingRelation, stateRelation)
100+
if (hasUpdated) return 'UPDATE'
101+
else return undefined
102+
}
103+
}
104+
105+
const hasChangesInRelations = relations
106+
.map((r) => getRelationStatus(r))
107+
.some((x) => x !== undefined)
108+
60109
return (
61110
<div className="flex w-full items-center">
62111
<div className={`w-[5%] ${!isNewRecord ? 'hidden' : ''}`}>
63112
<div className="cursor-drag" {...dragHandleProps}>
64-
<IconMenu strokeWidth={1} size={15} />
113+
<Menu strokeWidth={1} size={16} />
65114
</div>
66115
</div>
67116
<div className="w-[25%]">
68117
<div className="flex w-[95%] items-center justify-between">
69118
<Input
70-
value={column.name}
71119
size="small"
120+
value={column.name}
72121
title={column.name}
73122
disabled={hasImportContent}
74123
placeholder="column_name"
75-
className={`table-editor-columns-input bg-surface-100 lg:gap-0 ${
124+
className={cn(
125+
'[&>div>div>div>input]:py-1.5 [&>div>div>div>input]:border-r-transparent [&>div>div>div>input]:rounded-r-none',
76126
hasImportContent ? 'opacity-50' : ''
77-
} rounded-md`}
127+
)}
78128
onChange={(event: any) => onUpdateColumn({ name: event.target.value })}
79129
/>
130+
{relations.filter((r) => !r.toRemove).length === 0 ? (
131+
<Button
132+
type="dashed"
133+
className="rounded-l-none h-[30px] py-0 px-2"
134+
onClick={() => onEditForeignKey()}
135+
>
136+
<Link size={12} />
137+
</Button>
138+
) : (
139+
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
140+
<PopoverTrigger_Shadcn_ asChild>
141+
<Button type="default" className="rounded-l-none h-[30px] py-0 px-2">
142+
<Link size={12} />
143+
</Button>
144+
</PopoverTrigger_Shadcn_>
145+
<PopoverContent_Shadcn_
146+
className={cn('p-0', hasChangesInRelations ? 'w-96' : 'w-72')}
147+
side="bottom"
148+
align="end"
149+
>
150+
<div className="text-xs px-2 pt-2">
151+
Involved in {relations.length} foreign key{relations.length > 1 ? 's' : ''}
152+
</div>
153+
<Command_Shadcn_>
154+
<CommandList_Shadcn_>
155+
<CommandGroup_Shadcn_>
156+
{relations.map((relation, idx) => {
157+
const key = String(relation?.id ?? `${column.id}-relation-${idx}`)
158+
const status = getRelationStatus(relation)
159+
if (status === 'REMOVE') return null
160+
161+
return (
162+
<CommandItem_Shadcn_
163+
key={key}
164+
value={key}
165+
className="cursor-pointer w-full"
166+
onSelect={() => onEditForeignKey(relation)}
167+
onClick={() => onEditForeignKey(relation)}
168+
>
169+
{status === undefined ? (
170+
<div className="w-full flex items-center justify-between truncate">
171+
{relation.name}
172+
</div>
173+
) : (
174+
<div className="flex items-center gap-x-2 truncate">
175+
<Badge variant={status === 'ADD' ? 'brand' : 'warning'}>
176+
{status}
177+
</Badge>
178+
<p className="truncate">
179+
{relation.name || (
180+
<>
181+
To{' '}
182+
{relation.columns
183+
.filter((c) => c.source === column.name)
184+
.map((c) => {
185+
return (
186+
<code key={`${c.source}-${c.target}`}>
187+
{relation.schema}.{relation.table}.{c.target}
188+
</code>
189+
)
190+
})}
191+
{relation.columns.length > 1 && (
192+
<>
193+
and {relation.columns.length - 1} other column
194+
{relation.columns.length > 2 ? 's' : ''}
195+
</>
196+
)}
197+
</>
198+
)}
199+
</p>
200+
</div>
201+
)}
202+
</CommandItem_Shadcn_>
203+
)
204+
})}
205+
</CommandGroup_Shadcn_>
206+
<CommandSeparator_Shadcn_ />
207+
<CommandGroup_Shadcn_>
208+
<CommandItem_Shadcn_
209+
className="cursor-pointer w-full gap-x-2"
210+
onSelect={() => onEditForeignKey()}
211+
onClick={() => onEditForeignKey()}
212+
>
213+
<Plus size={14} strokeWidth={1.5} />
214+
<p>Add foreign key relation</p>
215+
</CommandItem_Shadcn_>
216+
</CommandGroup_Shadcn_>
217+
</CommandList_Shadcn_>
218+
</Command_Shadcn_>
219+
</PopoverContent_Shadcn_>
220+
</Popover_Shadcn_>
221+
)}
80222
</div>
81223
</div>
82224
<div className="w-[25%]">
@@ -200,12 +342,12 @@ const Column = ({
200342
>
201343
<div className="group flex items-center -space-x-1">
202344
{settingsCount > 0 && (
203-
<div className="rounded-full bg-foreground py-0.5 px-2 text-xs text-background">
345+
<div className="rounded-full bg-foreground h-4 w-4 flex items-center justify-center text-xs text-background">
204346
{settingsCount}
205347
</div>
206348
)}
207349
<div className="text-foreground-light transition-colors group-hover:text-foreground">
208-
<IconSettings size={18} strokeWidth={1} />
350+
<Settings size={16} strokeWidth={1} />
209351
</div>
210352
</div>
211353
</Popover>
@@ -215,7 +357,7 @@ const Column = ({
215357
{!hasImportContent && (
216358
<div className="flex w-[5%] justify-end">
217359
<button className="cursor-pointer" onClick={() => onRemoveColumn()}>
218-
<IconX strokeWidth={1} />
360+
<X size={16} strokeWidth={1} />
219361
</button>
220362
</div>
221363
)}

0 commit comments

Comments
 (0)