From d05bbb669f41a7111eedcc67a2ae28b0f0a2f97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 14 Apr 2025 20:37:19 +0800 Subject: [PATCH] perf: use subscription plan from org response (#34993) --- .../Auth/AdvancedAuthSettingsForm.tsx | 11 +- .../interfaces/Auth/Hooks/AddHookDropdown.tsx | 7 +- .../MfaAuthSettingsForm.tsx | 19 +- .../SessionsAuthSettingsForm.tsx | 7 +- .../Billing/NoProjectsOnPaidOrgInfo.tsx | 11 +- .../interfaces/Connect/ConnectTabContent.tsx | 4 +- .../Connect/DatabaseConnectionString.tsx | 6 +- .../RestoreToNewProject/BackupsList.tsx | 4 +- .../CreateNewProjectDialog.tsx | 4 +- .../DiskManagement/DiskManagementForm.tsx | 10 +- .../DiskManagementReviewAndSubmitDialog.tsx | 11 +- .../fields/ComputeSizeField.tsx | 18 +- .../DiskManagement/fields/DiskSizeField.tsx | 17 +- .../ui/SpendCapDisabledSection.tsx | 15 +- .../BillingSettings/BillingSettings.tsx | 8 +- .../BillingSettings/Restriction.tsx | 14 +- .../Subscription/EnterpriseCard.tsx | 4 +- .../Subscription/Subscription.tsx | 3 - .../Organization/Documents/SOC2.tsx | 51 +-- .../Documents/SecurityQuestionnaire.tsx | 68 ++- .../TeamSettings/InviteMemberButton.tsx | 31 +- .../interfaces/Organization/Usage/Usage.tsx | 1 - .../interfaces/Realtime/RealtimeSettings.tsx | 11 +- .../interfaces/Settings/Addons/Addons.tsx | 1 - .../Settings/Addons/CustomDomainSidePanel.tsx | 6 +- .../Settings/Addons/IPv4SidePanel.tsx | 14 +- .../ConnectionPooling/ConnectionPooling.tsx | 7 +- .../Database/DatabaseReadOnlyAlert.tsx | 6 +- .../Database/DiskSizeConfiguration.tsx | 19 +- .../CustomDomainConfig/CustomDomainConfig.tsx | 4 +- .../Infrastructure/PauseProjectButton.tsx | 4 +- .../ProjectUpgradeAlert.tsx | 4 +- .../Infrastructure/InfrastructureActivity.tsx | 10 +- .../DeployNewReplicaPanel.tsx | 11 +- .../Settings/Logs/LogsPreviewer.tsx | 8 +- .../interfaces/Support/SupportFormV2.tsx | 28 +- .../AppLayout/OrganizationDropdown.tsx | 6 +- .../LayoutHeader/LayoutHeader.tsx | 7 +- .../PausedState/ProjectPausedState.tsx | 4 +- .../StorageSettings/StorageSettings.tsx | 6 +- apps/studio/components/ui/UpgradeToPro.tsx | 4 +- .../org-subscription-update-mutation.ts | 3 + apps/studio/hooks/misc/useCurrentOrgPlan.ts | 16 +- apps/studio/hooks/misc/useUpgradePrompt.tsx | 4 +- .../pages/api/platform/organizations/index.ts | 4 + apps/studio/pages/new/[slug].tsx | 13 +- apps/studio/pages/new/v2/[slug].tsx | 15 +- .../project/[ref]/database/backups/pitr.tsx | 5 +- .../backups/restore-to-new-project.tsx | 9 +- .../project/[ref]/logs/explorer/index.tsx | 8 +- .../project/[ref]/reports/api-overview.tsx | 4 +- .../pages/project/[ref]/reports/storage.tsx | 4 +- .../pages/projects/LogsPreviewer.test.tsx | 390 ------------------ apps/studio/types/base.ts | 5 + packages/api-types/types/platform.d.ts | 14 +- 55 files changed, 193 insertions(+), 785 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx index dbbb01898b8..b0bc8294855 100644 --- a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx @@ -11,7 +11,6 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' @@ -57,16 +56,10 @@ export const AdvancedAuthSettingsForm = () => { isError, } = useAuthConfigQuery({ projectRef }) - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ - orgSlug: organization?.slug, - }) - const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() - const isTeamsEnterprisePlan = - isSuccessSubscription && subscription?.plan.id !== 'free' && subscription?.plan.id !== 'pro' - const promptTeamsEnterpriseUpgrade = - IS_PLATFORM && isSuccessSubscription && !isTeamsEnterprisePlan + const isTeamsEnterprisePlan = organization?.plan.id !== 'free' && organization?.plan.id !== 'pro' + const promptTeamsEnterpriseUpgrade = IS_PLATFORM && !isTeamsEnterprisePlan const requestDurationForm = useForm({ resolver: zodResolver( diff --git a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx index 12e55e0c8b2..31911d72da5 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx @@ -4,7 +4,6 @@ import { ChevronDown } from 'lucide-react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' @@ -32,10 +31,6 @@ export const AddHookDropdown = ({ const { ref: projectRef } = useParams() const organization = useSelectedOrganization() - const { data: subscription } = useOrgSubscriptionQuery( - { orgSlug: organization?.slug }, - { enabled: IS_PLATFORM } - ) const { data: authConfig } = useAuthConfigQuery({ projectRef }) const canUpdateAuthHook = useCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') @@ -54,7 +49,7 @@ export const AddHookDropdown = ({ const enterpriseHookOptions = hooks.filter((h) => !isValidHook(h) && h.enterprise) const isTeamsOrEnterprisePlan = - subscription?.plan.id === 'team' || subscription?.plan.id === 'enterprise' + organization?.plan.id === 'team' || organization?.plan.id === 'enterprise' if (!canUpdateAuthHook) { return ( diff --git a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx index 8ad78c1d55e..d93dbdb5dc8 100644 --- a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx @@ -11,7 +11,6 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' @@ -81,12 +80,7 @@ const phoneSchema = object({ const MfaAuthSettingsForm = () => { const { ref: projectRef } = useParams() - const { - data: authConfig, - error: authConfigError, - isLoading, - isError, - } = useAuthConfigQuery({ projectRef }) + const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() // Separate loading states for each form @@ -97,18 +91,9 @@ const MfaAuthSettingsForm = () => { const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const organization = useSelectedOrganization() - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ - orgSlug: organization?.slug, - }) - - const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free' + const isProPlanAndUp = organization?.plan?.id !== 'free' const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp - const projectAddons = subscription?.project_addons.find((addon) => addon.ref === projectRef) - const hasPurchasedAuthMFAAddOn = projectAddons?.addons.some( - (addon) => addon.type === 'auth_mfa_phone' - ) - // For now, we support Twilio and Vonage. Twilio Verify is not supported and the remaining providers are community maintained. const sendSMSHookIsEnabled = authConfig?.HOOK_SEND_SMS_URI !== null && authConfig?.HOOK_SEND_SMS_ENABLED === true diff --git a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx index 7b13041be85..55b4b7a9dac 100644 --- a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx @@ -11,7 +11,6 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' @@ -73,11 +72,7 @@ const SessionsAuthSettingsForm = () => { const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const organization = useSelectedOrganization() - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ - orgSlug: organization?.slug, - }) - - const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free' + const isProPlanAndUp = organization?.plan?.id !== 'free' const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp const refreshTokenForm = useForm>({ diff --git a/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx b/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx index e8f0c26b134..4a71ccc0e3b 100644 --- a/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx +++ b/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx @@ -1,5 +1,4 @@ import { useProjectsQuery } from 'data/projects/projects-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import type { Organization } from 'types' import { Admonition } from 'ui-patterns' import Link from 'next/link' @@ -13,20 +12,18 @@ export const NoProjectsOnPaidOrgInfo = ({ organization }: NoProjectsOnPaidOrgInf const projectCount = allProjects?.filter((project) => project.organization_id === organization?.id).length ?? 0 - const { data: orgSubscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - if ( projectCount > 0 || - orgSubscription?.plan === undefined || - orgSubscription.plan.id === 'free' || - orgSubscription.plan.id === 'enterprise' + organization?.plan === undefined || + organization.plan.id === 'free' || + organization.plan.id === 'enterprise' ) return null return ( The monthly fees for the paid plan still apply. To cancel your subscription, head over to diff --git a/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx b/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx index e8a1c8bdd8d..1f5098a4275 100644 --- a/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx @@ -10,7 +10,6 @@ import { pluckObjectFields } from 'lib/helpers' import { cn } from 'ui' import type { projectKeys } from './Connect.types' import { getConnectionStrings as getConnectionStringsV2 } from './DatabaseSettings.utils' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { getAddons } from '../Billing/Subscription/Subscription.utils' @@ -31,8 +30,7 @@ const ConnectTabContent = forwardRef( ({ projectKeys, filePath, ...props }, ref) => { const { ref: projectRef } = useParams() const selectedOrg = useSelectedOrganization() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrg?.slug }) - const allowPgBouncerSelection = useMemo(() => subscription?.plan.id !== 'free', [subscription]) + const allowPgBouncerSelection = useMemo(() => selectedOrg?.plan.id !== 'free', [selectedOrg]) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: pgbouncerConfig } = usePgbouncerConfigQuery({ projectRef }) diff --git a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx index 0ffd746f09c..c555f92c8d6 100644 --- a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx @@ -9,7 +9,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query' import { useSupavisorConfigurationQuery } from 'data/database/supavisor-configuration-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' @@ -67,10 +66,9 @@ export const DatabaseConnectionString = () => { const [selectedTab, setSelectedTab] = useState('uri') - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) const sharedPoolerPreferred = useMemo(() => { - return subscription?.plan?.id === 'free' - }, [subscription]) + return org?.plan?.id === 'free' + }, [org]) const { data: pgbouncerConfig, diff --git a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/BackupsList.tsx b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/BackupsList.tsx index cc7495d3e53..b02999f6667 100644 --- a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/BackupsList.tsx +++ b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/BackupsList.tsx @@ -1,7 +1,6 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import Panel from 'components/ui/Panel' import { useCloneBackupsQuery } from 'data/projects/clone-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { Badge, Button } from 'ui' import { TimestampInfo } from 'ui-patterns' @@ -16,8 +15,7 @@ export const BackupsList = ({ onSelectRestore, disabled }: BackupsListProps) => const { project } = useProjectContext() const organization = useSelectedOrganization() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = organization?.plan?.id === 'free' const { data: cloneBackups } = useCloneBackupsQuery( { projectRef: project?.ref }, diff --git a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/CreateNewProjectDialog.tsx b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/CreateNewProjectDialog.tsx index 62fbadd2ac0..db1beed78f5 100644 --- a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/CreateNewProjectDialog.tsx +++ b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/CreateNewProjectDialog.tsx @@ -9,7 +9,6 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte import PasswordStrengthBar from 'components/ui/PasswordStrengthBar' import { useProjectCloneMutation } from 'data/projects/clone-mutation' import { useCloneBackupsQuery } from 'data/projects/clone-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { passwordStrength } from 'lib/helpers' import { generateStrongPassword } from 'lib/project' @@ -68,8 +67,7 @@ export const CreateNewProjectDialog = ({ }, }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = organization?.plan?.id === 'free' const { data: cloneBackups } = useCloneBackupsQuery( { projectRef: project?.ref }, diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 7da06fd5de7..959f0677de6 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -22,7 +22,6 @@ import { useUpdateDiskAutoscaleConfigMutation } from 'data/config/disk-autoscale import { useDiskUtilizationQuery } from 'data/config/disk-utilization-query' import { setProjectStatus } from 'data/projects/projects-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { AddonVariantId } from 'data/subscriptions/types' @@ -132,9 +131,7 @@ export function DiskManagementForm() { }, { enabled: project != null && !isFlyArchitecture } ) - const { data: subscription, isSuccess: isSubscriptionSuccess } = useOrgSubscriptionQuery({ - orgSlug: org?.slug, - }) + const { data: diskAutoscaleConfig, isSuccess: isDiskAutoscaleConfigSuccess } = useDiskAutoscaleCustomConfigQuery( { projectRef }, @@ -178,13 +175,12 @@ export function DiskManagementForm() { isDiskAttributesSuccess && isDiskUtilizationSuccess && isReadReplicasSuccess && - isSubscriptionSuccess && isDiskAutoscaleConfigSuccess && isCooldownSuccess const isRequestingChanges = data?.requested_modification !== undefined const readReplicas = (databases ?? []).filter((db) => db.identifier !== projectRef) - const isPlanUpgradeRequired = subscription?.plan.id === 'free' + const isPlanUpgradeRequired = org?.plan.id === 'free' const { formState } = form const usedSize = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100 @@ -193,7 +189,7 @@ export function DiskManagementForm() { const disableIopsThroughputConfig = RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(form.watch('computeSize')) && - subscription?.plan.id !== 'free' + org?.plan.id !== 'free' const isBranch = project?.parent_project_ref !== undefined diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx index 856d13a7eb2..04cf1645802 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx @@ -5,7 +5,6 @@ import { UseFormReturn } from 'react-hook-form' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' @@ -161,16 +160,12 @@ export const DiskManagementReviewAndSubmitDialog = ({ /** * Queries * */ - const { - data: subscription, - // request deps in Form handle errors and loading - } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) const { data: addons, // request deps in Form handle errors and loading } = useProjectAddonsQuery({ projectRef: project?.ref }) - const planId = subscription?.plan.id ?? 'free' + const planId = org?.plan.id ?? 'free' const isDirty = !!Object.keys(form.formState.dirtyFields).length const replicaTooltipText = `Price change includes primary database and ${numReplicas} replica${numReplicas > 1 ? 's' : ''}` @@ -185,7 +180,7 @@ export const DiskManagementReviewAndSubmitDialog = ({ availableOptions, oldComputeSize: form.formState.defaultValues?.computeSize || 'ci_micro', newComputeSize: form.getValues('computeSize'), - plan: subscription?.plan.id ?? 'free', + plan: org?.plan.id ?? 'free', }) const diskSizePrice = calculateDiskSizePrice({ planId: planId, @@ -293,7 +288,7 @@ export const DiskManagementReviewAndSubmitDialog = ({ beforePrice={Number(computeSizePrice.oldPrice)} afterPrice={Number(computeSizePrice.newPrice)} upgradeIncluded={ - subscription?.plan.id !== 'free' && + org?.plan.id !== 'free' && project?.infra_compute_size === 'nano' && form.getValues('computeSize') === 'ci_micro' } diff --git a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx index 0663e4465a9..4f1cf7abdff 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx @@ -2,12 +2,10 @@ import { CpuIcon, Lock, Microchip } from 'lucide-react' import { useMemo } from 'react' import { UseFormReturn } from 'react-hook-form' -import { components } from 'api-types' import { useParams } from 'common' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { DocsButton } from 'components/ui/DocsButton' import { InlineLink } from 'components/ui/InlineLink' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils' @@ -26,11 +24,7 @@ import { import { ComputeBadge } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { DiskStorageSchemaType } from '../DiskManagement.schema' -import { - ComputeInstanceAddonVariantId, - ComputeInstanceSize, - InfraInstanceSize, -} from '../DiskManagement.types' +import { ComputeInstanceAddonVariantId, InfraInstanceSize } from '../DiskManagement.types' import { calculateComputeSizePrice, getAvailableComputeOptions, @@ -61,8 +55,6 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { const org = useSelectedOrganization() const { control, formState, setValue, trigger } = form - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) - const { project, isLoading: isProjectLoading } = useProjectContext() const { data: addons, @@ -90,11 +82,11 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { availableOptions: availableOptions, oldComputeSize: form.formState.defaultValues?.computeSize || 'ci_micro', newComputeSize: form.getValues('computeSize'), - plan: subscription?.plan.id ?? 'free', + plan: org?.plan.id ?? 'free', }) const showUpgradeBadge = showMicroUpgrade( - subscription?.plan.id ?? 'free', + org?.plan.id ?? 'free', project?.infra_compute_size ?? 'nano' ) @@ -179,14 +171,14 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { const lockedMicroDueToPITR = compute.identifier === 'ci_micro' && !!subscriptionPitr const lockedNanoDueToPlan = - subscription?.plan.id !== 'free' && + org?.plan.id !== 'free' && project?.infra_compute_size !== 'nano' && compute.identifier === 'ci_nano' const lockedOption = lockedNanoDueToPlan || lockedMicroDueToPITR const price = - subscription?.plan.id !== 'free' && + org?.plan.id !== 'free' && project?.infra_compute_size === 'nano' && compute.identifier === 'ci_nano' ? availableOptions.find( diff --git a/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx index 160445149b5..85c4bc6ea59 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx @@ -5,7 +5,6 @@ import { useParams } from 'common' import { DocsButton } from 'components/ui/DocsButton' import { useDiskAttributesQuery } from 'data/config/disk-attributes-query' import { useDiskUtilizationQuery } from 'data/config/disk-utilization-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import dayjs from 'dayjs' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' @@ -47,13 +46,7 @@ export function DiskSizeField({ { projectRef }, { enabled: project && project.cloud_provider !== 'FLY' } ) - const { - data: subscription, - error: subscriptionError, - isError: isSubscriptionError, - } = useOrgSubscriptionQuery({ - orgSlug: org?.slug, - }) + const { data: diskUtil, error: diskUtilError, @@ -65,8 +58,8 @@ export function DiskSizeField({ { enabled: project && project.cloud_provider !== 'FLY' } ) - const error = subscriptionError || diskUtilError || diskAttributesError - const isError = isSubscriptionError || isDiskUtilizationError || isDiskAttributesError + const error = diskUtilError || diskAttributesError + const isError = isDiskUtilizationError || isDiskAttributesError // coming up typically takes 5 minutes, and the request is cached for 5 mins // so doing less than 10 mins to account for both @@ -77,7 +70,7 @@ export function DiskSizeField({ const watchedStorageType = watch('storageType') const watchedTotalSize = watch('totalSize') - const planId = subscription?.plan.id ?? 'free' + const planId = org?.plan.id ?? 'free' const { includedDiskGB: includedDiskGBMeta } = PLAN_DETAILS?.[planId as keyof typeof PLAN_DETAILS] ?? {} @@ -159,7 +152,7 @@ export function DiskSizeField({ /> {includedDiskGB > 0 && - subscription?.plan.id && + org?.plan.id && `Your plan includes ${includedDiskGB} GB of disk size for ${watchedStorageType}.`}
diff --git a/apps/studio/components/interfaces/DiskManagement/ui/SpendCapDisabledSection.tsx b/apps/studio/components/interfaces/DiskManagement/ui/SpendCapDisabledSection.tsx index 35a56a77b87..812ff2a4f5b 100644 --- a/apps/studio/components/interfaces/DiskManagement/ui/SpendCapDisabledSection.tsx +++ b/apps/studio/components/interfaces/DiskManagement/ui/SpendCapDisabledSection.tsx @@ -1,7 +1,6 @@ import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { AlertDescription_Shadcn_ as AlertDescription, @@ -16,20 +15,8 @@ export function SpendCapDisabledSection() { const org = useSelectedOrganization() const project = useSelectedProject() - const { - data, - // no error/isError states handled here, as a parent component handles them - // we rely on this to check if the subscription has loaded - isSuccess, - } = useOrgSubscriptionQuery({ - orgSlug: org?.slug, - }) - const isSpendCapEnabled = - isSuccess && - data?.plan.id !== 'free' && - !data?.usage_billing_enabled && - project?.cloud_provider !== 'FLY' + org?.plan.id !== 'free' && !org?.usage_billing_enabled && project?.cloud_provider !== 'FLY' return ( diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx index 30fc4fe09a2..f306c24d7be 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx @@ -1,4 +1,3 @@ -import { useParams } from 'common' import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useOrgSubscriptionQuery } from '../../../../data/subscriptions/org-subscription-query' @@ -10,6 +9,7 @@ import CreditBalance from './CreditBalance' import PaymentMethods from './PaymentMethods/PaymentMethods' import Subscription from './Subscription/Subscription' import TaxID from './TaxID/TaxID' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' const BillingSettings = () => { const { @@ -18,8 +18,8 @@ const BillingSettings = () => { billingCredits: isBillingCreditsEnabledOnProfileLevel, } = useIsFeatureEnabled(['billing:account_data', 'billing:payment_methods', 'billing:credits']) - const { slug: orgSlug } = useParams() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug }) + const org = useSelectedOrganization() + const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) const isNotOrgWithPartnerBilling = !subscription?.billing_via_partner const billingAccountDataEnabled = @@ -41,7 +41,7 @@ const BillingSettings = () => { - {subscription?.plan.id !== 'free' && ( + {org?.plan.id !== 'free' && ( diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx index 917cab4c768..ad5206ed655 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx @@ -2,7 +2,6 @@ import dayjs from 'dayjs' import { ExternalLink } from 'lucide-react' import Link from 'next/link' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useOrgUsageQuery } from 'data/usage/org-usage-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui' @@ -12,9 +11,6 @@ import { PricingMetric } from 'data/analytics/org-daily-stats-query' export const Restriction = () => { const org = useSelectedOrganization() const { data: usage, isSuccess: isSuccessOrgUsage } = useOrgUsageQuery({ orgSlug: org?.slug }) - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ - orgSlug: org?.slug, - }) const hasExceededAnyLimits = Boolean( usage?.usages.find( @@ -27,13 +23,13 @@ export const Restriction = () => { ) // don't show any alerts until everything has been fetched - if (!isSuccessOrgUsage || !isSuccessSubscription || !org) { + if (!isSuccessOrgUsage || !org) { return null } let shownAlert: 'exceededLimits' | 'gracePeriod' | 'gracePeriodOver' | 'restricted' | null = null - if (subscription && hasExceededAnyLimits && !org?.restriction_status) { + if (hasExceededAnyLimits && !org?.restriction_status) { shownAlert = 'exceededLimits' } else if (org?.restriction_status === 'grace_period') { shownAlert = 'gracePeriod' @@ -59,7 +55,7 @@ export const Restriction = () => {

Your projects can become unresponsive or enter read-only mode.{' '} - {subscription.plan.id === 'free' + {org.plan.id === 'free' ? 'Please upgrade to the Pro Plan to ensure that your projects remain available.' : 'Please disable spend cap to ensure that your projects remain available.'}

@@ -67,10 +63,10 @@ export const Restriction = () => { + + ) : ( + )} - - {isError && } - - {isSuccess && ( -
- {currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? ( - - - - ) : ( - - )} -
- )} - +
)} { - const { slug } = useParams() + const organization = useSelectedOrganization() + const slug = organization?.slug const canReadSubscriptions = useCheckPermissions( PermissionAction.BILLING_READ, 'stripe.subscriptions' ) - const { - data: subscription, - error, - isLoading, - isError, - isSuccess, - } = useOrgSubscriptionQuery({ orgSlug: slug }, { enabled: canReadSubscriptions }) - const currentPlan = subscription?.plan + + const currentPlan = organization?.plan const fetchQuestionnaire = async (orgSlug: string) => { try { @@ -61,37 +53,25 @@ const SecurityQuestionnaire = () => { ) : ( <> - {isLoading && ( -
- - - -
- )} - - {isError && } - - {isSuccess && ( -
- {currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? ( - - - - ) : ( - - )} -
- )} +
+ {currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? ( + + + + ) : ( + + )} +
)} diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx index f7f0a9d7e51..c911fa36b9a 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx @@ -13,10 +13,7 @@ import { useOrganizationCreateInvitationMutation } from 'data/organization-membe import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query' import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { - useHasAccessToProjectLevelPermissions, - useOrgSubscriptionQuery, -} from 'data/subscriptions/org-subscription-query' +import { useHasAccessToProjectLevelPermissions } from 'data/subscriptions/org-subscription-query' import { doPermissionsCheck, useCheckPermissions, @@ -82,11 +79,8 @@ export const InviteMemberButton = () => { PermissionAction.BILLING_READ, 'stripe.subscriptions' ) - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery( - { orgSlug: slug }, - { enabled: canReadSubscriptions } - ) - const currentPlan = subscription?.plan + + const currentPlan = organization?.plan const hasAccessToProjectLevelPermissions = useHasAccessToProjectLevelPermissions(slug as string) const userMemberData = members?.find((m) => m.gotrue_id === profile?.gotrue_id) @@ -405,16 +399,15 @@ export const InviteMemberButton = () => { Learn more - {isSuccessSubscription && - (currentPlan?.id === 'free' || currentPlan?.id === 'pro') && ( - - )} + {(currentPlan?.id === 'free' || currentPlan?.id === 'pro') && ( + + )} } diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index 9729e8fed47..64066b64959 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -11,7 +11,6 @@ import { import AlertError from 'components/ui/AlertError' import DateRangePicker from 'components/ui/DateRangePicker' -import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectsQuery } from 'data/projects/projects-query' diff --git a/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx b/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx index db34a07eadf..8a4cc98cef7 100644 --- a/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx +++ b/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx @@ -17,7 +17,6 @@ import { REALTIME_DEFAULT_CONFIG, useRealtimeConfigurationQuery, } from 'data/realtime/realtime-config-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { @@ -30,10 +29,8 @@ import { FormField_Shadcn_, FormMessage_Shadcn_, Input_Shadcn_, - Switch, } from 'ui' import { Admonition } from 'ui-patterns' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const formId = 'realtime-configuration-form' @@ -50,10 +47,8 @@ export const RealtimeSettings = () => { const { data, error, isLoading, isSuccess, isError } = useRealtimeConfigurationQuery({ projectRef, }) - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ - orgSlug: organization?.slug, - }) - const isUsageBillingEnabled = subscription?.usage_billing_enabled + + const isUsageBillingEnabled = organization?.usage_billing_enabled const { mutate: updateRealtimeConfig, isLoading: isUpdatingConfig } = useRealtimeConfigurationUpdateMutation({ @@ -219,7 +214,7 @@ export const RealtimeSettings = () => { /> - {isSuccessSubscription && !isUsageBillingEnabled && ( + {!isUsageBillingEnabled && ( { const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrg?.slug }) - const computeSizeChangesDisabled = useFlag('disableComputeSizeChanges') const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') const hasHipaaAddon = subscriptionHasHipaaAddon(subscription) diff --git a/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx index c3c66febde3..ff7f31df486 100644 --- a/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation' import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' @@ -24,7 +23,7 @@ import { SidePanel, cn, } from 'ui' -import { ExternalLink, AlertCircle, AlertTriangle } from 'lucide-react' +import { ExternalLink, AlertCircle } from 'lucide-react' const CustomDomainSidePanel = () => { const { ref: projectRef } = useParams() @@ -42,7 +41,6 @@ const CustomDomainSidePanel = () => { const visible = panel === 'customDomain' const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({ onSuccess: () => { toast.success(`Successfully enabled custom domain`) @@ -69,7 +67,7 @@ const CustomDomainSidePanel = () => { const availableOptions = (addons?.available_addons ?? []).find((addon) => addon.type === 'custom_domain')?.variants ?? [] - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = organization?.plan?.id === 'free' const hasChanges = selectedOption !== (subscriptionCDOption?.variant.identifier ?? 'cd_none') const selectedCustomDomain = availableOptions.find( (option) => option.identifier === selectedOption diff --git a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx index 17be611f5d2..8a23de62477 100644 --- a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx @@ -5,7 +5,6 @@ import { toast } from 'sonner' import { useParams } from 'common' import { InlineLink } from 'components/ui/InlineLink' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation' import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' @@ -30,7 +29,6 @@ const IPv4SidePanel = () => { const visible = panel === 'ipv4' const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({ onSuccess: () => { toast.success(`Successfully enabled IPv4`) @@ -57,7 +55,7 @@ const IPv4SidePanel = () => { const availableOptions = (addons?.available_addons ?? []).find((addon) => addon.type === 'ipv4')?.variants ?? [] - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = organization?.plan?.id === 'free' const hasChanges = selectedOption !== (subscriptionIpV4Option?.variant.identifier ?? 'ipv4_none') const selectedIPv4 = availableOptions.find((option) => option.identifier === selectedOption) const isPgBouncerEnabled = !isFreePlan @@ -219,12 +217,10 @@ const IPv4SidePanel = () => { charge.

)} - {!subscription?.billing_via_partner && ( -

- There are no immediate charges. The addon is billed at the end of your billing - cycle based on your usage and prorated to the hour. -

- )} +

+ There are no immediate charges. The addon is billed at the end of your billing cycle + based on your usage and prorated to the hour. +

)} diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx index 51cf386d407..9f503dd922d 100644 --- a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx +++ b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx @@ -17,7 +17,6 @@ import Panel from 'components/ui/Panel' 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 { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' @@ -68,11 +67,9 @@ export const ConnectionPooling = () => { isSuccess: isSuccessPgbouncerConfig, } = usePgbouncerConfigQuery({ projectRef }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) - const disablePoolModeSelection = useMemo(() => { - return subscription?.plan?.id === 'free' - }, [subscription]) + return org?.plan?.id === 'free' + }, [org]) const { data: maxConnData } = useMaxConnectionsQuery({ projectRef: project?.ref, diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx index 930aed2d448..038df441a24 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx @@ -3,7 +3,6 @@ import { AlertTriangle, ExternalLink } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui' @@ -15,7 +14,6 @@ export const DatabaseReadOnlyAlert = () => { const [showConfirmationModal, setShowConfirmationModal] = useState(false) const { data: resourceWarnings } = useResourceWarningsQuery() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const isReadOnlyMode = (resourceWarnings ?? [])?.find((warning) => warning.project === projectRef) @@ -37,7 +35,7 @@ export const DatabaseReadOnlyAlert = () => {
  • Temporarily disable read-only mode to free up space and reduce your database size
  • - {subscription?.plan.id === 'free' ? ( + {organization?.plan.id === 'free' ? (
  • { {' '} to increase your database size limit to 8GB.
  • - ) : subscription?.plan.id === 'pro' && subscription?.usage_billing_enabled ? ( + ) : organization?.plan.id === 'pro' && organization?.usage_billing_enabled ? (
  • { const { ref: projectRef } = useParams() const { project } = useProjectContext() - const { lastDatabaseResizeAt } = project ?? {} const organization = useSelectedOrganization() - const timeTillNextAvailableDatabaseResize = - lastDatabaseResizeAt === null ? 0 : 6 * 60 - dayjs().diff(lastDatabaseResizeAt, 'minutes') - const [{ show_increase_disk_size_modal }, setUrlParams] = useUrlState() const showIncreaseDiskSizeModal = show_increase_disk_size_modal === 'true' const setShowIncreaseDiskSizeModal = (value: SetStateAction) => { @@ -48,7 +42,6 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) }, }) - const { data: projectSubscriptionData } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { isLoading: isUpdatingDiskSize } = useProjectDiskResizeMutation({ onSuccess: (_, variables) => { toast.success(`Successfully updated disk size to ${variables.volumeSize} GB`) @@ -67,7 +60,7 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) return (
    - {projectSubscriptionData?.usage_billing_enabled === true ? ( + {organization?.usage_billing_enabled === true ? (
    @@ -155,12 +148,12 @@ Read more about [disk management](https://supabase.com/docs/guides/platform/data - {projectSubscriptionData?.plan?.id === 'free' + {organization?.plan?.id === 'free' ? 'Disk size configuration is not available for projects on the Free Plan' : 'Disk size configuration is only available when the spend cap has been disabled'} - {projectSubscriptionData?.plan?.id === 'free' ? ( + {organization?.plan?.id === 'free' ? (

    If you are intending to use more than 500MB of disk space, then you will need to upgrade to at least the Pro Plan. @@ -174,13 +167,11 @@ Read more about [disk management](https://supabase.com/docs/guides/platform/data diff --git a/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx b/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx index 1759eef6cbd..4d224e353a8 100644 --- a/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx +++ b/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx @@ -6,7 +6,6 @@ import { FormHeader } from 'components/ui/Forms/FormHeader' import Panel from 'components/ui/Panel' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useFlag } from 'hooks/ui/useFlag' @@ -22,8 +21,7 @@ const CustomDomainConfig = () => { const customDomainsDisabledDueToQuota = useFlag('customDomainsDisabledDueToQuota') - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const plan = subscription?.plan?.id + const plan = organization?.plan?.id const { data: addons, isLoading: isLoadingAddons } = useProjectAddonsQuery({ projectRef: ref }) const hasCustomDomainAddon = !!addons?.selected_addons.find((x) => x.type === 'custom_domain') diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 35c2723a3ed..b94d15df87d 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -12,7 +12,6 @@ import { import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useProjectPauseMutation } from 'data/projects/project-pause-mutation' import { setProjectStatus } from 'data/projects/projects-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { PROJECT_STATUS } from 'lib/constants' @@ -33,8 +32,7 @@ const PauseProjectButton = () => { 'queue_jobs.projects.pause' ) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const isFreePlan = subscription?.plan.id === 'free' + const isFreePlan = organization?.plan.id === 'free' const { mutate: pauseProject, isLoading: isPausing } = useProjectPauseMutation({ onSuccess: (res, variables) => { diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx index 5255d5e978c..029139530f7 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx @@ -18,7 +18,6 @@ import { } from 'data/config/project-upgrade-eligibility-query' import { ReleaseChannel, useProjectUpgradeMutation } from 'data/projects/project-upgrade-mutation' import { setProjectStatus } from 'data/projects/projects-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useFlag } from 'hooks/ui/useFlag' import { PROJECT_STATUS } from 'lib/constants' @@ -72,8 +71,7 @@ const ProjectUpgradeAlert = () => { const projectUpgradeDisabled = useFlag('disableProjectUpgrade') - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) - const planId = subscription?.plan.id ?? 'free' + const planId = org?.plan.id ?? 'free' const { data: diskAttributes } = useDiskAttributesQuery({ projectRef: ref }) const { includedDiskGB: includedDiskGBMeta } = PLAN_DETAILS[planId] diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx index 5d7b259cf5f..d9a59026d8c 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx @@ -56,7 +56,7 @@ const InfrastructureActivity = () => { const { data: subscription, isLoading: isLoadingSubscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug, }) - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = organization?.plan?.id === 'free' const { data: resourceWarnings } = useResourceWarningsQuery() const projectResourceWarnings = resourceWarnings?.find((x) => x.project === projectRef) @@ -94,9 +94,9 @@ const InfrastructureActivity = () => { }, [dateRange, subscription]) const upgradeUrl = - subscription === undefined + organization === undefined ? `/` - : subscription.plan.id === 'free' + : organization.plan.id === 'free' ? `/org/${organization?.slug ?? '[slug]'}/billing#subscription` : `/project/${projectRef}/settings/addons` @@ -112,7 +112,7 @@ const InfrastructureActivity = () => { // LF seems to have an issue with the milliseconds, causes infinite loading sometimes return new Date(dateRange?.period_start?.date ?? 0).toISOString().slice(0, -5) + 'Z' } - }, [dateRange, subscription]) + }, [dateRange]) const endDate = useMemo(() => { if (dateRange?.period_end?.date === 'Invalid Date') return undefined @@ -127,7 +127,7 @@ const InfrastructureActivity = () => { // LF seems to have an issue with the milliseconds, causes infinite loading sometimes return new Date(dateRange.period_end.date ?? 0).toISOString().slice(0, -5) + 'Z' } - }, [dateRange, subscription]) + }, [dateRange]) // Switch to hourly interval, if the timeframe is <48 hours let interval: '1d' | '1h' = '1d' diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx index 9c5f88dcf82..5f957915332 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx @@ -23,7 +23,6 @@ import { MAX_REPLICAS_BELOW_XL, useReadReplicasQuery, } from 'data/read-replicas/replicas-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' @@ -73,12 +72,11 @@ const DeployNewReplicaPanel = ({ const { data } = useReadReplicasQuery({ projectRef }) const { data: addons, isSuccess } = useProjectAddonsQuery({ projectRef }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) const { data: diskConfiguration } = useDiskAttributesQuery({ projectRef }) const isNotOnTeamOrEnterprisePlan = useMemo( - () => !['team', 'enterprise'].includes(subscription?.plan.id ?? ''), - [subscription] + () => !['team', 'enterprise'].includes(org?.plan.id ?? ''), + [org] ) const { data: allOverdueInvoices } = useOverdueInvoicesQuery({ enabled: isNotOnTeamOrEnterprisePlan, @@ -166,14 +164,13 @@ const DeployNewReplicaPanel = ({ : MAX_REPLICAS_ABOVE_XL const reachedMaxReplicas = (data ?? []).filter((db) => db.identifier !== projectRef).length >= maxNumberOfReplicas - const isFreePlan = subscription?.plan.id === 'free' + const isFreePlan = org?.plan.id === 'free' const isAWSProvider = project?.cloud_provider === 'AWS' const isWalgEnabled = project?.is_physical_backups_enabled const currentComputeAddon = addons?.selected_addons.find( (addon) => addon.type === 'compute_instance' ) - const isProWithSpendCapEnabled = - subscription?.plan.id === 'pro' && !subscription.usage_billing_enabled + const isProWithSpendCapEnabled = org?.plan.id === 'pro' && !org.usage_billing_enabled const isMinimallyOnSmallCompute = currentComputeAddon?.variant.identifier !== undefined && currentComputeAddon?.variant.identifier !== 'ci_micro' diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx index 862cfdf0db1..8d90c07992a 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx @@ -8,7 +8,6 @@ import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilte import LoadingOpacity from 'components/ui/LoadingOpacity' import ShimmerLine from 'components/ui/ShimmerLine' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import useLogsPreview from 'hooks/analytics/useLogsPreview' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' @@ -65,7 +64,6 @@ export const LogsPreviewer = ({ const [selectedLogId, setSelectedLogId] = useSelectedLog() const { data: databases, isSuccess } = useReadReplicasQuery({ projectRef }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const table = !tableName ? LOGS_TABLES[queryType] : tableName @@ -97,12 +95,12 @@ export const LogsPreviewer = ({ // Show the prompt on page load based on query params useEffect(() => { if (timestampStart) { - const shouldShowUpgradePrompt = maybeShowUpgradePrompt(timestampStart, subscription?.plan?.id) + const shouldShowUpgradePrompt = maybeShowUpgradePrompt(timestampStart, organization?.plan?.id) if (shouldShowUpgradePrompt) { setShowUpgradePrompt(!showUpgradePrompt) } } - }, [timestampStart, subscription]) + }, [timestampStart, organization]) useEffect(() => { if (db !== undefined) { @@ -137,7 +135,7 @@ export const LogsPreviewer = ({ } else if (event === 'event-chart-bar-click') { setTimeRange(from || '', to || '') } else if (event === 'datepicker-change') { - const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from || '', subscription?.plan?.id) + const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from || '', organization?.plan?.id) if (shouldShowUpgradePrompt) { setShowUpgradePrompt(!showUpgradePrompt) diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx index 46b470d69ec..68617daa36d 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx @@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod' import * as Sentry from '@sentry/nextjs' import { ChevronRight, ExternalLink, Loader2, Mail, Plus, X } from 'lucide-react' import Link from 'next/link' -import { ChangeEvent, useEffect, useRef, useState } from 'react' +import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' @@ -14,7 +14,6 @@ import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import type { Project } from 'data/projects/project-detail-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { detectBrowser } from 'lib/helpers' import { useProfile } from 'lib/profile' import { @@ -133,14 +132,10 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo isSuccess: isSuccessOrganizations, } = useOrganizationsQuery() - const { - data: subscription, - isLoading: isLoadingSubscription, - isSuccess: isSuccessSubscription, - } = useOrgSubscriptionQuery({ - orgSlug: organizationSlug === 'no-org' ? undefined : organizationSlug, - }) - + const selectedOrganization = useMemo( + () => organizations?.find((org) => org.slug === organizationSlug), + [organizationSlug, organizations] + ) const { data: allProjects, isLoading: isLoadingProjects, @@ -161,7 +156,8 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo }) const respondToEmail = profile?.primary_email ?? 'your email' - const subscriptionPlanId = subscription?.plan.id + const subscriptionPlanId = selectedOrganization?.plan.id + const projects = [ ...(allProjects ?? []).filter((project) => project.organization_slug === organizationSlug), { ref: 'no-project', name: 'No specific project' } as Partial, @@ -268,9 +264,8 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo form.setValue('projectRef', selectedProject.ref) } } else if (slug) { - const selectedOrganization = organizations?.find((org) => org.slug === slug) - if (selectedOrganization !== undefined) { - form.setValue('organizationSlug', selectedOrganization.slug) + if (organizations.some((it) => it.slug === slug)) { + form.setValue('organizationSlug', slug) } } else if (ref === undefined && slug === undefined) { const firstOrganization = organizations?.[0] @@ -333,10 +328,7 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo ) : ( (organizations ?? []).find((o) => o.slug === field.value)?.name )} - {organizationSlug !== 'no-org' && isLoadingSubscription && ( - - )} - {isSuccessSubscription && ( + {subscriptionPlanId && ( {subscriptionPlanId} diff --git a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx index 6bc4bc229b7..47ca3fb8151 100644 --- a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx @@ -8,7 +8,6 @@ import { useNewLayout } from 'components/interfaces/App/FeaturePreview/FeaturePr import PartnerIcon from 'components/ui/PartnerIcon' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrganizationsQuery } from 'data/organizations/organizations-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { @@ -40,7 +39,6 @@ const OrganizationDropdown = () => { const slug = selectedOrganization?.slug const orgName = selectedOrganization?.name - const { data: subscription, isSuccess } = useOrgSubscriptionQuery({ orgSlug: slug }) const [open, setOpen] = useState(false) @@ -56,7 +54,7 @@ const OrganizationDropdown = () => { {orgName} - {isSuccess && {subscription?.plan.name}} + {selectedOrganization?.plan.name} )} @@ -71,7 +69,7 @@ const OrganizationDropdown = () => { )} diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx index d8b2f812bfc..e467a692474 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx @@ -14,7 +14,6 @@ import InlineEditorButton from 'components/layouts/AppLayout/InlineEditorButton' import OrganizationDropdown from 'components/layouts/AppLayout/OrganizationDropdown' import ProjectDropdown from 'components/layouts/AppLayout/ProjectDropdown' import { getResourcesExceededLimitsOrg } from 'components/ui/OveragesBanner/OveragesBanner.utils' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useOrgUsageQuery } from 'data/usage/org-usage-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' @@ -69,14 +68,10 @@ const LayoutHeader = ({ const isBranchingEnabled = selectedProject?.is_branch_enabled === true const { setMobileMenuOpen } = useAppStateSnapshot() - const { data: subscription } = useOrgSubscriptionQuery({ - orgSlug: selectedOrganization?.slug, - }) - // We only want to query the org usage and check for possible over-ages for plans without usage billing enabled (free or pro with spend cap) const { data: orgUsage } = useOrgUsageQuery( { orgSlug: selectedOrganization?.slug }, - { enabled: subscription?.usage_billing_enabled === false } + { enabled: selectedOrganization?.usage_billing_enabled === false } ) const exceedingLimits = useMemo(() => { diff --git a/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx b/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx index 193407cfe72..e02175e0484 100644 --- a/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx @@ -22,7 +22,6 @@ import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-l import { useProjectPauseStatusQuery } from 'data/projects/project-pause-status-query' import { useProjectRestoreMutation } from 'data/projects/project-restore-mutation' import { setProjectStatus } from 'data/projects/projects-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useFlag, usePHFlag } from 'hooks/ui/useFlag' @@ -78,7 +77,6 @@ export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => { const enableProBenefitWording = usePHFlag('proBenefitWording') const orgSlug = selectedOrganization?.slug - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug }) const { data: pauseStatus, error: pauseStatusError, @@ -97,7 +95,7 @@ export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => { pauseStatus?.max_days_till_restore_disabled ?? 0 - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = selectedOrganization?.plan?.id === 'free' const isRestoreDisabled = enforceNinetyDayUnpauseExpiry && isSuccess && !pauseStatus.can_restore const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery( diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx index d0d37954f61..536b07fde31 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx @@ -14,7 +14,6 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { @@ -56,10 +55,7 @@ const StorageSettings = () => { } = useProjectStorageConfigQuery({ projectRef }) const organization = useSelectedOrganization() - const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({ - orgSlug: organization?.slug, - }) - const isFreeTier = isSuccessSubscription && subscription.plan.id === 'free' + const isFreeTier = organization?.plan.id === 'free' const [initialValues, setInitialValues] = useState({ fileSizeLimit: 0, diff --git a/apps/studio/components/ui/UpgradeToPro.tsx b/apps/studio/components/ui/UpgradeToPro.tsx index 3854dcfbc15..a1adc8d103b 100644 --- a/apps/studio/components/ui/UpgradeToPro.tsx +++ b/apps/studio/components/ui/UpgradeToPro.tsx @@ -2,7 +2,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import Link from 'next/link' import { ReactNode } from 'react' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' @@ -31,8 +30,7 @@ const UpgradeToPro = ({ }: UpgradeToProProps) => { const project = useSelectedProject() const organization = useSelectedOrganization() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const plan = subscription?.plan?.id + const plan = organization?.plan?.id const canUpdateSubscription = useCheckPermissions( PermissionAction.BILLING_WRITE, diff --git a/apps/studio/data/subscriptions/org-subscription-update-mutation.ts b/apps/studio/data/subscriptions/org-subscription-update-mutation.ts index c8970e564a1..348eb311780 100644 --- a/apps/studio/data/subscriptions/org-subscription-update-mutation.ts +++ b/apps/studio/data/subscriptions/org-subscription-update-mutation.ts @@ -6,6 +6,7 @@ import { toast } from 'sonner' import type { ResponseError } from 'types/base' import { subscriptionKeys } from './keys' import type { SubscriptionTier } from './types' +import { organizationKeys } from 'data/organizations/keys' export type OrgSubscriptionUpdateVariables = { slug: string @@ -63,6 +64,8 @@ export const useOrgSubscriptionUpdateMutation = ({ queryClient.invalidateQueries(subscriptionKeys.orgPlans(slug)), queryClient.invalidateQueries(usageKeys.orgUsage(slug)), queryClient.invalidateQueries(invoicesKeys.orgUpcomingPreview(slug)), + queryClient.invalidateQueries(organizationKeys.detail(slug)), + queryClient.invalidateQueries(organizationKeys.list()), ]) await onSuccess?.(data, variables, context) diff --git a/apps/studio/hooks/misc/useCurrentOrgPlan.ts b/apps/studio/hooks/misc/useCurrentOrgPlan.ts index 8c93a16c6f0..f6da44ba56a 100644 --- a/apps/studio/hooks/misc/useCurrentOrgPlan.ts +++ b/apps/studio/hooks/misc/useCurrentOrgPlan.ts @@ -1,25 +1,21 @@ -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from './useSelectedOrganization' export function useCurrentOrgPlan() { const currentOrg = useSelectedOrganization() - const { data, isLoading, isSuccess } = useOrgSubscriptionQuery({ - orgSlug: currentOrg?.slug, - }) - if (isLoading) { + if (!currentOrg) { return { plan: null, usageBillingEnabled: null, - isLoading, + isLoading: true, isSuccess: false, } } else { return { - plan: data?.plan, - usageBillingEnabled: data?.usage_billing_enabled, - isLoading, - isSuccess, + plan: currentOrg?.plan, + usageBillingEnabled: currentOrg?.usage_billing_enabled, + isLoading: false, + isSuccess: true, } } } diff --git a/apps/studio/hooks/misc/useUpgradePrompt.tsx b/apps/studio/hooks/misc/useUpgradePrompt.tsx index 8fb0027488a..34542ea05c1 100644 --- a/apps/studio/hooks/misc/useUpgradePrompt.tsx +++ b/apps/studio/hooks/misc/useUpgradePrompt.tsx @@ -1,14 +1,12 @@ import { maybeShowUpgradePrompt } from 'components/interfaces/Settings/Logs/Logs.utils' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useEffect, useState } from 'react' import { useSelectedOrganization } from './useSelectedOrganization' export const useUpgradePrompt = (from: string) => { const organization = useSelectedOrganization() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const [showUpgradePrompt, setShowUpgradePrompt] = useState(false) - const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, subscription?.plan?.id) + const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, organization?.plan?.id) useEffect(() => { if (shouldShowUpgradePrompt) { diff --git a/apps/studio/pages/api/platform/organizations/index.ts b/apps/studio/pages/api/platform/organizations/index.ts index 48ac8439cfc..715dabb9b1f 100644 --- a/apps/studio/pages/api/platform/organizations/index.ts +++ b/apps/studio/pages/api/platform/organizations/index.ts @@ -24,6 +24,10 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { name: process.env.DEFAULT_ORGANIZATION_NAME || 'Default Organization', slug: 'default-org-slug', billing_email: 'billing@supabase.co', + plan: { + id: 'enterprise', + name: 'Enterprise', + }, }, ] return res.status(200).json(response) diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx index 68f8b414d72..85a23704596 100644 --- a/apps/studio/pages/new/[slug].tsx +++ b/apps/studio/pages/new/[slug].tsx @@ -41,7 +41,6 @@ import { useProjectCreateMutation, } from 'data/projects/project-create-mutation' import { useProjectsQuery } from 'data/projects/projects-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { withAuth } from 'hooks/misc/withAuth' @@ -161,11 +160,9 @@ const Wizard: NextPageWithLayout = () => { } }, [currentOrg, projectCreationExperimentGroup, router]) - const { data: orgSubscription } = useOrgSubscriptionQuery({ orgSlug: slug }) - const isNotOnTeamOrEnterprisePlan = useMemo( - () => !['team', 'enterprise'].includes(orgSubscription?.plan.id ?? ''), - [orgSubscription] + () => !['team', 'enterprise'].includes(currentOrg?.plan.id ?? ''), + [currentOrg] ) const { data: allOverdueInvoices } = useOverdueInvoicesQuery({ @@ -220,7 +217,7 @@ const Wizard: NextPageWithLayout = () => { const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' const freePlanWithExceedingLimits = - orgSubscription?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit + currentOrg?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit const isManagedByVercel = currentOrg?.managed_by === 'vercel-marketplace' @@ -348,7 +345,7 @@ const Wizard: NextPageWithLayout = () => { dbPricingTierId: 'tier_free', // only set the compute size on pro+ plans. Free plans always use micro (nano in the future) size. dbInstanceSize: - orgSubscription?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize), + currentOrg?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize), dataApiExposedSchemas: !dataApi ? [] : undefined, dataApiUseApiSchema: !dataApi ? false : useApiSchema, postgresEngine: useOrioleDb ? availableOrioleVersion?.postgres_engine : postgresEngine, @@ -565,7 +562,7 @@ const Wizard: NextPageWithLayout = () => { )} - {orgSubscription?.plan && orgSubscription?.plan.id !== 'free' && ( + {currentOrg?.plan && currentOrg?.plan.id !== 'free' && ( { const { data: organizations, isSuccess: isOrganizationsSuccess } = useOrganizationsQuery() const currentOrg = organizations?.find((o: any) => o.slug === slug) - const { data: orgSubscription } = useOrgSubscriptionQuery({ orgSlug: slug }) - const { data: allOverdueInvoices } = useOverdueInvoicesQuery({ - enabled: - orgSubscription !== undefined && - !['team', 'enterprise'].includes(orgSubscription?.plan.id ?? ''), + enabled: currentOrg !== undefined && !['team', 'enterprise'].includes(currentOrg.plan.id ?? ''), }) const overdueInvoices = (allOverdueInvoices ?? []).filter( (x) => x.organization_id === currentOrg?.id @@ -221,7 +216,7 @@ const WizardForm = () => { const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' const freePlanWithExceedingLimits = - orgSubscription?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit + currentOrg?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit const isManagedByVercel = currentOrg?.managed_by === 'vercel-marketplace' @@ -342,7 +337,7 @@ const WizardForm = () => { dbSql: sql, // only set the compute size on pro+ plans. Free plans always use micro (nano in the future) size. dbInstanceSize: - orgSubscription?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize), + currentOrg?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize), dataApiExposedSchemas: !dataApi ? [] : undefined, dataApiUseApiSchema: !dataApi ? false : useApiSchema, postgresEngine: useOrioleDb ? availableOrioleVersion?.postgres_engine : postgresEngine, @@ -782,7 +777,7 @@ const WizardForm = () => { )}

    - {orgSubscription?.plan && orgSubscription?.plan.id !== 'free' && ( + {currentOrg?.plan.id !== 'free' && (
    {
    } description={ -
    +
    { const isOrioleDb = useIsOrioleDb() const { data: backups, error, isLoading, isError, isSuccess } = useBackupsQuery({ projectRef }) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - - const plan = subscription?.plan?.id + const plan = organization?.plan?.id const isEnabled = backups?.pitr_enabled const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY diff --git a/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx b/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx index 48f057bc6c4..593ece22f46 100644 --- a/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx @@ -20,7 +20,6 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useCloneBackupsQuery } from 'data/projects/clone-query' import { useCloneStatusQuery } from 'data/projects/clone-status-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useIsOrioleDb } from 'hooks/misc/useSelectedProject' @@ -33,7 +32,6 @@ import Panel from 'components/ui/Panel' import { projectSpecToMonthlyPrice } from 'components/interfaces/Database/Backups/RestoreToNewProject/RestoreToNewProject.utils' import { useDiskAttributesQuery } from 'data/config/disk-attributes-query' import { DiskType } from 'components/interfaces/DiskManagement/ui/DiskManagement.constants' -import { InfraInstanceSize } from 'components/interfaces/DiskManagement/DiskManagement.types' import DefaultLayout from 'components/layouts/DefaultLayout' const RestoreToNewProjectPage: NextPageWithLayout = () => { @@ -63,8 +61,7 @@ RestoreToNewProjectPage.getLayout = (page) => ( const RestoreToNewProject = () => { const { project } = useProjectContext() const organization = useSelectedOrganization() - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const isFreePlan = subscription?.plan?.id === 'free' + const isFreePlan = organization?.plan?.id === 'free' const isOrioleDb = useIsOrioleDb() const [refetchInterval, setRefetchInterval] = useState(false) @@ -80,7 +77,7 @@ const RestoreToNewProject = () => { isError, } = useCloneBackupsQuery({ projectRef: project?.ref }, { enabled: !isFreePlan }) - const plan = subscription?.plan?.id + const plan = organization?.plan?.id const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY const isPermissionsLoaded = usePermissionsLoaded() @@ -95,7 +92,7 @@ const RestoreToNewProject = () => { const IS_PG15_OR_ABOVE = dbVersion >= 15 const targetVolumeSizeGb = cloneBackups?.target_volume_size_gb const targetComputeSize = cloneBackups?.target_compute_size - const planId = subscription?.plan?.id ?? 'free' + const planId = organization?.plan?.id ?? 'free' const { data } = useDiskAttributesQuery({ projectRef: project?.ref }) const storageType = data?.attributes?.type ?? 'gp3' diff --git a/apps/studio/pages/project/[ref]/logs/explorer/index.tsx b/apps/studio/pages/project/[ref]/logs/explorer/index.tsx index 768d91d200f..24b2ed53114 100644 --- a/apps/studio/pages/project/[ref]/logs/explorer/index.tsx +++ b/apps/studio/pages/project/[ref]/logs/explorer/index.tsx @@ -45,7 +45,6 @@ import { UpsertContentPayload, useContentUpsertMutation, } from 'data/content/content-upsert-mutation' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import useLogsQuery from 'hooks/analytics/useLogsQuery' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' @@ -95,7 +94,6 @@ export const LogsExplorerPage: NextPageWithLayout = () => { }) const query = content?.content.find((x) => x.id === queryId) - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { params, logData, @@ -287,7 +285,7 @@ export const LogsExplorerPage: NextPageWithLayout = () => { } const handleDateChange = ({ to, from }: DatePickerToFrom) => { - const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, subscription?.plan?.id) + const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, organization?.plan?.id) if (shouldShowUpgradePrompt) { setShowUpgradePrompt(!showUpgradePrompt) @@ -347,12 +345,12 @@ export const LogsExplorerPage: NextPageWithLayout = () => { // Show the prompt on page load based on query params useEffect(() => { if (its) { - const shouldShowUpgradePrompt = maybeShowUpgradePrompt(its as string, subscription?.plan?.id) + const shouldShowUpgradePrompt = maybeShowUpgradePrompt(its as string, organization?.plan?.id) if (shouldShowUpgradePrompt) { setShowUpgradePrompt(!showUpgradePrompt) } } - }, [its, subscription]) + }, [its, organization]) return (
    diff --git a/apps/studio/pages/project/[ref]/reports/api-overview.tsx b/apps/studio/pages/project/[ref]/reports/api-overview.tsx index 321e3bc4f7e..16a527d3e06 100644 --- a/apps/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/apps/studio/pages/project/[ref]/reports/api-overview.tsx @@ -15,7 +15,6 @@ import DefaultLayout from 'components/layouts/DefaultLayout' import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' import ShimmerLine from 'components/ui/ShimmerLine' import { useApiReport } from 'data/reports/api-report-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { NextPageWithLayout } from 'types' @@ -35,8 +34,7 @@ export const ApiReport: NextPageWithLayout = () => { refresh, } = report - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const plan = subscription?.plan + const plan = organization?.plan const handleDatepickerChange = ({ from, to }: DatePickerToFrom) => { mergeParams({ diff --git a/apps/studio/pages/project/[ref]/reports/storage.tsx b/apps/studio/pages/project/[ref]/reports/storage.tsx index 077cb742e0c..2b54fa5b141 100644 --- a/apps/studio/pages/project/[ref]/reports/storage.tsx +++ b/apps/studio/pages/project/[ref]/reports/storage.tsx @@ -12,7 +12,6 @@ import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import ShimmerLine from 'components/ui/ShimmerLine' import { useStorageReport } from 'data/reports/storage-report-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { RefreshCw } from 'lucide-react' import type { NextPageWithLayout } from 'types' @@ -24,8 +23,7 @@ export const StorageReport: NextPageWithLayout = () => { const { isLoading, refresh } = report - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) - const plan = subscription?.plan + const plan = organization?.plan const handleDatepickerChange = ({ from, to }: DatePickerToFrom) => { report.mergeParams({ diff --git a/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx b/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx index 1c025e11b96..9e369e279d2 100644 --- a/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx +++ b/apps/studio/tests/pages/projects/LogsPreviewer.test.tsx @@ -26,150 +26,6 @@ vi.mock('lib/gotrue', () => ({ auth: { onAuthStateChange: vi.fn() }, })) -// in the event that log metadata is not available, fall back to default renderer -// generate test cases for each query type -// const defaultRendererFallbacksCases = [ -// 'api', -// 'database', -// 'auth', -// 'supavisor', -// 'postgrest', -// 'storage', -// 'realtime', -// ].map((queryType) => ({ -// testName: 'fallback to default render', -// queryType, -// tableName: undefined, -// tableLog: logDataFixture({ -// event_message: 'some message', -// metadata: undefined, -// }), -// selectionLog: logDataFixture({ -// metadata: undefined, -// }), -// tableTexts: [/some message/], -// selectionTexts: [/some message/], -// })) - -// test.skip.each([ -// { -// queryType: 'api', -// tableName: undefined, -// tableLog: logDataFixture({ -// path: 'some-path', -// method: 'POST', -// status_code: '400', -// metadata: undefined, -// }), -// selectionLog: logDataFixture({ -// metadata: [{ request: [{ method: 'POST' }] }], -// }), -// tableTexts: [/POST/, /some\-path/, /400/], -// selectionTexts: [/POST/, /Timestamp/, RegExp(`${new Date().getFullYear()}.+`, 'g')], -// }, - -// { -// queryType: 'database', -// tableName: undefined, -// tableLog: logDataFixture({ -// error_severity: 'ERROR', -// event_message: 'some db event', -// metadata: undefined, -// }), -// selectionLog: logDataFixture({ -// metadata: [ -// { -// parsed: [ -// { -// application_type: 'client backend', -// error_severity: 'ERROR', -// hint: 'some pg hint', -// }, -// ], -// }, -// ], -// }), -// tableTexts: [/ERROR/, /some db event/], -// selectionTexts: [ -// /client backend/, -// /some pg hint/, -// /ERROR/, -// /Timestamp/, -// RegExp(`${new Date().getFullYear()}.+`, 'g'), -// ], -// }, -// { -// queryType: 'auth', -// tableName: undefined, -// tableLog: logDataFixture({ -// event_message: 'some event_message', -// level: 'info', -// path: '/auth-path', -// msg: 'some metadata_msg', -// status: 300, -// metadata: undefined, -// }), -// selectionLog: logDataFixture({ -// event_message: 'some event_message', -// metadata: { -// msg: 'some metadata_msg', -// path: '/auth-path', -// level: 'info', -// status: 300, -// }, -// }), -// tableTexts: [/auth\-path/, /some metadata_msg/, /INFO/], -// selectionTexts: [ -// /auth\-path/, -// /some metadata_msg/, -// /INFO/, -// /300/, -// /Timestamp/, -// RegExp(`${new Date().getFullYear()}.+`, 'g'), -// ], -// }, -// ...defaultRendererFallbacksCases, -// // these all use teh default selection/table renderers -// ...['supavisor', 'postgrest', 'storage', 'realtime', 'supavisor'].map((queryType) => ({ -// queryType, -// tableName: undefined, -// tableLog: logDataFixture({ -// event_message: 'some message', -// metadata: undefined, -// }), -// selectionLog: logDataFixture({ -// metadata: [{ some: [{ nested: 'value' }] }], -// }), -// tableTexts: [/some message/], -// selectionTexts: [/some/, /nested/, /value/, RegExp(`${new Date().getFullYear()}.+`, 'g')], -// })), -// ])( -// 'selection $queryType $queryType, $tableName , can display log data and metadata $testName', -// async ({ queryType, tableName, tableLog, selectionLog, tableTexts, selectionTexts }) => { -// render() -// // reset mock so that we can check for selection call -// get.mockClear() -// for (const text of tableTexts) { -// await screen.findByText(text) -// } -// const row = await screen.findByText(tableTexts[0]) -// fireEvent.click(row) -// await waitFor(() => { -// expect(get).toHaveBeenCalledWith( -// expect.stringContaining('iso_timestamp_start'), -// expect.anything() -// ) -// expect(get).not.toHaveBeenCalledWith( -// expect.stringContaining('iso_timestamp_end'), -// expect.anything() -// ) -// }) -// for (const text of selectionTexts) { -// await screen.findAllByText(text) -// } -// } -// ) - test.skip('Search will trigger a log refresh', async () => { render() @@ -220,249 +76,3 @@ test.skip('log event chart', async () => { await waitFor(() => screen.queryByText(/some-uuid123/) === null) }) - -test.skip('s= query param will populate the search bar', async () => { - // const router = routerMock -}) - -test.skip('te= query param will populate the timestamp to input', async () => { - // get time 20 mins before - // const newDate = new Date() - // newDate.setMinutes(new Date().getMinutes() - 20) - // const iso = newDate.toISOString() - // const router = defaultRouterMock() - // router.query = { ...router.query, ite: iso } - // useRouter.mockReturnValue(router) - // useParams.mockReturnValue(router.query) - // render() - // await waitFor(() => { - // expect(get).toHaveBeenCalledWith( - // expect.stringContaining(`iso_timestamp_end=${encodeURIComponent(iso)}`), - // expect.anything() - // ) - // }) -}) - -// test.skip('ts= query param will populate the timestamp from input', async () => { -// // get time 20 mins before -// const newDate = new Date() -// newDate.setMinutes(new Date().getMinutes() - 20) -// const iso = newDate.toISOString() -// const router = defaultRouterMock() -// router.query = { ...router.query, its: iso } -// useRouter.mockReturnValue(router) -// useParams.mockReturnValue(router.query) -// render() - -// await waitFor(() => { -// expect(get).toHaveBeenCalledWith( -// expect.stringContaining(`iso_timestamp_start=${encodeURIComponent(iso)}`), -// expect.anything() -// ) -// }) -// }) - -// test.skip('load older btn will fetch older logs', async () => { -// get.mockImplementation((url) => { -// if (url.includes('count')) { -// return {} -// } -// return { -// result: [logDataFixture({ id: 'some-uuid123', status_code: 200, method: 'GET' })], -// } -// }) -// render() -// // should display first log but not second -// await waitFor(() => screen.getByText('GET')) -// await expect(screen.findByText('POST')).rejects.toThrow() -// get.mockResolvedValueOnce({ -// result: [logDataFixture({ id: 'some-uuid234', status_code: 203, method: 'POST' })], -// }) -// // should display first and second log -// userEvent.click(await screen.findByText('Load older')) -// await screen.findByText('GET') -// await screen.findByText('POST') -// expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_end='), expect.anything()) -// }) - -// test.skip('bug: load older btn does not error out when previous page is empty', async () => { -// // bugfix for https://sentry.io/organizations/supabase/issues/2903331460/?project=5459134&referrer=slack -// get.mockImplementation((url) => { -// if (url.includes('count')) { -// return {} -// } -// return { result: [] } -// }) -// render() -// userEvent.click(await screen.findByText('Load older')) -// // NOTE: potential race condition, since we are asserting that something DOES NOT EXIST -// // wait for 500s to make sure all ui logic is complete -// // need to wrap in act because internal react state is changing during this time. -// await act(async () => await wait(100)) -// // clicking load older multiple times should not give error -// await waitFor(() => { -// expect(screen.queryByText(/Sorry/)).toBeNull() -// expect(screen.queryByText(/An error occurred/)).toBeNull() -// expect(screen.queryByText(/undefined/)).toBeNull() -// }) -// }) - -// test.skip('log event chart hide', async () => { -// get.mockImplementation((url) => { -// return { result: [] } -// }) -// render() -// await screen.findByText(/No data/) -// const toggle = await screen.findByText(/Chart/) -// userEvent.click(toggle) -// await expect(screen.findByText('Events')).rejects.toThrow() -// }) - -// test.skip('bug: nav backwards with params change results in ui changing', async () => { -// // bugfix for https://sentry.io/organizations/supabase/issues/2903331460/?project=5459134&referrer=slack -// get.mockImplementation((url) => { -// if (url.includes('count')) { -// return {} -// } -// return { data: [] } -// }) -// const { container, rerender } = render( -// -// ) -// await expect(screen.findByDisplayValue('simple-query')).rejects.toThrow() -// const router = defaultRouterMock() -// router.query = { ...router.query, s: 'simple-query' } -// useRouter.mockReturnValue(router) -// useParams.mockReturnValue(router.query) -// rerender() -// await screen.findByDisplayValue('simple-query') -// }) - -// test.skip('bug: nav to explorer preserves newlines', async () => { -// get.mockImplementation((url) => { -// return { result: [] } -// }) -// render() -// const button = screen.getByRole('link', { name: 'Explore via query' }) -// expect(button.href).toContain(encodeURIComponent('\n')) -// }) - -// test.skip('filters alter generated query', async () => { -// render() -// userEvent.click(await screen.findByRole('button', { name: 'Status' })) -// userEvent.click(await screen.findByText(/500 error codes/)) -// userEvent.click(await screen.findByText(/200 codes/)) -// userEvent.click(await screen.findByText(/Apply/)) -// await waitFor(() => { -// // counts are adjusted -// expect(get).toHaveBeenCalledWith( -// expect.stringMatching(/count.+\*.+as.count.+where.+500.+599/), -// expect.anything() -// ) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('500'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('599'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('200'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('299'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('where'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('and'), expect.anything()) -// }) - -// // should be able to clear the filters -// userEvent.click(await screen.findByRole('button', { name: 'Status' })) -// userEvent.click(await screen.findByRole('button', { name: 'Clear' })) -// // get.mockClear() - -// userEvent.click(await screen.findByRole('button', { name: 'Status' })) -// userEvent.click(await screen.findByText(/400 codes/)) -// userEvent.click(await screen.findByText(/Apply/)) - -// await waitFor(() => { -// // counts are adjusted -// expect(get).not.toHaveBeenCalledWith( -// expect.stringMatching(/count.+\*.+as.count.+where.+500.+599/), -// expect.anything() -// ) -// expect(get).toHaveBeenCalledWith( -// expect.stringMatching(/count.+\*.+as.count.+where.+400.+499/), -// expect.anything() -// ) -// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('500'), expect.anything()) -// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('599'), expect.anything()) -// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('200'), expect.anything()) -// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('299'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('400'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('499'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('where'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('and'), expect.anything()) -// }) -// }) - -// test.skip('filters accept filterOverride', async () => { -// render( -// -// ) - -// await waitFor(() => { -// expect(get).toHaveBeenCalledWith(expect.stringContaining('my.nestedkey'), expect.anything()) -// expect(get).toHaveBeenCalledWith(expect.stringContaining('myvalue'), expect.anything()) -// }) -// }) - -// describe.each(['free', 'pro', 'team', 'enterprise'])('upgrade modal for %s', (key) => { -// beforeEach(() => { -// useOrgSubscriptionQuery.mockReturnValue({ -// data: { -// plan: { -// id: key, -// }, -// }, -// }) -// }) -// test.skip('based on query params', async () => { -// const router = defaultRouterMock() -// router.query = { -// ...router.query, -// q: 'some_query', -// its: dayjs().subtract(4, 'months').toISOString(), -// ite: dayjs().toISOString(), -// } -// useRouter.mockReturnValue(router) -// useParams.mockReturnValue(router.query) -// render() -// // await screen.findByText('Log retention') // assert modal title is present -// }) -// }) - -// test.skip('datepicker onChange will set the query params for outbound api request', async () => { -// useOrgSubscriptionQuery.mockReturnValue({ -// data: { -// plan: { -// id: 'enterprise', -// }, -// }, -// }) -// get.mockImplementation((url) => { -// return { result: [] } -// }) -// render() -// // renders time locally -// userEvent.click(await screen.findByText('Custom')) -// // inputs with local time -// const toHH = await screen.findByDisplayValue('23') -// userEvent.clear(toHH) -// userEvent.type(toHH, '12') -// userEvent.click(await screen.findByText('20'), { selector: '.react-datepicker__day' }) -// userEvent.click(await screen.findByText('21'), { selector: '.react-datepicker__day' }) -// userEvent.click(await screen.findByText('Apply')) -// await waitFor(() => { -// expect(get).toHaveBeenCalledWith( -// expect.stringMatching(/.+select.+event_message.+iso_timestamp_end=/), -// expect.anything() -// ) -// }) -// }) diff --git a/apps/studio/types/base.ts b/apps/studio/types/base.ts index 0543089ff20..1739a4ca9e7 100644 --- a/apps/studio/types/base.ts +++ b/apps/studio/types/base.ts @@ -13,6 +13,11 @@ export interface Organization { restriction_data: Record | null managed_by: 'supabase' | 'vercel-marketplace' | 'aws-marketplace' partner_id?: string + plan: { + id: 'free' | 'pro' | 'team' | 'enterprise' + name: string + } + usage_billing_enabled: boolean } /** diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index f023eb69b4b..de596658974 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -5963,6 +5963,11 @@ export interface components { is_owner: boolean name: string opt_in_tags: string[] + plan: { + /** @enum {string} */ + id: 'free' | 'pro' | 'team' | 'enterprise' + name: string + } restriction_data: { [key: string]: string } | null @@ -5971,6 +5976,7 @@ export interface components { slug: string stripe_customer_id: string | null subscription_id: string | null + usage_billing_enabled: boolean } OrganizationRoleResponse: { org_scoped_roles: { @@ -6033,10 +6039,16 @@ export interface components { id: number name: string opt_in_tags: string[] + plan: { + /** @enum {string} */ + id?: 'free' | 'pro' | 'team' | 'enterprise' + name?: string + } projects: components['schemas']['OrganizationSlugProject'][] restriction_data: Record | null restriction_status: Record | null slug: string + usage_billing_enabled: boolean } OrgDocumentUrlResponse: { fileUrl: string @@ -13785,7 +13797,7 @@ export interface operations { UsageApiController_getApiCounts: { parameters: { query: { - interval: 'minutely' | 'hourly' | 'daily' + interval: 'minutely' | 'hourly' | 'daily' | '15min' | '30min' | '1hr' | '3hr' | '1day' } header?: never path: {