mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 04:45:08 +03:00
* wip * add formatTimestamp for reports * add tests for formatTimestamp * fix type err * rm old chart logic * do not retry replication sources * update report config schema * use report config * fix charts, simplify config * sync charts * rm comment * usefilltimeseries * move chart upsell to its own component * cleanup v2 chart component * fix chartheader function format bug * make attributes dynamic * move types to its own file * update reportv2chart types * update reportv2 chart type * use new edgefn report * rm mock * rm old code * fix type err * fix type err * rename hook * return sql in dataprovider * add button to open in log explorer in chart header * fix type * fix chart type toggle * fix type error * export data transformation for testing * add tests for data transformation * rm dupped code * add report settings to edge fn report * rm mocked data * rename * fix type err * Apply suggestion from @joshenlim Co-authored-by: Joshen Lim <joshenlimek@gmail.com> * rm unnecessary div * rm unnecessary props from report upsell * cleaner query keys * move loadin/error/empty states to inside panel content to prevent layout shift * use card instead of panel * use buttontooltip in chartheader * use buttontooltips in buttons with tooltips * rm export default * rm export defualt * rm i from key * fix status code transformation * fix filltimeseries * add tests for filltimeseries * fix data transform * pass xaxis key to chart * refactor hover state to use timestamp not index * fix tests * fix areachart * fix barchart * fix chartheader * fix composedchart * fix stacked * fix type missing xaxiskey * type fix * Revert "refactor hover state to use timestamp not index" This reverts commitcfd7886460. * undo charthover changes, fix data aggregation * Revert "fix tests" This reverts commite5200a2035. * move transforms to testable functions, add tests * nit refactors and clean up * feat: add aws nimbus cloud provider (#38312) * feat: add aws nimbus cloud provider * Fix return type of useCustomContent hook --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com> * add utc plugin to dayjs * move plugins to vitest setup * rm customparser plugin --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com> Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
168 lines
4.3 KiB
TypeScript
168 lines
4.3 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import { FC, PropsWithChildren, useMemo } from 'react'
|
|
import { ResponsiveContainer } from 'recharts'
|
|
|
|
import { DateTimeFormats } from './Charts.constants'
|
|
import type { CommonChartProps, StackedChartProps } from './Charts.types'
|
|
|
|
/**
|
|
* Auto formats a number to a default precision if it is a float
|
|
*
|
|
* @example
|
|
* numberFormatter(123) // "123"
|
|
* numberFormatter(123.123) // "123.12"
|
|
* numberFormatter(123, 2) // "123.00"
|
|
*/
|
|
export const numberFormatter = (num: number, precision = 2) =>
|
|
isFloat(num) ? precisionFormatter(num, precision) : num.toLocaleString()
|
|
|
|
/**
|
|
* Tests if a number is a float.
|
|
*
|
|
* @example
|
|
* isFloat(123) // false
|
|
* isFloat(123.123) // true
|
|
*/
|
|
export const isFloat = (num: number) => String(num).includes('.')
|
|
|
|
/**
|
|
* Formats a number to a particular precision.
|
|
*
|
|
* @example
|
|
* precisionFormatter(123, 2) // "123.00"
|
|
* precisionFormatter(123.123, 2) // "123.12"
|
|
*/
|
|
export const precisionFormatter = (num: number, precision: number): string => {
|
|
if (precision === 0) {
|
|
return String(Math.round(num))
|
|
}
|
|
if (isFloat(num)) {
|
|
const [head, tail] = String(num).split('.')
|
|
return Number(head).toLocaleString() + '.' + tail.slice(0, precision)
|
|
} else {
|
|
// pad int with 0
|
|
return num.toLocaleString() + '.' + '0'.repeat(precision)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Formats a timestamp.
|
|
* Optionally formats the string to UTC
|
|
* @param value
|
|
* @param format
|
|
* @param utc
|
|
* @returns
|
|
*/
|
|
export const timestampFormatter = (
|
|
value: string,
|
|
format: string = DateTimeFormats.FULL,
|
|
utc: boolean = false
|
|
) => {
|
|
if (utc) {
|
|
return dayjs.utc(value).format(format)
|
|
}
|
|
return dayjs(value).format(format)
|
|
}
|
|
|
|
/**
|
|
* Hook to create common wrapping components, perform data transformations
|
|
* returns a Container component and the minHeight set
|
|
*/
|
|
export const useChartSize = (
|
|
size: CommonChartProps<any>['size'] = 'normal',
|
|
sizeMap: {
|
|
tiny: number
|
|
small: number
|
|
normal: number
|
|
large: number
|
|
} = {
|
|
tiny: 76,
|
|
small: 96,
|
|
normal: 160,
|
|
large: 280,
|
|
}
|
|
) => {
|
|
const minHeight = sizeMap[size]
|
|
const Container: FC<PropsWithChildren & { className?: string }> = useMemo(
|
|
() =>
|
|
({ className, children }) => (
|
|
<ResponsiveContainer
|
|
className={className}
|
|
height={minHeight}
|
|
minHeight={minHeight}
|
|
width="100%"
|
|
>
|
|
{children as JSX.Element}
|
|
</ResponsiveContainer>
|
|
),
|
|
[size]
|
|
)
|
|
return {
|
|
Container,
|
|
minHeight,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transforms data points into a stacked data structure that can be consumed by recharts
|
|
*/
|
|
export const useStacked = ({
|
|
data,
|
|
xAxisKey,
|
|
yAxisKey,
|
|
stackKey,
|
|
variant = 'values',
|
|
}: Pick<StackedChartProps<any>, 'xAxisKey' | 'yAxisKey' | 'stackKey'> & {
|
|
variant?: 'values' | 'percentages'
|
|
} & Pick<CommonChartProps<Record<string, number>>, 'data'>) => {
|
|
const stackedData = useMemo(() => {
|
|
if (!data) return []
|
|
const mapping = data.reduce(
|
|
(acc, datum) => {
|
|
const x = datum[xAxisKey]
|
|
const y = datum[yAxisKey]
|
|
const s = datum[stackKey]
|
|
if (!acc[x]) {
|
|
acc[x] = {}
|
|
}
|
|
|
|
acc[x][s] = y
|
|
return acc
|
|
},
|
|
{} as Record<string, Record<string, number>>
|
|
)
|
|
|
|
const flattened = Object.entries(mapping).map(([x, sMap]) => ({
|
|
...sMap,
|
|
[xAxisKey]: Number.isNaN(Number(x)) ? x : Number(x),
|
|
}))
|
|
return flattened
|
|
}, [JSON.stringify(data)])
|
|
const dataKeys = useMemo(() => {
|
|
return Object.keys(stackedData[0] || {})
|
|
.filter((k) => k !== xAxisKey && k !== yAxisKey)
|
|
.sort()
|
|
}, [JSON.stringify(stackedData[0] || {})])
|
|
|
|
const percentagesStackedData = useMemo(() => {
|
|
if (variant !== 'percentages') return
|
|
|
|
return stackedData.map((stack) => {
|
|
const entries = Object.entries(stack) as Array<[string, number]>
|
|
let map
|
|
const sum = entries
|
|
.filter(([key, _value]) => dataKeys.includes(key))
|
|
.reduce((acc, [_key, value]) => acc + value, 0)
|
|
map = entries.reduce((acc, [key, value]) => {
|
|
if (!dataKeys.includes(key)) {
|
|
return { ...acc, [key]: value }
|
|
}
|
|
return { ...acc, [key]: value !== 0 ? value / sum : 0 }
|
|
}, {} as any)
|
|
return map
|
|
})
|
|
}, [JSON.stringify(stackedData)])
|
|
|
|
return { dataKeys, stackedData, percentagesStackedData }
|
|
}
|