mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
<img width="1086" height="810" alt="image" src="https://github.com/user-attachments/assets/8a9f0c64-145e-4475-a3df-67bd815e2200" /> Stack 2/4 · previous: #50686 · next: #50688 ## Problem The detail panel only showed an Overview tab for log types that have an inspection query. Every other type opened straight to Raw JSON. The panel also had no header saying which log was open. ## Solution - Adds `LogDetail`, which owns the inspection query, loading and error states, and the Raw JSON view. Errors now use `AlertError`. - Adds `LogOverview`, which maps each log type to its overview renderer. Types without one fall back to the new `LogFields`, a generic tree of expandable key/value rows built on `LogFieldRow`. - `ServiceFlowPanel` now shows a header (level dot + event message) above Overview / Raw JSON tabs for every log. - Moves `getLogDataForMetadataVisibility` to `ServiceFlowPanel.utils.ts` and adds `LogLevelDot`. ## Review instructions 1. Select a Realtime (or any other non-inspected) log. The Overview tab should list its fields, with nested objects that expand. 2. Select a Postgres log. The overview should look as before, and Raw JSON should still show the enriched log. 3. Switch between logs and confirm the tabs reset their scroll. ## Checklist - [ ] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added structured log overviews for Postgres, PostgREST, Auth, Storage, and Edge Functions, with a Raw JSON view for log details. - Added expandable log fields with filtering and copy actions, plus visual indicators for log severity. - **Bug Fixes** - Improved display of scalar, nested, date, and empty log values. - **Privacy** - Raw log data respects metadata visibility settings. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
69 lines
3.2 KiB
TypeScript
69 lines
3.2 KiB
TypeScript
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { LogFields } from './LogFields'
|
|
|
|
describe('LogFields', () => {
|
|
it('renders scalars, empty values, and dates without losing false or zero', () => {
|
|
render(
|
|
<LogFields
|
|
data={{
|
|
count: 0,
|
|
enabled: false,
|
|
absent: null,
|
|
emptyArray: [],
|
|
emptyObject: {},
|
|
timestamp: new Date('2026-09-22T00:00:00Z'),
|
|
omitted: undefined,
|
|
}}
|
|
/>
|
|
)
|
|
expect(screen.getByRole('button', { name: 'count 0' })).toBeVisible()
|
|
expect(screen.getByText('false')).toBeInTheDocument()
|
|
expect(screen.getByText('null')).toBeInTheDocument()
|
|
expect(screen.getByText('2026-09-22T00:00:00.000Z')).toBeInTheDocument()
|
|
expect(screen.queryByText('omitted')).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'Expand emptyArray' })).not.toBeInTheDocument()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Expand emptyObject' }))
|
|
expect(screen.getByText('[]')).toBeInTheDocument()
|
|
expect(screen.getByText('{}')).toBeInTheDocument()
|
|
})
|
|
|
|
it('expands nested objects and displays arrays inline with their full value available to copy', async () => {
|
|
const user = userEvent.setup()
|
|
const copy = vi.spyOn(navigator.clipboard, 'writeText')
|
|
const headers = [{ authorization: 'example' }]
|
|
render(<LogFields data={{ request: { headers } }} />)
|
|
expect(screen.queryByText(JSON.stringify(headers))).not.toBeInTheDocument()
|
|
fireEvent.click(screen.getByRole('button', { name: 'Expand request' }))
|
|
expect(screen.queryByRole('button', { name: 'Expand headers' })).not.toBeInTheDocument()
|
|
expect(screen.getByText(JSON.stringify(headers))).toBeVisible()
|
|
await user.click(screen.getByText('headers'))
|
|
await user.click(screen.getByRole('menuitem', { name: 'Copy headers' }))
|
|
expect(copy).toHaveBeenCalledWith(JSON.stringify(headers, null, 2))
|
|
})
|
|
it('opens a copy menu from a scalar row without a separate action button', async () => {
|
|
const user = userEvent.setup()
|
|
const copy = vi.spyOn(navigator.clipboard, 'writeText')
|
|
render(<LogFields data={{ enabled: false }} />)
|
|
expect(screen.queryByRole('button', { name: /copy/i })).not.toBeInTheDocument()
|
|
await user.click(screen.getByText('enabled'))
|
|
expect(screen.queryByRole('menuitem', { name: /filter/i })).not.toBeInTheDocument()
|
|
await user.click(screen.getByRole('menuitem', { name: 'Copy enabled' }))
|
|
expect(copy).toHaveBeenCalledWith('false')
|
|
})
|
|
|
|
it('opens a menu for a nested object independently of expanding it', async () => {
|
|
const user = userEvent.setup()
|
|
const copy = vi.spyOn(navigator.clipboard, 'writeText')
|
|
render(<LogFields data={{ request: { status: 200 } }} />)
|
|
await user.click(screen.getByText('request'))
|
|
await user.click(screen.getByRole('menuitem', { name: 'Copy request' }))
|
|
expect(copy).toHaveBeenCalledWith(JSON.stringify({ status: 200 }, null, 2))
|
|
expect(screen.queryByText('200')).not.toBeInTheDocument()
|
|
await user.click(screen.getByRole('button', { name: 'Expand request' }))
|
|
expect(screen.getByText('200')).toBeVisible()
|
|
})
|
|
})
|