mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore: dedicated pooler entitlement (#43400)
Adds the new `dedicated_pooler` entitlement and it's respective entitlement checks in the following components: `ConnectTabContent`, `ConnectStepsSection`, `LogsSidebarMenuV2` and `ConnectionPooling` ## Testing ### ConnectTabContent and ConnectStepsSection - Head to `project/_?showConnect=true` with an Org on the Pro Plan or above - Assert that the `Dedicated Pooler` option is available. <img width="1007" height="641" alt="image" src="https://github.com/user-attachments/assets/b4891544-b84b-4745-9e25-4cbc7c76686c" /> ### IPv4SidePanel - Head to `/project/_/database/settings` with an Org on the Pro Plan and above and assert that the Dedicated Pooler is available <img width="889" height="562" alt="image" src="https://github.com/user-attachments/assets/3150cb3d-18e9-4b34-bc9b-2589d0a33c5f" /> ### Dedicated Pooler Logs - Head to `/project/_/logs/dedicated-pooler-logs` with an Org on the Pro Plan and assert that you are in the `dedicated-poolers-logs` page. - Head to `/project/_/logs/dedicated-pooler-logs` with an Org on the Free Plan and assert that you are redirected to `/logs/pooler-logs` ### Logs Sidebar - Head to `/project/jdvjfujajfyywbaaakje/logs/explorer` with an Org on the Free Plan - Assert that only the "Poolers" collection is shown - Head to `/project/jdvjfujajfyywbaaakje/logs/explorer` with an Org on the Pro Plan - Assert that the Dedicated and Shared Poolers collections are shown <img width="251" height="774" alt="image" src="https://github.com/user-attachments/assets/747bceee-d5e7-4a8f-911d-6b02cdb115eb" />
This commit is contained in:
1 parent
6a873c67fa
commit
4f7d4a3b4a
7 files changed
+36
-34
No files matched your search
@@ -6,7 +6,7 @@ import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query
|
||||
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
|
||||
import { useSupavisorConfigurationQuery } from 'data/database/supavisor-configuration-query'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import { useTrack } from 'lib/telemetry/track'
|
||||
import { cn, CopyCallbackContext } from 'ui'
|
||||
@@ -34,8 +34,7 @@ export const ConnectTabContent = forwardRef<HTMLDivElement, ConnectContentTabPro
|
||||
({ projectKeys, filePath, connectionTab, selectedFrameworkOrTool, ...props }, ref) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const track = useTrack()
|
||||
const { data: selectedOrg } = useSelectedOrganizationQuery()
|
||||
const allowPgBouncerSelection = useMemo(() => selectedOrg?.plan.id !== 'free', [selectedOrg])
|
||||
const { hasAccess: allowPgBouncerSelection } = useCheckEntitlements('dedicated_pooler')
|
||||
|
||||
const handleCopy = () => {
|
||||
const trackingProperties: {
|
||||
|
||||
@@ -8,12 +8,13 @@ import { useSupavisorConfigurationQuery } from 'data/database/supavisor-configur
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { DOCS_URL, IS_PLATFORM } from 'lib/constants'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import { BookOpen, ChevronDown, ExternalLink } from 'lucide-react'
|
||||
import { parseAsString, useQueryState } from 'nuqs'
|
||||
import { HTMLAttributes, ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { HTMLAttributes, ReactNode, useEffect, useState } from 'react'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import {
|
||||
Badge,
|
||||
@@ -67,6 +68,12 @@ export const DatabaseConnectionString = () => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { data: org } = useSelectedOrganizationQuery()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const {
|
||||
hasAccess: hasDedicatedPooler,
|
||||
isLoading: isLoadingEntitlement,
|
||||
isSuccess: isSuccessEntitlement,
|
||||
} = useCheckEntitlements('dedicated_pooler')
|
||||
const sharedPoolerPreferred = !hasDedicatedPooler
|
||||
|
||||
// URL state management
|
||||
const [queryType, setQueryType] = useQueryState('type', parseAsString.withDefault('uri'))
|
||||
@@ -76,10 +83,6 @@ export const DatabaseConnectionString = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<DatabaseConnectionType>('uri')
|
||||
const [selectedMethod, setSelectedMethod] = useState<ConnectionStringMethod>('direct')
|
||||
|
||||
const sharedPoolerPreferred = useMemo(() => {
|
||||
return org?.plan?.id === 'free'
|
||||
}, [org])
|
||||
|
||||
// Sync URL state with component state on mount and when URL changes
|
||||
useEffect(() => {
|
||||
const validTypes = DATABASE_CONNECTION_TYPES.map((t) => t.id)
|
||||
@@ -177,9 +180,9 @@ export const DatabaseConnectionString = () => {
|
||||
: isSuccessSupavisorConfig
|
||||
|
||||
const error = poolerError || readReplicasError
|
||||
const isLoading = isLoadingPoolerConfig || isLoadingReadReplicas
|
||||
const isLoading = isLoadingPoolerConfig || isLoadingReadReplicas || isLoadingEntitlement
|
||||
const isError = isErrorPoolerConfig || isErrorReadReplicas
|
||||
const isSuccess = isSuccessPoolerConfig && isSuccessReadReplicas
|
||||
const isSuccess = isSuccessPoolerConfig && isSuccessReadReplicas && isSuccessEntitlement
|
||||
|
||||
const sharedPoolerConfig = supavisorConfig?.find((x) => x.identifier === state.selectedDatabaseId)
|
||||
const poolingConfiguration = sharedPoolerPreferred ? sharedPoolerConfig : pgbouncerConfig
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query
|
||||
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
|
||||
import { useSupavisorConfigurationQuery } from 'data/database/supavisor-configuration-query'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useMemo, useRef } from 'react'
|
||||
@@ -52,8 +52,7 @@ function resolveContentPath(template: string, state: ConnectState): string {
|
||||
*/
|
||||
function useConnectionStringPooler(): ConnectionStringPooler {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { data: selectedOrg } = useSelectedOrganizationQuery()
|
||||
const allowPgBouncerSelection = useMemo(() => selectedOrg?.plan.id !== 'free', [selectedOrg])
|
||||
const { hasAccess: allowPgBouncerSelection } = useCheckEntitlements('dedicated_pooler')
|
||||
|
||||
const { data: settings } = useProjectSettingsV2Query({ projectRef })
|
||||
const { data: pgbouncerConfig } = usePgbouncerConfigQuery({ projectRef })
|
||||
|
||||
+4
-7
@@ -1,7 +1,7 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { capitalize } from 'lodash'
|
||||
import { Fragment, useEffect, useMemo } from 'react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import z from 'zod'
|
||||
@@ -18,8 +18,8 @@ import { useMaxConnectionsQuery } from 'data/database/max-connections-query'
|
||||
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
|
||||
import { usePgbouncerConfigurationUpdateMutation } from 'data/database/pgbouncer-config-update-mutation'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { DOCS_URL } from 'lib/constants'
|
||||
import {
|
||||
@@ -62,8 +62,6 @@ const PoolingConfigurationFormSchema = z.object({
|
||||
export const ConnectionPooling = () => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { data: org } = useSelectedOrganizationQuery()
|
||||
|
||||
const { can: canUpdateConnectionPoolingConfiguration } = useAsyncCheckPermissions(
|
||||
PermissionAction.UPDATE,
|
||||
'projects',
|
||||
@@ -78,9 +76,8 @@ export const ConnectionPooling = () => {
|
||||
isSuccess: isSuccessPgbouncerConfig,
|
||||
} = usePgbouncerConfigQuery({ projectRef })
|
||||
|
||||
const disablePoolModeSelection = useMemo(() => {
|
||||
return org?.plan?.id === 'free'
|
||||
}, [org])
|
||||
const { hasAccess: hasDedicatedPooler } = useCheckEntitlements('dedicated_pooler')
|
||||
const disablePoolModeSelection = !hasDedicatedPooler
|
||||
|
||||
const { data: maxConnData } = useMaxConnectionsQuery({
|
||||
projectRef: project?.ref,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { LogsSidebarItem } from 'components/interfaces/Settings/Logs/SidebarV2/S
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { useContentQuery } from 'data/content/content-query'
|
||||
import { useReplicationSourcesQuery } from 'data/replication/sources-query'
|
||||
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { ChevronRight, CircleHelpIcon, Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
@@ -116,8 +116,7 @@ export function LogsSidebarMenuV2() {
|
||||
// [Jordi] We only want to show ETL logs if the user has the feature enabled AND they're using the feature aka they've created a source.
|
||||
const showETLLogs = enablePgReplicate && (etlData?.sources?.length ?? 0) > 0 && !isETLLoading
|
||||
|
||||
const { plan: orgPlan } = useCurrentOrgPlan()
|
||||
const isFreePlan = orgPlan?.id === 'free'
|
||||
const { hasAccess: hasDedicatedPooler } = useCheckEntitlements('dedicated_pooler')
|
||||
|
||||
const { data: savedQueriesRes, isPending: savedQueriesLoading } = useContentQuery({
|
||||
projectRef: ref,
|
||||
@@ -153,13 +152,13 @@ export function LogsSidebarMenuV2() {
|
||||
},
|
||||
IS_PLATFORM
|
||||
? {
|
||||
name: isFreePlan ? 'Pooler' : 'Shared Pooler',
|
||||
name: hasDedicatedPooler ? 'Shared Pooler' : 'Pooler',
|
||||
key: 'pooler-logs',
|
||||
url: `/project/${ref}/logs/pooler-logs`,
|
||||
items: [],
|
||||
}
|
||||
: null,
|
||||
!isFreePlan && IS_PLATFORM
|
||||
hasDedicatedPooler && IS_PLATFORM
|
||||
? {
|
||||
name: 'Dedicated Pooler',
|
||||
key: 'dedicated-pooler-logs',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { LogsTableName } from 'components/interfaces/Settings/Logs/Logs.constant
|
||||
import { LogsPreviewer } from 'components/interfaces/Settings/Logs/LogsPreviewer'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import LogsLayout from 'components/layouts/LogsLayout/LogsLayout'
|
||||
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
@@ -12,18 +12,18 @@ import { LogoLoader } from 'ui'
|
||||
export const LogPage: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const { plan: orgPlan, isLoading: isOrgPlanLoading } = useCurrentOrgPlan()
|
||||
const isFreePlan = !isOrgPlanLoading && orgPlan?.id === 'free'
|
||||
const { hasAccess: hasDedicatedPooler, isLoading: isLoadingEntitlement } =
|
||||
useCheckEntitlements('dedicated_pooler')
|
||||
|
||||
useEffect(() => {
|
||||
// Redirect to pooler logs if user is on free plan
|
||||
if (!isOrgPlanLoading && isFreePlan) {
|
||||
// Redirect to pooler logs if org is not entitled to dedicated pooler
|
||||
if (!isLoadingEntitlement && !hasDedicatedPooler) {
|
||||
router.push(`/project/${ref}/logs/pooler-logs`)
|
||||
}
|
||||
}, [isFreePlan, isOrgPlanLoading, ref, router])
|
||||
}, [hasDedicatedPooler, isLoadingEntitlement, ref, router])
|
||||
|
||||
// Prevent showing logs while checking plan or loading config
|
||||
if (isOrgPlanLoading || isFreePlan) {
|
||||
// Prevent showing logs while checking entitlement
|
||||
if (isLoadingEntitlement || !hasDedicatedPooler) {
|
||||
return <LogoLoader />
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -4645,7 +4645,6 @@ export interface components {
|
||||
}
|
||||
AccountRequestDetailsDto: {
|
||||
email: string
|
||||
email_matches: boolean
|
||||
expires_at: string
|
||||
id: string
|
||||
name?: string
|
||||
@@ -7174,6 +7173,7 @@ export interface components {
|
||||
| 'instances.read_replicas'
|
||||
| 'instances.disk_modifications'
|
||||
| 'instances.high_availability'
|
||||
| 'instances.orioledb'
|
||||
| 'replication.etl'
|
||||
| 'storage.max_file_size'
|
||||
| 'storage.max_file_size.configurable'
|
||||
@@ -7219,8 +7219,10 @@ export interface components {
|
||||
| 'security.member_roles'
|
||||
| 'project_pausing'
|
||||
| 'project_cloning'
|
||||
| 'project_restore_after_expiry'
|
||||
| 'assistant.advance_model'
|
||||
| 'integrations.github_connections'
|
||||
| 'dedicated_pooler'
|
||||
/** @enum {string} */
|
||||
type: 'boolean' | 'numeric' | 'set'
|
||||
}
|
||||
@@ -17208,6 +17210,7 @@ export interface operations {
|
||||
| 'instances.read_replicas'
|
||||
| 'instances.disk_modifications'
|
||||
| 'instances.high_availability'
|
||||
| 'instances.orioledb'
|
||||
| 'replication.etl'
|
||||
| 'storage.max_file_size'
|
||||
| 'storage.max_file_size.configurable'
|
||||
@@ -17253,8 +17256,10 @@ export interface operations {
|
||||
| 'security.member_roles'
|
||||
| 'project_pausing'
|
||||
| 'project_cloning'
|
||||
| 'project_restore_after_expiry'
|
||||
| 'assistant.advance_model'
|
||||
| 'integrations.github_connections'
|
||||
| 'dedicated_pooler'
|
||||
}
|
||||
header?: never
|
||||
path?: never
|
||||
|
||||
Reference in new issue
Block a user