feat(studio) Link observability pages to relevant docs (#47351)

Closes DOCS-488

<img width="1266" height="353" alt="Screenshot 2026-06-26 at 11 02
57 AM"
src="https://github.com/user-attachments/assets/67b5d47b-249e-4e53-9230-2bbcb7f037b7"
/>


## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## Problem

We have helpful documentation that delves into each observability
metric, but it is not easily findable in the moment it is needed while
viewing the dashboards.

## Solution

Solution includes:
- Add a docs link in Studio in every relevant place with the
`DocsButton` component
- Add aria-hidden on the `DocsButton` icon
- An added `constants.ts` to see all of the docs links in one place
- A contextual aria-label for the docs so that screenreader users know
where they're going


| Page | Docs link |
|------|-----------|
| Overview | `/guides/telemetry/reports` |
| Query Performance / Query Insights |
`/guides/platform/performance#examining-query-performance` |
| API Gateway | `/guides/telemetry/reports#api-gateway` |
| Database | `/guides/telemetry/reports#database` |
| Data API | `/guides/telemetry/reports#postgrest` |
| Auth | `/guides/telemetry/reports#auth` |
| Edge Functions | `/guides/telemetry/reports#edge-functions` |
| Storage | `/guides/telemetry/reports#storage` |
| Realtime | `/guides/realtime/reports` |
| Custom reports | `/guides/telemetry/reports#using-reports` |

Query Performance and Query Insights already had the button in their
custom headers. They now use the shared constants.

## Tophatting

1. Go to a project `/observability`.
2. Click into each of the panels and see a **Docs** link in the top
right.

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

## Summary by CodeRabbit

* **New Features**
* Added direct documentation links across observability report pages,
making it easier to open relevant help content from each view.
* Added clearer, page-specific labels for observability headers and docs
links.
* **Bug Fixes**
* Improved accessibility for icon buttons so icons are hidden from
assistive technologies while button labels remain clear.
* Adjusted report navigation layouts to keep controls aligned with the
new docs buttons.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Miranda Limonczenko authored and GitHub committed 2026-06-26 12:11:19 -07:00
1 parent f78c9db32e
commit 5fc0c86007
16 files changed
+139 -64

No files matched your search

@@ -0,0 +1,15 @@
import { DOCS_URL } from '@/lib/constants'
export const OBSERVABILITY_DOCS_HREFS = {
overview: `${DOCS_URL}/guides/telemetry/reports`,
queryPerformance: `${DOCS_URL}/guides/platform/performance#examining-query-performance`,
queryInsights: `${DOCS_URL}/guides/platform/performance#examining-query-performance`,
apiGateway: `${DOCS_URL}/guides/telemetry/reports#api-gateway`,
database: `${DOCS_URL}/guides/telemetry/reports#database`,
dataApi: `${DOCS_URL}/guides/telemetry/reports#postgrest`,
auth: `${DOCS_URL}/guides/telemetry/reports#auth`,
edgeFunctions: `${DOCS_URL}/guides/telemetry/reports#edge-functions`,
storage: `${DOCS_URL}/guides/telemetry/reports#storage`,
realtime: `${DOCS_URL}/guides/realtime/reports`,
customReport: `${DOCS_URL}/guides/telemetry/reports#using-reports`,
} as const
@@ -8,12 +8,14 @@ import { Badge, Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { useUnifiedLogsPreview } from '../App/FeaturePreview/FeaturePreviewContext'
import { DatabaseInfrastructureSection } from './DatabaseInfrastructureSection'
import { OBSERVABILITY_DOCS_HREFS } from './Observability.constants'
import { useObservabilityOverviewData } from './ObservabilityOverview.utils'
import { ObservabilityOverviewFooter } from './ObservabilityOverviewFooter'
import { ServiceHealthTable } from './ServiceHealthTable'
import { useSlowQueriesCount } from './useSlowQueriesCount'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { DocsButton } from '@/components/ui/DocsButton'
import { ChartIntervalDropdown } from '@/components/ui/Logs/ChartIntervalDropdown'
import { CHART_INTERVALS } from '@/components/ui/Logs/logs.utils'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
@@ -23,6 +25,8 @@ import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganizati
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
const REPORT_TITLE = 'Overview'
type ChartIntervalKey = '1hr' | '1day' | '7day'
export const ObservabilityOverview = () => {
@@ -167,7 +171,7 @@ export const ObservabilityOverview = () => {
<ReportPadding>
<div className="flex flex-row justify-between items-center">
<div className="flex items-center gap-3">
<ReportHeader title="Overview" />
<ReportHeader title={REPORT_TITLE} />
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="warning">Beta</Badge>
@@ -178,6 +182,7 @@ export const ObservabilityOverview = () => {
</Tooltip>
</div>
<div className="flex items-center gap-2">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.overview} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -9,7 +9,7 @@ const ReportStickyNav = ({
return (
<section className={cn('relative flex flex-col gap-4 pt-16 -mt-2', className)}>
<div className="absolute inset-0 z-40 pointer-events-none flex flex-col gap-4">
<div className="sticky top-0 py-4 mb-4 flex items-center gap-2 pointer-events-auto dark:bg-background-200 bg-background">
<div className="sticky top-0 py-4 mb-4 flex w-full items-center gap-2 pointer-events-auto dark:bg-background-200 bg-background">
{content}
</div>
</div>
@@ -12,10 +12,12 @@ import { ChartConfig } from '../SQLEditor/UtilityPanel/ChartConfig'
import { GridResize } from './GridResize'
import { MetricOptions } from './MetricOptions'
import { LAYOUT_COLUMN_COUNT } from './Reports.constants'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
import { DateRangePicker } from '@/components/ui/DateRangePicker'
import { DocsButton } from '@/components/ui/DocsButton'
import NoPermission from '@/components/ui/NoPermission'
import { DEFAULT_CHART_CONFIG } from '@/components/ui/QueryBlock/QueryBlock'
import { AnalyticsInterval } from '@/data/analytics/constants'
@@ -366,12 +368,14 @@ const Reports = () => {
return <NoPermission isFullPage resourceText="access this custom report" />
}
const reportTitle = currentReport?.name || 'Reports'
return (
<>
<div className="flex flex-col space-y-4" style={{ maxHeight: '100%' }}>
<div className="flex items-center justify-between">
<div>
<h1>{currentReport?.name || 'Reports'}</h1>
<h1>{reportTitle}</h1>
<p className="text-foreground-light">{currentReport?.description}</p>
</div>
{hasEdits && (
@@ -396,14 +400,6 @@ const Reports = () => {
</div>
<div className={cn('mb-4 flex items-center gap-x-3 justify-between')}>
<div className="flex items-center gap-x-2">
<ButtonTooltip
variant="default"
icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
className="w-7"
disabled={isRefreshing}
tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
onClick={onRefreshReport}
/>
<div className="flex items-center gap-x-3">
<DateRangePicker
value="7d"
@@ -423,6 +419,15 @@ const Reports = () => {
</div>
<div className="flex items-center gap-x-2">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.customReport} topic={reportTitle} />
<ButtonTooltip
variant="default"
icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
className="w-7"
disabled={isRefreshing}
tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
onClick={onRefreshReport}
/>
{canUpdateReport ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
+8 -2
View File
@@ -5,9 +5,10 @@ interface DocsButtonProps {
href: string
abbrev?: boolean
className?: string
topic?: string
}
export const DocsButton = ({ href, abbrev = true, className }: DocsButtonProps) => {
export const DocsButton = ({ href, abbrev = true, className, topic }: DocsButtonProps) => {
return (
<Button
asChild
@@ -16,7 +17,12 @@ export const DocsButton = ({ href, abbrev = true, className }: DocsButtonProps)
icon={<BookOpen />}
onClick={(e) => e.stopPropagation()}
>
<a target="_blank" rel="noopener noreferrer" href={href}>
<a
target="_blank"
rel="noopener noreferrer"
href={href}
aria-label={topic ? `${topic} documentation (opens in new tab)` : undefined}
>
{abbrev ? 'Docs' : 'Documentation'}
</a>
</Button>
@@ -1,5 +1,6 @@
import { useCallback } from 'react'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import {
ErrorCountsChartRenderer,
NetworkTrafficRenderer,
@@ -18,11 +19,14 @@ import { DatePickerValue } from '@/components/interfaces/Settings/Logs/Logs.Date
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
import DefaultLayout from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { useApiReport } from '@/data/reports/api-report-query'
import { useReportDateRange } from '@/hooks/misc/useReportDateRange'
import type { NextPageWithLayout } from '@/types'
const REPORT_TITLE = 'API Gateway'
export const ApiReport: NextPageWithLayout = () => {
const report = useApiReport()
@@ -94,10 +98,10 @@ export const ApiReport: NextPageWithLayout = () => {
return (
<ReportPadding>
<ReportHeader title="API Gateway" showDatabaseSelector={false} />
<ReportHeader title={REPORT_TITLE} showDatabaseSelector={false} />
<ReportStickyNav
content={
<div className="flex items-center gap-3">
<div className="flex w-full items-center gap-3">
<ReportFilterBar
onRemoveFilters={removeFilters}
onDatepickerChange={handleDatepickerChange}
@@ -109,9 +113,10 @@ export const ApiReport: NextPageWithLayout = () => {
filters={filters}
datepickerHelpers={datePickerHelpers}
initialDatePickerValue={datePickerValue}
className="w-full"
className="flex-1 min-w-0"
showDatabaseSelector={false}
/>
<DocsButton href={OBSERVABILITY_DOCS_HREFS.apiGateway} topic={REPORT_TITLE} />
<UpgradePrompt
show={showUpgradePrompt}
setShowUpgradePrompt={setShowUpgradePrompt}
@@ -6,6 +6,7 @@ import { parseAsJson, useQueryState } from 'nuqs'
import { useState } from 'react'
import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
@@ -29,6 +30,7 @@ import DefaultLayout from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import type { ChartHighlightAction } from '@/components/ui/Charts/ChartHighlightActions'
import { ReportSettings } from '@/components/ui/Charts/ReportSettings'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import {
@@ -58,6 +60,8 @@ AuthReport.getLayout = (page) => (
export type UpdateDateRange = (from: string, to: string) => void
export default AuthReport
const REPORT_TITLE = 'Auth'
const AuthUsage = () => {
const { ref } = useParams()
const chartSyncId = `auth-report`
@@ -209,11 +213,12 @@ const AuthUsage = () => {
return (
<>
<ReportHeader title="Auth" showDatabaseSelector={false} />
<ReportHeader title={REPORT_TITLE} showDatabaseSelector={false} />
<ReportStickyNav
content={
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<div className="flex flex-col gap-2 w-full">
<div className="ml-auto flex items-center gap-2 flex-wrap">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.auth} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -8,6 +8,7 @@ import { useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
import { Alert, AlertDescription, Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
@@ -25,6 +26,7 @@ import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import type { MultiAttribute } from '@/components/ui/Charts/ComposedChart.utils'
import { LazyComposedChartHandler } from '@/components/ui/Charts/ComposedChartHandler'
import { ReportSettings } from '@/components/ui/Charts/ReportSettings'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { analyticsKeys } from '@/data/analytics/keys'
@@ -65,6 +67,8 @@ DatabaseReport.getLayout = (page) => (
export type UpdateDateRange = (from: string, to: string) => void
export default DatabaseReport
const REPORT_TITLE = 'Database'
const DatabaseUsage = () => {
const { db, chart, ref } = useParams()
const { data: project } = useSelectedProjectQuery()
@@ -227,10 +231,11 @@ const DatabaseUsage = () => {
return (
<>
<ReportHeader showDatabaseSelector title="Database" />
<ReportHeader showDatabaseSelector title={REPORT_TITLE} />
<ReportStickyNav
content={
<>
<div className="ml-auto flex items-center gap-2 flex-wrap">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.database} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -275,7 +280,7 @@ const DatabaseUsage = () => {
</div>
)}
</div>
</>
</div>
}
>
{selectedDateRange &&
@@ -6,6 +6,7 @@ import { parseAsJson, useQueryState } from 'nuqs'
import { useMemo, useState } from 'react'
import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import {
@@ -28,6 +29,7 @@ import DefaultLayout from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import { ReportSettings } from '@/components/ui/Charts/ReportSettings'
import { useChartHoverState } from '@/components/ui/Charts/useChartHoverState'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
@@ -54,6 +56,8 @@ EdgeFunctionsReportV2.getLayout = (page) => (
export default EdgeFunctionsReportV2
const REPORT_TITLE = 'Edge Functions'
const EdgeFunctionsUsage = () => {
const { ref } = useParams()
const { data: functions } = useEdgeFunctionsQuery({
@@ -143,11 +147,12 @@ const EdgeFunctionsUsage = () => {
return (
<>
<ReportHeader title="Edge Functions" showDatabaseSelector={false} />
<ReportHeader title={REPORT_TITLE} showDatabaseSelector={false} />
<ReportStickyNav
content={
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<div className="flex flex-col gap-2 w-full">
<div className="ml-auto flex items-center gap-2 flex-wrap">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.edgeFunctions} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -4,6 +4,7 @@ import { ArrowRight, RefreshCw } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
@@ -18,6 +19,7 @@ import {
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
import DefaultLayout from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange'
@@ -43,6 +45,8 @@ PostgRESTReport.getLayout = (page) => (
export type UpdateDateRange = (from: string, to: string) => void
export default PostgRESTReport
const REPORT_TITLE = 'Data API'
const PostgrestReport = () => {
const { db, chart } = useParams()
const {
@@ -115,11 +119,12 @@ const PostgrestReport = () => {
return (
<>
<ReportHeader showDatabaseSelector={false} title="Data API" />
<ReportHeader showDatabaseSelector={false} title={REPORT_TITLE} />
<ReportStickyNav
content={
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<div className="flex flex-col gap-2 w-full">
<div className="ml-auto flex items-center gap-2 flex-wrap">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.dataApi} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -1,5 +1,6 @@
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import { QueryInsights } from '@/components/interfaces/QueryInsights/QueryInsights'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
@@ -7,7 +8,6 @@ import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/Observ
import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
import { DocsButton } from '@/components/ui/DocsButton'
import { useReportDateRange } from '@/hooks/misc/useReportDateRange'
import { DOCS_URL } from '@/lib/constants'
import type { NextPageWithLayout } from '@/types'
const PRESETS = [
@@ -16,6 +16,8 @@ const PRESETS = [
REPORT_DATERANGE_HELPER_LABELS.LAST_24_HOURS,
]
const REPORT_TITLE = 'Query Insights'
const QueryInsightsReport: NextPageWithLayout = () => {
const { selectedDateRange, datePickerValue, datePickerHelpers, handleDatePickerChange } =
useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
@@ -30,11 +32,9 @@ const QueryInsightsReport: NextPageWithLayout = () => {
return (
<div className="h-full flex flex-col">
<div className="w-full mb-0 flex lg:items-center justify-between gap-4 py-2 px-6 lg:flex-row flex-col border-b lg:h-[48px]">
<h3 className="text-foreground text-xl prose">Query Insights</h3>
<h3 className="text-foreground text-xl prose">{REPORT_TITLE}</h3>
<div className="flex items-center gap-2 flex-wrap">
<DocsButton
href={`${DOCS_URL}/guides/platform/performance#examining-query-performance`}
/>
<DocsButton href={OBSERVABILITY_DOCS_HREFS.queryInsights} topic={REPORT_TITLE} />
<DatabaseSelector />
<Select
value={datePickerValue.isHelper ? datePickerValue.text : undefined}
@@ -2,6 +2,7 @@ import { useParams } from 'common'
import { parseAsArrayOf, parseAsInteger, parseAsJson, parseAsString, useQueryStates } from 'nuqs'
import { Admonition } from 'ui-patterns'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
import { useQueryPerformanceSort } from '@/components/interfaces/QueryPerformance/hooks/useQueryPerformanceSort'
import { QueryPerformance } from '@/components/interfaces/QueryPerformance/QueryPerformance'
@@ -16,9 +17,10 @@ import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/Observ
import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
import { DocsButton } from '@/components/ui/DocsButton'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
import type { NextPageWithLayout } from '@/types'
const REPORT_TITLE = 'Query Performance'
const QueryPerformanceReport: NextPageWithLayout = () => {
const { ref } = useParams()
const { data: project, isLoading: isLoadingProject } = useSelectedProjectQuery()
@@ -88,11 +90,9 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
return (
<div className="h-full flex flex-col">
<div className="w-full mb-0 flex lg:items-center justify-between gap-4 py-4 px-6 lg:flex-row flex-col">
<h3 className="text-foreground text-xl prose">Query Performance</h3>
<h3 className="text-foreground text-xl prose">{REPORT_TITLE}</h3>
<div className="flex items-center gap-2 flex-wrap">
<DocsButton
href={`${DOCS_URL}/guides/platform/performance#examining-query-performance`}
/>
<DocsButton href={OBSERVABILITY_DOCS_HREFS.queryPerformance} topic={REPORT_TITLE} />
<DatabaseSelector />
</div>
</div>
@@ -5,6 +5,7 @@ import { ArrowRight, RefreshCw } from 'lucide-react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
@@ -17,6 +18,7 @@ import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DateP
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
import DefaultLayout from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { realtimeReports } from '@/data/reports/v2/realtime.config'
@@ -43,6 +45,8 @@ RealtimeReport.getLayout = (page) => (
export type UpdateDateRange = (from: string, to: string) => void
export default RealtimeReport
const REPORT_TITLE = 'Realtime'
const RealtimeUsage = () => {
const [isRefreshing, setIsRefreshing] = useState(false)
const [showDatePicker, setShowDatePicker] = useState(false)
@@ -140,11 +144,12 @@ const RealtimeUsage = () => {
return (
<>
<ReportHeader showDatabaseSelector={false} title="Realtime" />
<ReportHeader showDatabaseSelector={false} title={REPORT_TITLE} />
<ReportStickyNav
content={
<div className="flex flex-col gap-3">
<div className="flex gap-2">
<div className="flex flex-col gap-3 w-full">
<div className="ml-auto flex items-center gap-2 flex-wrap">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.realtime} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -4,6 +4,7 @@ import Link from 'next/link'
import { useState } from 'react'
import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import {
NetworkTrafficRenderer,
ResponseSpeedChartRenderer,
@@ -27,6 +28,7 @@ import {
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
import DefaultLayout from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
import { DocsButton } from '@/components/ui/DocsButton'
import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useStorageReport } from '@/data/reports/storage-report-query'
@@ -36,6 +38,8 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
import type { NextPageWithLayout } from '@/types'
const REPORT_TITLE = 'Storage'
export const StorageReport: NextPageWithLayout = () => {
const report = useStorageReport()
@@ -88,11 +92,12 @@ export const StorageReport: NextPageWithLayout = () => {
return (
<ReportPadding>
<ReportHeader title="Storage" showDatabaseSelector={false} />
<ReportHeader title={REPORT_TITLE} showDatabaseSelector={false} />
<ReportStickyNav
content={
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<div className="flex flex-col gap-2 w-full">
<div className="ml-auto flex items-center gap-2 flex-wrap">
<DocsButton href={OBSERVABILITY_DOCS_HREFS.storage} topic={REPORT_TITLE} />
<ShortcutTooltip
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
label="Refresh report"
@@ -74,6 +74,13 @@ describe('#Button', () => {
expect(screen.queryByRole('button')).toHaveClass('w-full')
})
it('should hide decorative icons from assistive technology', () => {
render(<Button icon={<svg data-testid="button-icon" />}>Save</Button>)
expect(screen.getByTestId('button-icon').parentElement).toHaveAttribute('aria-hidden', 'true')
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument()
})
it('should forward ref', () => {
const ref: React.MutableRefObject<HTMLButtonElement | null> = {
current: null,
+22 -20
View File
@@ -239,6 +239,12 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
// - Otherwise, default to 0 for keyboard accessibility
const computedTabIndex = tabIndex !== undefined ? tabIndex : disabled ? -1 : 0
const renderIconContainer = (content: ReactNode) => (
<div aria-hidden className={cn(IconContainerVariants({ size, variant }))}>
{content}
</div>
)
return (
<Comp
ref={ref}
@@ -260,35 +266,31 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
children,
undefined,
showIcon &&
(loading ? (
<div className={cn(IconContainerVariants({ size, variant }))}>
<Loader2 className={cn(loadingVariants({ loading, variant }))} />
</div>
) : _iconLeft ? (
<div className={cn(IconContainerVariants({ size, variant }))}>{_iconLeft}</div>
) : null),
(loading
? renderIconContainer(
<Loader2 className={cn(loadingVariants({ loading, variant }))} />
)
: _iconLeft
? renderIconContainer(_iconLeft)
: null),
children.props.children && (
<span className={'truncate'}>{children.props.children}</span>
),
iconRight && !loading && (
<div className={cn(IconContainerVariants({ size, variant }))}>{iconRight}</div>
)
iconRight && !loading && renderIconContainer(iconRight)
)
) : null
) : (
<>
{showIcon &&
(loading ? (
<div className={cn(IconContainerVariants({ size, variant }))}>
<Loader2 className={cn(loadingVariants({ loading, variant }))} />
</div>
) : _iconLeft ? (
<div className={cn(IconContainerVariants({ size, variant }))}>{_iconLeft}</div>
) : null)}{' '}
(loading
? renderIconContainer(
<Loader2 className={cn(loadingVariants({ loading, variant }))} />
)
: _iconLeft
? renderIconContainer(_iconLeft)
: null)}{' '}
{children && <span className={'truncate'}>{children}</span>}{' '}
{iconRight && !loading && (
<div className={cn(IconContainerVariants({ size, variant }))}>{iconRight}</div>
)}
{iconRight && !loading && renderIconContainer(iconRight)}
</>
)}
</Comp>