Files
supabase/apps/studio/components/interfaces/UnifiedLogs/ServiceFlowPanel.tsx
T
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

277 lines
9.9 KiB
TypeScript

import { useParams } from 'common'
import { Check, Clock, Copy } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import {
Button,
copyToClipboard,
ResizableHandle,
ResizablePanel,
Skeleton,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { PostgresFlowDetail } from './ServiceFlow/components/PostgresFlowDetail'
import {
MemoizedEdgeFunctionBlock,
MemoizedGoTrueBlock,
MemoizedNetworkBlock,
MemoizedPostgresBlock,
MemoizedPostgRESTBlock,
MemoizedStorageBlock,
} from './ServiceFlow/components/ServiceBlocks'
import { ServiceFlowPanelControls } from './ServiceFlow/components/ServiceFlowPanelControls'
import { DetailSectionHeader } from './ServiceFlow/components/shared/DetailSection'
import { ColumnSchema } from './UnifiedLogs.schema'
import { QuerySearchParamsType } from './UnifiedLogs.types'
import { getRawLogData, getRowTimestampMs } from './UnifiedLogs.utils'
import { useDataTable } from '@/components/ui/DataTable/providers/DataTableProvider'
import {
SERVICE_FLOW_TYPES,
ServiceFlowType,
useUnifiedLogInspectionQuery,
} from '@/data/logs/unified-log-inspection-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
interface ServiceFlowPanelProps {
dock: 'bottom' | 'right'
setDock: (value: 'bottom' | 'right') => void
selectedRow?: ColumnSchema
selectedRowKey: string
searchParameters: QuerySearchParamsType
}
export function getLogDataForMetadataVisibility(data: unknown, metadataVisible: boolean) {
if (metadataVisible || typeof data !== 'object' || data === null) return data
const redactedData = { ...data, metadata: undefined }
const rawLogData = 'raw_log_data' in data ? data.raw_log_data : undefined
if (typeof rawLogData !== 'object' || rawLogData === null) return redactedData
return {
...redactedData,
raw_log_data: { ...rawLogData, metadata: undefined },
}
}
export function ServiceFlowPanel({
dock,
setDock,
selectedRow,
selectedRowKey,
searchParameters,
}: ServiceFlowPanelProps) {
const { table, filterFields } = useDataTable()
const { ref: projectRef } = useParams()
const [activeTab, setActiveTab] = useState('overview')
const [jsonCopied, setJsonCopied] = useState(false)
const overviewScrollRef = useRef<HTMLDivElement>(null)
const jsonScrollRef = useRef<HTMLDivElement>(null)
useEffect(() => {
overviewScrollRef.current?.scrollTo({ top: 0 })
jsonScrollRef.current?.scrollTo({ top: 0 })
}, [selectedRowKey])
const logType = selectedRow?.log_type
const normalizedLogType = logType === 'edge function' ? 'edge-function' : logType
const serviceFlowType: ServiceFlowType | undefined = SERVICE_FLOW_TYPES.includes(
normalizedLogType as ServiceFlowType
)
? (normalizedLogType as ServiceFlowType)
: undefined
const shouldShowServiceFlow = !!serviceFlowType
useEffect(() => {
if (!shouldShowServiceFlow && activeTab === 'overview') {
setActiveTab('raw-json')
}
}, [shouldShowServiceFlow, activeTab])
const { logsMetadata } = useIsFeatureEnabled(['logs:metadata'])
const timestampMs = getRowTimestampMs(selectedRow)
// Query the logs API directly
const {
data: serviceFlowData,
isPending: isLoading,
error,
} = useUnifiedLogInspectionQuery(
{
projectRef: projectRef,
logId: selectedRow?.id,
type: serviceFlowType,
search: searchParameters,
logTimestampMs: timestampMs,
},
{ enabled: Boolean(selectedRow?.id) && Boolean(serviceFlowType) }
)
if (!selectedRowKey || !selectedRow) return null
const formattedTime = timestampMs ? new Date(timestampMs).toLocaleString() : null
// Prepare JSON data for Raw JSON tab
const jsonData =
shouldShowServiceFlow && serviceFlowData?.result?.[0] ? serviceFlowData.result[0] : selectedRow
const rawLogData = getRawLogData(jsonData)
const formattedJsonData = getLogDataForMetadataVisibility(rawLogData, logsMetadata)
return (
<>
<ResizableHandle withHandle />
<ResizablePanel
id="log-sidepanel"
defaultSize={400}
minSize={dock === 'bottom' ? 300 : 400}
className="bg-dash-sidebar"
>
<div className="flex h-full flex-col overflow-hidden">
<Tabs
defaultValue={shouldShowServiceFlow ? 'overview' : 'raw-json'}
value={activeTab}
onValueChange={setActiveTab}
className="flex h-full w-full flex-col"
>
<div className="flex items-center justify-between px-4 border-b border-border">
<TabsList className="flex h-auto gap-x-4 rounded-none border-none!">
{shouldShowServiceFlow && (
<TabsTrigger
value="overview"
className="border-b py-3 font-mono text-xs uppercase"
>
Overview
</TabsTrigger>
)}
<TabsTrigger value="raw-json" className="border-b py-3 font-mono text-xs uppercase">
Raw JSON
</TabsTrigger>
</TabsList>
<ServiceFlowPanelControls dock={dock} setDock={setDock} />
</div>
{shouldShowServiceFlow && (
<TabsContent
ref={overviewScrollRef}
value="overview"
className="mt-0 grow overflow-auto py-2"
>
{error ? (
<div className="py-8 text-center text-destructive">Error: {error.toString()}</div>
) : serviceFlowType === 'postgres' ? (
<PostgresFlowDetail
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : (
<div>
<DetailSectionHeader
title="Request started"
icon={Clock}
summary={formattedTime ?? undefined}
/>
<MemoizedNetworkBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
{serviceFlowType === 'auth' ? (
<MemoizedGoTrueBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : serviceFlowType === 'edge-function' ? (
<MemoizedEdgeFunctionBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : serviceFlowType === 'storage' ? (
<MemoizedStorageBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
) : (
<>
<MemoizedPostgRESTBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
<MemoizedPostgresBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
</>
)}
</div>
)}
</TabsContent>
)}
<TabsContent
ref={jsonScrollRef}
value="raw-json"
className="mt-0 grow overflow-auto bg-surface-100/50"
>
{isLoading && shouldShowServiceFlow && (
<div className="flex items-center gap-3 border-b border-border bg-surface-100 p-3 text-foreground-light">
<Skeleton className="h-4 w-4 animate-pulse rounded-full" />
<span className="text-sm">Enriching log...</span>
</div>
)}
<div className="sticky top-2 z-10 flex justify-end px-2 -mb-9 pointer-events-none">
<Button
size="tiny"
className="pointer-events-auto px-1.5"
icon={jsonCopied ? <Check size={12} /> : <Copy size={12} />}
onClick={() => {
copyToClipboard(JSON.stringify(formattedJsonData, null, 2))
setJsonCopied(true)
setTimeout(() => setJsonCopied(false), 1000)
}}
>
{jsonCopied ? 'Copied' : ''}
</Button>
</div>
<CodeBlock
language="json"
hideCopy
wrapperClassName="!overflow-visible bg-surface-100/50 [&_pre]:!bg-surface-100/50"
className="rounded-none border-none [&_code]:!leading-tight [&_pre]:!leading-tight"
>
{JSON.stringify(formattedJsonData, null, 2)}
</CodeBlock>
</TabsContent>
</Tabs>
</div>
</ResizablePanel>
</>
)
}