diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx index 7a578f453d1..ebae4de5b4c 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx @@ -13,6 +13,7 @@ import { } from 'react' import { useFeaturePreviews } from './useFeaturePreviews' +import { IS_PLATFORM } from '@/lib/constants' import { EMPTY_OBJ } from '@/lib/void' type FeaturePreviewContextType = { @@ -43,6 +44,11 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren) = const initializeFlags = useEffectEvent(() => { setFlags( featurePreviews.reduce((a, b) => { + // Platform-only previews can never be enabled outside the hosted platform + if (!IS_PLATFORM && b.isPlatformOnly) { + return { ...a, [b.key]: false } + } + const defaultOptIn = b.isDefaultOptIn const localStorageValue = safeLocalStorage.getItem(b.key) return { @@ -101,8 +107,7 @@ export const useUnifiedLogsPreview = () => { export const useIsPgDeltaDiffEnabled = () => { const { flags } = useFeaturePreviewContext() - const pgDeltaDiffEnabled = useFlag('pgdeltaDiff') - return pgDeltaDiffEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF] + return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF] } export const useIsAdvisorRulesEnabled = () => { diff --git a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts index 137ae7c8df2..79d6a6059dc 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts +++ b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts @@ -20,7 +20,6 @@ export type FeaturePreview = { } export const useFeaturePreviews = (): FeaturePreview[] => { - const pgDeltaDiffEnabled = useFlag('pgdeltaDiff') const platformWebhooksEnabled = useFlag('platformWebhooks') const jitDbAccessEnabled = useFlag('jitDbAccess') const isMarketplaceEnabled = useFlag('marketplaceIntegrations') @@ -68,7 +67,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => { isNew: false, isPlatformOnly: true, isDefaultOptIn: true, - enabled: pgDeltaDiffEnabled, + enabled: true, }, { key: LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS, @@ -111,12 +110,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => { getRoute: (ref?: string) => `/project/${ref}/integrations`, }, ].sort((a, b) => Number(b.isNew) - Number(a.isNew)), - [ - unifiedLogsDefaultOptIn, - pgDeltaDiffEnabled, - platformWebhooksEnabled, - jitDbAccessEnabled, - isMarketplaceEnabled, - ] + [unifiedLogsDefaultOptIn, platformWebhooksEnabled, jitDbAccessEnabled, isMarketplaceEnabled] ) } diff --git a/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx b/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx index 162e5bc1909..4bbcfbdd78a 100644 --- a/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx +++ b/apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx @@ -1,5 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { IS_PLATFORM, useFlag } from 'common' +import { IS_PLATFORM } from 'common' import Link from 'next/link' import { ReactNode, useEffect, useMemo, useRef } from 'react' import { useForm } from 'react-hook-form' @@ -51,6 +51,7 @@ import { logDrainHeaderEntriesSchema, type LogDrainHeaderRow, } from './LogDrains.utils' +import { useEnabledLogDrainTypes } from './useEnabledLogDrainTypes' import { TaxDisclaimer } from '@/components/interfaces/Billing/TaxDisclaimer' import { Shortcut } from '@/components/ui/Shortcut' import { LogDrainData } from '@/data/log-drains/log-drains-query' @@ -347,12 +348,7 @@ export function LogDrainDestinationSheetForm({ ) }, [defaultValues, mode]) - const sentryEnabled = useFlag('SentryLogDrain') - const s3Enabled = useFlag('S3logdrain') - const axiomEnabled = useFlag('axiomLogDrain') - const otlpEnabled = useFlag('otlpLogDrain') - const last9Enabled = useFlag('Last9LogDrain') - const syslogEnabled = useFlag('syslogLogDrain') + const enabledLogDrainTypes = useEnabledLogDrainTypes() const formRef = useRef(null) @@ -474,15 +470,7 @@ export function LogDrainDestinationSheetForm({ {LOG_DRAIN_TYPES.find((t) => t.value === type)?.name} - {LOG_DRAIN_TYPES.filter((t) => { - if (t.value === 'sentry') return sentryEnabled - if (t.value === 's3') return s3Enabled - if (t.value === 'axiom') return axiomEnabled - if (t.value === 'otlp') return otlpEnabled - if (t.value === 'last9') return last9Enabled - if (t.value === 'syslog') return syslogEnabled - return true - }).map((type) => ( + {enabledLogDrainTypes.map((type) => ( { - if (t.value === 'sentry') return sentryEnabled - if (t.value === 's3') return s3Enabled - if (t.value === 'axiom') return axiomEnabled - if (t.value === 'otlp') return otlpEnabled - if (t.value === 'last9') return last9Enabled if (t.value === 'syslog') return syslogEnabled return true }) diff --git a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx index 99eebc3db0b..9593bf99ec8 100644 --- a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx +++ b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx @@ -1,4 +1,4 @@ -import { useFeatureFlags, useFlag, useParams } from 'common' +import { useFeatureFlags, useParams } from 'common' import { UseFormReturn } from 'react-hook-form' import type { CloudProvider } from 'shared-data' import { @@ -29,7 +29,7 @@ import { useDefaultRegionQuery } from '@/data/misc/get-default-region-query' import { useOrganizationAvailableRegionsQuery } from '@/data/organizations/organization-available-regions-query' import { useIncidentStatusQuery } from '@/data/platform/incident-status-query' import type { DesiredInstanceSize } from '@/data/projects/new-project.constants' -import { BASE_PATH, PROVIDERS } from '@/lib/constants' +import { BASE_PATH } from '@/lib/constants' interface RegionSelectorProps { form: UseFormReturn @@ -70,7 +70,7 @@ export const RegionSelector = ({ const cloudProvider = form.getValues('cloudProvider') as CloudProvider const { hasLoaded: flagsLoaded } = useFeatureFlags() - const smartRegionEnabled = useFlag('enableSmartRegion') + const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS' const { data: statusData } = useIncidentStatusQuery() const { incidents = [] } = statusData ?? {} @@ -100,7 +100,7 @@ export const RegionSelector = ({ availableRegionsData?.recommendations.specific.map((region) => region.code) ) - const availableRegions = getAvailableRegions(PROVIDERS[cloudProvider].id) + const availableRegions = getAvailableRegions(cloudProvider) const regionsArray = Object.entries(availableRegions).map(([_key, value]) => { return { code: value.code, diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index 6d1f261f253..1e943610ae0 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -126,7 +126,6 @@ export const SQLEditor = () => { const getImpersonatedRoleState = useGetImpersonatedRoleState() const databaseSelectorState = useDatabaseSelectorStateSnapshot() const { aiOptInLevel } = useOrgAiOptInLevel() - const showPrettyExplain = useFlag('ShowPrettyExplain') // [Ali] Kill switch to hide the SQL Editor Explain tab and its entry points const disablePrettyExplain = useFlag('DisablePrettyExplainOnSqlEditor') @@ -236,7 +235,7 @@ export const SQLEditor = () => { if (id) { sessionSnap.addResult(id, data.result, vars.autoLimit) - if (!disablePrettyExplain && showPrettyExplain && isExplainQuery(data.result)) { + if (!disablePrettyExplain && isExplainQuery(data.result)) { sessionSnap.addExplainResult(id, data.result) setActiveUtilityTab('explain') } else if (activeUtilityTab === 'explain') { diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx deleted file mode 100644 index 2f444fed2f2..00000000000 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useParams } from 'common' -import dayjs from 'dayjs' -import { useState } from 'react' -import { toast } from 'sonner' -import { - Button, - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogSection, - DialogSectionSeparator, - DialogTitle, - DialogTrigger, -} from 'ui' -import { Admonition } from 'ui-patterns/admonition' -import { TimestampInfo } from 'ui-patterns/TimestampInfo' - -import { ButtonTooltip } from '@/components/ui/ButtonTooltip' -import { InlineLink } from '@/components/ui/InlineLink' -import { useProjectStorageConfigUpdateUpdateMutation } from '@/data/config/project-storage-config-update-mutation' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' - -// [Joshen] Will be decided by Storage team, temp setting to 15th December 2025 UTC (3 months buffer) -const MIGRATION_DEADLINE = '2025-12-15T00:00:00' - -export const StorageListV2MigrationCallout = () => { - const deadline = dayjs(MIGRATION_DEADLINE).utc(true) - const currentDate = dayjs.utc() - const remainingMonths = Math.ceil(deadline.diff(currentDate, 'months', true)) - - return ( - -

- Get access to the List-V2 endpoint for improved performance and the ability to enable - Analytics buckets to your storage system. -

- {remainingMonths <= 1 && ( -

- Your project's Storage will be automatically upgraded by{' '} - {' '} - if the upgrade is not completed by then. -

- )} - - } - actions={} - /> - ) -} - -export const StorageListV2MigratingCallout = () => { - return ( - - ) -} - -const StorageListV2MigrationDialog = () => { - const { ref } = useParams() - const { can: canUpdateStorageSettings } = useAsyncCheckPermissions( - PermissionAction.STORAGE_ADMIN_WRITE, - '*' - ) - - const [open, setOpen] = useState(false) - - const { mutate: updateStorageConfig, isPending: isUpdating } = - useProjectStorageConfigUpdateUpdateMutation({ - onSuccess: () => { - toast.success(`Project's storage will be upgraded shortly!`) - setOpen(false) - }, - }) - - const onConfirmUpgrade = () => { - if (!ref) return console.error('Project ref is required') - updateStorageConfig({ projectRef: ref, external: { upstreamTarget: 'canary' } }) - } - - return ( - - - - Upgrade Storage - - - - - Upgrade your project's Storage - - Get access to Analytics buckets and an improved list method - - - - - - - - -

- Depending on the number of objects in your Storage, the migration can take up to 24 - hours to finish. -

- -

- The upgrade will increase your disk size to about 15 - 25% and IOPS will be used to - create new efficient indexes as well as denormalising tables. -

- -

- Ensure that your database instance has not{' '} - - scaled disk - {' '} - within the last 6h and you have at least 60%{' '} - - CPU capacity - {' '} - before proceeding. -

-
- - - - - -
-
- ) -} diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx index b918ebd41f0..245ef423660 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx @@ -1,6 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { IS_PLATFORM, useFlag, useParams } from 'common' +import { IS_PLATFORM, useParams } from 'common' import { useEffect, useMemo, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' @@ -29,10 +29,6 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import * as z from 'zod' import { StorageFileSizeLimitErrorMessage } from './StorageFileSizeLimitErrorMessage' -import { - StorageListV2MigratingCallout, - StorageListV2MigrationCallout, -} from './StorageListV2MigrationCallout' import { STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_CAPPED, STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED, @@ -70,8 +66,6 @@ export const StorageSettings = () => { const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() - const showMigrationCallout = useFlag('storageMigrationCallout') - const { can: canReadStorageSettings, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_READ, '*' @@ -88,10 +82,6 @@ export const StorageSettings = () => { isSuccess, isError, } = useProjectStorageConfigQuery({ projectRef }) - const isListV2UpgradeAvailable = - !!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'main' - const isListV2Upgrading = - !!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'canary' const { runCondition: sizeLimitCheckCondition, @@ -282,209 +272,200 @@ export const StorageSettings = () => { /> )} {isSuccess && ( - <> - {showMigrationCallout && ( - <> - {isListV2UpgradeAvailable && } - {isListV2Upgrading && } - - )} -
- - - ( - - Optimize and resize images on the fly.{' '} - - Learn more - - . - - } - > - - - - - )} - /> - - - - ( - - Restrict the size of files uploaded across all buckets.{' '} - - Learn more - - . - {!shouldAutoValidateBucketLimits && ( -

- Ensure that the global limit is greater than that of - individual buckets -

- )} - - } - > - -
- { - field.onChange(e) - form.clearErrors('fileSizeLimit') - }} - className="w-32 rounded-r-none border-r-0" - disabled={ - !hasAccessToFileSizeConfiguration || - !canUpdateStorageSettings - } - /> - ( - - )} - /> -
-
- {sizeLimitCheckCondition === 'confirm' && ( - - )} -
- )} - /> - {fileSizeLimitError && ( - - - - )} -
- {hasLimitedStorageAccess && ( - - )} - {isSpendCapOn && ( - - )} - - {!canUpdateStorageSettings && ( - -

- You need additional permissions to update storage settings -

-
- )} - - - {form.formState.isDirty && ( - + + + + )} + /> + + + + ( + + Restrict the size of files uploaded across all buckets.{' '} + + Learn more + + . + {!shouldAutoValidateBucketLimits && ( +

+ Ensure that the global limit is greater than that of + individual buckets +

+ )} + + } + > + +
+ { + field.onChange(e) + form.clearErrors('fileSizeLimit') + }} + className="w-32 rounded-r-none border-r-0" + disabled={ + !hasAccessToFileSizeConfiguration || !canUpdateStorageSettings + } + /> + ( + + )} + /> +
+
+ {sizeLimitCheckCondition === 'confirm' && ( + + )} +
+ )} + /> + {fileSizeLimitError && ( + + + + )} +
+ {hasLimitedStorageAccess && ( + + )} + {isSpendCapOn && ( + + )} + + {!canUpdateStorageSettings && ( + +

+ You need additional permissions to update storage settings +

+
+ )} + + + {form.formState.isDirty && ( - -
-
- + )} + + + + )} )} diff --git a/apps/studio/components/interfaces/UserDropdown.tsx b/apps/studio/components/interfaces/UserDropdown.tsx index 8a87fa472ca..2cdf0a18ba5 100644 --- a/apps/studio/components/interfaces/UserDropdown.tsx +++ b/apps/studio/components/interfaces/UserDropdown.tsx @@ -1,4 +1,3 @@ -import { useFlag } from 'common' import { FlaskConical, Loader2, ScrollText, User2 } from 'lucide-react' import { useTheme } from 'next-themes' import Link from 'next/link' @@ -42,7 +41,6 @@ export function UserDropdown({ const { theme, setTheme } = useTheme() const appStateSnapshot = useAppStateSnapshot() const profileShowEmailEnabled = useIsFeatureEnabled('profile:show_email') - const timezonePickerEnabled = useFlag('timezonePicker') const { username, avatarUrl, primaryEmail, isLoading } = useProfileNameAndPicture() const { toggleFeaturePreviewModal } = useFeaturePreviewModal() @@ -105,7 +103,9 @@ export function UserDropdown({ )} + + )} + Theme - {timezonePickerEnabled && ( - <> - - - - - - )} + + + + + + + {showUpgradeCta && ( <> diff --git a/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx b/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx index 8844d7d7a06..f48990d77ec 100644 --- a/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx +++ b/apps/studio/components/interfaces/UserDropdown/TimezoneDropdown.tsx @@ -23,8 +23,9 @@ import { useTrack } from '@/lib/telemetry/track' const AUTO_OPTION_VALUE = '__auto__' export const TimezoneDropdown = () => { - const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone() const track = useTrack() + const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone() + const [open, setOpen] = useState(false) // The "Auto detect" row always advertises the browser's own timezone, even diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx index 93a0784e951..f4d2b74a564 100644 --- a/apps/studio/pages/new/[slug].tsx +++ b/apps/studio/pages/new/[slug].tsx @@ -107,7 +107,6 @@ const Wizard: NextPageWithLayout = () => { ) const { hasLoaded: flagsLoaded } = useFeatureFlags() - const smartRegionEnabled = useFlag('enableSmartRegion') const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate') const showInternalOnlyConfiguration = useFlag('newProjectInternalOnlyConfiguration') @@ -172,6 +171,7 @@ const Wizard: NextPageWithLayout = () => { } = useWatch({ control: form.control }) const { dirtyFields } = useFormState(form) const isDbRegionDirty = dirtyFields.dbRegion + const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS' // Read dirty state during render rather than depending on form.formState in the // effect — form.formState is a Proxy that gets a new reference every render, which @@ -221,9 +221,10 @@ const Wizard: NextPageWithLayout = () => { ? 0 : monthlyInstancePrice(instanceSize) - availableComputeCredits + const selectedCloudProvider = cloudProvider as CloudProvider const { data: autoDefaultRegion, error: defaultRegionError } = useDefaultRegionQuery( { - cloudProvider: PROVIDERS[defaultProvider].id, + cloudProvider: selectedCloudProvider, }, { enabled: flagsLoaded && !smartRegionEnabled, @@ -255,7 +256,7 @@ const Wizard: NextPageWithLayout = () => { ? availableRegionsData?.recommendations.smartGroup.name : '' - const fixedDefaultRegion = PROVIDERS[defaultProvider].default_region.displayName + const fixedDefaultRegion = PROVIDERS[selectedCloudProvider].default_region.displayName const regionError = smartRegionEnabled && defaultProvider !== 'AWS_NIMBUS' ? availableRegionsError diff --git a/apps/studio/pages/project/[ref]/auth/providers.tsx b/apps/studio/pages/project/[ref]/auth/providers.tsx index 5ddea6a4336..70d51d19b4d 100644 --- a/apps/studio/pages/project/[ref]/auth/providers.tsx +++ b/apps/studio/pages/project/[ref]/auth/providers.tsx @@ -1,4 +1,3 @@ -import { useFlag } from 'common' import { PageContainer } from 'ui-patterns/PageContainer' import { AuthProvidersForm } from '@/components/interfaces/Auth/AuthProvidersForm' @@ -12,13 +11,12 @@ import type { NextPageWithLayout } from '@/types' const ProvidersPage: NextPageWithLayout = () => { const showProviders = useIsFeatureEnabled('authentication:show_providers') const showCustomProviders = useIsFeatureEnabled('authentication:show_custom_providers') - const isOauthProvidersEnabled = useFlag('CustomOauthProviders') return ( {showProviders && } - {showCustomProviders && isOauthProvidersEnabled && } + {showCustomProviders && } ) }