mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(unified-logs): compact toggle banner and ArrowDown selects first log (#46812)
Visual cleanup of the Logs sidebar (unified logs): - Restyle the "Go back to old logs" and "Try Unified Logs" banners as full-bleed bordered strips (no card), with an icon-only switch-back button and a tooltip - Add a "Beta" badge next to the "Logs" sidebar title - Rename the product from "Logs & Analytics" to "Logs" <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Unified Logs banner with “Try Unified Logs” and “More information” actions, plus a “Go back to old logs” utility layout. * Show a **“Beta”** badge for Unified Logs eligibility. * **UX Updates** * Updated the Logs experience to consistently display under the **“Logs”** product layout. * **Tests / Documentation** * Updated end-to-end checks and examples to expect the **“Logs”** heading text exactly. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: kemal <hello@kemal.earth>
This commit is contained in:
6 files changed
+93
-63
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { CircleHelpIcon, Undo2 } from 'lucide-react'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
|
||||
interface UnifiedLogsBannerProps {
|
||||
variant: 'promo' | 'utility'
|
||||
onEnable?: () => void
|
||||
onMoreInfo?: () => void
|
||||
onSwitchBack?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function UnifiedLogsBanner({
|
||||
variant,
|
||||
onEnable,
|
||||
onMoreInfo,
|
||||
onSwitchBack,
|
||||
className,
|
||||
}: UnifiedLogsBannerProps) {
|
||||
const stripClassName = cn(
|
||||
'flex items-center justify-between gap-3 border-b border-border px-4 py-3',
|
||||
className
|
||||
)
|
||||
|
||||
if (variant === 'utility') {
|
||||
return (
|
||||
<div className={stripClassName}>
|
||||
<div className="min-w-0 space-y-0.5">
|
||||
<p className="truncate text-xs font-medium text-foreground">Go back to old logs</p>
|
||||
<p className="truncate text-xs text-foreground-light">Use the traditional interface</p>
|
||||
</div>
|
||||
<ButtonTooltip
|
||||
type="default"
|
||||
className="shrink-0 px-1.5"
|
||||
icon={<Undo2 />}
|
||||
onClick={onSwitchBack}
|
||||
tooltip={{ content: { side: 'bottom', text: 'Switch back' } }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={stripClassName}>
|
||||
<p className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">
|
||||
Try Unified Logs
|
||||
</p>
|
||||
<div className="flex shrink-0 items-center gap-x-2">
|
||||
<Button size="tiny" type="default" onClick={onEnable}>
|
||||
Enable
|
||||
</Button>
|
||||
<ButtonTooltip
|
||||
type="default"
|
||||
className="px-1.5"
|
||||
icon={<CircleHelpIcon />}
|
||||
onClick={onMoreInfo}
|
||||
tooltip={{ content: { side: 'bottom', text: 'More information' } }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ const LogsLayout = ({ title, children }: PropsWithChildren<LogsLayoutProps>) =>
|
||||
|
||||
if (!logsEnabled) {
|
||||
return (
|
||||
<ProjectLayout product="Logs & Analytics" browserTitle={{ section: title }}>
|
||||
<ProjectLayout product="Logs" browserTitle={{ section: title }}>
|
||||
<UnknownInterface urlBack={`/project/${ref}`} />
|
||||
</ProjectLayout>
|
||||
)
|
||||
@@ -33,14 +33,12 @@ const LogsLayout = ({ title, children }: PropsWithChildren<LogsLayoutProps>) =>
|
||||
|
||||
if (!canUseLogsExplorer) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<ProjectLayout isLoading product="Logs & Analytics" browserTitle={{ section: title }} />
|
||||
)
|
||||
return <ProjectLayout isLoading product="Logs" browserTitle={{ section: title }} />
|
||||
}
|
||||
|
||||
if (!isLoading && !canUseLogsExplorer) {
|
||||
return (
|
||||
<ProjectLayout product="Logs & Analytics" browserTitle={{ section: title }}>
|
||||
<ProjectLayout product="Logs" browserTitle={{ section: title }}>
|
||||
<NoPermission isFullPage resourceText="access your project's logs" />
|
||||
</ProjectLayout>
|
||||
)
|
||||
@@ -49,7 +47,7 @@ const LogsLayout = ({ title, children }: PropsWithChildren<LogsLayoutProps>) =>
|
||||
|
||||
return (
|
||||
<ProjectLayout
|
||||
product="Logs & Analytics"
|
||||
product="Logs"
|
||||
browserTitle={{ section: title }}
|
||||
productMenu={<LogsSidebarMenuV2 />}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IS_PLATFORM, useFlag, useParams } from 'common'
|
||||
import { ChevronRight, CircleHelpIcon, Plus } from 'lucide-react'
|
||||
import { ChevronRight, Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useState } from 'react'
|
||||
@@ -29,7 +29,7 @@ import { useIsETLPrivateAlpha } from '@/components/interfaces/Database/Replicati
|
||||
import { LOG_DRAIN_TYPES } from '@/components/interfaces/LogDrains/LogDrains.constants'
|
||||
import SavedQueriesItem from '@/components/interfaces/Settings/Logs/Logs.SavedQueriesItem'
|
||||
import { LogsSidebarItem } from '@/components/interfaces/Settings/Logs/SidebarV2/SidebarItem'
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
import { UnifiedLogsBanner } from '@/components/interfaces/UnifiedLogs/UnifiedLogsBanner'
|
||||
import { useContentQuery } from '@/data/content/content-query'
|
||||
import { useReplicationSourcesQuery } from '@/data/replication/sources-query'
|
||||
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
||||
@@ -241,32 +241,13 @@ export function LogsSidebarMenuV2() {
|
||||
/>
|
||||
)}
|
||||
{isUnifiedLogsEligible && (
|
||||
<FeaturePreviewSidebarPanel
|
||||
className="mx-4 mt-4"
|
||||
title="Introducing unified logs"
|
||||
description="A unified view across all services with improved filtering and real-time updates."
|
||||
illustration={<Badge variant="success">New</Badge>}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
size="tiny"
|
||||
type="default"
|
||||
onClick={() => {
|
||||
enableUnifiedLogs()
|
||||
router.push(`/project/${ref}/logs`)
|
||||
}}
|
||||
>
|
||||
Enable preview
|
||||
</Button>
|
||||
<ButtonTooltip
|
||||
type="default"
|
||||
className="px-1.5"
|
||||
icon={<CircleHelpIcon />}
|
||||
onClick={() => selectFeaturePreview('supabase-ui-preview-unified-logs')}
|
||||
tooltip={{ content: { side: 'bottom', text: 'More information' } }}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<UnifiedLogsBanner
|
||||
variant="promo"
|
||||
onEnable={() => {
|
||||
enableUnifiedLogs()
|
||||
router.push(`/project/${ref}/logs`)
|
||||
}}
|
||||
onMoreInfo={() => selectFeaturePreview('supabase-ui-preview-unified-logs')}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, type ReactNode } from 'react'
|
||||
import { Button, cn, ResizablePanel, usePanelRef } from 'ui'
|
||||
import { Badge, Button, cn, ResizablePanel, usePanelRef } from 'ui'
|
||||
|
||||
import { FeaturePreviewBadge } from '../FeaturePreviewBadge'
|
||||
import { FeaturePreviewSidebarPanel } from '../FeaturePreviewSidebarPanel'
|
||||
import { DateRangeDisabled } from './DataTable.types'
|
||||
import { DataTableFilterControls } from './DataTableFilters/DataTableFilterControls'
|
||||
@@ -12,6 +11,7 @@ import { DataTableResetButton } from './DataTableResetButton'
|
||||
import { useDataTable } from './providers/DataTableProvider'
|
||||
import { useUnifiedLogsPreview } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
||||
import { LOG_DRAIN_TYPES } from '@/components/interfaces/LogDrains/LogDrains.constants'
|
||||
import { UnifiedLogsBanner } from '@/components/interfaces/UnifiedLogs/UnifiedLogsBanner'
|
||||
|
||||
interface FilterSideBarProps {
|
||||
isFilterBarOpen: boolean
|
||||
@@ -30,11 +30,7 @@ export function FilterSideBar({
|
||||
const { ref } = useParams()
|
||||
const { table } = useDataTable()
|
||||
|
||||
const {
|
||||
disable: disableUnifiedLogs,
|
||||
isEligible: isUnifiedLogsEligible,
|
||||
isEnabled: isUnifiedLogsEnabled,
|
||||
} = useUnifiedLogsPreview()
|
||||
const { disable: disableUnifiedLogs, isEligible: isUnifiedLogsEligible } = useUnifiedLogsPreview()
|
||||
|
||||
const handleGoBackToOldLogs = () => {
|
||||
disableUnifiedLogs()
|
||||
@@ -70,29 +66,19 @@ export function FilterSideBar({
|
||||
>
|
||||
<div className="border-b border-border px-4 md:top-0">
|
||||
<div className="flex h-[48px] items-center justify-between gap-3">
|
||||
<p className="text-foreground text-lg">Logs</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-foreground text-lg">Logs</p>
|
||||
{isUnifiedLogsEligible && <Badge variant="default">Beta</Badge>}
|
||||
</div>
|
||||
{table.getState().columnFilters.length ? <DataTableResetButton /> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isUnifiedLogsEligible && (
|
||||
<UnifiedLogsBanner variant="utility" onSwitchBack={handleGoBackToOldLogs} />
|
||||
)}
|
||||
|
||||
<div className="flex-1 p-2 sm:overflow-y-scroll">
|
||||
{isUnifiedLogsEligible && (
|
||||
<FeaturePreviewSidebarPanel
|
||||
className="mx-2 mt-2 mb-3"
|
||||
title="Go back to old logs"
|
||||
description="Use the traditional interface"
|
||||
illustration={
|
||||
isUnifiedLogsEnabled ? (
|
||||
<FeaturePreviewBadge featureKey={LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS} />
|
||||
) : undefined
|
||||
}
|
||||
actions={
|
||||
<Button type="default" size="tiny" onClick={handleGoBackToOldLogs}>
|
||||
Switch back
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<DataTableFilterControls dateRangeDisabled={dateRangeDisabled} />
|
||||
{afterFilters}
|
||||
<FeaturePreviewSidebarPanel
|
||||
|
||||
@@ -142,7 +142,7 @@ pnpm run e2e -- --ui
|
||||
Example:
|
||||
|
||||
```ts
|
||||
await expect(page.getByRole('heading', { name: 'Logs & Analytics' }), {
|
||||
await expect(page.getByRole('heading', { name: 'Logs', exact: true }), {
|
||||
message: 'Logs heading should be visible',
|
||||
}).toBeVisible()
|
||||
```
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { test } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
|
||||
const LOGS_PAGES = [
|
||||
{ label: 'API Gateway', route: 'edge-logs' },
|
||||
{ label: 'Postgres', route: 'postgres-logs' },
|
||||
@@ -48,8 +50,8 @@ test.describe('Logs', () => {
|
||||
*/
|
||||
await page.goto(toUrl(`/project/${ref}/logs/${logPage.route}`))
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Logs & Analytics' }), {
|
||||
message: 'Logs & Analytics heading should be visible',
|
||||
await expect(page.getByRole('heading', { name: 'Logs', exact: true }), {
|
||||
message: 'Logs heading should be visible',
|
||||
}).toBeVisible()
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user