adds react-query queries for table-like objects

This commit is contained in:
Alaister Young committed 2023-05-02 18:41:02 +10:00
1 parent 3b93964cd1
commit faecbd3736
16 files changed
+541 -88

No files matched your search

@@ -38,11 +38,13 @@ import { JsonEditValue } from './SidePanelEditor/RowEditor/RowEditor.types'
import { ForeignRowSelectorProps } from './SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
import TableDefinition from './TableDefinition'
import APIDocumentationPanel from './APIDocumentationPanel'
import Connecting from 'components/ui/Loading/Loading'
export interface TableGridEditorProps {
/** Theme for the editor */
theme?: 'dark' | 'light'
isLoadingSelectedTable?: boolean
selectedTable: any // PostgresTable | SchemaView
/** Determines what side panel editor to show */
@@ -77,6 +79,7 @@ export interface TableGridEditorProps {
const TableGridEditor = ({
theme = 'dark',
isLoadingSelectedTable = false,
selectedTable,
sidePanelKey,
isDuplicating,
@@ -202,6 +205,10 @@ const TableGridEditor = ({
const entityType = useEntityType(selectedTable?.id)
if (isLoadingSelectedTable) {
return <Connecting />
}
// NOTE: DO NOT PUT HOOKS AFTER THIS LINE
if (isUndefined(selectedTable)) {
return <NotFoundState id={Number(id)} />
@@ -1,21 +1,13 @@
import { PropsWithChildren, useEffect } from 'react'
import { useRouter } from 'next/router'
import { noop } from 'lodash'
import { observer } from 'mobx-react-lite'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { Entity } from 'data/entity-types/entity-type-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useIsTableLoaded, useTableEditorGlobalStateSnapshot } from 'state/table-editor-global'
import ProjectLayout from '../'
import TableEditorMenu from './TableEditorMenu'
import NoPermission from 'components/ui/NoPermission'
import useEntityType from 'hooks/misc/useEntityType'
import Connecting from 'components/ui/Loading/Loading'
import useLatest from 'hooks/misc/useLatest'
import useTableRowsPrefetchWrapper from './TableEditorLayout.utils'
export interface TableEditorLayoutProps {
onAddTable: () => void
@@ -32,11 +24,7 @@ const TableEditorLayout = ({
children,
}: PropsWithChildren<TableEditorLayoutProps>) => {
const { vault, meta, ui } = useStore()
const router = useRouter()
const { ref, id: _id } = useParams()
const id = _id ? Number(_id) : undefined
const snap = useTableEditorGlobalStateSnapshot()
const canReadTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables')
const vaultExtension = meta.extensions.byId('supabase_vault')
@@ -51,56 +39,6 @@ const TableEditorLayout = ({
}
}, [ui.selectedProject?.ref])
const isLoaded = useIsTableLoaded(ref, id)
const entity = useEntityType(id, function onNotFound(id) {
if (ref) snap.addLoadedId(ref, id)
})
const prefetch = useLatest(useTableRowsPrefetchWrapper())
useEffect(() => {
let mounted = true
function loadTable() {
if (entity?.type) {
switch (entity.type) {
case ENTITY_TYPE.MATERIALIZED_VIEW:
return meta.materializedViews.loadById(entity.id)
case ENTITY_TYPE.VIEW:
return meta.views.loadById(entity.id)
case ENTITY_TYPE.FOREIGN_TABLE:
return meta.foreignTables.loadById(entity.id)
default:
return meta.tables.loadById(entity.id)
}
}
}
loadTable()
?.then(async (entity: any) => {
await prefetch.current(entity)
return entity
})
.then((entity: any) => {
if (mounted && ref) {
snap.addLoadedId(ref, entity.id)
}
})
.catch(() => {
if (mounted && entity?.id && ref) {
snap.addLoadedId(ref, entity.id)
}
})
return () => {
mounted = false
}
}, [entity?.id])
useEffect(() => {
if (isVaultEnabled) {
vault.load()
@@ -127,7 +65,7 @@ const TableEditorLayout = ({
/>
}
>
{router.isReady && id !== undefined ? isLoaded ? children : <Connecting /> : children}
{children}
</ProjectLayout>
)
}
@@ -0,0 +1,57 @@
import { PostgresTable } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { foreignTableKeys } from './keys'
export type ForeignTableVariables = {
id?: number
projectRef?: string
connectionString?: string
}
export type ForeignTable = PostgresTable
export type ForeignTableResponse = ForeignTable | { error?: any }
export async function getForeignTable(
{ id, projectRef, connectionString }: ForeignTableVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!id) {
throw new Error('id is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/foreign-tables?id=${id}`, {
headers: Object.fromEntries(headers),
signal,
})) as ForeignTableResponse
if ('error' in response) {
throw response.error
}
return response as PostgresTable
}
export type ForeignTableData = Awaited<ReturnType<typeof getForeignTable>>
export type ForeignTableError = unknown
export const useForeignTableQuery = <TData = ForeignTableData>(
{ projectRef, id }: ForeignTableVariables,
{ enabled = true, ...options }: UseQueryOptions<ForeignTableData, ForeignTableError, TData> = {}
) =>
useQuery<ForeignTableData, ForeignTableError, TData>(
foreignTableKeys.foreignTable(projectRef, id),
({ signal }) => getForeignTable({ projectRef, id }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
...options,
}
)
@@ -0,0 +1,49 @@
import { PostgresTable } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { ForeignTable } from './foreign-table-query'
import { foreignTableKeys } from './keys'
export type ForeignTablesVariables = {
projectRef?: string
connectionString?: string
}
export type ForeignTablesResponse = ForeignTable[] | { error?: any }
export async function getForeignTables(
{ projectRef, connectionString }: ForeignTablesVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/foreign-tables`, {
headers: Object.fromEntries(headers),
signal,
})) as ForeignTablesResponse
if (!Array.isArray(response) && response.error) {
throw response.error
}
return response as PostgresTable[]
}
export type ForeignTablesData = Awaited<ReturnType<typeof getForeignTables>>
export type ForeignTablesError = unknown
export const useForeignTablesQuery = <TData = ForeignTablesData>(
{ projectRef, connectionString }: ForeignTablesVariables,
{ enabled = true, ...options }: UseQueryOptions<ForeignTablesData, ForeignTablesError, TData> = {}
) =>
useQuery<ForeignTablesData, ForeignTablesError, TData>(
foreignTableKeys.list(projectRef),
({ signal }) => getForeignTables({ projectRef, connectionString }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
+5
View File
@@ -0,0 +1,5 @@
export const foreignTableKeys = {
list: (projectRef: string | undefined) => ['projects', projectRef, 'foreignTables'] as const,
foreignTable: (projectRef: string | undefined, id: number | undefined) =>
['projects', projectRef, 'foreignTables', id] as const,
}
+5
View File
@@ -0,0 +1,5 @@
export const materializedViewKeys = {
list: (projectRef: string | undefined) => ['projects', projectRef, 'materializedViews'] as const,
materializedView: (projectRef: string | undefined, id: number | undefined) =>
['projects', projectRef, 'materializedViews', id] as const,
}
@@ -0,0 +1,60 @@
import { PostgresMaterializedView } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { materializedViewKeys } from './keys'
export type MaterializedViewVariables = {
id?: number
projectRef?: string
connectionString?: string
}
export type MaterializedView = PostgresMaterializedView
export type MaterializedViewResponse = MaterializedView | { error?: any }
export async function getMaterializedView(
{ id, projectRef, connectionString }: MaterializedViewVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!id) {
throw new Error('id is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/materialized-views?id=${id}`, {
headers: Object.fromEntries(headers),
signal,
})) as MaterializedViewResponse
if ('error' in response) {
throw response.error
}
return response as PostgresMaterializedView
}
export type MaterializedViewData = Awaited<ReturnType<typeof getMaterializedView>>
export type MaterializedViewError = unknown
export const useMaterializedViewQuery = <TData = MaterializedViewData>(
{ projectRef, connectionString, id }: MaterializedViewVariables,
{
enabled = true,
...options
}: UseQueryOptions<MaterializedViewData, MaterializedViewError, TData> = {}
) =>
useQuery<MaterializedViewData, MaterializedViewError, TData>(
materializedViewKeys.materializedView(projectRef, id),
({ signal }) => getMaterializedView({ projectRef, connectionString, id }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
...options,
}
)
@@ -0,0 +1,52 @@
import { PostgresMaterializedView } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { materializedViewKeys } from './keys'
import { MaterializedView } from './materialized-view-query'
export type MaterializedViewsVariables = {
projectRef?: string
connectionString?: string
}
export type MaterializedViewsResponse = MaterializedView[] | { error?: any }
export async function getMaterializedViews(
{ projectRef, connectionString }: MaterializedViewsVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/materialized-views`, {
headers: Object.fromEntries(headers),
signal,
})) as MaterializedViewsResponse
if (!Array.isArray(response) && response.error) {
throw response.error
}
return response as PostgresMaterializedView[]
}
export type MaterializedViewsData = Awaited<ReturnType<typeof getMaterializedViews>>
export type MaterializedViewsError = unknown
export const useMaterializedViewsQuery = <TData = MaterializedViewsData>(
{ projectRef, connectionString }: MaterializedViewsVariables,
{
enabled = true,
...options
}: UseQueryOptions<MaterializedViewsData, MaterializedViewsError, TData> = {}
) =>
useQuery<MaterializedViewsData, MaterializedViewsError, TData>(
materializedViewKeys.list(projectRef),
({ signal }) => getMaterializedViews({ projectRef, connectionString }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
+5
View File
@@ -0,0 +1,5 @@
export const tableKeys = {
list: (projectRef: string | undefined) => ['projects', projectRef, 'tables'] as const,
table: (projectRef: string | undefined, id: number | undefined) =>
['projects', projectRef, 'tables', id] as const,
}
+57
View File
@@ -0,0 +1,57 @@
import { PostgresTable } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { tableKeys } from './keys'
export type TableVariables = {
id?: number
projectRef?: string
connectionString?: string
}
export type Table = PostgresTable
export type TableResponse = Table | { error?: any }
export async function getTable(
{ id, projectRef, connectionString }: TableVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!id) {
throw new Error('id is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/tables?id=${id}`, {
headers: Object.fromEntries(headers),
signal,
})) as TableResponse
if ('error' in response) {
throw response.error
}
return response as PostgresTable
}
export type TableData = Awaited<ReturnType<typeof getTable>>
export type TableError = unknown
export const useTableQuery = <TData = TableData>(
{ projectRef, connectionString, id }: TableVariables,
{ enabled = true, ...options }: UseQueryOptions<TableData, TableError, TData> = {}
) =>
useQuery<TableData, TableError, TData>(
tableKeys.table(projectRef, id),
({ signal }) => getTable({ projectRef, connectionString, id }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
...options,
}
)
+49
View File
@@ -0,0 +1,49 @@
import { PostgresTable } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { tableKeys } from './keys'
import { Table } from './table-query'
export type TablesVariables = {
projectRef?: string
connectionString?: string
}
export type TablesResponse = Table[] | { error?: any }
export async function getTables(
{ projectRef, connectionString }: TablesVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/tables`, {
headers: Object.fromEntries(headers),
signal,
})) as TablesResponse
if (!Array.isArray(response) && response.error) {
throw response.error
}
return response as PostgresTable[]
}
export type TablesData = Awaited<ReturnType<typeof getTables>>
export type TablesError = unknown
export const useTablesQuery = <TData = TablesData>(
{ projectRef, connectionString }: TablesVariables,
{ enabled = true, ...options }: UseQueryOptions<TablesData, TablesError, TData> = {}
) =>
useQuery<TablesData, TablesError, TData>(
tableKeys.list(projectRef),
({ signal }) => getTables({ projectRef, connectionString }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
+5
View File
@@ -0,0 +1,5 @@
export const viewKeys = {
list: (projectRef: string | undefined) => ['projects', projectRef, 'views'] as const,
view: (projectRef: string | undefined, id: number | undefined) =>
['projects', projectRef, 'views', id] as const,
}
+57
View File
@@ -0,0 +1,57 @@
import { PostgresView } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { viewKeys } from './keys'
export type ViewVariables = {
id?: number
projectRef?: string
connectionString?: string
}
export type View = PostgresView
export type ViewResponse = View | { error?: any }
export async function getView(
{ id, projectRef, connectionString }: ViewVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!id) {
throw new Error('id is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/views?id=${id}`, {
headers: Object.fromEntries(headers),
signal,
})) as ViewResponse
if ('error' in response) {
throw response.error
}
return response as PostgresView
}
export type ViewData = Awaited<ReturnType<typeof getView>>
export type ViewError = unknown
export const useViewQuery = <TData = ViewData>(
{ projectRef, connectionString, id }: ViewVariables,
{ enabled = true, ...options }: UseQueryOptions<ViewData, ViewError, TData> = {}
) =>
useQuery<ViewData, ViewError, TData>(
viewKeys.view(projectRef, id),
({ signal }) => getView({ projectRef, connectionString, id }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
...options,
}
)
+49
View File
@@ -0,0 +1,49 @@
import { PostgresView } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { viewKeys } from './keys'
import { View } from './view-query'
export type ViewsVariables = {
projectRef?: string
connectionString?: string
}
export type ViewsResponse = View[] | { error?: any }
export async function getViews(
{ projectRef, connectionString }: ViewsVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = (await get(`${API_URL}/pg-meta/${projectRef}/views`, {
headers: Object.fromEntries(headers),
signal,
})) as ViewsResponse
if (!Array.isArray(response) && response.error) {
throw response.error
}
return response as PostgresView[]
}
export type ViewsData = Awaited<ReturnType<typeof getViews>>
export type ViewsError = unknown
export const useViewsQuery = <TData = ViewsData>(
{ projectRef, connectionString }: ViewsVariables,
{ enabled = true, ...options }: UseQueryOptions<ViewsData, ViewsError, TData> = {}
) =>
useQuery<ViewsData, ViewsError, TData>(
viewKeys.list(projectRef),
({ signal }) => getViews({ projectRef, connectionString }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
+76
View File
@@ -0,0 +1,76 @@
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useForeignTableQuery } from 'data/foreign-tables/foreign-table-query'
import { useMaterializedViewQuery } from 'data/materialized-views/materialized-view-query'
import { useTableQuery } from 'data/tables/table-query'
import { useViewQuery } from 'data/views/view-query'
import useEntityType from './useEntityType'
/**
* A hook that loads all table-like objects. e.g. tables, views, materialized views, etc...
*/
function useTable(id?: number) {
const { project } = useProjectContext()
const entity = useEntityType(id)
const tableResult = useTableQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: entity?.id,
},
{
enabled: entity?.type === ENTITY_TYPE.TABLE,
}
)
const viewResult = useViewQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: entity?.id,
},
{
enabled: entity?.type === ENTITY_TYPE.VIEW,
}
)
const materializedViewResult = useMaterializedViewQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: entity?.id,
},
{
enabled: entity?.type === ENTITY_TYPE.MATERIALIZED_VIEW,
}
)
const foreignTableResult = useForeignTableQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: entity?.id,
},
{
enabled: entity?.type === ENTITY_TYPE.FOREIGN_TABLE,
}
)
switch (entity?.type) {
case ENTITY_TYPE.TABLE:
return tableResult
case ENTITY_TYPE.VIEW:
return viewResult
case ENTITY_TYPE.MATERIALIZED_VIEW:
return materializedViewResult
case ENTITY_TYPE.FOREIGN_TABLE:
return foreignTableResult
default:
// While entity is loading, we can fallback
// to the loading state of the table query.
return tableResult
}
}
export default useTable
+7 -25
View File
@@ -1,9 +1,9 @@
import { useQueryClient } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { observer } from 'mobx-react-lite'
import { isUndefined, isNaN } from 'lodash'
import { isUndefined } from 'lodash'
import { Alert, Button, Checkbox, IconExternalLink, Modal } from 'ui'
import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
@@ -25,11 +25,13 @@ import {
import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
import { useTheme } from 'common'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import useTable from 'hooks/misc/useTable'
const TableEditorPage: NextPageWithLayout = () => {
const router = useRouter()
const { isDarkMode } = useTheme()
const { id, ref: projectRef } = useParams()
const { id: _id, ref: projectRef } = useParams()
const id = _id ? Number(_id) : undefined
const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
const { project } = useProjectContext()
@@ -59,28 +61,7 @@ const TableEditorPage: NextPageWithLayout = () => {
column: any
}>()
const tables: PostgresTable[] = meta.tables.list()
const views: SchemaView[] = meta.views.list()
const materializedViews = meta.materializedViews.list()
const foreignTables: Partial<PostgresTable>[] = meta.foreignTables.list()
const selectedTable = !isNaN(Number(id))
? // @ts-ignore
tables
// @ts-ignore
.concat(views)
// @ts-ignore
.concat(materializedViews)
// @ts-ignore
.concat(foreignTables)
.find((table) => table.id === Number(id))
: undefined
// useEffect(() => {
// if (selectedTable && 'schema' in selectedTable) {
// setSelectedSchema(selectedTable.schema)
// }
// }, [selectedTable?.name])
const { data: selectedTable, isLoading } = useTable(id)
const onAddRow = () => {
setSidePanelKey('row')
@@ -278,6 +259,7 @@ const TableEditorPage: NextPageWithLayout = () => {
onDuplicateTable={onDuplicateTable}
>
<TableGridEditor
isLoadingSelectedTable={isLoading}
selectedTable={selectedTable}
sidePanelKey={sidePanelKey}
isDuplicating={isDuplicating}