diff --git a/apps/docs/content/guides/database/extensions/postgis.mdx b/apps/docs/content/guides/database/extensions/postgis.mdx index c5588a1b93fac..c338c1ac912b4 100644 --- a/apps/docs/content/guides/database/extensions/postgis.mdx +++ b/apps/docs/content/guides/database/extensions/postgis.mdx @@ -436,12 +436,6 @@ val data = supabase.postgrest.rpc( ## Troubleshooting - - -The [official PostGIS documentation](https://postgis.net/documentation/tips/tip-move-postgis-schema/) for relocating the schema will cause issues for Supabase projects. These issues might not be apparent immediately but will eventually surface. To relocate your schema, use the following steps instead. - - - As of PostGIS 2.3 or newer, the PostGIS extension is no longer relocatable from one schema to another. If you need to move it from one schema to another for any reason (e.g. from the public schema to the extensions schema for security reasons), you would normally run a ALTER EXTENSION to relocate the schema. However, you will now to do the following steps: 1. Backup your Database to prevent data loss - You can do this through the [CLI](https://supabase.com/docs/reference/cli/supabase-db-dump) or Postgres backup tools such as [pg_dumpall](https://www.postgresql.org/docs/current/backup-dump.html#BACKUP-DUMP-ALL) @@ -452,6 +446,28 @@ As of PostGIS 2.3 or newer, the PostGIS extension is no longer relocatable from 4. Restore dropped data via the Backup if necessary from step 1 with your tool of choice. +Alternatively, you can contact the [Supabase Support Team](https://supabase.com/dashboard/support/new) and ask them to run the following SQL on your instance: + +```sql +BEGIN; + UPDATE pg_extension + SET extrelocatable = true + WHERE extname = 'postgis'; + + ALTER EXTENSION postgis + SET SCHEMA extensions; + + ALTER EXTENSION postgis + UPDATE TO "next"; + + ALTER EXTENSION postgis UPDATE; + + UPDATE pg_extension + SET extrelocatable = false + WHERE extname = 'postgis'; +COMMIT; +``` + ## Resources - [Official PostGIS documentation](https://postgis.net/documentation/) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx index 04386674eeb7f..5e693553554be 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link' import { useEffect, useState } from 'react' import { toast } from 'sonner' @@ -6,24 +7,22 @@ import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { useDeleteDestinationMutation } from 'data/replication/delete-destination-mutation' import { useReplicationPipelineStatusQuery } from 'data/replication/pipeline-status-query' -import { ReplicationPipelinesData } from 'data/replication/pipelines-query' +import { Pipeline } from 'data/replication/pipelines-query' import { useStopPipelineMutation } from 'data/replication/stop-pipeline-mutation' import { PipelineStatusRequestStatus, usePipelineRequestStatus, } from 'state/replication-pipeline-request-status' import { ResponseError } from 'types' +import { Button } from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import DeleteDestination from './DeleteDestination' import DestinationPanel from './DestinationPanel' import { getStatusName, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' import { PipelineStatus, PipelineStatusName } from './PipelineStatus' +import { STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants' import { RowMenu } from './RowMenu' -export type Pipeline = ReplicationPipelinesData['pipelines'][0] - -const refreshFrequencyMs: number = 2000 - interface DestinationRowProps { sourceId: number | undefined destinationId: number @@ -34,7 +33,6 @@ interface DestinationRowProps { isLoading: boolean isError: boolean isSuccess: boolean - onSelectPipeline?: (pipelineId: number, destinationName: string) => void } export const DestinationRow = ({ @@ -47,7 +45,6 @@ export const DestinationRow = ({ isLoading: isPipelineLoading, isError: isPipelineError, isSuccess: isPipelineSuccess, - onSelectPipeline, }: DestinationRowProps) => { const { ref: projectRef } = useParams() const [showDeleteDestinationForm, setShowDeleteDestinationForm] = useState(false) @@ -64,7 +61,7 @@ export const DestinationRow = ({ projectRef, pipelineId: pipeline?.id, }, - { refetchInterval: refreshFrequencyMs } + { refetchInterval: STATUS_REFRESH_FREQUENCY_MS } ) const { getRequestStatus, updatePipelineStatus } = usePipelineRequestStatus() const requestStatus = pipeline?.id @@ -107,12 +104,7 @@ export const DestinationRow = ({ )} {isPipelineSuccess && ( - { - if (pipeline) onSelectPipeline?.(pipeline.id, destinationName) - }} - > + {isPipelineLoading ? : destinationName} {isPipelineLoading ? : type} @@ -138,6 +130,11 @@ export const DestinationRow = ({
+ void -} - -export const Destinations = ({ onSelectPipeline = noop }: DestinationsProps) => { +export const Destinations = () => { const [showNewDestinationPanel, setShowNewDestinationPanel] = useState(false) const [filterString, setFilterString] = useState('') const { ref: projectRef } = useParams() @@ -122,7 +117,6 @@ export const Destinations = ({ onSelectPipeline = noop }: DestinationsProps) => isLoading={isPipelinesLoading} isError={isPipelinesError} isSuccess={isPipelinesSuccess} - onSelectPipeline={onSelectPipeline} /> ) })} diff --git a/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts b/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts new file mode 100644 index 0000000000000..273adac9f5bf6 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts @@ -0,0 +1 @@ +export const STATUS_REFRESH_FREQUENCY_MS: number = 5000 diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx index 24d41862f06be..29ccd56c33392 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx @@ -20,23 +20,15 @@ import { Badge, Button, cn, copyToClipboard, Input_Shadcn_ } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import { getStatusName, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' import { PipelineStatus } from './PipelineStatus' +import { STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants' import { TableState } from './ReplicationPipelineStatus.types' import { getDisabledStateConfig, getStatusConfig } from './ReplicationPipelineStatus.utils' -interface ReplicationPipelineStatusProps { - pipelineId: number - destinationName?: string - onSelectBack: () => void -} - -export const ReplicationPipelineStatus = ({ - pipelineId, - destinationName, - onSelectBack, -}: ReplicationPipelineStatusProps) => { - const { ref: projectRef } = useParams() +export const ReplicationPipelineStatus = () => { + const { ref: projectRef, pipelineId: _pipelineId } = useParams() const [filterString, setFilterString] = useState('') + const pipelineId = Number(_pipelineId) const { getRequestStatus, updatePipelineStatus, setRequestStatus } = usePipelineRequestStatus() const requestStatus = getRequestStatus(pipelineId) @@ -60,7 +52,7 @@ export const ReplicationPipelineStatus = ({ { projectRef, pipelineId }, { enabled: !!pipelineId, - refetchInterval: 2000, // Poll every 2 seconds + refetchInterval: STATUS_REFRESH_FREQUENCY_MS, } ) @@ -73,13 +65,14 @@ export const ReplicationPipelineStatus = ({ { projectRef, pipelineId }, { enabled: !!pipelineId, - refetchInterval: 2000, // Poll every 2 seconds + refetchInterval: STATUS_REFRESH_FREQUENCY_MS, } ) const { mutateAsync: startPipeline, isLoading: isStartingPipeline } = useStartPipelineMutation() const { mutateAsync: stopPipeline, isLoading: isStoppingPipeline } = useStopPipelineMutation() + const destinationName = pipeline?.destination_name const statusName = getStatusName(pipelineStatusData?.status) const config = getDisabledStateConfig({ requestStatus, statusName }) @@ -138,36 +131,13 @@ export const ReplicationPipelineStatus = ({ updatePipelineStatus(pipelineId, statusName) }, [pipelineId, statusName, updatePipelineStatus]) - if (isPipelineError) { - return ( -
-
-
-
-
- -
- ) - } - return (
- {/* Header with back button and filters */}
-

{destinationName || 'Pipeline'}

@@ -192,13 +162,14 @@ export const ReplicationPipelineStatus = ({ className="pl-7 h-[26px] text-xs" placeholder="Search for tables" value={filterString} + disabled={isPipelineError} onChange={(e) => setFilterString(e.target.value)} />
diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/PublishAppSidePanel/Scopes.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/PublishAppSidePanel/Scopes.tsx index 1dc9e366a5d36..7466baa694db0 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/PublishAppSidePanel/Scopes.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/PublishAppSidePanel/Scopes.tsx @@ -11,6 +11,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from 'ui' +import { PERMISSIONS_DESCRIPTIONS } from '../OAuthApps.constants' const ScopeDropdownCheckboxItem = ({ children, @@ -66,12 +67,12 @@ const Scope = ({ return (
{title} - {description} + {description}
@@ -109,7 +110,7 @@ export const ScopesPanel = ({
diff --git a/apps/studio/data/replication/pipelines-query.ts b/apps/studio/data/replication/pipelines-query.ts index 6e09e1cc409a9..82d0117f080f0 100644 --- a/apps/studio/data/replication/pipelines-query.ts +++ b/apps/studio/data/replication/pipelines-query.ts @@ -24,6 +24,7 @@ async function fetchReplicationPipelines( } export type ReplicationPipelinesData = Awaited> +export type Pipeline = ReplicationPipelinesData['pipelines'][0] export const useReplicationPipelinesQuery = ( { projectRef }: ReplicationPipelinesParams, diff --git a/apps/studio/pages/authorize.tsx b/apps/studio/pages/authorize.tsx index 70ebd6c5e4750..8e56592aadbf3 100644 --- a/apps/studio/pages/authorize.tsx +++ b/apps/studio/pages/authorize.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' -import AuthorizeRequesterDetails from 'components/interfaces/Organization/OAuthApps/AuthorizeRequesterDetails' +import { AuthorizeRequesterDetails } from 'components/interfaces/Organization/OAuthApps/AuthorizeRequesterDetails' import APIAuthorizationLayout from 'components/layouts/APIAuthorizationLayout' import { FormPanel } from 'components/ui/Forms/FormPanel' import ShimmeringLoader from 'components/ui/ShimmeringLoader' diff --git a/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx b/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx new file mode 100644 index 0000000000000..2e45d0d18789b --- /dev/null +++ b/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx @@ -0,0 +1,50 @@ +import { useRouter } from 'next/router' +import { useContext, useEffect } from 'react' + +import { FeatureFlagContext, useParams } from 'common' +import { ReplicationPipelineStatus } from 'components/interfaces/Database/Replication/ReplicationPipelineStatus' +import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' +import DefaultLayout from 'components/layouts/DefaultLayout' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' +import { FormHeader } from 'components/ui/Forms/FormHeader' +import { useFlag } from 'hooks/ui/useFlag' +import { PipelineRequestStatusProvider } from 'state/replication-pipeline-request-status' +import type { NextPageWithLayout } from 'types' + +const DatabaseReplicationPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = useParams() + const { hasLoaded } = useContext(FeatureFlagContext) + const enablePgReplicate = useFlag('enablePgReplicate') + + useEffect(() => { + if (hasLoaded && !enablePgReplicate) { + router.replace(`/project/${ref}/database/replication}`) + } + }, [router, hasLoaded, ref, enablePgReplicate]) + + return ( + <> + {enablePgReplicate && ( + + + +
+ + +
+
+
+
+ )} + + ) +} + +DatabaseReplicationPage.getLayout = (page) => ( + + {page} + +) + +export default DatabaseReplicationPage diff --git a/apps/studio/pages/project/[ref]/database/replication/index.tsx b/apps/studio/pages/project/[ref]/database/replication/index.tsx index bc1a2a5445ac4..9fb8b9a2287df 100644 --- a/apps/studio/pages/project/[ref]/database/replication/index.tsx +++ b/apps/studio/pages/project/[ref]/database/replication/index.tsx @@ -1,8 +1,5 @@ -import { useState } from 'react' - import { ReplicationComingSoon } from 'components/interfaces/Database/Replication/ComingSoon' import { Destinations } from 'components/interfaces/Database/Replication/Destinations' -import { ReplicationPipelineStatus } from 'components/interfaces/Database/Replication/ReplicationPipelineStatus' import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' @@ -13,22 +10,6 @@ import type { NextPageWithLayout } from 'types' const DatabaseReplicationPage: NextPageWithLayout = () => { const enablePgReplicate = useFlag('enablePgReplicate') - const [selectedPipelineId, setSelectedPipelineId] = useState() - const [selectedDestinationName, setSelectedDestinationName] = useState() - - // [Joshen] Ideally selecting a pipeline should be a route on its own with pipelineId as the param - // e.g /project/ref/database/replication/[pipelineId] - // Can destinationName be derived from pipeline ID or something? - - const handleSelectPipeline = (pipelineId: number, destinationName: string) => { - setSelectedPipelineId(pipelineId) - setSelectedDestinationName(destinationName) - } - - const handleSelectBack = () => { - setSelectedPipelineId(undefined) - setSelectedDestinationName(undefined) - } return ( <> @@ -38,15 +19,7 @@ const DatabaseReplicationPage: NextPageWithLayout = () => {
- {selectedPipelineId === undefined ? ( - - ) : ( - - )} +