feat(studio): remove unified logs banner and deprioritise tos banner (#49239)

## What kind of change does this PR introduce?

Studio UI cleanup for sidebar BannerStack items and Unified Logs preview
defaults.

## What is the current behavior?

The sidebar BannerStack shows both a Unified Logs promo banner and a
Terms of Service update notice. Unified Logs has been default opt-in for
a while, and the ToS banner currently shares priority with other
higher-value notices. The default opt-in behaviour is still gated behind
the `unifiedLogsDefaultOptIn` feature flag.

Closes
[DEPR-646](https://linear.app/supabase/issue/DEPR-646/remove-unified-logs-banner-and-deprioritise-tos-banner).

| Before |
| --- |
| <img width="1024" height="759" alt="5717"
src="https://github.com/user-attachments/assets/1a19e6f6-0c7f-49e8-8e7e-9f83196b2353"
/> |

## What is the new behavior?

- Removes the Unified Logs BannerStack item and its component
- Keeps the ToS update banner but lowers its priority so other banners
surface first
- Sets Unified Logs `isDefaultOptIn` to `true` and removes
`unifiedLogsDefaultOptIn` flag usage

## To test

- Open any project in Studio (e.g. `/project/<ref>`)
- Confirm the sidebar BannerStack no longer shows the "Unified Logs is
here" banner
- If you have not dismissed the ToS notice and it is still before the
expiry date, confirm it still appears but sits behind higher-priority
banners (e.g. free micro upgrade on eligible projects)
- Open `/project/<ref>/logs` and confirm Unified Logs loads by default
for users who have not previously toggled the preview off

## After merge

- [ ] Retire the `unifiedLogsDefaultOptIn` PostHog flag

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

- **New Features**
- Unified Logs preview is now enabled by default when available, while
preserving individual user choices.

- **Bug Fixes**
- Terms of Service update notifications now appear with higher priority.

- **Changes**
- Removed the Unified Logs promotional banner, including related
navigation, dismissal, and tracking behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Danny White authored and GitHub committed 2026-08-19 17:37:14 +10:00
1 parent 7bac134fa5
commit b3edaa02f0
7 files changed
+4 -216

No files matched your search

@@ -30,7 +30,7 @@ export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
id: 'tos-update-banner',
isDismissed: false,
content: <BannerTOSUpdate />,
priority: 2,
priority: 0,
})
} else {
dismissBanner('tos-update-banner')
@@ -98,15 +98,13 @@ export const useIsColumnLevelPrivilegesEnabled = () => {
}
export const useUnifiedLogsPreview = () => {
const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn')
const { flags, isInitialized, onUpdateFlag } = useFeaturePreviewContext()
const isLoading = !isInitialized
const isEnabled = IS_PLATFORM && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS]
const hasToggledPreview = !!safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS)
const isDefaultOptIn =
IS_PLATFORM && isInitialized && unifiedLogsDefaultOptIn && !hasToggledPreview
const isDefaultOptIn = IS_PLATFORM && !hasToggledPreview
const enable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, true)
const disable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, false)
@@ -36,7 +36,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
const isDatabaseConnectionsEnabled = useFlag('topForPostgres')
const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn')
const isSqlEditorManualSaveForced = useFlag('sqlEditorManualSaveForced')
return useMemo(() => {
@@ -49,7 +48,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
enabled: true,
isNew: true,
isPlatformOnly: true,
isDefaultOptIn: unifiedLogsDefaultOptIn,
isDefaultOptIn: true,
getRoute: (ref?: string) => `/project/${ref}/logs`,
},
{
@@ -142,7 +141,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
return previews.sort((a, b) => Number(b.isNew) - Number(a.isNew))
}, [
unifiedLogsDefaultOptIn,
isSqlEditorManualSaveForced,
isPlatformWebhooksEnabled,
jitDbAccessEnabled,
@@ -79,7 +79,6 @@ vi.mock('common', () => ({
`free-micro-upgrade-banner-dismissed-${ref}`,
PROJECT_INTEGRATION_BANNER_DISMISSED: (ref: string, integrationSource: string) =>
`project-integration-banner-dismissed-${ref}-${integrationSource}`,
UNIFIED_LOGS_BANNER_DISMISSED: 'unified-logs-banner-dismissed',
},
isFeatureEnabled: () => false,
}))
@@ -177,7 +176,6 @@ vi.mock('@/hooks/misc/useLocalStorage', () => ({
vi.mock('@/components/ui/BannerStack/BannerStackProvider', () => ({
BANNER_ID: {
FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner',
UNIFIED_LOGS: 'unified-logs-banner',
},
useBannerStack: () => ({
addBanner: mockAddBanner,
@@ -190,19 +188,6 @@ vi.mock('@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade', () => ({
BannerFreeMicroUpgrade: () => null,
}))
vi.mock('@/components/ui/BannerStack/Banners/BannerUnifiedLogs', () => ({
BannerUnifiedLogs: () => null,
}))
vi.mock('@/components/interfaces/App/FeaturePreview/FeaturePreviewContext', () => ({
useUnifiedLogsPreview: () => ({
isEnabled: false,
isLoading: false,
enable: () => {},
disable: () => {},
}),
}))
vi.mock('@/data/usage/resource-warnings-query', () => ({
useResourceWarningsQuery: () => ({ data: mockResourceWarningsState.current }),
}))
@@ -1,4 +1,4 @@
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, mergeRefs, useParams } from 'common'
import { LOCAL_STORAGE_KEYS, mergeRefs, useParams } from 'common'
import { AnimatePresence, motion } from 'framer-motion'
import { XIcon } from 'lucide-react'
import Head from 'next/head'
@@ -43,7 +43,6 @@ import { UpgradingState } from './UpgradingState'
import { CreateBranchModal } from '@/components/interfaces/BranchManagement/CreateBranchModal'
import { ProjectAPIDocs } from '@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
import { BannerFreeMicroUpgrade } from '@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade'
import { BannerUnifiedLogs } from '@/components/ui/BannerStack/Banners/BannerUnifiedLogs'
import { BANNER_ID, useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import PartnerIcon from '@/components/ui/PartnerIcon'
@@ -155,10 +154,6 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
LOCAL_STORAGE_KEYS.FREE_MICRO_UPGRADE_BANNER_DISMISSED(selectedProject?.ref ?? ''),
false
)
const [isUnifiedLogsBannerDismissed] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UNIFIED_LOGS_BANNER_DISMISSED,
false
)
const [isProjectIntegrationBannerDismissed, setIsProjectIntegrationBannerDismissed] =
useLocalStorageQuery(
getProjectIntegrationBannerDismissKey({
@@ -236,20 +231,6 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
dismissBanner,
])
useEffect(() => {
if (!selectedProject?.ref) return
if (IS_PLATFORM && !isUnifiedLogsBannerDismissed) {
addBanner({
id: BANNER_ID.UNIFIED_LOGS,
isDismissed: false,
content: <BannerUnifiedLogs />,
priority: 1,
})
} else {
dismissBanner(BANNER_ID.UNIFIED_LOGS)
}
}, [selectedProject?.ref, isUnifiedLogsBannerDismissed, addBanner, dismissBanner])
useLayoutEffect(() => {
const unregister = registerOpenMenu(() => {
setMobileSheetContent(
@@ -7,7 +7,6 @@ export const BANNER_ID = {
RLS_EVENT_TRIGGER: 'rls-event-trigger-banner',
FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner',
TOS_UPDATE: 'tos-update-banner',
UNIFIED_LOGS: 'unified-logs-banner',
} as const
export type BannerId = (typeof BANNER_ID)[keyof typeof BANNER_ID]
@@ -1,173 +0,0 @@
import { LOCAL_STORAGE_KEYS } from 'common'
import { useParams } from 'common/hooks'
import dayjs from 'dayjs'
import { AnimatePresence, motion } from 'framer-motion'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import { Badge, Button, cn } from 'ui'
import { BannerCard } from '../BannerCard'
import { useBannerStack } from '../BannerStackProvider'
import {
useFeaturePreviewModal,
useUnifiedLogsPreview,
} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useTrack } from '@/lib/telemetry/track'
// Number of rows visible in the carousel viewport. We keep one extra row in
// state so the row sliding out of view has something to animate towards.
const VISIBLE_ROWS = 3
const ROW_HEIGHT = 28
const TICK_MS = 3000
const SAMPLES = [200, 200, 201, 200, 304, 400, 200, 500] as const
interface LogEntry {
id: number
timestamp: string
status: number
}
const makeEntry = (id: number, offsetSeconds = 0): LogEntry => ({
id,
timestamp: dayjs().subtract(offsetSeconds, 'second').format('DD MMM YY HH:mm:ss'),
status: SAMPLES[id % SAMPLES.length],
})
const LogRow = ({ entry }: { entry: LogEntry }) => {
const isDestructive = entry.status >= 500
const isWarning = entry.status >= 400 && entry.status < 500
return (
<div
className="flex items-center gap-x-3 font-mono text-xs whitespace-nowrap text-foreground-light"
style={{ height: ROW_HEIGHT }}
>
<span
className={cn(
'size-1.5 shrink-0 rounded-full bg-foreground-muted',
isWarning && 'bg-warning',
isDestructive && 'bg-destructive'
)}
/>
<span
className={cn(
'tabular-nums',
isWarning && 'text-warning',
isDestructive && 'text-destructive'
)}
>
{entry.timestamp}
</span>
<span
className={cn(
'tabular-nums',
isWarning && 'text-warning',
isDestructive && 'text-destructive'
)}
>
{entry.status}
</span>
</div>
)
}
const UnifiedLogsCarousel = () => {
const counter = useRef(VISIBLE_ROWS)
const [logs, setLogs] = useState<LogEntry[]>(() =>
Array.from({ length: VISIBLE_ROWS + 1 }, (_, i) => makeEntry(VISIBLE_ROWS - i, i))
)
useEffect(() => {
const interval = setInterval(() => {
counter.current += 1
const next = makeEntry(counter.current)
setLogs((prev) => [next, ...prev].slice(0, VISIBLE_ROWS + 1))
}, TICK_MS)
return () => clearInterval(interval)
}, [])
return (
<div className="relative w-full overflow-hidden" style={{ height: ROW_HEIGHT * VISIBLE_ROWS }}>
<AnimatePresence initial={false}>
{logs.map((entry, index) => (
<motion.div
key={entry.id}
layout
initial={{ opacity: 0 }}
animate={{ opacity: Math.max(1 - index * 0.4, 0) }}
exit={{ opacity: 0 }}
>
<LogRow entry={entry} />
</motion.div>
))}
</AnimatePresence>
</div>
)
}
export const BannerUnifiedLogs = () => {
const { ref } = useParams()
const track = useTrack()
const { dismissBanner } = useBannerStack()
const { isEnabled } = useUnifiedLogsPreview()
const { selectFeaturePreview } = useFeaturePreviewModal()
const [, setIsDismissed] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UNIFIED_LOGS_BANNER_DISMISSED,
false
)
return (
<BannerCard
onDismiss={() => {
setIsDismissed(true)
dismissBanner('unified-logs-banner')
track('unified_logs_banner_dismiss_button_clicked')
}}
>
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-2 items-start">
<Badge variant="success" className="-ml-0.5 uppercase inline-flex items-center">
Beta
</Badge>
<div className="-mx-6 w-[calc(100%+3rem)] bg-linear-to-t from-background to-transparent px-6 py-2 border-b">
<UnifiedLogsCarousel />
</div>
</div>
<div className="flex flex-col gap-y-1 mb-2">
<p className="text-sm font-medium">Unified Logs is here</p>
<p className="text-xs text-foreground-lighter text-balance">
Search and correlate logs across all of your services from a single place.
</p>
</div>
<div className="flex gap-2">
{isEnabled ? (
<Button variant="default" size="tiny" asChild>
<Link
href={`/project/${ref}/logs`}
onClick={() => {
track('unified_logs_banner_cta_button_clicked', { is_enabled: true })
setIsDismissed(true)
dismissBanner('unified-logs-banner')
}}
>
Explore Unified Logs
</Link>
</Button>
) : (
<Button
variant="default"
size="tiny"
onClick={() => {
track('unified_logs_banner_cta_button_clicked', { is_enabled: false })
selectFeaturePreview(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS)
}}
>
Enable Unified Logs
</Button>
)}
</div>
</div>
</BannerCard>
)
}