mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): filter on every column when viewing a composite FK record (#50256)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Bug fix ## What is the current behavior? In the Table Editor, the "View referencing record" arrow on a foreign key cell builds its filter from the table's `relationships` list. That list is one entry per source-by-target column combination, and the formatter takes the first entry whose source column matches the clicked cell, then filters the referenced table on that single target column. For a composite foreign key this fails in two ways: - The value from the clicked column can be applied to the wrong target column (whichever target column happens to be listed first for that source column). - Only one of the key columns is ever filtered on, so the peek and the "Open table" link return zero rows or too many rows. This is distinct from #41068 / #41080, which fixed the cartesian expansion in the shared `tables.ts` introspection query. The bug reproduces with that fix in place because the mis-pairing happens in Studio when building the filter. Reported by a customer via support. ### Reproduction Run this in the SQL editor. The referenced column list `(org_id, bucket_id)` is deliberately not in the referenced table's physical column order, which is what exposes the bug. ```sql create schema if not exists dcs; -- bucket_id declared first, org_id second create table dcs.org_metering_buckets ( bucket_id bigint not null, org_id bigint not null, primary key (org_id, bucket_id) ); create table dcs.machine_storage_usage_buckets ( org_id bigint not null, org_metering_bucket_id bigint not null, constraint machine_storage_usage_buckets_org_metering_bucket_fkey foreign key (org_id, org_metering_bucket_id) references dcs.org_metering_buckets (org_id, bucket_id) ); insert into dcs.org_metering_buckets (bucket_id, org_id) values (901, 1), (902, 1), (903, 2); insert into dcs.machine_storage_usage_buckets (org_id, org_metering_bucket_id) values (1, 901), (1, 902), (2, 903); ``` 1. Open `dcs.machine_storage_usage_buckets` in the Table Editor (select the `dcs` schema). 2. Hover the `org_id` cell on the row where `org_id = 2`. 3. Click the "View referencing record" arrow. 4. Click "Open table" in the popover. **Before this PR:** the popover shows "No results were returned". "Open table" opens `dcs.org_metering_buckets` with a single filter `bucket_id = 2`, which matches nothing. **After this PR:** the popover shows the one row `(bucket_id = 903, org_id = 2)`. "Open table" opens `dcs.org_metering_buckets` with two filters, `org_id = 2` and `bucket_id = 903`, and the URL carries two `filter=` params. Clicking the arrow on the `org_metering_bucket_id` cell of the same row produces the same result. Extra checks worth doing while you are there: - Set one of the two key columns to NULL on a row. The arrow should disappear for both key cells on that row, since a row with a null key column does not reference anything under MATCH SIMPLE. - A single-column foreign key (any existing table) should behave exactly as before. ## What is the new behavior? - New `ForeignKeyFormatter.utils.ts` with two pure functions. `findColumnForeignKeyConstraint` locates the constraint on the current table that contains the clicked column. `getReferencingRecordFilters` pairs each source column with the target column at the same ordinal position and builds one equality filter per pair from the row's values, keeping the existing bytea-to-hex handling per column. It returns no filters when any key column is null. - `ForeignKeyFormatter` now reads the foreign key constraints query, which returns ordinally paired source and target column arrays, instead of the `relationships` list. The grid already fetches that query for the same schema, so it is served from the React Query cache. - `ReferenceRecordPeek` takes a `filters` array instead of a single column and value. Both the peek query and the "Open table" link use the full set, and the link emits one URI-encoded `filter=` param per column. - Unit tests cover the reproduction above, single-column keys, bytea values, null and missing values, falsy-but-valid values such as `0`, and malformed constraints. ## Additional context The table-editor introspection SQL in `packages/pg-meta/src/sql/studio/table-editor/table.ts` and `tables-paginated.ts` still expands composite foreign keys as a cartesian product. #41080 only fixed the shared `tables.ts` query. The arrow no longer depends on that data, but it can still mislabel the referenced column elsewhere in Studio, so that is left for a follow-up rather than widening this change into pg-meta SQL. Before: <img width="836" height="504" alt="Screenshot 2026-09-14 at 11 22 49" src="https://github.com/user-attachments/assets/4645d64f-9bcb-44b0-b5b1-ab11ba7436db" /> After: <img width="873" height="570" alt="Screenshot 2026-09-14 at 11 23 14" src="https://github.com/user-attachments/assets/f2c47121-fd75-4efe-a370-28015c1b674e" /> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01SkH86UV1KD4Xs5k9vt43tW <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved foreign-key record previews to correctly identify referenced records across schemas and tables. * Added support for composite foreign keys, ensuring previews and “Open table” links apply all required column filters. * Improved handling of binary values and incomplete or null foreign-key data. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a409db31c
commit
10eaab766f
4 files changed
+352
-26
No files matched your search
@@ -8,9 +8,14 @@ import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
import type { SupaRow } from '../../types'
|
||||
import { isColumnMasked } from '../../utils/sensitive-data'
|
||||
import { NullValue } from '../common/NullValue'
|
||||
import {
|
||||
findColumnForeignKeyConstraint,
|
||||
getReferencingRecordFilters,
|
||||
} from './ForeignKeyFormatter.utils'
|
||||
import { ReferenceRecordPeek } from './ReferenceRecordPeek'
|
||||
import { convertByteaToHex } from '@/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils'
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
import { useForeignKeyConstraintsQuery } from '@/data/database/foreign-key-constraints-query'
|
||||
import { useTableEditorQuery } from '@/data/table-editor/table-editor-query'
|
||||
import { isTableLike } from '@/data/table-editor/table-editor-types'
|
||||
import { useTableQuery } from '@/data/tables/table-retrieve-query'
|
||||
@@ -39,41 +44,55 @@ export const ForeignKeyFormatter = (props: Props) => {
|
||||
const foreignKeyColumn = data?.columns.find((x) => x.name === column.key)
|
||||
const selectedTable = isTableLike(data) ? data : undefined
|
||||
|
||||
const relationship = (selectedTable?.relationships ?? []).find(
|
||||
(r) =>
|
||||
r.source_schema === selectedTable?.schema &&
|
||||
r.source_table_name === selectedTable?.name &&
|
||||
r.source_column_name === column.name
|
||||
)
|
||||
// The constraints query returns source/target columns as ordinally paired
|
||||
// arrays, which is what a composite foreign key needs to filter correctly.
|
||||
const { data: foreignKeys, isPending: isLoadingForeignKeys } = useForeignKeyConstraintsQuery({
|
||||
projectRef: project?.ref,
|
||||
schema: selectedTable?.schema,
|
||||
})
|
||||
|
||||
const foreignKey =
|
||||
selectedTable !== undefined
|
||||
? findColumnForeignKeyConstraint({
|
||||
foreignKeys: foreignKeys ?? [],
|
||||
schema: selectedTable.schema,
|
||||
table: selectedTable.name,
|
||||
columnName: column.key,
|
||||
})
|
||||
: undefined
|
||||
|
||||
const { data: targetTable, isPending: isLoadingTargetTable } = useTableQuery<PGTable>(
|
||||
{
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
schema: relationship?.target_table_schema ?? '',
|
||||
name: relationship?.target_table_name ?? '',
|
||||
schema: foreignKey?.target_schema ?? '',
|
||||
name: foreignKey?.target_table ?? '',
|
||||
},
|
||||
{
|
||||
enabled:
|
||||
!!project?.ref && !!relationship?.target_table_schema && !!relationship?.target_table_name,
|
||||
}
|
||||
{ enabled: !!project?.ref && foreignKey !== undefined }
|
||||
)
|
||||
|
||||
const value = row[column.key]
|
||||
const formattedValue =
|
||||
foreignKeyColumn?.format === 'bytea' && !!value ? convertByteaToHex(value) : value
|
||||
|
||||
const filters =
|
||||
foreignKey !== undefined
|
||||
? getReferencingRecordFilters({ foreignKey, row, columns: data?.columns ?? [] })
|
||||
: []
|
||||
const hasReferencingRecord = filters.length > 0
|
||||
const isLoadingMetadata = isLoading || (selectedTable !== undefined && isLoadingForeignKeys)
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between flex justify-between">
|
||||
<span className="m-0 grow overflow-hidden text-ellipsis">
|
||||
{formattedValue === null ? <NullValue /> : isMasked ? '••••••••' : formattedValue}
|
||||
</span>
|
||||
{isLoading && formattedValue !== null && (
|
||||
{isLoadingMetadata && formattedValue !== null && (
|
||||
<div className="w-6 h-6 flex items-center justify-center">
|
||||
<ShimmeringLoader className="w-4 h-4" />
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && relationship !== undefined && formattedValue !== null && (
|
||||
{!isLoadingMetadata && hasReferencingRecord && (
|
||||
<>
|
||||
{isLoadingTargetTable && (
|
||||
<div className="w-6 h-6 flex items-center justify-center">
|
||||
@@ -103,11 +122,7 @@ export const ForeignKeyFormatter = (props: Props) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<ReferenceRecordPeek
|
||||
table={targetTable}
|
||||
column={relationship.target_column_name}
|
||||
value={formattedValue}
|
||||
/>
|
||||
<ReferenceRecordPeek table={targetTable} filters={filters} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
findColumnForeignKeyConstraint,
|
||||
getReferencingRecordFilters,
|
||||
} from './ForeignKeyFormatter.utils'
|
||||
import type { ForeignKeyConstraint } from '@/data/database/foreign-key-constraints-query'
|
||||
|
||||
const compositeForeignKey: ForeignKeyConstraint = {
|
||||
id: 1,
|
||||
constraint_name: 'machine_storage_usage_buckets_org_metering_bucket_fkey',
|
||||
deletion_action: 'a',
|
||||
update_action: 'a',
|
||||
source_id: 10,
|
||||
source_schema: 'dcs',
|
||||
source_table: 'machine_storage_usage_buckets',
|
||||
source_columns: ['org_id', 'org_metering_bucket_id'],
|
||||
target_id: 11,
|
||||
target_schema: 'dcs',
|
||||
target_table: 'org_metering_buckets',
|
||||
// Deliberately not in the target table's physical column order
|
||||
target_columns: ['org_id', 'bucket_id'],
|
||||
}
|
||||
|
||||
const simpleForeignKey: ForeignKeyConstraint = {
|
||||
id: 2,
|
||||
constraint_name: 'orders_customer_id_fkey',
|
||||
deletion_action: 'a',
|
||||
update_action: 'a',
|
||||
source_id: 20,
|
||||
source_schema: 'public',
|
||||
source_table: 'orders',
|
||||
source_columns: ['customer_id'],
|
||||
target_id: 21,
|
||||
target_schema: 'public',
|
||||
target_table: 'customers',
|
||||
target_columns: ['id'],
|
||||
}
|
||||
|
||||
const bigintColumns = [
|
||||
{ name: 'org_id', format: 'int8' },
|
||||
{ name: 'org_metering_bucket_id', format: 'int8' },
|
||||
]
|
||||
|
||||
describe('findColumnForeignKeyConstraint', () => {
|
||||
const foreignKeys = [simpleForeignKey, compositeForeignKey]
|
||||
|
||||
test('finds the constraint containing the column for the given table', () => {
|
||||
expect(
|
||||
findColumnForeignKeyConstraint({
|
||||
foreignKeys,
|
||||
schema: 'dcs',
|
||||
table: 'machine_storage_usage_buckets',
|
||||
columnName: 'org_metering_bucket_id',
|
||||
})
|
||||
).toBe(compositeForeignKey)
|
||||
})
|
||||
|
||||
test('finds the constraint for any column of a composite key', () => {
|
||||
expect(
|
||||
findColumnForeignKeyConstraint({
|
||||
foreignKeys,
|
||||
schema: 'dcs',
|
||||
table: 'machine_storage_usage_buckets',
|
||||
columnName: 'org_id',
|
||||
})
|
||||
).toBe(compositeForeignKey)
|
||||
})
|
||||
|
||||
test('ignores constraints from other tables with the same column name', () => {
|
||||
expect(
|
||||
findColumnForeignKeyConstraint({
|
||||
foreignKeys,
|
||||
schema: 'public',
|
||||
table: 'orders',
|
||||
columnName: 'org_id',
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
test('ignores constraints from the same table name in another schema', () => {
|
||||
expect(
|
||||
findColumnForeignKeyConstraint({
|
||||
foreignKeys,
|
||||
schema: 'public',
|
||||
table: 'machine_storage_usage_buckets',
|
||||
columnName: 'org_id',
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
test('returns undefined for a column that is not part of any constraint', () => {
|
||||
expect(
|
||||
findColumnForeignKeyConstraint({
|
||||
foreignKeys,
|
||||
schema: 'public',
|
||||
table: 'orders',
|
||||
columnName: 'total',
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
test('returns undefined when there are no constraints', () => {
|
||||
expect(
|
||||
findColumnForeignKeyConstraint({
|
||||
foreignKeys: [],
|
||||
schema: 'public',
|
||||
table: 'orders',
|
||||
columnName: 'customer_id',
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getReferencingRecordFilters', () => {
|
||||
test('pairs each composite key column with the target column at the same position', () => {
|
||||
const filters = getReferencingRecordFilters({
|
||||
foreignKey: compositeForeignKey,
|
||||
row: { idx: 0, org_id: 2, org_metering_bucket_id: 903 },
|
||||
columns: bigintColumns,
|
||||
})
|
||||
|
||||
expect(filters).toStrictEqual([
|
||||
{ column: 'org_id', operator: '=', value: 2 },
|
||||
{ column: 'bucket_id', operator: '=', value: 903 },
|
||||
])
|
||||
})
|
||||
|
||||
test('builds the same filters regardless of which source column was clicked', () => {
|
||||
const row = { idx: 0, org_id: 1, org_metering_bucket_id: 901 }
|
||||
const filters = getReferencingRecordFilters({
|
||||
foreignKey: compositeForeignKey,
|
||||
row,
|
||||
columns: bigintColumns,
|
||||
})
|
||||
|
||||
expect(filters).toStrictEqual([
|
||||
{ column: 'org_id', operator: '=', value: 1 },
|
||||
{ column: 'bucket_id', operator: '=', value: 901 },
|
||||
])
|
||||
})
|
||||
|
||||
test('builds a single filter for a single-column foreign key', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: simpleForeignKey,
|
||||
row: { idx: 0, customer_id: 'abc' },
|
||||
columns: [{ name: 'customer_id', format: 'text' }],
|
||||
})
|
||||
).toStrictEqual([{ column: 'id', operator: '=', value: 'abc' }])
|
||||
})
|
||||
|
||||
test('converts bytea source values to hex', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: simpleForeignKey,
|
||||
row: { idx: 0, customer_id: { type: 'Buffer', data: [222, 173, 190, 239] } },
|
||||
columns: [{ name: 'customer_id', format: 'bytea' }],
|
||||
})
|
||||
).toStrictEqual([{ column: 'id', operator: '=', value: '\\xdeadbeef' }])
|
||||
})
|
||||
|
||||
test('keeps non-bytea values untouched when the column format is unknown', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: simpleForeignKey,
|
||||
row: { idx: 0, customer_id: 42 },
|
||||
columns: [],
|
||||
})
|
||||
).toStrictEqual([{ column: 'id', operator: '=', value: 42 }])
|
||||
})
|
||||
|
||||
test('returns no filters when any composite key value is null', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: compositeForeignKey,
|
||||
row: { idx: 0, org_id: 2, org_metering_bucket_id: null },
|
||||
columns: bigintColumns,
|
||||
})
|
||||
).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('returns no filters when a source value is missing from the row', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: compositeForeignKey,
|
||||
row: { idx: 0, org_id: 2 },
|
||||
columns: bigintColumns,
|
||||
})
|
||||
).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('returns no filters when the single key value is null', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: simpleForeignKey,
|
||||
row: { idx: 0, customer_id: null },
|
||||
columns: [{ name: 'customer_id', format: 'text' }],
|
||||
})
|
||||
).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('keeps falsy but non-null values such as 0 and empty strings', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: compositeForeignKey,
|
||||
row: { idx: 0, org_id: 0, org_metering_bucket_id: '' },
|
||||
columns: bigintColumns,
|
||||
})
|
||||
).toStrictEqual([
|
||||
{ column: 'org_id', operator: '=', value: 0 },
|
||||
{ column: 'bucket_id', operator: '=', value: '' },
|
||||
])
|
||||
})
|
||||
|
||||
test('returns no filters when source and target column counts differ', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: { source_columns: ['org_id', 'bucket_id'], target_columns: ['org_id'] },
|
||||
row: { idx: 0, org_id: 2, bucket_id: 903 },
|
||||
columns: bigintColumns,
|
||||
})
|
||||
).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('returns no filters for a constraint without columns', () => {
|
||||
expect(
|
||||
getReferencingRecordFilters({
|
||||
foreignKey: { source_columns: [], target_columns: [] },
|
||||
row: { idx: 0 },
|
||||
columns: [],
|
||||
})
|
||||
).toStrictEqual([])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { Filter, SupaRow } from '@/components/grid/types'
|
||||
import { convertByteaToHex } from '@/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils'
|
||||
import type { ForeignKeyConstraint } from '@/data/database/foreign-key-constraints-query'
|
||||
|
||||
type ForeignKeyColumns = Pick<ForeignKeyConstraint, 'source_columns' | 'target_columns'>
|
||||
|
||||
/**
|
||||
* Finds the foreign key constraint on `schema.table` that includes `columnName`
|
||||
* among its source columns.
|
||||
*/
|
||||
export function findColumnForeignKeyConstraint({
|
||||
foreignKeys,
|
||||
schema,
|
||||
table,
|
||||
columnName,
|
||||
}: {
|
||||
foreignKeys: ForeignKeyConstraint[]
|
||||
schema: string
|
||||
table: string
|
||||
columnName: string
|
||||
}): ForeignKeyConstraint | undefined {
|
||||
return foreignKeys.find(
|
||||
(foreignKey) =>
|
||||
foreignKey.source_schema === schema &&
|
||||
foreignKey.source_table === table &&
|
||||
foreignKey.source_columns.includes(columnName)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the filters that identify the record `row` references through
|
||||
* `foreignKey`. Source and target columns are paired by their ordinal position
|
||||
* in the constraint, so a composite foreign key filters on every column of the
|
||||
* referenced table, each with the value from its own paired source column.
|
||||
*
|
||||
* Returns an empty array when any source value is null or missing: with the
|
||||
* default MATCH SIMPLE semantics, a row with a null foreign key column does not
|
||||
* reference any record.
|
||||
*/
|
||||
export function getReferencingRecordFilters({
|
||||
foreignKey,
|
||||
row,
|
||||
columns,
|
||||
}: {
|
||||
foreignKey: ForeignKeyColumns
|
||||
row: SupaRow
|
||||
columns: { name: string; format: string }[]
|
||||
}): Filter[] {
|
||||
if (
|
||||
foreignKey.source_columns.length === 0 ||
|
||||
foreignKey.source_columns.length !== foreignKey.target_columns.length
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
const filters: Filter[] = []
|
||||
|
||||
for (const [index, sourceColumn] of foreignKey.source_columns.entries()) {
|
||||
const value = row[sourceColumn]
|
||||
if (value === null || value === undefined) return []
|
||||
|
||||
const format = columns.find((column) => column.name === sourceColumn)?.format
|
||||
filters.push({
|
||||
column: foreignKey.target_columns[index],
|
||||
operator: '=',
|
||||
value: format === 'bytea' ? convertByteaToHex(value) : value,
|
||||
})
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import { CellContextMenuWrapper } from './CellContextMenuWrapper'
|
||||
import { DefaultFormatter } from './DefaultFormatter'
|
||||
import { JsonFormatter } from './JsonFormatter'
|
||||
import { COLUMN_MIN_WIDTH } from '@/components/grid/constants'
|
||||
import type { SupaColumn, SupaRow } from '@/components/grid/types'
|
||||
import { filtersToUrlParams } from '@/components/grid/SupabaseGrid.utils'
|
||||
import type { Filter, SupaColumn, SupaRow } from '@/components/grid/types'
|
||||
import {
|
||||
ESTIMATED_CHARACTER_PIXEL_WIDTH,
|
||||
getColumnDefaultWidth,
|
||||
@@ -30,11 +31,11 @@ import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
|
||||
interface ReferenceRecordPeekProps {
|
||||
table: PGTable
|
||||
column: string
|
||||
value: string | number | Record<string, unknown>
|
||||
/** One equality filter per foreign key column, identifying the referenced record */
|
||||
filters: Filter[]
|
||||
}
|
||||
|
||||
export const ReferenceRecordPeek = ({ table, column, value }: ReferenceRecordPeekProps) => {
|
||||
export const ReferenceRecordPeek = ({ table, filters }: ReferenceRecordPeekProps) => {
|
||||
const { ref } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
|
||||
@@ -48,13 +49,17 @@ export const ReferenceRecordPeek = ({ table, column, value }: ReferenceRecordPee
|
||||
{
|
||||
projectRef: project?.ref,
|
||||
tableId: table.id,
|
||||
filters: [{ column, operator: '=', value }],
|
||||
filters,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
{ placeholderData: keepPreviousData }
|
||||
)
|
||||
|
||||
const filterSearchParams = filtersToUrlParams(filters)
|
||||
.map((filter) => `filter=${encodeURIComponent(filter)}`)
|
||||
.join('&')
|
||||
|
||||
const rows = useMemo(() => data?.rows ?? [], [data?.rows])
|
||||
const selectedCellRef = useRef<{ idx: number; rowIdx: number } | null>(null)
|
||||
|
||||
@@ -159,10 +164,10 @@ export const ReferenceRecordPeek = ({ table, column, value }: ReferenceRecordPee
|
||||
</div>
|
||||
<div className="flex items-center justify-end px-2 py-1">
|
||||
<EditorTablePageLink
|
||||
href={`/project/${ref}/editor/${table.id}?schema=${table.schema}&filter=${column}%3Aeq%3A${value}`}
|
||||
href={`/project/${ref}/editor/${table.id}?schema=${table.schema}&${filterSearchParams}`}
|
||||
projectRef={ref}
|
||||
id={String(table.id)}
|
||||
filters={[{ column, operator: '=', value: String(value) }]}
|
||||
filters={filters}
|
||||
>
|
||||
<Button>Open table</Button>
|
||||
</EditorTablePageLink>
|
||||
|
||||
Reference in new issue
Block a user