Files
supabase/apps/studio/components/ui/Charts/ChartHandler.tsx
Francesco SansalvadoreandTheOtherBrian1 939e5c855a feat: reports infra o11y (#33310)
* test client connections charts

* add granularity to database dashboard

* add range selection

* add selection to area charts

* refactor ChartHeader

* fix default

* fix time preset selected label and add tooltip actions

* client connections stacked chart

* polish all new chartts

* change attribute

* fix

* add real queries

* memory stack

* cleanup

* precision 1

* update charts

* add endpoints

* tooltip

* format

* improve charts

* toggle max value

* show limit

* cleanup

* cleanup

* update api schema

* fix ram_usage_max_available

* remove pgbouncer and comments

* show total with percentage in chart header

* pretty

* hide

* update reports database

* remove client_connections

* revert

* fix connectionStrings error

* restore correct queries and conditions

* vertically zoom in to percentage data

* add feature flag logic on charts

* add feature flag logic to date picker

* chore: remove comment

* add tooltip to charts metrics

* chore: comment new pg endpoints

* feat: lazy load ComposedChartHandler queries

* feat: dim non-hovered chart values when hovering chart label item

* feat: improve dim styling

* chore: improve light mode stacked chart colors

* chore: remove 15s interval

* chore: sync types

* chore: remove type

* chore: restore type

* fix hook error

* show chart type toggle

* remove console log

* temp

* keep connections charts as before

* restore DateRangePicker

* show last aggregate value when not hovering

* clean up imports

* disk IOps read and write

* restore flag

* refactor queries

* clean up grafana promo banner

* Update database-charts.ts | updated language

* fix hook error

* fix hook error

* fix hook error

* attempt fix hook error

---------

Co-authored-by: TheOtherBrian1 <91111415+TheOtherBrian1@users.noreply.github.com>
2025-04-14 10:53:52 +02:00

196 lines
6.0 KiB
TypeScript

import { useRouter } from 'next/router'
import { PropsWithChildren, useState } from 'react'
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import AreaChart from 'components/ui/Charts/AreaChart'
import BarChart from 'components/ui/Charts/BarChart'
import { AnalyticsInterval } from 'data/analytics/constants'
import {
InfraMonitoringAttribute,
useInfraMonitoringQuery,
} from 'data/analytics/infra-monitoring-query'
import {
ProjectDailyStatsAttribute,
useProjectDailyStatsQuery,
} from 'data/analytics/project-daily-stats-query'
import { Activity, BarChartIcon, Loader2 } from 'lucide-react'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import { WarningIcon } from 'ui'
import type { ChartData } from './Charts.types'
interface ChartHandlerProps {
id?: string
label: string
attribute: string
provider: 'infra-monitoring' | 'daily-stats'
startDate: string
endDate: string
interval: string
customDateFormat?: string
defaultChartStyle?: 'bar' | 'line'
hideChartType?: boolean
data?: ChartData
isLoading?: boolean
format?: string
highlightedValue?: string | number
}
/**
* Controls chart display state. Optionally fetches static chart data if data is not provided.
*
* If the `data` prop is provided, it will disable automatic chart data fetching and pass the data directly to the chart render.
* - loading state can also be provided through the `isLoading` prop, to display loading placeholders. Ignored if `data` key not provided.
* - if `isLoading=true` and `data` is `undefined`, loading error message will be shown.
*
* Provided data must be in the expected chart format.
*/
const ChartHandler = ({
label,
attribute,
provider,
startDate,
endDate,
interval,
customDateFormat,
children = null,
defaultChartStyle = 'bar',
hideChartType = false,
data,
isLoading,
format,
highlightedValue,
}: PropsWithChildren<ChartHandlerProps>) => {
const router = useRouter()
const { ref } = router.query
const state = useDatabaseSelectorStateSnapshot()
const [chartStyle, setChartStyle] = useState<string>(defaultChartStyle)
const databaseIdentifier = state.selectedDatabaseId
const { data: dailyStatsData, isLoading: isFetchingDailyStats } = useProjectDailyStatsQuery(
{
projectRef: ref as string,
attribute: attribute as ProjectDailyStatsAttribute,
startDate,
endDate,
interval: interval as AnalyticsInterval,
databaseIdentifier,
},
{ enabled: provider === 'daily-stats' && data === undefined }
)
const { data: infraMonitoringData, isLoading: isFetchingInfraMonitoring } =
useInfraMonitoringQuery(
{
projectRef: ref as string,
attribute: attribute as InfraMonitoringAttribute,
startDate,
endDate,
interval: interval as AnalyticsInterval,
databaseIdentifier,
},
{ enabled: provider === 'infra-monitoring' && data === undefined }
)
const chartData =
data ||
(provider === 'infra-monitoring'
? infraMonitoringData
: provider === 'daily-stats'
? dailyStatsData
: undefined)
const loading =
isLoading ||
(provider === 'infra-monitoring'
? isFetchingInfraMonitoring
: provider === 'daily-stats'
? isFetchingDailyStats
: isLoading)
const shouldHighlightMaxValue =
provider === 'daily-stats' &&
!attribute.includes('ingress') &&
!attribute.includes('egress') &&
chartData !== undefined &&
'maximum' in chartData
const shouldHighlightTotalGroupedValue = chartData !== undefined && 'totalGrouped' in chartData
const _highlightedValue =
highlightedValue !== undefined
? highlightedValue
: shouldHighlightMaxValue
? chartData?.maximum
: provider === 'daily-stats'
? chartData?.total
: shouldHighlightTotalGroupedValue
? chartData?.totalGrouped?.[attribute]
: (chartData?.data[chartData?.data.length - 1] as any)?.[attribute as any]
if (loading) {
return (
<div className="flex h-52 w-full flex-col items-center justify-center gap-y-2">
<Loader2 size={18} className="animate-spin text-border-strong" />
<p className="text-xs text-foreground-lighter">Loading data for {label}</p>
</div>
)
}
if (chartData === undefined) {
return (
<div className="flex h-52 w-full flex-col items-center justify-center gap-y-2">
<WarningIcon />
<p className="text-xs text-foreground-lighter">Unable to load data for {label}</p>
</div>
)
}
return (
<div className="h-full w-full">
<div className="absolute right-6 z-10 flex justify-between">
{!hideChartType && (
<Tooltip>
<TooltipTrigger asChild>
<Button
type="default"
className="px-1.5"
icon={chartStyle === 'bar' ? <Activity /> : <BarChartIcon />}
onClick={() => setChartStyle(chartStyle === 'bar' ? 'line' : 'bar')}
/>
</TooltipTrigger>
<TooltipContent side="left" align="center">
View as {chartStyle === 'bar' ? 'line chart' : 'bar chart'}
</TooltipContent>
</Tooltip>
)}
{children}
</div>
{chartStyle === 'bar' ? (
<BarChart
YAxisProps={{ width: 1 }}
data={(chartData?.data ?? []) as any}
format={format || chartData?.format}
xAxisKey={'period_start'}
yAxisKey={attribute}
highlightedValue={_highlightedValue}
title={label}
customDateFormat={customDateFormat}
/>
) : (
<AreaChart
data={(chartData?.data ?? []) as any}
format={format || chartData?.format}
xAxisKey="period_start"
yAxisKey={attribute}
highlightedValue={_highlightedValue}
title={label}
customDateFormat={customDateFormat}
/>
)}
</div>
)
}
export default ChartHandler