Files
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

256 lines
6.7 KiB
TypeScript

import dayjs from 'dayjs'
import { ChevronDown } from 'lucide-react'
import { ReactNode, useEffect, useState } from 'react'
import { DATE_FORMAT } from 'lib/constants'
import {
Button,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'ui'
/**
* There's timestamp rounding that kicks in if there are more than 50 data points
* being returned, in order to increase cache hit rates.
* Ensure that whenever we create a new period_start under handleChange, it should return
* at point 50 data points. (e.g 3 hours, 5 mins interval = 36 points)
* Otherwise there won't be any data shown on the graphs
*/
interface DateRangePickerProps {
value: string
loading: boolean
className?: string
onChange: ({
period_start,
period_end,
interval,
}: {
period_start: { date: string; time_period: string }
period_end: { date: string; time_period: string }
interval?: string
}) => void
options: { key: string; label: string; interval?: string }[]
currentBillingPeriodStart?: number
currentBillingPeriodEnd?: number
footer?: ReactNode
}
export const DateRangePicker = ({
onChange,
value,
options,
className,
loading,
currentBillingPeriodStart,
currentBillingPeriodEnd,
footer,
}: DateRangePickerProps) => {
const [timePeriod, setTimePeriod] = useState(value)
useEffect(() => {
handleChange(value)
}, [loading])
const startOfMonth = dayjs().startOf('month').format(DATE_FORMAT)
const nextMonth = dayjs().add(1, 'month').startOf('month').format(DATE_FORMAT)
const today = dayjs().format(DATE_FORMAT)
function handleChange(x: any) {
setTimePeriod(x)
switch (x) {
// please note: these are fixed date ranges based on subscription
// billing cycles are always assumed to be 1 month long
// only currentPeriodStart is used to calculate dates
case 'currentBillingCycle':
onChange({
period_start: {
date: dayjs.unix(currentBillingPeriodStart ?? 0).format(DATE_FORMAT),
time_period: '1d',
},
period_end: {
date: dayjs.unix(currentBillingPeriodEnd ?? 0).format(DATE_FORMAT),
time_period: 'today',
},
interval: '1d',
})
break
case 'previousBillingCycle':
onChange({
period_start: {
date: dayjs
.unix(currentBillingPeriodStart ?? 0)
.subtract(1, 'month')
.format(DATE_FORMAT),
time_period: '1d',
},
period_end: {
date: dayjs.unix(currentBillingPeriodStart ?? 0).format(DATE_FORMAT),
time_period: 'today',
},
interval: '1d',
})
break
// all other time periods below are based on current date and time
// they will generate flexible dynamic date ranges
case '10m':
onChange({
period_start: {
date: dayjs().subtract(10, 'minutes').format(DATE_FORMAT),
time_period: '1d',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '1m',
})
break
case '30m':
onChange({
period_start: {
date: dayjs().subtract(30, 'minutes').format(DATE_FORMAT),
time_period: '1d',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '1m',
})
break
case '1d':
onChange({
period_start: {
date: dayjs().subtract(24, 'hour').format(DATE_FORMAT),
time_period: '1d',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '1h',
})
break
case '3h':
onChange({
period_start: {
date: dayjs().subtract(3, 'hour').format(DATE_FORMAT),
time_period: '3h',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '5m',
})
break
case '1h':
onChange({
period_start: {
date: dayjs().subtract(1, 'hour').format(DATE_FORMAT),
time_period: '1h',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '1m',
})
break
case '7d':
onChange({
period_start: {
date: dayjs().subtract(7, 'day').format(DATE_FORMAT),
time_period: '7d',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '1d',
})
break
case '30d':
onChange({
period_start: {
date: dayjs().subtract(30, 'day').format(DATE_FORMAT),
time_period: '30d',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '1d',
})
break
case '60d':
onChange({
period_start: {
date: dayjs().subtract(60, 'day').format(DATE_FORMAT),
time_period: '60d',
},
period_end: {
date: today,
time_period: 'today',
},
interval: '3d',
})
break
case 'startMonth':
onChange({
period_start: {
date: startOfMonth,
time_period: 'startMonth',
},
period_end: {
date: nextMonth,
time_period: 'endMonth',
},
interval: '1d',
})
break
default:
console.warn(`Unknown period encountered: ${x}`)
break
}
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="default" iconRight={<ChevronDown />}>
<span>{timePeriod && options.find((x) => x.key === timePeriod)?.label}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="start" className={cn(!footer && 'w-36', className)}>
<DropdownMenuRadioGroup value={timePeriod} onValueChange={(x) => handleChange(x)}>
{options.map((option) => {
return (
<DropdownMenuRadioItem value={option.key} key={option.key}>
{option.label}
</DropdownMenuRadioItem>
)
})}
</DropdownMenuRadioGroup>
{!!footer && (
<>
<DropdownMenuSeparator />
{footer}
</>
)}
</DropdownMenuContent>
</DropdownMenu>
)
}
export default DateRangePicker