chore: remove pg meta schema store

This commit is contained in:
Alaister Young committed 2023-05-02 00:13:39 +10:00
1 parent 64ba7be134
commit 04575b65b1
35 files changed
+378 -364

No files matched your search

@@ -4,19 +4,25 @@ import type { PostgresExtension, PostgresSchema } from '@supabase/postgres-meta'
import { useStore } from 'hooks'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
interface Props {
export interface EnableExtensionModalProps {
visible: boolean
extension: PostgresExtension
onCancel: () => void
}
const EnableExtensionModal: FC<Props> = ({ visible, extension, onCancel }) => {
const EnableExtensionModal = ({ visible, extension, onCancel }: EnableExtensionModalProps) => {
const { project } = useProjectContext()
const { ui, meta } = useStore()
const [defaultSchema, setDefaultSchema] = useState()
const [fetchingSchemaInfo, setFetchingSchemaInfo] = useState(false)
const schemas = meta.schemas.list()
const { data: schemas, isLoading: isSchemasLoading } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
// [Joshen] Worth checking in with users - whether having this schema selection
// might be confusing, and if we should have a tooltip to explain that schemas
@@ -44,7 +50,7 @@ const EnableExtensionModal: FC<Props> = ({ visible, extension, onCancel }) => {
return () => {
cancel = true
}
}, [visible])
}, [visible, extension.name])
const validate = (values: any) => {
const errors: any = {}
@@ -124,7 +130,7 @@ const EnableExtensionModal: FC<Props> = ({ visible, extension, onCancel }) => {
return (
<div className="space-y-4 py-4">
<Modal.Content>
{fetchingSchemaInfo ? (
{fetchingSchemaInfo || isSchemasLoading ? (
<div className="space-y-2">
<ShimmeringLoader />
<div className="w-3/4">
@@ -156,8 +162,7 @@ const EnableExtensionModal: FC<Props> = ({ visible, extension, onCancel }) => {
Create a new schema "{extension.name}"
</Listbox.Option>
<Modal.Separator />
{/* @ts-ignore */}
{schemas.map((schema: PostgresSchema) => {
{schemas?.map((schema: PostgresSchema) => {
return (
<Listbox.Option
key={schema.id}
@@ -1,4 +1,3 @@
import { FC } from 'react'
import { useRouter } from 'next/router'
import { includes } from 'lodash'
import { Button, Dropdown, IconEdit3, IconFileText, IconMoreVertical, IconTrash } from 'ui'
@@ -7,24 +6,25 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import Table from 'components/to-be-cleaned/Table'
interface Props {
export interface FunctionListProps {
schema: string
filterString: string
editFunction: (fn: any) => void
deleteFunction: (fn: any) => void
}
const FunctionList: FC<Props> = ({
const FunctionList = ({
schema,
filterString,
editFunction = () => {},
deleteFunction = () => {},
}) => {
}: FunctionListProps) => {
const router = useRouter()
const { ui, meta } = useStore()
const functions = meta.functions.list((fn: any) => !meta.excludedSchemas.includes(fn.schema))
const functions = meta.functions.list((fn: any) => !EXCLUDED_SCHEMAS.includes(fn.schema))
const filteredFunctions = functions.filter((x: any) =>
includes(x.name.toLowerCase(), filterString.toLowerCase())
)
@@ -1,5 +1,5 @@
import { FC, useState } from 'react'
import { uniqBy, map as lodashMap, includes } from 'lodash'
import { useState } from 'react'
import { uniqBy, map as lodashMap, includes, noop } from 'lodash'
import { Button, IconSearch, IconLoader, Input } from 'ui'
import { observer } from 'mobx-react-lite'
import * as Tooltip from '@radix-ui/react-tooltip'
@@ -7,26 +7,26 @@ import { PostgresFunction } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
import AlphaPreview from 'components/to-be-cleaned/AlphaPreview'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import SchemaTable from './SchemaTable'
interface Props {
export interface FunctionsListProps {
createFunction: () => void
editFunction: (fn: PostgresFunction) => void
deleteFunction: (fn: PostgresFunction) => void
}
const FunctionsList: FC<Props> = ({
createFunction = () => {},
editFunction = () => {},
deleteFunction = () => {},
}) => {
const FunctionsList = ({
createFunction = noop,
editFunction = noop,
deleteFunction = noop,
}: FunctionsListProps) => {
const { meta } = useStore()
const [filterString, setFilterString] = useState<string>('')
const functions = meta.functions.list(
(fn: PostgresFunction) => !meta.excludedSchemas.includes(fn.schema)
(fn: PostgresFunction) => !EXCLUDED_SCHEMAS.includes(fn.schema)
)
const filteredFunctions = functions.filter((x: PostgresFunction) =>
includes(x.name?.toLowerCase(), filterString.toLowerCase())
@@ -1,21 +1,22 @@
import { FC, useState } from 'react'
import { useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Button, Input, IconChevronLeft, IconSearch, IconAlertCircle } from 'ui'
import type { PostgresPublication } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore } from 'hooks'
import PublicationsTableItem from './PublicationsTableItem'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import InformationBox from 'components/ui/InformationBox'
import PublicationsTableItem from './PublicationsTableItem'
interface Props {
export interface PublicationsTablesProps {
selectedPublication: PostgresPublication
onSelectBack: () => void
}
const PublicationsTables: FC<Props> = ({ selectedPublication, onSelectBack }) => {
const PublicationsTables = ({ selectedPublication, onSelectBack }: PublicationsTablesProps) => {
const { meta } = useStore()
const [filterString, setFilterString] = useState<string>('')
@@ -26,10 +27,10 @@ const PublicationsTables: FC<Props> = ({ selectedPublication, onSelectBack }) =>
const tables =
filterString.length === 0
? meta.tables.list((table: any) => !meta.excludedSchemas.includes(table.schema))
? meta.tables.list((table: any) => !EXCLUDED_SCHEMAS.includes(table.schema))
: meta.tables.list(
(table: any) =>
!meta.excludedSchemas.includes(table.schema) && table.name.includes(filterString)
!EXCLUDED_SCHEMAS.includes(table.schema) && table.name.includes(filterString)
)
// const publication = selectedPublication
@@ -1,15 +1,17 @@
import { FC, useState } from 'react'
import { useState } from 'react'
import { observer } from 'mobx-react-lite'
import * as Tooltip from '@radix-ui/react-tooltip'
import { noop } from 'lodash'
import { Input, Button, IconSearch, IconPlus, IconChevronLeft, IconEdit3, IconTrash } from 'ui'
import type { PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, checkPermissions } from 'hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
interface Props {
export interface ColumnListProps {
selectedTable: PostgresTable
onSelectBack: () => void
onAddColumn: () => void
@@ -17,13 +19,13 @@ interface Props {
onDeleteColumn: (column: any) => void
}
const ColumnList: FC<Props> = ({
const ColumnList = ({
selectedTable,
onSelectBack = () => {},
onAddColumn = () => {},
onEditColumn = () => {},
onDeleteColumn = () => {},
}) => {
onSelectBack = noop,
onAddColumn = noop,
onEditColumn = noop,
onDeleteColumn = noop,
}: ColumnListProps) => {
const { meta } = useStore()
const [filterString, setFilterString] = useState<string>('')
const columns =
@@ -31,7 +33,7 @@ const ColumnList: FC<Props> = ({
? selectedTable.columns
: selectedTable.columns?.filter((column: any) => column.name.includes(filterString))) ?? []
const isLocked = meta.excludedSchemas.includes(selectedTable.schema ?? '')
const isLocked = EXCLUDED_SCHEMAS.includes(selectedTable.schema ?? '')
const canUpdateColumns = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns')
return (
@@ -1,4 +1,4 @@
import { FC, useState } from 'react'
import { useState } from 'react'
import { observer } from 'mobx-react-lite'
import {
Button,
@@ -12,45 +12,50 @@ import {
IconLock,
IconCheck,
} from 'ui'
import { partition } from 'lodash'
import { noop, partition } from 'lodash'
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import type { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
import { useStore, checkPermissions } from 'hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import type { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
interface Props {
selectedSchema: string
onSelectSchema: (schema: string) => void
export interface TableListProps {
onAddTable: () => void
onEditTable: (table: any) => void
onDeleteTable: (table: any) => void
onOpenTable: (table: any) => void
}
const TableList: FC<Props> = ({
selectedSchema,
onSelectSchema = () => {},
onAddTable = () => {},
onEditTable = () => {},
onDeleteTable = () => {},
onOpenTable = () => {},
}) => {
const TableList = ({
onAddTable = noop,
onEditTable = noop,
onDeleteTable = noop,
onOpenTable = noop,
}: TableListProps) => {
const { project } = useProjectContext()
const snap = useTableEditorStateSnapshot()
const { meta } = useStore()
const { isLoading } = meta.tables
const [filterString, setFilterString] = useState<string>('')
const canUpdateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
const schemas: PostgresSchema[] = meta.schemas.list()
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
meta.excludedSchemas.includes(schema?.name ?? '')
EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
const allTables: PostgresTable[] = meta.tables.list(
(table: PostgresTable) => table.schema === selectedSchema
(table: PostgresTable) => table.schema === snap.selectedSchemaName
)
const tables =
filterString.length === 0
@@ -62,8 +67,8 @@ const TableList: FC<Props> = ({
const realtimePublication = publications.find(
(publication) => publication.name === 'supabase_realtime'
)
// @ts-ignore
const schema = schemas.find((schema) => schema.name === selectedSchema)
const schema = schemas?.find((schema) => schema.name === snap.selectedSchemaName)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
return (
@@ -73,8 +78,8 @@ const TableList: FC<Props> = ({
<div className="w-[230px]">
<Listbox
size="small"
value={selectedSchema}
onChange={onSelectSchema}
value={snap.selectedSchemaName}
onChange={snap.setSelectedSchemaName}
icon={isLocked && <IconLock size={14} strokeWidth={2} />}
>
<Listbox.Option disabled key="normal-schemas" value="normal-schemas" label="Schemas">
@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react'
import { Form, IconDatabase, Input, Listbox, SidePanel, Modal, IconPlus } from 'ui'
import { useStore } from 'hooks'
import { useSchemasQuery } from 'data/database/schemas-query'
import ActionBar from 'components/interfaces/TableGridEditor/SidePanelEditor/ActionBar'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { Table, TableOption } from './Wrappers.types'
import { makeValidateRequired } from './Wrappers.utils'
import ActionBar from 'components/interfaces/TableGridEditor/SidePanelEditor/ActionBar'
export type WrapperTableEditorProps = {
visible: boolean
@@ -126,8 +127,11 @@ const TableForm = ({
onSubmit: OnSubmitFn
initialData: any
}) => {
const { meta } = useStore()
const schemas = meta.schemas.list()
const { project } = useProjectContext()
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const requiredOptions =
table.options.filter((option) => option.editable && option.required && !option.defaultValue) ??
@@ -173,8 +177,8 @@ const TableForm = ({
Create a new schema
</Listbox.Option>
<Modal.Separator />
{/* @ts-ignore */}
{schemas.map((schema: PostgresSchema) => {
{schemas?.map((schema) => {
return (
<Listbox.Option
key={schema.id}
@@ -1,4 +1,4 @@
import { useContext, FC, useEffect } from 'react'
import { useEffect } from 'react'
import { indexOf } from 'lodash'
import { observer } from 'mobx-react-lite'
import { Input, Form, IconAlertCircle, InputNumber } from 'ui'
@@ -8,8 +8,8 @@ import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
import { useProjectPostgrestConfigUpdateMutation } from 'data/config/project-postgrest-config-update-mutation'
import { useSchemasQuery } from 'data/database/schemas-query'
import MultiSelect from 'components/ui/MultiSelect'
import { PageContext } from 'pages/project/[ref]/settings/api'
import {
FormActions,
@@ -18,19 +18,22 @@ import {
FormSectionContent,
FormSectionLabel,
} from 'components/ui/Forms'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
interface Props {}
const PostgrestConfig: FC<Props> = ({}) => {
const PageState: any = useContext(PageContext)
const PostgrestConfig = () => {
const { project } = useProjectContext()
const { ui } = useStore()
const { meta } = PageState
const { ref: projectRef } = useParams()
const formId = 'project-postgres-config'
const { data: config, isError } = useProjectPostgrestConfigQuery({ projectRef })
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const initialValues = {
db_schema: '',
max_rows: '',
@@ -68,15 +71,12 @@ const PostgrestConfig: FC<Props> = ({}) => {
const permanentSchema = ['public', 'storage']
const hiddenSchema = ['auth', 'pgbouncer', 'hooks', 'extensions']
const schema =
meta.schemas
.list(
(x: any) => {
const find = indexOf(hiddenSchema, x.name)
if (find < 0) return x
},
{ allSchemas: true }
)
.map((x: any) => {
schemas
?.filter((x) => {
const find = indexOf(hiddenSchema, x.name)
if (find < 0) return x
})
.map((x) => {
return {
id: x.id,
value: x.name,
@@ -1,19 +1,18 @@
import { FC } from 'react'
import { observer } from 'mobx-react-lite'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useLocalStorage, useStore } from 'hooks'
import { checkPermissions, useLocalStorage } from 'hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
interface Props {
selectedSchema: string
export interface EmptyStateProps {
onAddTable: () => void
}
const EmptyState: FC<Props> = ({ selectedSchema, onAddTable }) => {
const { meta } = useStore()
const isProtectedSchema = meta.excludedSchemas.includes(selectedSchema)
const EmptyState = ({ onAddTable }: EmptyStateProps) => {
const snap = useTableEditorStateSnapshot()
const isProtectedSchema = EXCLUDED_SCHEMAS.includes(snap.selectedSchemaName)
const canCreateTables =
!isProtectedSchema && checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
@@ -27,7 +26,7 @@ const EmptyState: FC<Props> = ({ selectedSchema, onAddTable }) => {
{
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: selectedSchema,
schema: snap.selectedSchemaName,
sort,
},
{
@@ -65,4 +64,4 @@ const EmptyState: FC<Props> = ({ selectedSchema, onAddTable }) => {
)
}
export default observer(EmptyState)
export default EmptyState
@@ -12,6 +12,7 @@ import type {
import { useFlag, useStore } from 'hooks'
import { useParams } from 'common/hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import ActionBar from '../ActionBar'
import HeaderTitle from './HeaderTitle'
import ColumnType from './ColumnType'
@@ -35,7 +36,6 @@ import {
} from '../SidePanelEditor.types'
import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms'
import { EncryptionKeySelector } from 'components/interfaces/Settings/Vault'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
@@ -84,9 +84,7 @@ const ColumnEditor = ({
const foreignKeyMeta = data || []
const keys = vault.listKeys()
const enumTypes = meta.types.list(
(type: PostgresType) => !meta.excludedSchemas.includes(type.schema)
)
const enumTypes = meta.types.list((type: PostgresType) => !EXCLUDED_SCHEMAS.includes(type.schema))
const [pgsodiumExtension] = meta.extensions.list(
(ext: PostgresExtension) => ext.name.toLowerCase() === 'pgsodium'
@@ -1,19 +1,21 @@
import React, { FC, useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { get, find, isEmpty, sortBy } from 'lodash'
import { Dictionary } from 'components/grid'
import { SidePanel, Input, Listbox, IconHelpCircle, IconDatabase, Toggle } from 'ui'
import { SidePanel, Input, Listbox, IconHelpCircle, IconDatabase } from 'ui'
import type { PostgresTable, PostgresColumn, PostgresSchema } from '@supabase/postgres-meta'
import { useStore } from 'hooks'
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
import { useSchemasQuery } from 'data/database/schemas-query'
import InformationBox from 'components/ui/InformationBox'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ActionBar from '../ActionBar'
import { ForeignKey } from './ForeignKeySelector.types'
import { ColumnField } from '../SidePanelEditor.types'
import InformationBox from 'components/ui/InformationBox'
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
import { FOREIGN_KEY_DELETION_OPTIONS } from './ForeignKeySelector.constants'
import { generateDeletionActionDescription } from './ForeignKeySelector.utils'
interface Props {
export interface ForeignKeySelectorProps {
column: ColumnField
metadata?: any
visible: boolean
@@ -23,7 +25,13 @@ interface Props {
) => void
}
const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, saveChanges }) => {
const ForeignKeySelector = ({
column,
visible = false,
closePanel,
saveChanges,
}: ForeignKeySelectorProps) => {
const { project } = useProjectContext()
const { meta } = useStore()
const [errors, setErrors] = useState<any>({})
const [selectedForeignKey, setSelectedForeignKey] = useState<ForeignKey>({
@@ -33,7 +41,10 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
})
const schemas = meta.schemas.list()
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const tables = meta.tables.list(
(table: PostgresTable) => table.schema === selectedForeignKey.schema
)
@@ -190,7 +201,7 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
error={errors.schema}
onChange={(value: string) => updateSelectedSchema(value)}
>
{schemas.map((schema: PostgresSchema) => {
{schemas?.map((schema: PostgresSchema) => {
return (
<Listbox.Option
key={schema.id}
@@ -27,7 +27,6 @@ import ForeignRowSelector, {
} from './RowEditor/ForeignRowSelector/ForeignRowSelector'
export interface SidePanelEditorProps {
selectedSchema: string
selectedTable?: PostgresTable
selectedRowToEdit?: Dictionary<any>
selectedColumnToEdit?: PostgresColumn
@@ -51,7 +50,6 @@ export interface SidePanelEditorProps {
}
const SidePanelEditor = ({
selectedSchema,
selectedTable,
selectedRowToEdit,
selectedColumnToEdit,
@@ -551,7 +549,6 @@ const SidePanelEditor = ({
)}
<TableEditor
table={selectedTableToEdit}
selectedSchema={selectedSchema}
isDuplicating={isDuplicating}
visible={sidePanelKey === 'table'}
closePanel={onClosePanel}
@@ -1,10 +1,15 @@
import Link from 'next/link'
import { FC, useEffect, useState } from 'react'
import { isUndefined, isEmpty } from 'lodash'
import { useEffect, useState } from 'react'
import { isUndefined, isEmpty, noop } from 'lodash'
import { Badge, Checkbox, SidePanel, Input, Alert, IconBookOpen, Button, Modal } from 'ui'
import type { PostgresTable, PostgresType } from '@supabase/postgres-meta'
import { useStore } from 'hooks'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import ActionBar from '../ActionBar'
import HeaderTitle from './HeaderTitle'
import ColumnManagement from './ColumnManagement'
@@ -18,14 +23,10 @@ import {
generateTableFieldFromPostgresTable,
formatImportedContentToColumnFields,
} from './TableEditor.utils'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import RLSDisableModalContent from './RLSDisableModal'
interface Props {
export interface TableEditorProps {
table?: PostgresTable
selectedSchema: string
isDuplicating: boolean
visible: boolean
closePanel: () => void
@@ -45,22 +46,20 @@ interface Props {
updateEditorDirty: () => void
}
const TableEditor: FC<Props> = ({
const TableEditor = ({
table,
selectedSchema,
isDuplicating,
visible = false,
closePanel = () => {},
saveChanges = () => {},
updateEditorDirty = () => {},
}) => {
closePanel = noop,
saveChanges = noop,
updateEditorDirty = noop,
}: TableEditorProps) => {
const snap = useTableEditorStateSnapshot()
const { ui, meta } = useStore()
const { project } = useProjectContext()
const isNewRecord = isUndefined(table)
const enumTypes = meta.types.list(
(type: PostgresType) => !meta.excludedSchemas.includes(type.schema)
)
const enumTypes = meta.types.list((type: PostgresType) => !EXCLUDED_SCHEMAS.includes(type.schema))
const publications = meta.publications.list()
const realtimePublication = publications.find(
@@ -135,7 +134,7 @@ const TableEditor: FC<Props> = ({
if (isEmpty(errors)) {
const payload: CreateTablePayload | UpdateTablePayload = {
name: tableFields.name,
schema: selectedSchema,
schema: snap.selectedSchemaName,
comment: tableFields.comment,
...(!isNewRecord && { rls_enabled: tableFields.isRLSEnabled }),
}
@@ -170,8 +169,13 @@ const TableEditor: FC<Props> = ({
size="large"
key="TableEditor"
visible={visible}
// @ts-ignore
header={<HeaderTitle schema={selectedSchema} table={table} isDuplicating={isDuplicating} />}
header={
<HeaderTitle
schema={snap.selectedSchemaName}
table={table as any}
isDuplicating={isDuplicating}
/>
}
className={`transition-all duration-100 ease-in ${isImportingSpreadsheet ? ' mr-32' : ''}`}
onCancel={closePanel}
onConfirm={() => (resolve: () => void) => onSaveChanges(resolve)}
@@ -297,7 +301,7 @@ const TableEditor: FC<Props> = ({
<div className="space-y-10 py-6">
{!isDuplicating && (
<ColumnManagement
table={{ name: tableFields.name, schema: selectedSchema }}
table={{ name: tableFields.name, schema: snap.selectedSchemaName }}
columns={tableFields?.columns}
enumTypes={enumTypes}
isNewRecord={isNewRecord}
@@ -9,6 +9,7 @@ import { QueryKey, useQueryClient } from '@tanstack/react-query'
import { SchemaView } from 'types'
import { checkPermissions, useFlag, useStore, useUrlState } from 'hooks'
import useEntityType from 'hooks/misc/useEntityType'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useParams } from 'common/hooks'
import GridHeaderActions from './GridHeaderActions'
import NotFoundState from './NotFoundState'
@@ -23,25 +24,25 @@ import {
import { sqlKeys } from 'data/sql/keys'
import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query'
import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation'
import { JsonEditValue } from './SidePanelEditor/RowEditor/RowEditor.types'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import {
ForeignKeyConstraint,
useForeignKeyConstraintsQuery,
} from 'data/database/foreign-key-constraints-query'
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
import { ForeignRowSelectorProps } from './SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import TwoOptionToggle from 'components/ui/TwoOptionToggle'
import { ERROR_PRIMARY_KEY_NOTFOUND } from 'components/grid/constants'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { JsonEditValue } from './SidePanelEditor/RowEditor/RowEditor.types'
import { ForeignRowSelectorProps } from './SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
import TableDefinition from './TableDefinition'
import APIDocumentationPanel from './APIDocumentationPanel'
import { ERROR_PRIMARY_KEY_NOTFOUND } from 'components/grid/constants'
export interface TableGridEditorProps {
/** Theme for the editor */
theme?: 'dark' | 'light'
selectedSchema?: string
selectedTable: any // PostgresTable | SchemaView
/** Determines what side panel editor to show */
@@ -76,8 +77,6 @@ export interface TableGridEditorProps {
const TableGridEditor = ({
theme = 'dark',
selectedSchema,
selectedTable,
sidePanelKey,
isDuplicating,
@@ -97,12 +96,13 @@ const TableGridEditor = ({
onClosePanel = noop,
onImportData = noop,
}: TableGridEditorProps) => {
const { project } = useProjectContext()
const snap = useTableEditorStateSnapshot()
const { meta, ui, vault } = useStore()
const router = useRouter()
const { ref: projectRef, id } = useParams()
const gridRef = useRef<SupabaseGridRef>(null)
const { project } = useProjectContext()
const isVaultEnabled = useFlag('vaultExtension')
const [encryptedColumns, setEncryptedColumns] = useState([])
const [apiPreviewPanelOpen, setApiPreviewPanelOpen] = useState(false)
@@ -213,7 +213,7 @@ const TableGridEditor = ({
entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW
const isTableSelected = entityType?.type === ENTITY_TYPE.TABLE
const isForeignTableSelected = entityType?.type === ENTITY_TYPE.FOREIGN_TABLE
const isLocked = meta.excludedSchemas.includes(entityType?.schema ?? '')
const isLocked = EXCLUDED_SCHEMAS.includes(entityType?.schema ?? '')
const canUpdateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
const canEditViaTableEditor = isTableSelected && !isLocked
@@ -411,9 +411,8 @@ const TableGridEditor = ({
{(isViewSelected || isTableSelected) && <TableDefinition id={selectedTable?.id} />}
</SupabaseGrid>
{!isUndefined(selectedSchema) && (
{!isUndefined(snap.selectedSchemaName) && (
<SidePanelEditor
selectedSchema={selectedSchema}
isDuplicating={isDuplicating}
selectedTable={selectedTable as PostgresTable}
selectedRowToEdit={selectedRowToEdit}
@@ -1,4 +1,4 @@
import { FC, ReactNode, useState, useEffect } from 'react'
import { useState, useEffect, PropsWithChildren } from 'react'
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
@@ -8,12 +8,11 @@ import Error from 'components/ui/Error'
import ProductMenu from 'components/ui/ProductMenu'
import { generateAuthMenu } from './AuthLayout.utils'
interface Props {
export interface AuthLayoutProps {
title?: string
children: ReactNode
}
const AuthLayout: FC<Props> = ({ title, children }) => {
const AuthLayout = ({ title, children }: PropsWithChildren<AuthLayoutProps>) => {
const { ui, meta } = useStore()
const { isInitialized, isLoading, error } = meta.tables
const projectRef = ui.selectedProject?.ref ?? 'default'
@@ -28,7 +27,6 @@ const AuthLayout: FC<Props> = ({ title, children }) => {
meta.policies.load()
meta.tables.load()
meta.roles.load()
meta.schemas.load()
}
}, [ui.selectedProject?.ref])
@@ -16,8 +16,7 @@ interface Props {
const DatabaseLayout: FC<Props> = ({ title, children }) => {
const { meta, ui, vault, backups } = useStore()
const { isLoading } = meta.schemas
const { isInitialized, error } = meta.tables
const { error } = meta.tables
const project = ui.selectedProject
const router = useRouter()
@@ -27,12 +26,8 @@ const DatabaseLayout: FC<Props> = ({ title, children }) => {
const isVaultEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
const foreignDataWrappersEnabled = useFlag('foreignDataWrappers')
const [loaded, setLoaded] = useState<boolean>(isInitialized)
useEffect(() => {
if (ui.selectedProject?.ref) {
// Eventually should only load the required stores based on the pages
meta.schemas.load()
meta.tables.load()
meta.roles.load()
@@ -52,14 +47,6 @@ const DatabaseLayout: FC<Props> = ({ title, children }) => {
}
}, [ui.selectedProject?.ref, isVaultEnabled])
// Optimization required: load logic should be at the page level
// e.g backups page is waiting for meta.tables to load finish when it doesnt even need that data
useEffect(() => {
if (!isLoading && !loaded) {
setLoaded(true)
}
}, [isLoading])
if (error) {
return (
<ProjectLayout>
@@ -70,7 +57,7 @@ const DatabaseLayout: FC<Props> = ({ title, children }) => {
return (
<ProjectLayout
isLoading={!loaded}
// isLoading={!loaded}
product="Database"
productMenu={
<ProductMenu page={page} menu={generateDatabaseMenu(project, foreignDataWrappersEnabled)} />
@@ -1,6 +1,7 @@
import { useParams } from 'common/hooks'
import { useStore } from 'hooks'
import { PROJECT_STATUS } from 'lib/constants'
import { TableEditorStateContextProvider } from 'state/table-editor'
import { observer } from 'mobx-react-lite'
import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
import { Project } from 'types'
@@ -43,7 +44,13 @@ export const ProjectContextProvider = observer(
}
}, [ui.selectedProject?.ref, projectRef])
return <ProjectContext.Provider value={value}>{children}</ProjectContext.Provider>
return (
<ProjectContext.Provider value={value}>
<TableEditorStateContextProvider key={`table-editor-state-${projectRef}`}>
{children}
</TableEditorStateContextProvider>
</ProjectContext.Provider>
)
}
)
@@ -8,7 +8,7 @@ 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, useTableEditorStateSnapshot } from 'state/table-editor'
import { useIsTableLoaded, useTableEditorGlobalStateSnapshot } from 'state/table-editor-global'
import ProjectLayout from '../'
import TableEditorMenu from './TableEditorMenu'
import NoPermission from 'components/ui/NoPermission'
@@ -18,8 +18,6 @@ import useLatest from 'hooks/misc/useLatest'
import useTableRowsPrefetchWrapper from './TableEditorLayout.utils'
export interface TableEditorLayoutProps {
selectedSchema?: string
onSelectSchema: (schema: string) => void
onAddTable: () => void
onEditTable: (table: Entity) => void
onDeleteTable: (table: Entity) => void
@@ -27,8 +25,6 @@ export interface TableEditorLayoutProps {
}
const TableEditorLayout = ({
selectedSchema,
onSelectSchema = noop,
onAddTable = noop,
onEditTable = noop,
onDeleteTable = noop,
@@ -40,7 +36,7 @@ const TableEditorLayout = ({
const { ref, id: _id } = useParams()
const id = _id ? Number(_id) : undefined
const snap = useTableEditorStateSnapshot()
const snap = useTableEditorGlobalStateSnapshot()
const canReadTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables')
const vaultExtension = meta.extensions.byId('supabase_vault')
@@ -48,7 +44,6 @@ const TableEditorLayout = ({
useEffect(() => {
if (ui.selectedProject?.ref) {
meta.schemas.load()
meta.types.load()
meta.policies.load()
meta.publications.load()
@@ -125,8 +120,6 @@ const TableEditorLayout = ({
product="Table editor"
productMenu={
<TableEditorMenu
selectedSchema={selectedSchema}
onSelectSchema={onSelectSchema}
onAddTable={onAddTable}
onEditTable={onEditTable}
onDeleteTable={onDeleteTable}
@@ -1,6 +1,5 @@
import { useMemo, useState } from 'react'
import { noop, partition } from 'lodash'
import { observer } from 'mobx-react-lite'
import {
Button,
Dropdown,
@@ -17,19 +16,19 @@ import {
} from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import type { PostgresSchema } from '@supabase/postgres-meta'
import { useParams } from 'common/hooks'
import { checkPermissions, useStore, useLocalStorage } from 'hooks'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { checkPermissions, useLocalStorage } from 'hooks'
import InfiniteList from 'components/ui/InfiniteList'
import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
import { Entity } from 'data/entity-types/entity-type-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useProjectContext } from '../ProjectLayout/ProjectContext'
import InfiniteList from 'components/ui/InfiniteList'
import EntityListItem from './EntityListItem'
export interface TableEditorMenuProps {
selectedSchema?: string
onSelectSchema: (schema: string) => void
onAddTable: () => void
onEditTable: (table: Entity) => void
onDeleteTable: (table: Entity) => void
@@ -37,15 +36,13 @@ export interface TableEditorMenuProps {
}
const TableEditorMenu = ({
selectedSchema,
onSelectSchema = noop,
onAddTable = noop,
onEditTable = noop,
onDeleteTable = noop,
onDuplicateTable = noop,
}: TableEditorMenuProps) => {
const { meta } = useStore()
const { id } = useParams()
const snap = useTableEditorStateSnapshot()
const [searchText, setSearchText] = useState<string>('')
const [sort, setSort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>(
@@ -67,7 +64,7 @@ const TableEditorMenu = ({
{
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: selectedSchema,
schema: snap.selectedSchemaName,
search: searchText || undefined,
sort,
},
@@ -82,19 +79,24 @@ const TableEditorMenu = ({
[data?.pages]
)
const schemas: PostgresSchema[] = meta.schemas.list()
const {
data: schemas,
isLoading: isSchemasLoading,
isSuccess: isSchemasSuccess,
} = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const schema = schemas.find((schema) => schema.name === selectedSchema)
const schema = schemas?.find((schema) => schema.name === snap.selectedSchemaName)
const canCreateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
const isLoadingTableMetadata = id ? !meta.tables.byId(id) : true
const refreshTables = async () => {
await refetch()
}
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
meta.excludedSchemas.includes(schema?.name ?? '')
EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
@@ -105,18 +107,20 @@ const TableEditorMenu = ({
>
{/* Schema selection dropdown */}
<div className="px-3 mx-4">
{!meta.schemas.isInitialized ? (
{isSchemasLoading && (
<div className="flex h-[26px] items-center space-x-3 rounded border border-gray-500 px-3">
<IconLoader className="animate-spin" size={12} />
<span className="text-xs text-scale-900">Loading schemas...</span>
</div>
) : (
)}
{isSchemasSuccess && (
<Listbox
size="tiny"
value={selectedSchema}
value={snap.selectedSchemaName}
onChange={(name: string) => {
setSearchText('')
onSelectSchema(name)
snap.setSelectedSchemaName(name)
}}
>
<Listbox.Option
@@ -348,7 +352,7 @@ const TableEditorMenu = ({
onEditTable,
onDeleteTable,
onDuplicateTable,
isLoadingTableMetadata,
isLoadingTableMetadata: false, // TODO(alaister)
}}
getItemSize={() => 28}
hasNextPage={hasNextPage}
@@ -362,4 +366,4 @@ const TableEditorMenu = ({
)
}
export default observer(TableEditorMenu)
export default TableEditorMenu
+5
View File
@@ -0,0 +1,5 @@
export const databaseKeys = {
schemaList: (projectRef: string | undefined) => ['projects', projectRef, 'schemas'] as const,
schema: (projectRef: string | undefined, id: string | undefined) =>
['projects', projectRef, 'schemas', id] as const,
}
+40 -50
View File
@@ -1,60 +1,50 @@
import { useCallback } from 'react'
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query'
export type Schema = {
name: string
}
export const getSchemasQuery = () => {
const sql = /* SQL */ `
SELECT nspname as name
FROM pg_namespace
WHERE
nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
AND nspname not like 'pg_temp_%'
AND nspname not like 'pg_toast_temp_%'
AND has_schema_privilege(oid, 'CREATE, USAGE')
ORDER BY nspname;
`.trim()
return sql
}
import { PostgresSchema } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { databaseKeys } from './keys'
export type SchemasVariables = {
projectRef?: string
connectionString?: string
}
export type SchemasData = { result: Schema[] }
export type Schema = PostgresSchema
export type SchemasResponse = Schema[] | { error?: any }
export async function getSchemas(
{ projectRef, connectionString }: SchemasVariables,
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}/schemas`, {
headers: Object.fromEntries(headers),
signal,
})) as SchemasResponse
if (!Array.isArray(response) && response.error) {
throw response.error
}
return response as PostgresSchema[]
}
export type SchemasData = Awaited<ReturnType<typeof getSchemas>>
export type SchemasError = unknown
export const useSchemasQuery = <TData extends SchemasData = SchemasData>(
export const useSchemasQuery = <TData = SchemasData>(
{ projectRef, connectionString }: SchemasVariables,
options: UseQueryOptions<ExecuteSqlData, SchemasError, TData> = {}
) => {
return useExecuteSqlQuery(
{
projectRef,
connectionString,
sql: getSchemasQuery(),
queryKey: ['schemas'],
},
options
{ enabled = true, ...options }: UseQueryOptions<SchemasData, SchemasError, TData> = {}
) =>
useQuery<SchemasData, SchemasError, TData>(
databaseKeys.schemaList(projectRef),
({ signal }) => getSchemas({ projectRef, connectionString }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
}
export const useSchemasPrefetch = () => {
const prefetch = useExecuteSqlPrefetch()
return useCallback(
({ projectRef, connectionString }: SchemasVariables) =>
prefetch({
projectRef,
connectionString,
sql: getSchemasQuery(),
queryKey: ['schemas'],
}),
[prefetch]
)
}
+20
View File
@@ -0,0 +1,20 @@
/**
* A list of system schemas that users should not interact with
*/
export const EXCLUDED_SCHEMAS = [
'auth',
'extensions',
'information_schema',
'net',
'pgsodium',
'pgsodium_masks',
'pgbouncer',
'realtime',
'storage',
'supabase_functions',
'vault',
'graphql',
'graphql_public',
'pgtle',
'supabase_migrations',
]
@@ -33,8 +33,6 @@ const GraphiQLPage: NextPageWithLayout = () => {
useEffect(() => {
if (ui.selectedProject?.ref) {
// Schemas may be needed when enabling the GraphQL extension
meta.schemas.load()
meta.extensions.load()
}
}, [ui.selectedProject?.ref])
+17 -8
View File
@@ -11,6 +11,10 @@ import { useParams } from 'common/hooks'
import { AuthLayout } from 'components/layouts'
import { Policies } from 'components/interfaces/Auth/Policies'
import NoPermission from 'components/ui/NoPermission'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
/**
* Filter tables by table name and policy name
@@ -48,26 +52,31 @@ const onFilterTables = (
}
const AuthPoliciesPage: NextPageWithLayout = () => {
const { project } = useProjectContext()
const snap = useTableEditorStateSnapshot()
const { meta } = useStore()
const { search } = useParams()
const [selectedSchema, setSelectedSchema] = useState<string>('public')
const [searchString, setSearchString] = useState<string>('')
useEffect(() => {
if (search) setSearchString(search)
}, [search])
const schemas = meta.schemas.list()
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
meta.excludedSchemas.includes(schema?.name ?? '')
EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
// @ts-ignore
const schema = schemas.find((schema) => schema.name === selectedSchema)
const schema = schemas?.find((schema) => schema.name === snap.selectedSchemaName)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
const policies = meta.policies.list()
const tables = meta.tables.list((table: { schema: string }) => table.schema === selectedSchema)
const tables = meta.tables.list(
(table: { schema: string }) => table.schema === snap.selectedSchemaName
)
const filteredTables = onFilterTables(tables, policies, searchString)
const canReadPolicies = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'policies')
@@ -84,9 +93,9 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
<div className="w-[230px]">
<Listbox
size="small"
value={selectedSchema}
value={snap.selectedSchemaName}
onChange={(schema: string) => {
setSelectedSchema(schema)
snap.setSelectedSchemaName(schema)
setSearchString('')
}}
icon={isLocked && <IconLock size={14} strokeWidth={2} />}
@@ -1,12 +1,12 @@
import clsx from 'clsx'
import { IconLoader } from 'ui'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { observer } from 'mobx-react-lite'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { NextPageWithLayout } from 'types'
import { checkPermissions, useStore } from 'hooks'
import { useParams } from 'common'
import { API_URL } from 'lib/constants'
import { post } from 'lib/common/fetch'
import { DatabaseLayout } from 'components/layouts'
@@ -15,20 +15,30 @@ import HooksList from 'components/interfaces/Database/Hooks/HooksList/HooksList'
import DeleteHookModal from 'components/interfaces/Database/Hooks/DeleteHookModal'
import EditHookPanel from 'components/interfaces/Database/Hooks/EditHookPanel'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
const HooksPage: NextPageWithLayout = () => {
const { project } = useProjectContext()
const { meta, ui } = useStore()
const router = useRouter()
const { ref } = router.query
const schemas = meta.schemas.list()
const { isLoading: isLoadingSchemas } = meta.schemas
const { ref: projectRef } = useParams()
const {
data: schemas,
isLoading: isSchemasLoading,
refetch,
} = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const [selectedHook, setSelectedHook] = useState<any>()
const [showCreateHookForm, setShowCreateHookForm] = useState<boolean>(false)
const [showDeleteHookForm, setShowDeleteHookForm] = useState<boolean>(false)
const isHooksEnabled = schemas.some((schema: any) => schema.name === 'supabase_functions')
const isHooksEnabled =
schemas?.some((schema: any) => schema.name === 'supabase_functions') ?? false
const canReadWebhooks = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers')
const canCreateWebhooks = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers')
@@ -37,14 +47,14 @@ const HooksPage: NextPageWithLayout = () => {
}, [ui.selectedProject?.ref])
const enableHooksForProject = async () => {
const res = await post(`${API_URL}/database/${ref}/hook-enable`, {})
const res = await post(`${API_URL}/database/${projectRef}/hook-enable`, {})
if (res.error) {
ui.setNotification({
category: 'error',
message: `Failed to enable webhooks: ${res.error.message}`,
})
} else {
meta.schemas.load()
refetch()
ui.setNotification({
category: 'success',
message: `Successfully enabled webhooks`,
@@ -71,7 +81,7 @@ const HooksPage: NextPageWithLayout = () => {
return <NoPermission isFullPage resourceText="view database webhooks" />
}
if (isLoadingSchemas) {
if (isSchemasLoading) {
return (
<div className="w-full h-full flex items-center justify-center space-x-2">
<IconLoader className="animate-spin" size="tiny" strokeWidth={1.5} />
@@ -23,7 +23,6 @@ const DatabaseTables: NextPageWithLayout = () => {
const queryClient = useQueryClient()
const [isDeleting, setIsDeleting] = useState<boolean>(false)
const [selectedSchema, setSelectedSchema] = useState('public')
const [selectedTable, setSelectedTable] = useState<any>()
const [sidePanelKey, setSidePanelKey] = useState<'column' | 'table'>()
@@ -147,8 +146,6 @@ const DatabaseTables: NextPageWithLayout = () => {
<div className="p-4">
{isUndefined(selectedTable) ? (
<TableList
selectedSchema={selectedSchema}
onSelectSchema={setSelectedSchema}
onAddTable={onAddTable}
onEditTable={onEditTable}
onDeleteTable={onDeleteTable}
@@ -198,7 +195,6 @@ const DatabaseTables: NextPageWithLayout = () => {
</ConfirmationModal>
<SidePanelEditor
sidePanelKey={sidePanelKey}
selectedSchema={selectedSchema}
selectedTable={selectedTable}
onColumnSaved={onColumnUpdated}
closePanel={onClosePanel}
+12 -12
View File
@@ -24,6 +24,7 @@ import {
} from 'components/layouts/ProjectLayout/ProjectContext'
import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
import { useTheme } from 'common'
import { useTableEditorStateSnapshot } from 'state/table-editor'
const TableEditorPage: NextPageWithLayout = () => {
const router = useRouter()
@@ -32,11 +33,11 @@ const TableEditorPage: NextPageWithLayout = () => {
const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
const { project } = useProjectContext()
const snap = useTableEditorStateSnapshot()
const queryClient = useQueryClient()
const { meta, ui } = useStore()
const [selectedSchema, setSelectedSchema] = useState<string>()
const [isDeleting, setIsDeleting] = useState<boolean>(false)
const [isDuplicating, setIsDuplicating] = useState<boolean>(false)
@@ -75,11 +76,11 @@ const TableEditorPage: NextPageWithLayout = () => {
.find((table) => table.id === Number(id))
: undefined
useEffect(() => {
if (selectedTable && 'schema' in selectedTable) {
setSelectedSchema(selectedTable.schema)
}
}, [selectedTable?.name])
// useEffect(() => {
// if (selectedTable && 'schema' in selectedTable) {
// setSelectedSchema(selectedTable.schema)
// }
// }, [selectedTable?.name])
const onAddRow = () => {
setSidePanelKey('row')
@@ -221,7 +222,7 @@ const TableEditorPage: NextPageWithLayout = () => {
),
])
if (selectedSchema) await meta.views.loadBySchema(selectedSchema)
if (snap.selectedSchemaName) await meta.views.loadBySchema(snap.selectedSchemaName)
} catch (error: any) {
ui.setNotification({
category: 'error',
@@ -240,7 +241,9 @@ const TableEditorPage: NextPageWithLayout = () => {
const response: any = await meta.tables.del(selectedTableToDelete.id, isDeleteWithCascade)
if (response.error) throw response.error
const tables = meta.tables.list((table: PostgresTable) => table.schema === selectedSchema)
const tables = meta.tables.list(
(table: PostgresTable) => table.schema === snap.selectedSchemaName
)
await queryClient.invalidateQueries(entityTypeKeys.list(projectRef))
@@ -254,7 +257,7 @@ const TableEditorPage: NextPageWithLayout = () => {
category: 'success',
message: `Successfully deleted table "${selectedTableToDelete.name}"`,
})
if (selectedSchema) await meta.views.loadBySchema(selectedSchema)
if (snap.selectedSchemaName) await meta.views.loadBySchema(snap.selectedSchemaName)
} catch (error: any) {
ui.setNotification({
error,
@@ -269,15 +272,12 @@ const TableEditorPage: NextPageWithLayout = () => {
return (
<TableEditorLayout
selectedSchema={selectedSchema}
onSelectSchema={setSelectedSchema}
onAddTable={onAddTable}
onEditTable={onEditTable}
onDeleteTable={onDeleteTable}
onDuplicateTable={onDuplicateTable}
>
<TableGridEditor
selectedSchema={selectedSchema}
selectedTable={selectedTable}
sidePanelKey={sidePanelKey}
isDuplicating={isDuplicating}
+4 -6
View File
@@ -11,14 +11,15 @@ import { TableEditorLayout } from 'components/layouts'
import { EmptyState, SidePanelEditor } from 'components/interfaces/TableGridEditor'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import { ProjectContextFromParamsProvider } from 'components/layouts/ProjectLayout/ProjectContext'
import { useTableEditorStateSnapshot } from 'state/table-editor'
const TableEditorPage: NextPageWithLayout = () => {
const snap = useTableEditorStateSnapshot()
const { meta, ui } = useStore()
const projectRef = ui.selectedProject?.ref
const [sidePanelKey, setSidePanelKey] = useState<'row' | 'column' | 'table'>()
const [isDeleting, setIsDeleting] = useState<boolean>(false)
const [isDuplicating, setIsDuplicating] = useState<boolean>(false)
const [selectedSchema, setSelectedSchema] = useState<string>('public')
const [selectedTableToEdit, setSelectedTableToEdit] = useState<PostgresTable>()
const [selectedTableToDelete, setSelectedTableToDelete] = useState<PostgresTable>()
@@ -63,7 +64,7 @@ const TableEditorPage: NextPageWithLayout = () => {
category: 'success',
message: `Successfully deleted ${selectedTableToDelete!.name}`,
})
await meta.views.loadBySchema(selectedSchema)
await meta.views.loadBySchema(snap.selectedSchemaName)
} catch (error: any) {
ui.setNotification({
category: 'error',
@@ -74,14 +75,12 @@ const TableEditorPage: NextPageWithLayout = () => {
return (
<TableEditorLayout
selectedSchema={selectedSchema}
onSelectSchema={setSelectedSchema}
onAddTable={onAddTable}
onEditTable={onEditTable}
onDeleteTable={onDeleteTable}
onDuplicateTable={onDuplicateTable}
>
<EmptyState selectedSchema={selectedSchema} onAddTable={onAddTable} />
<EmptyState onAddTable={onAddTable} />
{/* On this page it'll only handle tables */}
<ConfirmationModal
danger
@@ -96,7 +95,6 @@ const TableEditorPage: NextPageWithLayout = () => {
onSelectConfirm={onConfirmDeleteTable}
/>
<SidePanelEditor
selectedSchema={selectedSchema}
isDuplicating={isDuplicating}
selectedTableToEdit={selectedTableToEdit}
sidePanelKey={sidePanelKey}
@@ -28,10 +28,6 @@ const ApiSettings: NextPageWithLayout = () => {
PageContext.project = project
if (meta) PageState.meta = meta
useEffect(() => {
if (project?.ref) meta.schemas.load()
}, [project?.ref])
return (
<PageContext.Provider value={PageState}>
<div className="flex flex-col gap-8 px-5 py-6 mx-auto 1xl:px-28 lg:px-16 xl:px-24 2xl:px-32">
+1 -1
View File
@@ -65,7 +65,7 @@ const SqlEditor: NextPageWithLayout = () => {
pgInfoRef.current = {}
}
pgInfoRef.current.tableColumns = tableColumns?.result
pgInfoRef.current.schemas = schemas?.result
pgInfoRef.current.schemas = schemas
pgInfoRef.current.keywords = keywords?.result
pgInfoRef.current.functions = functions?.result
}
+28
View File
@@ -0,0 +1,28 @@
import { proxy, snapshot, useSnapshot } from 'valtio'
import { proxySet } from 'valtio/utils'
export const tableEditorGlobalState = proxy({
projectRef: undefined as undefined | string,
loadedIds: proxySet<number>([]),
addLoadedId: (ref: string, id: number) => {
if (ref !== tableEditorGlobalState.projectRef) {
tableEditorGlobalState.loadedIds.clear()
tableEditorGlobalState.projectRef = ref
}
tableEditorGlobalState.loadedIds.add(id)
},
})
export const getTableEditorGlobalStateSnapshot = () => snapshot(tableEditorGlobalState)
export const useTableEditorGlobalStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
useSnapshot(tableEditorGlobalState, options)
export const useIsTableLoaded = (ref?: string, id?: number) => {
const snap = useTableEditorGlobalStateSnapshot()
if (!ref || !id) return false
if (ref !== snap.projectRef) return false
return snap.loadedIds.has(id)
}
-28
View File
@@ -1,28 +0,0 @@
import { proxy, snapshot, useSnapshot } from 'valtio'
import { proxySet } from 'valtio/utils'
export const tableEditorState = proxy({
projectRef: undefined as undefined | string,
loadedIds: proxySet<number>([]),
addLoadedId: (ref: string, id: number) => {
if (ref !== tableEditorState.projectRef) {
tableEditorState.loadedIds.clear()
tableEditorState.projectRef = ref
}
tableEditorState.loadedIds.add(id)
},
})
export const getTableEditorStateSnapshot = () => snapshot(tableEditorState)
export const useTableEditorStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
useSnapshot(tableEditorState, options)
export const useIsTableLoaded = (ref?: string, id?: number) => {
const snap = useTableEditorStateSnapshot()
if (!ref || !id) return false
if (ref !== snap.projectRef) return false
return snap.loadedIds.has(id)
}
+30
View File
@@ -0,0 +1,30 @@
import { PropsWithChildren, createContext, useContext, useRef } from 'react'
import { proxy, useSnapshot } from 'valtio'
export const createTableEditorState = () => {
const state = proxy({
selectedSchemaName: 'public',
setSelectedSchemaName: (schemaName: string) => {
state.selectedSchemaName = schemaName
},
})
return state
}
export type TableEditorState = ReturnType<typeof createTableEditorState>
export const TableEditorStateContext = createContext<TableEditorState>(createTableEditorState())
export const TableEditorStateContextProvider = ({ children }: PropsWithChildren<{}>) => {
const state = useRef(createTableEditorState()).current
return (
<TableEditorStateContext.Provider value={state}>{children}</TableEditorStateContext.Provider>
)
}
export const useTableEditorStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) => {
const state = useContext(TableEditorStateContext)
return useSnapshot(state, options)
}
+1 -29
View File
@@ -18,7 +18,6 @@ import { ResponseError } from 'types'
import { IRootStore } from '../RootStore'
import ColumnStore from './ColumnStore'
import SchemaStore from './SchemaStore'
import TableStore, { ITableStore } from './TableStore'
import OpenApiStore, { IOpenApiStore } from './OpenApiStore'
import { IPostgresMetaInterface } from '../common/PostgresMetaInterface'
@@ -51,12 +50,9 @@ const BATCH_SIZE = 1000
const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB
export interface IMetaStore {
excludedSchemas: string[]
openApi: IOpenApiStore
tables: ITableStore
columns: IPostgresMetaInterface<PostgresColumn>
schemas: IPostgresMetaInterface<PostgresSchema>
views: IViewStore
materializedViews: IMaterializedViewStore
foreignTables: IForeignTableStore
@@ -150,7 +146,6 @@ export default class MetaStore implements IMetaStore {
openApi: OpenApiStore
tables: TableStore
columns: ColumnStore
schemas: SchemaStore
views: ViewStore
materializedViews: MaterializedViewStore
foreignTables: ForeignTableStore
@@ -169,23 +164,6 @@ export default class MetaStore implements IMetaStore {
baseUrl: string
headers: { [prop: string]: any }
// [Joshen] I'm going to treat this as a list of system schemas
excludedSchemas = [
'auth',
'extensions',
'information_schema',
'net',
'pgsodium',
'pgsodium_masks',
'pgbouncer',
'realtime',
'storage',
'supabase_functions',
'vault',
'graphql',
'graphql_public',
]
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString?: string }) {
const { projectRef, connectionString } = options
this.rootStore = rootStore
@@ -204,7 +182,6 @@ export default class MetaStore implements IMetaStore {
)
this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers)
this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers)
this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers)
this.views = new ViewStore(this.rootStore, `${this.baseUrl}/views`, this.headers)
this.materializedViews = new MaterializedViewStore(
this.rootStore,
@@ -237,9 +214,7 @@ export default class MetaStore implements IMetaStore {
)
this.types = new TypesStore(this.rootStore, `${this.baseUrl}/types`, this.headers)
makeObservable(this, {
excludedSchemas: observable,
})
makeObservable(this)
}
/**
@@ -989,9 +964,6 @@ export default class MetaStore implements IMetaStore {
this.columns.setUrl(`${this.baseUrl}/columns`)
this.columns.setHeaders(this.headers)
this.schemas.setUrl(`${this.baseUrl}/schemas`)
this.schemas.setHeaders(this.headers)
this.views.setUrl(`${this.baseUrl}/views`)
this.views.setHeaders(this.headers)
-24
View File
@@ -1,24 +0,0 @@
import type { PostgresSchema } from '@supabase/postgres-meta'
import PostgresMetaInterface from '../common/PostgresMetaInterface'
import { IRootStore } from '../RootStore'
export default class SchemaStore extends PostgresMetaInterface<PostgresSchema> {
constructor(
rootStore: IRootStore,
dataUrl: string,
headers?: {
[prop: string]: any
},
options?: { identifier: string }
) {
super(rootStore, dataUrl, headers, options)
}
// Dashboard to hide schemas with pg_ prefix
list(filter: any) {
const schemasFilter = (schema: PostgresSchema) =>
!schema.name.startsWith('pg_') && (typeof filter === 'function' ? filter(schema) : true)
return super.list(schemasFilter)
}
}