diff --git a/packages/ui/src/components/SidePanel/SidePanel.tsx b/packages/ui/src/components/SidePanel/SidePanel.tsx index b9595e7fec31b..6dc69c307e913 100644 --- a/packages/ui/src/components/SidePanel/SidePanel.tsx +++ b/packages/ui/src/components/SidePanel/SidePanel.tsx @@ -126,10 +126,16 @@ export function Separator() { return
} -export function Content({ children }: { children: React.ReactNode }) { +export function Content({ + children, + className, +}: { + children: React.ReactNode + className?: string +}) { let __styles = styleHandler('sidepanel') - return
{children}
+ return
{children}
} SidePanel.Content = Content diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignKeySelector.tsx deleted file mode 100644 index 1fb0e7ddb0bc1..0000000000000 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignKeySelector.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { SidePanel, IconLoader, IconXCircle } from 'ui' -import type { PostgresRelationship } from '@supabase/postgres-meta' - -import ActionBar from '../ActionBar' -import InputField from './InputField' -import { RowField } from './RowEditor.types' -import { generateRowFieldsWithoutColumnMeta } from './RowEditor.utils' - -export interface ForeignKeySelectorProps { - visible: boolean - referenceRow?: { loading: boolean; foreignKey: any; row: any } - closePanel: () => void -} - -const ForeignKeySelector = ({ visible, referenceRow, closePanel }: ForeignKeySelectorProps) => { - const loading = referenceRow?.loading ?? true - const foreignKey: PostgresRelationship = referenceRow?.foreignKey ?? undefined - const row = referenceRow?.row ?? undefined - const [rowFields, setRowFields] = useState([]) - - useEffect(() => { - if (visible) { - if (!foreignKey) { - setRowFields([]) - } else if (row) { - const rowFields = generateRowFieldsWithoutColumnMeta(row) - setRowFields(rowFields) - } - } - }, [visible, referenceRow]) - - return ( - - Selecting foreign key for{' '} - - {foreignKey?.target_table_schema ?? ''}.{foreignKey?.target_table_name ?? ''} - - - } - hideFooter={false} - onCancel={closePanel} - customFooter={} - > - -
- {loading ? ( -
- -

Loading reference row

-
- ) : !row ? ( -
- -

- Unable to find the corresponding row in {foreignKey?.target_table_schema}. - {foreignKey?.target_table_name} -

-
- ) : ( -
- {rowFields.map((field: RowField) => { - return - })} -
- )} -
-
-
- ) -} - -export default ForeignKeySelector diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx new file mode 100644 index 0000000000000..48094b4001c23 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx @@ -0,0 +1,117 @@ +import { PostgresTable } from '@supabase/postgres-meta' +import { parseSupaTable } from 'components/grid' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useTableRowsQuery } from 'data/table-rows/table-rows-query' +import { useStore } from 'hooks' +import { IconLoader, SidePanel } from 'ui' + +import ActionBar from '../../ActionBar' +import { RowField } from '../RowEditor.types' +import { useEncryptedColumns } from './ForeignRowSelector.utils' +import SelectorTable from './SelectorTable' + +export interface ForeignRowSelectorProps { + visible: boolean + referenceRow?: RowField + onSelect: (value: any) => void + closePanel: () => void +} + +const ForeignRowSelector = ({ + visible, + referenceRow, + onSelect, + closePanel, +}: ForeignRowSelectorProps) => { + const { meta } = useStore() + const { project } = useProjectContext() + + const { + target_table_schema: schemaName, + target_table_name: tableName, + target_column_name: columnName, + } = referenceRow?.foreignKey ?? {} + + const tables = meta.tables.list() + const table = tables.find((table) => table.schema === schemaName && table.name === tableName) + const encryptedColumns = useEncryptedColumns({ schemaName, tableName }) + + const supaTable = + table && + parseSupaTable( + { + table: table as PostgresTable, + columns: (table as PostgresTable).columns ?? [], + primaryKeys: (table as PostgresTable).primary_keys, + relationships: (table as PostgresTable).relationships, + }, + encryptedColumns + ) + + const { data, isLoading, isSuccess, isError } = useTableRowsQuery( + { + queryKey: [schemaName, tableName], + projectRef: project?.ref, + connectionString: project?.connectionString, + table: supaTable, + // sorts, + // filters, + // page: state.page, + // limit: state.rowsPerPage, + }, + { + keepPreviousData: true, + } + ) + + return ( + + Selecting foreign key from{' '} + + {schemaName}.{tableName} + + + } + hideFooter={false} + onCancel={closePanel} + customFooter={} + > + +
+ {isLoading && ( +
+ +

Loading rows

+
+ )} + + {isError && ( +
+

+ Unable to load rows from{' '} + + {schemaName}.{tableName} + + . Please try again or contact support. +

+
+ )} + + {isSuccess && supaTable && ( + onSelect(row[columnName ?? ''])} + /> + )} +
+
+
+ ) +} + +export default ForeignRowSelector diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.utils.ts new file mode 100644 index 0000000000000..7bb0a05c1547d --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.utils.ts @@ -0,0 +1,36 @@ +import { useFlag, useStore } from 'hooks' +import { useEffect, useState } from 'react' + +export interface UseEncryptedColumnsArgs { + schemaName?: string + tableName?: string +} + +export function useEncryptedColumns({ schemaName, tableName }: UseEncryptedColumnsArgs) { + const isVaultEnabled = useFlag('vaultExtension') + const { vault } = useStore() + + const [encryptedColumns, setEncryptedColumns] = useState([]) + + useEffect(() => { + let isMounted = true + + const getEncryptedColumns = async () => { + if (schemaName !== undefined && tableName !== undefined && isVaultEnabled) { + const columns = await vault.listEncryptedColumns(schemaName, tableName) + + if (isMounted) { + setEncryptedColumns(columns) + } + } + } + + getEncryptedColumns() + + return () => { + isMounted = false + } + }, [schemaName, tableName, isVaultEnabled]) + + return encryptedColumns +} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorTable.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorTable.tsx new file mode 100644 index 0000000000000..92d58b964ff12 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorTable.tsx @@ -0,0 +1,59 @@ +import DataGrid, { Column } from '@supabase/react-data-grid' +import * as Tooltip from '@radix-ui/react-tooltip' +import { SupaRow, SupaTable } from 'components/grid' +import { IconKey } from 'ui' + +export interface SelectorTableProps { + table: SupaTable + rows: SupaRow[] + onRowSelect: (row: SupaRow) => void +} + +const columnRender = (name: string, isPrimaryKey = false) => { + return ( +
+ {isPrimaryKey && ( + + +
+ +
+
+ + +
+ Primary key +
+
+
+ )} + + {name} +
+ ) +} + +const formatter = (column: string, row: any) => { + return ( +
+ {JSON.stringify(row[column])} +
+ ) +} + +const SelectorTable = ({ table, rows, onRowSelect }: SelectorTableProps) => { + const columns: Column[] = table.columns.map((column) => ({ + key: column.name, + name: column.name, + formatter: ({ row }: any) => formatter(column.name, row), + headerRenderer: () => columnRender(column.name, column.isPrimaryKey), + resizable: true, + minWidth: 120, + })) + + return ( + + ) +} + +export default SelectorTable diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx index aeab5f0991344..11d77b70a01d3 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx @@ -8,14 +8,14 @@ import ActionBar from '../ActionBar' import HeaderTitle from './HeaderTitle' import InputField from './InputField' import JsonEdit from './JsonEditor' -import ForeignKeySelector from './ForeignKeySelector' +import ForeignRowSelector from './ForeignRowSelector/ForeignRowSelector' import { generateRowFields, validateFields, generateRowObjectFromFields, generateUpdateRowPayload, } from './RowEditor.utils' -import { JsonEditValue, ReferenceRow, RowField } from './RowEditor.types' +import { JsonEditValue, RowField } from './RowEditor.types' export interface RowEditorProps { row?: Dictionary @@ -39,7 +39,7 @@ const RowEditor = ({ const [selectedValueForJsonEdit, setSelectedValueForJsonEdit] = useState() const [isSelectingForeignKey, setIsSelectingForeignKey] = useState(false) - const [referenceRow, setReferenceRow] = useState() + const [referenceRow, setReferenceRow] = useState() const isNewRecord = isUndefined(row) const isEditingJson = !isUndefined(selectedValueForJsonEdit) @@ -72,47 +72,18 @@ const RowEditor = ({ updateEditorDirty() } - const onSelectForeignKey = async (row: RowField) => { + const onOpenForeignRowSelector = async (row: RowField) => { setIsSelectingForeignKey(true) + setReferenceRow(row) + } + + const onSelectForeignRowValue = (value: any) => { + if (!referenceRow) return + + onUpdateField({ [referenceRow.name]: value }) - // Possible low prio refactor: Shift fetching reference row retrieval to ForeignKeySelector - // in a useEffect, rather than trying to manage a loading state in this method - // if (!row.value) { - // ui.setNotification({ - // category: 'error', - // message: `Please enter a value in the ${row.name} field first`, - // duration: 4000, - // }) - // } - // const foreignKey = row.foreignKey - // setReferenceRow({ loading: true, foreignKey, row: undefined }) - // setIsSelectingForeignKey(true) - - // if (foreignKey) { - // const schema = foreignKey.target_table_schema - // const table = foreignKey.target_table_name - // const column = foreignKey.target_column_name - - // const query = new Query() - // .from(table, schema) - // .select() - // .match({ [column]: row.value }) - // .toSql() - // const res = await meta.query(query) - // if (res.error) { - // setReferenceRow({ loading: false, foreignKey, row: undefined }) - // return ui.setNotification({ category: 'error', message: res.error.message }) - // } - // if (res.length === 0) { - // setReferenceRow({ loading: false, foreignKey, row: undefined }) - // return ui.setNotification({ - // category: 'error', - // message: `Unable to find the corresponding row in ${foreignKey.target_table_schema}.${foreignKey.target_table_name} where ${foreignKey.target_column_name} equals ${row.value}`, - // duration: 4000, - // }) - // } - // setReferenceRow({ loading: false, foreignKey, row: res[0] }) - // } + setIsSelectingForeignKey(false) + setReferenceRow(undefined) } const onSaveChanges = (e: React.FormEvent) => { @@ -175,7 +146,7 @@ const RowEditor = ({ errors={errors} onUpdateField={onUpdateField} onEditJson={setSelectedValueForJsonEdit} - onSelectForeignKey={() => onSelectForeignKey(field)} + onSelectForeignKey={() => onOpenForeignRowSelector(field)} /> ) })} @@ -200,7 +171,7 @@ const RowEditor = ({ errors={errors} onUpdateField={onUpdateField} onEditJson={setSelectedValueForJsonEdit} - onSelectForeignKey={() => onSelectForeignKey(field)} + onSelectForeignKey={() => onOpenForeignRowSelector(field)} /> ) })} @@ -220,9 +191,10 @@ const RowEditor = ({ }} /> - { setIsSelectingForeignKey(false) setReferenceRow(undefined) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts index fe7eade0c1f6d..b909379875457 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts @@ -6,12 +6,6 @@ export interface JsonEditValue { jsonString: string } -export interface ReferenceRow { - loading: boolean - foreignKey: any // ForeignKey - row: any // RowField -} - export interface RowField { id: string name: string diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts index 7bbe183f5f7ea..919fe7421be6f 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts @@ -70,26 +70,6 @@ export const validateFields = (fields: RowField[]) => { return errors } -// Currently only used in ReferenceRowViewer for rows outside of public schema -export const generateRowFieldsWithoutColumnMeta = (row: any): RowField[] => { - const properties = Object.keys(row) - return properties.map((property) => { - return { - id: uuidv4(), - value: row[property] || '', - name: property, - comment: '', - format: '', - enums: [], - defaultValue: '', - foreignKey: undefined, - isNullable: false, - isIdentity: false, - isPrimaryKey: false, - } - }) -} - const parseValue = (originalValue: string, format: string) => { try { if (originalValue === null || originalValue.length === 0) {