Files
supabase/apps/studio/components/interfaces/UnifiedLogs/ServiceFlowPanel.tsx
kemal.earth 65365213af feat(studio): logs header improvements (#45275)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

A little bit of tidy up here so the header area of unified logs isn't so
dominant. Moved actions to the same line as search bar and made other
parts a little more subtle, so the focus reamains on the logs
themselves.

| Before | After |
|--------|--------|
| <img width="980" height="213" alt="Screenshot 2026-04-27 at 11 47 37"
src="https://github.com/user-attachments/assets/ae22e7dd-272f-4433-a270-67b550a00536"
/> | <img width="893" height="153" alt="Screenshot 2026-04-27 at 12 27
17"
src="https://github.com/user-attachments/assets/87b8cfc9-66a4-4634-a3c6-c45e4b8fc486"
/> |






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

* **New Features**
  * Keyboard shortcut to toggle filter visibility in logs.
* Consolidated top bar with refresh, view options, download, and live
controls—desktop and mobile optimized.

* **Style**
* More compact, organized header with tooltips showing live status and
shortcut hints.
* Reduced filter input typography and streamlined mobile filter trigger.

* **Other**
* Side panel sizing and logs area layout refined for clearer visuals and
consistent header/body styling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 11:51:00 +01:00

239 lines
9.2 KiB
TypeScript

import { useParams } from 'common'
import { useState } from 'react'
import {
cn,
ResizableHandle,
ResizablePanel,
Skeleton,
Tabs_Shadcn_ as Tabs,
TabsContent_Shadcn_ as TabsContent,
TabsList_Shadcn_ as TabsList,
TabsTrigger_Shadcn_ as TabsTrigger,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { MemoizedRequestStartedBlock } from './ServiceFlow/components/blocks/RequestStartedBlock'
import { MemoizedResponseCompletedBlock } from './ServiceFlow/components/blocks/ResponseCompletedBlock'
import {
MemoizedEdgeFunctionBlock,
MemoizedGoTrueBlock,
MemoizedNetworkBlock,
MemoizedPostgresBlock,
MemoizedPostgRESTBlock,
MemoizedStorageBlock,
} from './ServiceFlow/components/ServiceBlocks'
import { ServiceFlowHeader } from './ServiceFlow/components/ServiceFlowHeader'
import { ColumnSchema } from './UnifiedLogs.schema'
import { QuerySearchParamsType } from './UnifiedLogs.types'
import { useDataTable } from '@/components/ui/DataTable/providers/DataTableProvider'
import {
ServiceFlowType,
useUnifiedLogInspectionQuery,
} from '@/data/logs/unified-log-inspection-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
interface ServiceFlowPanelProps {
selectedRow: ColumnSchema
selectedRowKey: string
searchParameters: QuerySearchParamsType
}
export function ServiceFlowPanel({
selectedRow,
selectedRowKey,
searchParameters,
}: ServiceFlowPanelProps) {
const { table, filterFields } = useDataTable()
const { ref: projectRef } = useParams()
const [activeTab, setActiveTab] = useState('service-flow')
const { logsMetadata } = useIsFeatureEnabled(['logs:metadata'])
const logType = selectedRow?.log_type
const serviceFlowType: ServiceFlowType | undefined =
logType === 'edge function' ? 'edge-function' : (logType as ServiceFlowType)
const shouldShowServiceFlow = !!serviceFlowType
// Query the logs API directly
const {
data: serviceFlowData,
isPending: isLoading,
error,
} = useUnifiedLogInspectionQuery(
{
projectRef: projectRef,
logId: selectedRow?.id,
type: serviceFlowType,
search: searchParameters,
},
{
enabled: Boolean(projectRef) && Boolean(selectedRow?.id) && Boolean(serviceFlowType),
}
)
// Prepare JSON data for Raw JSON tab
const jsonData =
shouldShowServiceFlow && serviceFlowData?.result?.[0] ? serviceFlowData.result[0] : selectedRow
const formattedJsonData =
!logsMetadata && 'raw_log_data' in jsonData && 'metadata' in jsonData.raw_log_data
? {
...jsonData,
raw_log_data: { ...jsonData.raw_log_data, metadata: undefined },
}
: jsonData
if (selectedRowKey) {
return (
<>
<ResizableHandle withHandle className="z-10" />
<ResizablePanel
id="log-sidepanel"
defaultSize={45}
minSize={400}
maxSize={400}
className={cn('bg-dash-sidebar border-t')}
>
<div className="h-full overflow-auto">
{/* Service Flow Header with navigation */}
<ServiceFlowHeader
selectedRow={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
/>
<div className="relative">
<Tabs
defaultValue="service-flow"
value={activeTab}
onValueChange={setActiveTab}
className="w-full h-full flex flex-col pt-2"
>
<TabsList className="flex gap-3 px-5">
{shouldShowServiceFlow && (
<TabsTrigger value="service-flow">Overview</TabsTrigger>
)}
<TabsTrigger value="raw-json">Raw JSON</TabsTrigger>
</TabsList>
{shouldShowServiceFlow && (
<TabsContent value="service-flow">
<div className="p-4">
{error ? (
<div className="text-center py-8 text-destructive">
Error: {error.toString()}
</div>
) : (
<>
{serviceFlowType === 'postgres' ? (
// Postgres flows: Connection Started -> Postgres -> Response
<>
<MemoizedRequestStartedBlock data={selectedRow} />
<MemoizedPostgresBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
isLoading={isLoading}
isLast={false}
filterFields={filterFields}
table={table}
/>
<MemoizedResponseCompletedBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
/>
</>
) : (
// HTTP-based flows: Request Started -> Network -> Service -> Response
<>
<MemoizedRequestStartedBlock data={selectedRow} />
<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}
/>
</>
)}
<MemoizedResponseCompletedBlock
data={selectedRow}
enrichedData={serviceFlowData?.result?.[0]}
/>
</>
)}
</>
)}
</div>
</TabsContent>
)}
<TabsContent value="raw-json" className="grow overflow-auto">
{isLoading && shouldShowServiceFlow && (
<div className="flex items-center gap-3 text-foreground-light p-3 bg-surface-100 border-b border-border">
<Skeleton className="h-4 w-4 rounded-full animate-pulse" />
<span className="text-sm">Enriching log...</span>
</div>
)}
<CodeBlock
language="json"
className="max-h-[800px] overflow-auto border-none rounded-none [&_pre]:!leading-tight [&_code]:!leading-tight"
>
{JSON.stringify(formattedJsonData, null, 2)}
</CodeBlock>
</TabsContent>
</Tabs>
</div>
</div>
</ResizablePanel>
</>
)
}
return null
}