Files
Monica Khoury cb21b89899 feat: make feature previews easier to discover (#50086)
## What kind of change does this PR introduce?

Feature previews are only discoverable if you already know to look for
the "Feature previews" item under the account profile dropdown. There's
no way to find or toggle them from Cmd+K.

Fixes:
[FE-4305](https://linear.app/supabase/issue/FE-4305/make-feature-previews-easier-to-discover).

## What is the new behavior?

- Adds a "Feature previews..." command to Cmd+K that opens a drill-down
page listing all available previews, grouped by category (mirroring the
existing settings modal's grouping via a new shared
useVisibleFeaturePreviewsByCategory hook, so the two can't drift apart).
- Each preview can be toggled on/off directly from the list, with a
"New" badge for new previews and a "Default" badge + hint for previews
that can no longer be turned off.
- Each preview also has a hidden "View details" command (surfaces via
search) that opens the full settings modal to that preview's
description/discussion link.

## Heads up for reviewers/testing

Enabling a feature preview only navigates to its page when you're
already within a project route in the URL (`/project/<ref>/...`). In the
preview environment, there’s no project in scope, so enabling the
feature still works and updates the flag, but it won't navigate to the
feature's project-specific page.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
  - Added feature preview pages and actions to the command menu.
  - Feature previews are organized by category for easier browsing.
- Enabling a preview with a dedicated page takes you directly to that
page.
- Preview activation and deactivation now provide status notifications.

- **Bug Fixes**
- Preview toggles and project navigation no longer unexpectedly return
users to the root command menu.
- Only relevant, available previews are shown based on the current
environment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-21 17:52:15 +03:00

191 lines
6.7 KiB
TypeScript

import { LOCAL_STORAGE_KEYS, useFlag } from 'common'
import { useMemo } from 'react'
import { type BannerId } from '@/components/ui/BannerStack/BannerStackProvider'
import { IS_PLATFORM } from '@/lib/constants'
export type FeaturePreview = {
key: string
name: string
discussionsUrl?: string
isNew: boolean
/** If feature flag is only relevant for the hosted platform */
isPlatformOnly: boolean
/** If feature flag should be enabled by default for users, if not yet toggled before */
isDefaultOptIn: boolean
/** Visibility in the feature preview modal (For feature flagging a feature preview) */
enabled: boolean
/**
* Forces the preview on for this user, whatever they previously chose — for a
* preview that has become the default behavior. Overrides both `isDefaultOptIn`
* and a stored opt-out, and takes away the ability to turn the preview back off.
*/
isForced?: boolean
/** Optional category that the feature preview falls under, defaults to "Others" in the UI otherwise */
category?: 'observability' | 'database' | 'editors'
/**
* Where to send the user after enabling, to try the feature out. Omit if the
* feature has no single destination (e.g. a global layout change).
*/
getRoute?: (ref?: string) => string
bannerId?: BannerId
}
export const useFeaturePreviews = (): FeaturePreview[] => {
const isPlatformWebhooksEnabled = useFlag('platformWebhooks')
const jitDbAccessEnabled = useFlag('jitDbAccess')
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
const isExplorerEnabled = useFlag('explorer')
const isStorageVersioningEnabled = useFlag('storageVersioningPrivateAlpha')
const isSqlEditorManualSaveForced = useFlag('sqlEditorManualSaveForced')
return useMemo(() => {
const previews: FeaturePreview[] = [
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_EXPLORER,
name: 'Explorer & Notebooks',
category: 'editors',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/49916',
enabled: isExplorerEnabled,
isNew: true,
isPlatformOnly: true,
isDefaultOptIn: false,
getRoute: (ref?: string) => `/project/${ref}/explorer`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS,
name: 'Updated Logs interface',
category: 'observability',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/37234',
enabled: true,
isNew: true,
isPlatformOnly: true,
isDefaultOptIn: true,
getRoute: (ref?: string) => `/project/${ref}/logs`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_ADVISOR_RULES,
name: 'Disable Advisor rules',
discussionsUrl: undefined,
enabled: true,
isNew: false,
isPlatformOnly: true,
isDefaultOptIn: false,
getRoute: (ref?: string) => `/project/${ref}/advisors/rules/security`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF,
name: 'PG Delta Diff',
discussionsUrl: undefined,
isNew: false,
isPlatformOnly: true,
isDefaultOptIn: true,
enabled: true,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS,
name: 'Platform webhooks',
discussionsUrl: undefined,
isNew: false,
isPlatformOnly: true,
isDefaultOptIn: false,
enabled: isPlatformWebhooksEnabled,
getRoute: (ref?: string) => `/project/${ref}/settings/webhooks`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS,
name: 'Temporary database access',
category: 'database',
discussionsUrl: undefined,
isNew: false,
isPlatformOnly: true,
isDefaultOptIn: false,
enabled: jitDbAccessEnabled,
getRoute: (ref?: string) => `/project/${ref}/database/settings`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS,
name: 'Column-level privileges',
category: 'database',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/20295',
enabled: true,
isNew: false,
isPlatformOnly: false,
isDefaultOptIn: false,
getRoute: (ref?: string) => `/project/${ref}/database/column-privileges`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE,
name: 'One-Click Integrations',
discussionsUrl: undefined,
enabled: isMarketplaceEnabled,
isNew: true,
isPlatformOnly: false,
isDefaultOptIn: true,
getRoute: (ref?: string) => `/project/${ref}/integrations`,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE,
category: 'editors',
name: 'Disable snippet auto-saving',
discussionsUrl: undefined,
isNew: true,
isPlatformOnly: true,
isDefaultOptIn: false,
enabled: true,
// Manual saving is becoming the default for the SQL Editor. The preview
// stays listed so users who lose their local storage can opt back in
// before the rollout reaches them.
isForced: isSqlEditorManualSaveForced,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_STORAGE_VERSIONING,
name: 'Storage versioning',
discussionsUrl: undefined,
isNew: true,
isPlatformOnly: true,
isDefaultOptIn: true,
enabled: isStorageVersioningEnabled,
getRoute: (ref?: string) => `/project/${ref}/storage/files`,
},
]
return previews.sort((a, b) => Number(b.isNew) - Number(a.isNew))
}, [
isSqlEditorManualSaveForced,
isPlatformWebhooksEnabled,
jitDbAccessEnabled,
isMarketplaceEnabled,
isExplorerEnabled,
isStorageVersioningEnabled,
])
}
export type FeaturePreviewCategoryGroup = {
category: FeaturePreview['category']
previews: FeaturePreview[]
}
/**
* The visible feature previews (respecting platform-only/enabled gating),
* grouped by category in first-seen order, uncategorized previews last as a
* final `category: undefined` group. Shared by the feature preview modal and
* the Cmd+K "Feature previews" page so both list the exact same previews.
*/
export const useVisibleFeaturePreviewsByCategory = (): FeaturePreviewCategoryGroup[] => {
const featurePreviews = useFeaturePreviews()
return useMemo(() => {
const previews = (
IS_PLATFORM ? featurePreviews : featurePreviews.filter((preview) => !preview.isPlatformOnly)
).filter((preview) => preview.enabled)
const categories = [...new Set(previews.map((preview) => preview.category).filter(Boolean))]
return categories.concat(undefined).map((category) => ({
category,
previews: previews.filter((preview) => preview.category === category),
}))
}, [featurePreviews])
}