Files
supabase/apps/studio/components/interfaces/App/AppBannerWrapper.tsx
65033221fb feat(studio): add logs.all deprecation banner (#49059)
Informational banner for the `logs.all` Management API removal on Sept
23, in the Logs and Observability sections.

* Untargeted. Whether a project calls the endpoint is behaviour that no
API response carries, so precise targeting needs a mgmt-api change (we
aimed for speed and less complexity here). Copy is informational rather
than "action required" since most viewers won't be affected.
* Uses `BannerStack` (bottom-right card) rather than the top header
banner, at priority 4 so it renders as the front card. Note this pushes
`database-connections-banner` (p2) and `index-advisor-banner` (p3) into
peek slivers on Observability.
* Short Notice card: title, one line of copy with `logs.all` inline, and
a Learn more link to the changelog.
* Waits for localStorage before showing, and BannerStack ignores stale
dismiss timers when a banner is revived (avoids flash-then-disappear on
refresh).
* Dismiss is browser-level; self-expires Sept 24 via
`LogsAllDeprecationExpiry`.
* Cleanup tracked in GROWTH-1104.
* Tested in staging.

Check in:

- /project/_/logs (unified logs)
- /project/_/logs/explorer
- /project/_/observability

| After |
| --- |
| <img width="626" height="528" alt="CleanShot 2026-08-20 at 12 29
49@2x"
src="https://github.com/user-attachments/assets/2044966d-bc83-4f88-ac75-1b8ff80be08d"
/> | <img width="622" height="440" alt="CleanShot 2026-08-20 at 12 28
33@2x"
src="https://github.com/user-attachments/assets/1dc768cd-837c-4a5a-a3fa-7b6986fe5876"
/> |

Resolves GROWTH-1093.

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

## New Features
* Added a dismissible notice about the `logs.all` endpoint retirement on
September 23, 2026.
* The notice appears on relevant Logs and Observability pages with
streamlined migration guidance.
* Clarified that dashboard logs remain unchanged.
* Dismissal preferences are saved, and notices remain visible or are
removed reliably during navigation.

## Telemetry
* Added tracking for notice display and dismissal interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Danny White <dnywh@users.noreply.github.com>
2026-08-20 14:17:20 +00:00

127 lines
4.1 KiB
TypeScript

import { IS_PLATFORM, LOCAL_STORAGE_KEYS, useFlag } from 'common'
import dayjs from 'dayjs'
import { usePathname } from 'next/navigation'
import { PropsWithChildren, useEffect, useRef, useState } from 'react'
import { OrganizationResourceBanner } from '../Organization/HeaderBanner'
import { isLogsOrObservabilityPath } from './AppBannerWrapper.utils'
import { ClockSkewBanner } from '@/components/layouts/AppLayout/ClockSkewBanner'
import { NoticeBanner } from '@/components/layouts/AppLayout/NoticeBanner'
import { StatusPageBanner } from '@/components/layouts/AppLayout/StatusPageBanner'
import { BannerLogsAllDeprecation } from '@/components/ui/BannerStack/Banners/BannerLogsAllDeprecation'
import { BannerTOSUpdate } from '@/components/ui/BannerStack/Banners/BannerTOSUpdate'
import { BANNER_ID, useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useTrack } from '@/lib/telemetry/track'
const TOSUpdateExpiry = new Date('2026-08-29T00:00:00Z')
// Update this whenever the banner content changes so old client bundles stop
// displaying the notice after the removal date passes.
const LogsAllDeprecationExpiry = dayjs('2026-09-24T00:00:00Z')
// setTimeout overflows above ~24.8 days; re-arm until the real expiry.
const MAX_TIMEOUT_MS = 2_147_483_647
export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
const showNoticeBanner = useFlag('showNoticeBanner')
const clockSkewBanner = useFlag('clockSkewBanner')
const { addBanner, dismissBanner } = useBannerStack()
const pathname = usePathname()
const track = useTrack()
const [TOSUpdateAcknowledged, , { isSuccess }] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.TERMS_OF_SERVICE_UPDATE,
false
)
useEffect(() => {
if (Date.now() >= TOSUpdateExpiry.getTime()) return
if (isSuccess && !TOSUpdateAcknowledged) {
addBanner({
id: 'tos-update-banner',
isDismissed: false,
content: <BannerTOSUpdate />,
priority: 0,
})
} else {
dismissBanner('tos-update-banner')
}
}, [TOSUpdateAcknowledged, isSuccess, addBanner, dismissBanner])
const [isLogsAllDeprecationDismissed, , { isSuccess: isLogsAllDeprecationLoaded }] =
useLocalStorageQuery(LOCAL_STORAGE_KEYS.LOGS_ALL_DEPRECATION_2026_09_23, false)
const [isLogsAllDeprecationExpired, setIsLogsAllDeprecationExpired] = useState(
() => !dayjs().isBefore(LogsAllDeprecationExpiry)
)
useEffect(() => {
if (isLogsAllDeprecationExpired) return
let timeoutId: ReturnType<typeof setTimeout> | undefined
const armExpiryTimer = () => {
const msUntilExpiry = LogsAllDeprecationExpiry.diff(dayjs())
if (msUntilExpiry <= 0) {
setIsLogsAllDeprecationExpired(true)
return
}
timeoutId = setTimeout(armExpiryTimer, Math.min(msUntilExpiry, MAX_TIMEOUT_MS))
}
armExpiryTimer()
return () => clearTimeout(timeoutId)
}, [isLogsAllDeprecationExpired])
const hasTrackedLogsAllExposure = useRef(false)
useEffect(() => {
if (!isLogsAllDeprecationLoaded || pathname == null) return
const shouldShow =
IS_PLATFORM &&
!isLogsAllDeprecationExpired &&
isLogsOrObservabilityPath(pathname) &&
!isLogsAllDeprecationDismissed
if (!shouldShow) {
dismissBanner(BANNER_ID.LOGS_ALL_DEPRECATION)
return
}
addBanner({
id: BANNER_ID.LOGS_ALL_DEPRECATION,
isDismissed: false,
content: <BannerLogsAllDeprecation />,
priority: 4,
})
if (!hasTrackedLogsAllExposure.current) {
hasTrackedLogsAllExposure.current = true
track('logs_all_deprecation_banner_exposed')
}
}, [
pathname,
isLogsAllDeprecationLoaded,
isLogsAllDeprecationDismissed,
isLogsAllDeprecationExpired,
addBanner,
dismissBanner,
track,
])
return (
<div className="flex flex-col">
<div className="shrink-0">
<StatusPageBanner />
{showNoticeBanner && <NoticeBanner />}
<OrganizationResourceBanner />
{clockSkewBanner && <ClockSkewBanner />}
</div>
{children}
</div>
)
}