diff --git a/apps/studio/components/grid/SupabaseGrid.tsx b/apps/studio/components/grid/SupabaseGrid.tsx index 50d317d5c74..ff5d1b28f23 100644 --- a/apps/studio/components/grid/SupabaseGrid.tsx +++ b/apps/studio/components/grid/SupabaseGrid.tsx @@ -1,4 +1,4 @@ -import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { DataGridHandle } from 'react-data-grid' import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' @@ -22,195 +22,178 @@ import { Grid } from './components/grid' import Header from './components/header' import { RowContextMenu } from './components/menu' import { StoreProvider, useDispatch, useTrackedState } from './store' -import { Dictionary, SupabaseGridProps, SupabaseGridRef } from './types' +import { SupabaseGridProps } from './types' /** Supabase Grid: React component to render database table */ -export const SupabaseGrid = forwardRef((props, ref) => { +export const SupabaseGrid = (props: SupabaseGridProps) => { const _props = cleanupProps(props) return ( - + ) -}) +} -const SupabaseGridLayout = forwardRef( - function SupabaseGridLayout(props, ref) { - const { - editable, - storageRef, - gridProps, - headerActions, - showCustomChildren, - customHeader, - children, - onAddRow, - onAddColumn, - updateTableRow, - onEditForeignKeyColumnValue, - onImportData, - } = props - const dispatch = useDispatch() - const state = useTrackedState() - const snap = useTableEditorStateSnapshot() +const SupabaseGridLayout = (props: SupabaseGridProps) => { + const { + editable, + storageRef, + gridProps, + headerActions, + showCustomChildren, + customHeader, + children, + onAddRow, + onAddColumn, + updateTableRow, + onEditForeignKeyColumnValue, + onImportData, + } = props + const dispatch = useDispatch() + const state = useTrackedState() + const snap = useTableEditorStateSnapshot() - const gridRef = useRef(null) - const [mounted, setMounted] = useState(false) + const gridRef = useRef(null) + const [mounted, setMounted] = useState(false) - const [{ sort, filter }, setParams] = useUrlState({ - arrayKeys: ['sort', 'filter'], + const [{ sort, filter }, setParams] = useUrlState({ + arrayKeys: ['sort', 'filter'], + }) + const sorts = formatSortURLParams(sort as string[]) + const filters = formatFilterURLParams(filter as string[]) + + const roleImpersonationState = useRoleImpersonationStateSnapshot() + + const { project } = useProjectContext() + const { data, error, isSuccess, isError, isLoading, isRefetching } = useTableRowsQuery( + { + queryKey: [props.table.schema, props.table.name], + projectRef: project?.ref, + connectionString: project?.connectionString, + table: props.table, + sorts, + filters, + page: snap.page, + limit: snap.rowsPerPage, + impersonatedRole: roleImpersonationState.role, + }, + { + keepPreviousData: true, + onSuccess(data) { + dispatch({ + type: 'SET_ROWS_COUNT', + payload: data.rows.length, + }) + }, + } + ) + + useEffect(() => { + if (!mounted) setMounted(true) + }, []) + + useEffect(() => { + if (mounted) { + dispatch({ type: 'UPDATE_FILTERS', payload: {} }) + } + }, [JSON.stringify(filters)]) + + useEffect(() => { + if (mounted) { + dispatch({ type: 'UPDATE_SORTS', payload: {} }) + } + }, [JSON.stringify(sorts)]) + + useEffect(() => { + if (state.isInitialComplete && storageRef && state.table) { + saveStorageDebounced(state, storageRef, sort as string[], filter as string[]) + } + }, [ + state.table, + state.isInitialComplete, + state.gridColumns, + JSON.stringify(sorts), + JSON.stringify(filters), + storageRef, + ]) + + useEffect(() => { + dispatch({ + type: 'INIT_CALLBACK', + payload: { ...props }, }) - const sorts = formatSortURLParams(sort as string[]) - const filters = formatFilterURLParams(filter as string[]) + }, []) - const roleImpersonationState = useRoleImpersonationStateSnapshot() + useEffect(() => { + const initializeData = async () => { + const { savedState } = await initTable( + props, + state, + dispatch, + sort as string[], + filter as string[] + ) - const { project } = useProjectContext() - const { data, error, isSuccess, isError, isLoading, isRefetching } = useTableRowsQuery( - { - queryKey: [props.table.schema, props.table.name], - projectRef: project?.ref, - connectionString: project?.connectionString, - table: props.table, - sorts, - filters, - page: snap.page, - limit: snap.rowsPerPage, - impersonatedRole: roleImpersonationState.role, - }, - { - keepPreviousData: true, - onSuccess(data) { - dispatch({ - type: 'SET_ROWS_COUNT', - payload: data.rows.length, - }) - }, - } - ) - - useImperativeHandle(ref, () => ({ - rowAdded(row: Dictionary) { - dispatch({ - type: 'ADD_NEW_ROW', - payload: row, + if (savedState.sorts || savedState.filters) { + setParams((prevParams) => { + return { + ...prevParams, + ...(savedState.sorts && { sort: savedState.sorts }), + ...(savedState.filters && { filter: savedState.filters }), + } }) - }, - rowEdited(row: Dictionary, idx: number) { - dispatch({ - type: 'EDIT_ROW', - payload: { row, idx }, - }) - }, - })) - - useEffect(() => { - if (!mounted) setMounted(true) - }, []) - - useEffect(() => { - if (mounted) { - dispatch({ type: 'UPDATE_FILTERS', payload: {} }) } - }, [JSON.stringify(filters)]) + } - useEffect(() => { - if (mounted) { - dispatch({ type: 'UPDATE_SORTS', payload: {} }) - } - }, [JSON.stringify(sorts)]) + const refreshTable = JSON.stringify(props.table) !== JSON.stringify(state.table) - useEffect(() => { - if (state.isInitialComplete && storageRef && state.table) { - saveStorageDebounced(state, storageRef, sort as string[], filter as string[]) - } - }, [ - state.table, - state.isInitialComplete, - state.gridColumns, - JSON.stringify(sorts), - JSON.stringify(filters), - storageRef, - ]) + if (!state.table || refreshTable) { + initializeData() + } + }, [state.table, props.table, props.schema]) - useEffect(() => { - dispatch({ - type: 'INIT_CALLBACK', - payload: { ...props }, - }) - }, []) + return ( +
+
+ {showCustomChildren && children !== undefined ? ( + <>{children} + ) : ( + <> + +
+ + + )} - useEffect(() => { - const initializeData = async () => { - const { savedState } = await initTable( - props, - state, - dispatch, - sort as string[], - filter as string[] - ) - - if (savedState.sorts || savedState.filters) { - setParams((prevParams) => { - return { - ...prevParams, - ...(savedState.sorts && { sort: savedState.sorts }), - ...(savedState.filters && { filter: savedState.filters }), - } - }) - } - } - - const refreshTable = JSON.stringify(props.table) !== JSON.stringify(state.table) - - if (!state.table || refreshTable) { - initializeData() - } - }, [state.table, props.table, props.schema]) - - return ( -
-
- {showCustomChildren && children !== undefined ? ( - <>{children} - ) : ( - <> - -
- - - )} - - {mounted && createPortal(, document.body)} -
- ) - } -) + {mounted && createPortal(, document.body)} +
+ ) +} diff --git a/apps/studio/components/grid/store/reducers/row.ts b/apps/studio/components/grid/store/reducers/row.ts index cdc53345146..81df9400eb6 100644 --- a/apps/studio/components/grid/store/reducers/row.ts +++ b/apps/studio/components/grid/store/reducers/row.ts @@ -35,8 +35,6 @@ type ROW_ACTIONTYPE = type: 'SET_ROWS_COUNT' payload: number } - | { type: 'ADD_NEW_ROW'; payload: Dictionary } - | { type: 'EDIT_ROW'; payload: { row: Dictionary; idx: number } } | { type: 'REMOVE_ROWS'; payload: { rowIdxs: number[] } } | { type: 'SELECT_ALL_ROWS'; payload: { selectedRows: ReadonlySet } } @@ -82,24 +80,6 @@ const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { totalRows: action.payload, } } - case 'ADD_NEW_ROW': { - const supaRow = { ...action.payload, idx: state.rows.length } - const totalRows = state.totalRows + 1 - return { - ...state, - rows: update(state.rows, { $push: [supaRow] }), - totalRows: totalRows, - } - } - case 'EDIT_ROW': { - const supaRow = { ...action.payload.row, idx: action.payload.idx } - return { - ...state, - rows: update(state.rows, { - [action.payload.idx]: { $set: supaRow }, - }), - } - } default: return state } diff --git a/apps/studio/components/grid/types/grid.ts b/apps/studio/components/grid/types/grid.ts index 66777ad9821..0d6c0be7e0c 100644 --- a/apps/studio/components/grid/types/grid.ts +++ b/apps/studio/components/grid/types/grid.ts @@ -1,6 +1,5 @@ import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' import React, { ReactNode } from 'react' -import { Dictionary } from './base' import { SupaRow, SupaTable } from './table' export interface GridProps { @@ -93,19 +92,3 @@ export interface SupabaseGridProps { */ onImportData?: () => void } - -export interface SupabaseGridRef { - /** - * callback when a new row is added - * - * @param row newly added row data - */ - rowAdded(row: Dictionary): void - /** - * callback when a row is edited - * - * @param row edited row data - * @param idx edited row index - */ - rowEdited(row: Dictionary, idx: number): void -} diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 65b31353dd6..101aee31ebb 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -31,8 +31,6 @@ import { ImportContent } from './TableEditor/TableEditor.types' export interface SidePanelEditorProps { editable?: boolean selectedTable?: PostgresTable - onRowCreated?: (row: Dictionary) => void - onRowUpdated?: (row: Dictionary, idx: number) => void // Because the panel is shared between grid editor and database pages // Both require different responses upon success of these events @@ -42,8 +40,6 @@ export interface SidePanelEditorProps { const SidePanelEditor = ({ editable = true, selectedTable, - onRowCreated = noop, - onRowUpdated = noop, onTableCreated = noop, }: SidePanelEditorProps) => { const snap = useTableEditorStateSnapshot() @@ -61,10 +57,14 @@ const SidePanelEditor = ({ .map((column) => column.name) const { project } = useProjectContext() - const { mutateAsync: createTableRows } = useTableRowCreateMutation() + const { mutateAsync: createTableRows } = useTableRowCreateMutation({ + onSuccess() { + toast.success('Successfully created row') + }, + }) const { mutateAsync: updateTableRow } = useTableRowUpdateMutation({ onSuccess() { - ui.setNotification({ category: 'success', message: 'Successfully updated row' }) + toast.success('Successfully updated row') }, }) @@ -83,7 +83,7 @@ const SidePanelEditor = ({ let saveRowError: Error | undefined if (isNewRecord) { try { - const result = await createTableRows({ + await createTableRows({ projectRef: project.ref, connectionString: project.connectionString, table: selectedTable as any, @@ -91,7 +91,6 @@ const SidePanelEditor = ({ enumArrayColumns, impersonatedRole: getImpersonatedRole(), }) - onRowCreated(result[0]) } catch (error: any) { saveRowError = error } @@ -100,7 +99,7 @@ const SidePanelEditor = ({ if (hasChanges) { if (selectedTable.primary_keys.length > 0) { try { - const result = await updateTableRow({ + await updateTableRow({ projectRef: project.ref, connectionString: project.connectionString, table: selectedTable as any, @@ -109,7 +108,6 @@ const SidePanelEditor = ({ enumArrayColumns, impersonatedRole: getImpersonatedRole(), }) - onRowUpdated(result[0], configuration.rowIdx) } catch (error: any) { saveRowError = error } diff --git a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 3202dfc5685..2f38d5a4cae 100644 --- a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -5,15 +5,9 @@ import { useParams } from 'common' import { find, isUndefined } from 'lodash' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useState } from 'react' -import { - Dictionary, - parseSupaTable, - SupabaseGrid, - SupabaseGridRef, - SupaTable, -} from 'components/grid' +import { Dictionary, parseSupaTable, SupabaseGrid, SupaTable } from 'components/grid' import { ERROR_PRIMARY_KEY_NOTFOUND } from 'components/grid/constants' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import Connecting from 'components/ui/Loading/Loading' @@ -59,7 +53,6 @@ const TableGridEditor = ({ const { project } = useProjectContext() const snap = useTableEditorStateSnapshot() - const gridRef = useRef(null) const getImpersonatedRole = useGetImpersonatedRole() @@ -211,14 +204,6 @@ const TableGridEditor = ({ const gridKey = `${selectedTable.schema}_${selectedTable.name}` - const onRowCreated = (row: Dictionary) => { - if (gridRef.current) gridRef.current.rowAdded(row) - } - - const onRowUpdated = (row: Dictionary, idx: number) => { - if (gridRef.current) gridRef.current.rowEdited(row, idx) - } - const onTableCreated = (table: PostgresTable) => { router.push(`/project/${projectRef}/editor/${table.id}`) } @@ -311,7 +296,6 @@ const TableGridEditor = ({ <> )} diff --git a/apps/studio/data/sql/execute-sql-query.ts b/apps/studio/data/sql/execute-sql-query.ts index 91aaea1dd13..be84b596ad6 100644 --- a/apps/studio/data/sql/execute-sql-query.ts +++ b/apps/studio/data/sql/execute-sql-query.ts @@ -44,7 +44,7 @@ export async function executeSql( | 'isRoleImpersonationEnabled' >, signal?: AbortSignal -) { +): Promise<{ result: any }> { if (!projectRef) throw new Error('projectRef is required') let headers = new Headers() diff --git a/apps/studio/data/table-rows/table-row-create-mutation.ts b/apps/studio/data/table-rows/table-row-create-mutation.ts index b8405847f5a..c7f6f4e7964 100644 --- a/apps/studio/data/table-rows/table-row-create-mutation.ts +++ b/apps/studio/data/table-rows/table-row-create-mutation.ts @@ -14,17 +14,19 @@ export type TableRowCreateVariables = { table: SupaTable payload: any enumArrayColumns: string[] + returning?: boolean impersonatedRole?: ImpersonationRole } export function getTableRowCreateSql({ table, payload, + returning = false, enumArrayColumns, -}: Pick) { +}: Pick) { return new Query() .from(table.name, table.schema ?? undefined) - .insert([payload], { returning: true, enumArrayColumns }) + .insert([payload], { returning, enumArrayColumns }) .toSql() } @@ -34,10 +36,11 @@ export async function createTableRow({ table, payload, enumArrayColumns, + returning, impersonatedRole, }: TableRowCreateVariables) { const sql = wrapWithRoleImpersonation( - getTableRowCreateSql({ table, payload, enumArrayColumns }), + getTableRowCreateSql({ table, payload, enumArrayColumns, returning }), { projectRef, role: impersonatedRole, diff --git a/apps/studio/data/table-rows/table-row-update-mutation.ts b/apps/studio/data/table-rows/table-row-update-mutation.ts index 4be0dfaef5d..e11e1095458 100644 --- a/apps/studio/data/table-rows/table-row-update-mutation.ts +++ b/apps/studio/data/table-rows/table-row-update-mutation.ts @@ -15,6 +15,7 @@ export type TableRowUpdateVariables = { configuration: { identifiers: any } payload: any enumArrayColumns: string[] + returning?: boolean impersonatedRole?: ImpersonationRole } @@ -22,11 +23,15 @@ export function getTableRowUpdateSql({ table, configuration, payload, + returning = false, enumArrayColumns, -}: Pick) { +}: Pick< + TableRowUpdateVariables, + 'table' | 'payload' | 'configuration' | 'enumArrayColumns' | 'returning' +>) { return new Query() .from(table.name, table.schema ?? undefined) - .update(payload, { returning: true, enumArrayColumns }) + .update(payload, { returning, enumArrayColumns }) .match(configuration.identifiers) .toSql() } @@ -38,10 +43,11 @@ export async function updateTableRow({ payload, configuration, enumArrayColumns, + returning, impersonatedRole, }: TableRowUpdateVariables) { const sql = wrapWithRoleImpersonation( - getTableRowUpdateSql({ table, configuration, payload, enumArrayColumns }), + getTableRowUpdateSql({ table, configuration, payload, enumArrayColumns, returning }), { projectRef, role: impersonatedRole,