[FE-3493] fix(studio): respect role impersonation when copying truncated rows (#46442)

Copy/export of selected rows in the Table Editor refetches full values
for cells truncated in the grid (via `getCellValue`), but that refetch
was bypassing role impersonation. The main grid query respects the
impersonated role; the truncated-cell hydration didn't, so the copy
could fetch as the service role even when "View as <role>" was active –
an inconsistency, since the UI still indicates the impersonated role is
in effect.

Threads `roleImpersonationState` through `hydrateTruncatedRows` →
`getCellValue`, and wraps the SQL in `wrapWithRoleImpersonation`
(matching how `getTableRows` does it). Addresses FE-3493.

**Changed:**
- `getCellValue` accepts an optional `roleImpersonationState` and wraps
its SQL with `wrapWithRoleImpersonation` + flags
`isRoleImpersonationEnabled` on `executeSql`
- `hydrateTruncatedRows` threads `roleImpersonationState` through to
`getCellValue`
- `Header.tsx`'s `onCopyRows` passes the in-scope
`roleImpersonationState` into `hydrateTruncatedRows`

## To test

1. Open the Table Editor on a table with a row containing a
large/truncated string value and a primary key
2. Enable role impersonation → "View as role" → pick any role with read
access to the table
3. Select the row, then `Copy → Copy as JSON` (also try CSV / SQL)
4. The copy should succeed and contain the full (non-truncated) value
5. Inspect the SQL request – it should now be wrapped with the
impersonation context, matching how the main grid query is wrapped

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-05-28 15:22:20 +08:00
1 parent cc35414ed6
commit 29af5308f3
3 files changed
+19 -2

No files matched your search

@@ -235,6 +235,7 @@ const RowHeader = ({ tableQueriesEnabled = true }: RowHeaderProps) => {
table: snap.table,
projectRef: project.ref,
connectionString: project.connectionString ?? null,
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
})
if (hydrated.status !== 'ok') {
throw new Error('Failed to fetch full values for truncated cells')
@@ -3,6 +3,7 @@ import Papa from 'papaparse'
import type { SupaTable } from '@/components/grid/types'
import { isValueTruncated } from '@/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils'
import { getCellValue } from '@/data/table-rows/get-cell-value-mutation'
import type { RoleImpersonationState } from '@/lib/role-impersonation'
export const formatRowsForCSV = ({ rows, columns }: { rows: any[]; columns: string[] }) => {
const formattedRows = rows.map((row) => {
@@ -34,11 +35,13 @@ export const hydrateTruncatedRows = async ({
table,
projectRef,
connectionString,
roleImpersonationState,
}: {
rows: Record<string, unknown>[]
table: SupaTable
projectRef: string
connectionString: string | null
roleImpersonationState?: RoleImpersonationState
}): Promise<HydrateTruncatedRowsResult> => {
const jobs: { rowIdx: number; column: string }[] = []
rows.forEach((row, rowIdx) => {
@@ -73,6 +76,7 @@ export const hydrateTruncatedRows = async ({
table: { schema: table.schema ?? 'public', name: table.name },
column,
pkMatch,
roleImpersonationState,
})
})
)
@@ -4,6 +4,8 @@ import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
import { executeSql } from '@/data/sql/execute-sql-query'
import { RoleImpersonationState, wrapWithRoleImpersonation } from '@/lib/role-impersonation'
import { isRoleImpersonationEnabled } from '@/state/role-impersonation-state'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export type GetCellValueVariables = {
@@ -12,6 +14,7 @@ export type GetCellValueVariables = {
table: { schema: string; name: string }
column: string
pkMatch: { [key: string]: any }
roleImpersonationState?: RoleImpersonationState
}
export function getCellValueSql({
@@ -32,9 +35,18 @@ export async function getCellValue({
table,
column,
pkMatch,
roleImpersonationState,
}: GetCellValueVariables) {
const sql = getCellValueSql({ table, column, pkMatch })
const { result } = await executeSql({ projectRef, connectionString, sql })
const sql = wrapWithRoleImpersonation(
getCellValueSql({ table, column, pkMatch }),
roleImpersonationState
)
const { result } = await executeSql({
projectRef,
connectionString,
sql,
isRoleImpersonationEnabled: isRoleImpersonationEnabled(roleImpersonationState?.role),
})
return result?.[0][column]
}