diff --git a/studio/components/interfaces/Database/Extensions/EnableExtensionModal.tsx b/studio/components/interfaces/Database/Extensions/EnableExtensionModal.tsx index 9f569536579..f83d463e198 100644 --- a/studio/components/interfaces/Database/Extensions/EnableExtensionModal.tsx +++ b/studio/components/interfaces/Database/Extensions/EnableExtensionModal.tsx @@ -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 = ({ 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 = ({ visible, extension, onCancel }) => { return () => { cancel = true } - }, [visible]) + }, [visible, extension.name]) const validate = (values: any) => { const errors: any = {} @@ -124,7 +130,7 @@ const EnableExtensionModal: FC = ({ visible, extension, onCancel }) => { return (
- {fetchingSchemaInfo ? ( + {fetchingSchemaInfo || isSchemasLoading ? (
@@ -156,8 +162,7 @@ const EnableExtensionModal: FC = ({ visible, extension, onCancel }) => { Create a new schema "{extension.name}" - {/* @ts-ignore */} - {schemas.map((schema: PostgresSchema) => { + {schemas?.map((schema: PostgresSchema) => { return ( void deleteFunction: (fn: any) => void } -const FunctionList: FC = ({ +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()) ) diff --git a/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index b5571b31790..7f5f13c4255 100644 --- a/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -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 = ({ - createFunction = () => {}, - editFunction = () => {}, - deleteFunction = () => {}, -}) => { +const FunctionsList = ({ + createFunction = noop, + editFunction = noop, + deleteFunction = noop, +}: FunctionsListProps) => { const { meta } = useStore() const [filterString, setFilterString] = useState('') 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()) diff --git a/studio/components/interfaces/Database/Publications/PublicationsTables.tsx b/studio/components/interfaces/Database/Publications/PublicationsTables.tsx index 73d2f67bbd5..ad2b4afd075 100644 --- a/studio/components/interfaces/Database/Publications/PublicationsTables.tsx +++ b/studio/components/interfaces/Database/Publications/PublicationsTables.tsx @@ -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 = ({ selectedPublication, onSelectBack }) => { +const PublicationsTables = ({ selectedPublication, onSelectBack }: PublicationsTablesProps) => { const { meta } = useStore() const [filterString, setFilterString] = useState('') @@ -26,10 +27,10 @@ const PublicationsTables: FC = ({ 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 diff --git a/studio/components/interfaces/Database/Tables/ColumnList.tsx b/studio/components/interfaces/Database/Tables/ColumnList.tsx index 447e4a82750..e6fb58e8143 100644 --- a/studio/components/interfaces/Database/Tables/ColumnList.tsx +++ b/studio/components/interfaces/Database/Tables/ColumnList.tsx @@ -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 = ({ +const ColumnList = ({ selectedTable, - onSelectBack = () => {}, - onAddColumn = () => {}, - onEditColumn = () => {}, - onDeleteColumn = () => {}, -}) => { + onSelectBack = noop, + onAddColumn = noop, + onEditColumn = noop, + onDeleteColumn = noop, +}: ColumnListProps) => { const { meta } = useStore() const [filterString, setFilterString] = useState('') const columns = @@ -31,7 +33,7 @@ const ColumnList: FC = ({ ? 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 ( diff --git a/studio/components/interfaces/Database/Tables/TableList.tsx b/studio/components/interfaces/Database/Tables/TableList.tsx index bfb97d7e2ac..5a3ed7209f7 100644 --- a/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/studio/components/interfaces/Database/Tables/TableList.tsx @@ -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 = ({ - 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('') 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 = ({ 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 = ({
} > diff --git a/studio/components/interfaces/Database/Wrappers/WrapperTableEditor.tsx b/studio/components/interfaces/Database/Wrappers/WrapperTableEditor.tsx index 7aa5d2fefad..fecbd3ab716 100644 --- a/studio/components/interfaces/Database/Wrappers/WrapperTableEditor.tsx +++ b/studio/components/interfaces/Database/Wrappers/WrapperTableEditor.tsx @@ -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 - {/* @ts-ignore */} - {schemas.map((schema: PostgresSchema) => { + + {schemas?.map((schema) => { return ( = ({}) => { - 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 = ({}) => { 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, diff --git a/studio/components/interfaces/TableGridEditor/EmptyState.tsx b/studio/components/interfaces/TableGridEditor/EmptyState.tsx index 3667bf1e4ef..8aeb7128028 100644 --- a/studio/components/interfaces/TableGridEditor/EmptyState.tsx +++ b/studio/components/interfaces/TableGridEditor/EmptyState.tsx @@ -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 = ({ 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 = ({ selectedSchema, onAddTable }) => { { projectRef: project?.ref, connectionString: project?.connectionString, - schema: selectedSchema, + schema: snap.selectedSchemaName, sort, }, { @@ -65,4 +64,4 @@ const EmptyState: FC = ({ selectedSchema, onAddTable }) => { ) } -export default observer(EmptyState) +export default EmptyState diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx index a82e5d41a4d..0710c1c6415 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx @@ -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' diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index d40c56df3ee..aaa15bec5fe 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -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 = ({ column, visible = false, closePanel, saveChanges }) => { +const ForeignKeySelector = ({ + column, + visible = false, + closePanel, + saveChanges, +}: ForeignKeySelectorProps) => { + const { project } = useProjectContext() const { meta } = useStore() const [errors, setErrors] = useState({}) const [selectedForeignKey, setSelectedForeignKey] = useState({ @@ -33,7 +41,10 @@ const ForeignKeySelector: FC = ({ 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 = ({ column, visible = false, closePanel, sa error={errors.schema} onChange={(value: string) => updateSelectedSchema(value)} > - {schemas.map((schema: PostgresSchema) => { + {schemas?.map((schema: PostgresSchema) => { return ( selectedColumnToEdit?: PostgresColumn @@ -51,7 +50,6 @@ export interface SidePanelEditorProps { } const SidePanelEditor = ({ - selectedSchema, selectedTable, selectedRowToEdit, selectedColumnToEdit, @@ -551,7 +549,6 @@ const SidePanelEditor = ({ )} void @@ -45,22 +46,20 @@ interface Props { updateEditorDirty: () => void } -const TableEditor: FC = ({ +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 = ({ 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 = ({ size="large" key="TableEditor" visible={visible} - // @ts-ignore - header={} + header={ + + } className={`transition-all duration-100 ease-in ${isImportingSpreadsheet ? ' mr-32' : ''}`} onCancel={closePanel} onConfirm={() => (resolve: () => void) => onSaveChanges(resolve)} @@ -297,7 +301,7 @@ const TableEditor: FC = ({
{!isDuplicating && ( { + const { project } = useProjectContext() + const snap = useTableEditorStateSnapshot() const { meta, ui, vault } = useStore() const router = useRouter() const { ref: projectRef, id } = useParams() const gridRef = useRef(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) && } - {!isUndefined(selectedSchema) && ( + {!isUndefined(snap.selectedSchemaName) && ( = ({ title, children }) => { +const AuthLayout = ({ title, children }: PropsWithChildren) => { const { ui, meta } = useStore() const { isInitialized, isLoading, error } = meta.tables const projectRef = ui.selectedProject?.ref ?? 'default' @@ -28,7 +27,6 @@ const AuthLayout: FC = ({ title, children }) => { meta.policies.load() meta.tables.load() meta.roles.load() - meta.schemas.load() } }, [ui.selectedProject?.ref]) diff --git a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx index deb9fb8ab91..b38073847c2 100644 --- a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx +++ b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx @@ -16,8 +16,7 @@ interface Props { const DatabaseLayout: FC = ({ 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 = ({ title, children }) => { const isVaultEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null const foreignDataWrappersEnabled = useFlag('foreignDataWrappers') - const [loaded, setLoaded] = useState(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 = ({ 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 ( @@ -70,7 +57,7 @@ const DatabaseLayout: FC = ({ title, children }) => { return ( diff --git a/studio/components/layouts/ProjectLayout/ProjectContext.tsx b/studio/components/layouts/ProjectLayout/ProjectContext.tsx index 8fc28f52926..d8af63e6159 100644 --- a/studio/components/layouts/ProjectLayout/ProjectContext.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectContext.tsx @@ -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 {children} + return ( + + + {children} + + + ) } ) diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index af9d3c9d067..8ab79999ccf 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -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={ 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('') 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 */}
- {!meta.schemas.isInitialized ? ( + {isSchemasLoading && (
Loading schemas...
- ) : ( + )} + + {isSchemasSuccess && ( { setSearchText('') - onSelectSchema(name) + snap.setSelectedSchemaName(name) }} > 28} hasNextPage={hasNextPage} @@ -362,4 +366,4 @@ const TableEditorMenu = ({ ) } -export default observer(TableEditorMenu) +export default TableEditorMenu diff --git a/studio/data/database/keys.ts b/studio/data/database/keys.ts new file mode 100644 index 00000000000..b1ae0abf417 --- /dev/null +++ b/studio/data/database/keys.ts @@ -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, +} diff --git a/studio/data/database/schemas-query.ts b/studio/data/database/schemas-query.ts index 964b1228f54..0476e211b3f 100644 --- a/studio/data/database/schemas-query.ts +++ b/studio/data/database/schemas-query.ts @@ -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> export type SchemasError = unknown -export const useSchemasQuery = ( +export const useSchemasQuery = ( { projectRef, connectionString }: SchemasVariables, - options: UseQueryOptions = {} -) => { - return useExecuteSqlQuery( - { - projectRef, - connectionString, - sql: getSchemasQuery(), - queryKey: ['schemas'], - }, - options + { enabled = true, ...options }: UseQueryOptions = {} +) => + useQuery( + 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] - ) -} diff --git a/studio/lib/constants/schemas.ts b/studio/lib/constants/schemas.ts new file mode 100644 index 00000000000..5531f5b32cb --- /dev/null +++ b/studio/lib/constants/schemas.ts @@ -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', +] diff --git a/studio/pages/project/[ref]/api/graphiql.tsx b/studio/pages/project/[ref]/api/graphiql.tsx index 95492f7a2d1..8c8b02807bb 100644 --- a/studio/pages/project/[ref]/api/graphiql.tsx +++ b/studio/pages/project/[ref]/api/graphiql.tsx @@ -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]) diff --git a/studio/pages/project/[ref]/auth/policies.tsx b/studio/pages/project/[ref]/auth/policies.tsx index 82bf69f90b2..7ae77e49e06 100644 --- a/studio/pages/project/[ref]/auth/policies.tsx +++ b/studio/pages/project/[ref]/auth/policies.tsx @@ -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('public') const [searchString, setSearchString] = useState('') 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 = () => {
{ - setSelectedSchema(schema) + snap.setSelectedSchemaName(schema) setSearchString('') }} icon={isLocked && } diff --git a/studio/pages/project/[ref]/database/hooks/index.tsx b/studio/pages/project/[ref]/database/hooks/index.tsx index 4981cae2e65..1aa5b8371f1 100644 --- a/studio/pages/project/[ref]/database/hooks/index.tsx +++ b/studio/pages/project/[ref]/database/hooks/index.tsx @@ -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() const [showCreateHookForm, setShowCreateHookForm] = useState(false) const [showDeleteHookForm, setShowDeleteHookForm] = useState(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 } - if (isLoadingSchemas) { + if (isSchemasLoading) { return (
diff --git a/studio/pages/project/[ref]/database/tables.tsx b/studio/pages/project/[ref]/database/tables.tsx index 74bfaa45637..60d7a31acd1 100644 --- a/studio/pages/project/[ref]/database/tables.tsx +++ b/studio/pages/project/[ref]/database/tables.tsx @@ -23,7 +23,6 @@ const DatabaseTables: NextPageWithLayout = () => { const queryClient = useQueryClient() const [isDeleting, setIsDeleting] = useState(false) - const [selectedSchema, setSelectedSchema] = useState('public') const [selectedTable, setSelectedTable] = useState() const [sidePanelKey, setSidePanelKey] = useState<'column' | 'table'>() @@ -147,8 +146,6 @@ const DatabaseTables: NextPageWithLayout = () => {
{isUndefined(selectedTable) ? ( { { 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() const [isDeleting, setIsDeleting] = useState(false) const [isDuplicating, setIsDuplicating] = useState(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 ( { + const snap = useTableEditorStateSnapshot() const { meta, ui } = useStore() const projectRef = ui.selectedProject?.ref const [sidePanelKey, setSidePanelKey] = useState<'row' | 'column' | 'table'>() const [isDeleting, setIsDeleting] = useState(false) const [isDuplicating, setIsDuplicating] = useState(false) - const [selectedSchema, setSelectedSchema] = useState('public') const [selectedTableToEdit, setSelectedTableToEdit] = useState() const [selectedTableToDelete, setSelectedTableToDelete] = useState() @@ -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 ( - + {/* On this page it'll only handle tables */} { onSelectConfirm={onConfirmDeleteTable} /> { PageContext.project = project if (meta) PageState.meta = meta - useEffect(() => { - if (project?.ref) meta.schemas.load() - }, [project?.ref]) - return (
diff --git a/studio/pages/project/[ref]/sql/[id].tsx b/studio/pages/project/[ref]/sql/[id].tsx index 538f48a21ca..2f052c32ae9 100644 --- a/studio/pages/project/[ref]/sql/[id].tsx +++ b/studio/pages/project/[ref]/sql/[id].tsx @@ -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 } diff --git a/studio/state/table-editor-global.ts b/studio/state/table-editor-global.ts new file mode 100644 index 00000000000..f8d1ae7e04d --- /dev/null +++ b/studio/state/table-editor-global.ts @@ -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([]), + 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[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) +} diff --git a/studio/state/table-editor.ts b/studio/state/table-editor.ts deleted file mode 100644 index 99fe3309946..00000000000 --- a/studio/state/table-editor.ts +++ /dev/null @@ -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([]), - 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[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) -} diff --git a/studio/state/table-editor.tsx b/studio/state/table-editor.tsx new file mode 100644 index 00000000000..94135e0d035 --- /dev/null +++ b/studio/state/table-editor.tsx @@ -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 + +export const TableEditorStateContext = createContext(createTableEditorState()) + +export const TableEditorStateContextProvider = ({ children }: PropsWithChildren<{}>) => { + const state = useRef(createTableEditorState()).current + + return ( + {children} + ) +} + +export const useTableEditorStateSnapshot = (options?: Parameters[1]) => { + const state = useContext(TableEditorStateContext) + return useSnapshot(state, options) +} diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index f8e9dcdd1f1..b3afae54e0d 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -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 - schemas: IPostgresMetaInterface 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) diff --git a/studio/stores/pgmeta/SchemaStore.ts b/studio/stores/pgmeta/SchemaStore.ts deleted file mode 100644 index 2d9a0cc38bf..00000000000 --- a/studio/stores/pgmeta/SchemaStore.ts +++ /dev/null @@ -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 { - 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) - } -}