Skip to content

Commit ae1d25d

Browse files
authored
fix filltimeseries for reports (supabase#37160)
* fix filltimeseries * fix type err * fix seconds in tooltip changing * fix filter default values
1 parent 3597b79 commit ae1d25d

File tree

5 files changed

+89
-38
lines changed

5 files changed

+89
-38
lines changed

apps/studio/components/interfaces/Reports/ReportChart.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* This component acts as a bridge between the data-fetching logic and the
88
* presentational chart component.
99
*/
10+
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'
1011
import LogChartHandler from 'components/ui/Charts/LogChartHandler'
1112
import { useChartData } from 'hooks/useChartData'
1213
import type { UpdateDateRange } from 'pages/project/[ref]/reports/database'
@@ -60,6 +61,28 @@ const ReportChart = ({
6061
: chart.showMaxValue,
6162
})
6263

64+
const isTopListChart = chart.id === 'top-api-routes' || chart.id === 'top-rpc-functions'
65+
66+
const chartDataArray = Array.isArray(data) ? data : []
67+
68+
const { data: filledData, isError: isFillError } = useFillTimeseriesSorted(
69+
chartDataArray,
70+
'period_start',
71+
(chartAttributes.length > 0 ? chartAttributes : chart.attributes).map(
72+
(attr: any) => attr.attribute
73+
),
74+
0,
75+
startDate,
76+
endDate,
77+
undefined,
78+
interval
79+
)
80+
81+
const finalData =
82+
chartDataArray.length > 0 && chartDataArray.length < 20 && !isFillError && !isTopListChart
83+
? filledData
84+
: chartDataArray
85+
6386
const getExpDemoChartData = () =>
6487
new Array(20).fill(0).map((_, index) => ({
6588
period_start: new Date(startDate).getTime() + index * 1000,
@@ -129,7 +152,7 @@ const ReportChart = ({
129152
attributes={
130153
(chartAttributes.length > 0 ? chartAttributes : chart.attributes) as MultiAttribute[]
131154
}
132-
data={data}
155+
data={finalData}
133156
isLoading={isLoadingChart || isLoading}
134157
highlightedValue={highlightedValue as any}
135158
updateDateRange={updateDateRange}

apps/studio/components/interfaces/Reports/Reports.constants.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,38 @@ export const REPORTS_DATEPICKER_HELPERS: ReportsDatetimeHelper[] = [
3636
},
3737
{
3838
text: REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES,
39-
calcFrom: () => dayjs().subtract(1, 'hour').startOf('day').toISOString(),
39+
calcFrom: () => dayjs().subtract(1, 'hour').toISOString(),
4040
calcTo: () => dayjs().toISOString(),
4141
default: true,
4242
availableIn: ['free', 'pro', 'team', 'enterprise'],
4343
},
4444
{
4545
text: REPORT_DATERANGE_HELPER_LABELS.LAST_3_HOURS,
46-
calcFrom: () => dayjs().subtract(3, 'hour').startOf('day').toISOString(),
46+
calcFrom: () => dayjs().subtract(3, 'hour').toISOString(),
4747
calcTo: () => dayjs().toISOString(),
4848
availableIn: ['free', 'pro', 'team', 'enterprise'],
4949
},
5050
{
5151
text: REPORT_DATERANGE_HELPER_LABELS.LAST_24_HOURS,
52-
calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(),
52+
calcFrom: () => dayjs().subtract(1, 'day').toISOString(),
5353
calcTo: () => dayjs().toISOString(),
5454
availableIn: ['free', 'pro', 'team', 'enterprise'],
5555
},
5656
{
5757
text: REPORT_DATERANGE_HELPER_LABELS.LAST_7_DAYS,
58-
calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(),
58+
calcFrom: () => dayjs().subtract(7, 'day').toISOString(),
5959
calcTo: () => dayjs().toISOString(),
6060
availableIn: ['pro', 'team', 'enterprise'],
6161
},
6262
{
6363
text: REPORT_DATERANGE_HELPER_LABELS.LAST_14_DAYS,
64-
calcFrom: () => dayjs().subtract(14, 'day').startOf('day').toISOString(),
64+
calcFrom: () => dayjs().subtract(14, 'day').toISOString(),
6565
calcTo: () => dayjs().toISOString(),
6666
availableIn: ['team', 'enterprise'],
6767
},
6868
{
6969
text: REPORT_DATERANGE_HELPER_LABELS.LAST_28_DAYS,
70-
calcFrom: () => dayjs().subtract(28, 'day').startOf('day').toISOString(),
70+
calcFrom: () => dayjs().subtract(28, 'day').toISOString(),
7171
calcTo: () => dayjs().toISOString(),
7272
availableIn: ['team', 'enterprise'],
7373
},

apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,12 @@ export const fillTimeseries = (
474474
defaultValue: number,
475475
min?: string,
476476
max?: string,
477-
minPointsToFill: number = 20
477+
minPointsToFill: number = 20,
478+
interval?: string
478479
) => {
480+
if (timeseriesData.length === 0 && !(min && max)) {
481+
return []
482+
}
479483
// If we have more points than minPointsToFill, just normalize timestamps and return
480484
if (timeseriesData.length > minPointsToFill) {
481485
return timeseriesData.map((datum) => {
@@ -493,41 +497,61 @@ export const fillTimeseries = (
493497

494498
// const truncationSample = timeseriesData.length > 0 ? timeseriesData[0][timestampKey] : min || max
495499
const truncationSamples = timeseriesData.length > 0 ? dates : [minDate, maxDate]
496-
const truncation = getTimestampTruncation(truncationSamples as Dayjs[])
500+
let truncation: 'second' | 'minute' | 'hour' | 'day'
501+
let step = 1
502+
503+
if (interval) {
504+
const match = interval.match(/^(\d+)(m|h|d|s)$/)
505+
if (match) {
506+
step = parseInt(match[1], 10)
507+
const unitChar = match[2] as 'm' | 'h' | 'd' | 's'
508+
const unitMap = { s: 'second', m: 'minute', h: 'hour', d: 'day' } as const
509+
truncation = unitMap[unitChar]
510+
} else {
511+
// Fallback for invalid format
512+
truncation = getTimestampTruncation(truncationSamples as Dayjs[])
513+
}
514+
} else {
515+
truncation = getTimestampTruncation(truncationSamples as Dayjs[])
516+
}
497517

498518
const newData = timeseriesData.map((datum) => {
499-
const iso = dayjs.utc(datum[timestampKey]).toISOString()
519+
const timestamp = datum[timestampKey]
520+
const iso = isUnixMicro(timestamp)
521+
? unixMicroToIsoTimestamp(timestamp)
522+
: dayjs.utc(timestamp).toISOString()
500523
datum[timestampKey] = iso
501524
return datum
502525
})
503526

504-
const diff = maxDate.diff(minDate, truncation as dayjs.UnitType)
505-
// Intentional throwing of error here to be caught by Sentry, as this would indicate a bug since charts shouldn't be rendering more than 10k data points
506-
if (diff > 10000) {
507-
throw new Error(
508-
'The selected date range will render more than 10,000 data points within the charts, which will degrade browser performance. Please select a smaller date range.'
509-
)
510-
}
511-
512-
for (let i = 0; i <= diff; i++) {
513-
const dateToMaybeAdd = minDate.add(i, truncation as dayjs.ManipulateType)
514-
515-
const keys = typeof valueKey === 'string' ? [valueKey] : valueKey
516-
517-
const toMerge = keys.reduce(
518-
(acc, key) => ({
519-
...acc,
520-
[key]: defaultValue,
521-
}),
522-
{}
523-
)
524-
525-
if (!dates.find((d) => isEqual(d, dateToMaybeAdd))) {
527+
let currentDate = minDate
528+
while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate)) {
529+
const found = dates.find((d) => {
530+
const d_date = d as Dayjs
531+
return (
532+
d_date.year() === currentDate.year() &&
533+
d_date.month() === currentDate.month() &&
534+
d_date.date() === currentDate.date() &&
535+
d_date.hour() === currentDate.hour() &&
536+
d_date.minute() === currentDate.minute()
537+
)
538+
})
539+
if (!found) {
540+
const keys = typeof valueKey === 'string' ? [valueKey] : valueKey
541+
542+
const toMerge = keys.reduce(
543+
(acc, key) => ({
544+
...acc,
545+
[key]: defaultValue,
546+
}),
547+
{}
548+
)
526549
newData.push({
527-
[timestampKey]: dateToMaybeAdd.toISOString(),
550+
[timestampKey]: currentDate.toISOString(),
528551
...toMerge,
529552
})
530553
}
554+
currentDate = currentDate.add(step, truncation)
531555
}
532556

533557
return newData

apps/studio/components/ui/Charts/ComposedChart.utils.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export const calculateTotalChartAggregate = (
131131
const CustomTooltip = ({
132132
active,
133133
payload,
134+
label,
134135
attributes,
135136
isPercentage,
136137
format,
@@ -139,7 +140,6 @@ const CustomTooltip = ({
139140
isActiveHoveredChart,
140141
}: TooltipProps) => {
141142
if (active && payload && payload.length) {
142-
const timestamp = payload[0].payload.timestamp
143143
const maxValueAttribute = isMaxAttribute(attributes)
144144
const maxValueData =
145145
maxValueAttribute && payload?.find((p: any) => p.dataKey === maxValueAttribute.attribute)
@@ -203,7 +203,7 @@ const CustomTooltip = ({
203203
!isActiveHoveredChart && 'opacity-0'
204204
)}
205205
>
206-
<p className="font-medium">{dayjs(timestamp).format(DateTimeFormats.FULL_SECONDS)}</p>
206+
<p className="font-medium">{label}</p>
207207
<div className="grid gap-0">
208208
{payload
209209
.reverse()

apps/studio/hooks/misc/useReportDateRange.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,21 @@ export const useReportDateRange = (
175175

176176
const handleIntervalGranularity = useCallback((from: string, to: string) => {
177177
const diffInDays = dayjs(to).diff(from, 'day', true)
178+
const diffInHours = dayjs(to).diff(from, 'hour', true)
178179
const conditions = {
179-
'1m': dayjs(to).diff(from, 'hour') < 3, // less than 3 hours
180-
'10m': dayjs(to).diff(from, 'hour') < 6, // less than 6 hours
181-
'30m': dayjs(to).diff(from, 'hour') < 18, // less than 18 hours
180+
'1m': diffInHours < 1.1, // less than 1.1 hours
181+
'5m': diffInHours < 3.1, // less than 3.1 hours
182+
'10m': diffInHours < 6.1, // less than 6.1 hours
183+
'30m': diffInHours < 25, // less than 25 hours
182184
'1h': diffInDays < 10, // less than 10 days
183185
'1d': diffInDays >= 10, // more than 10 days
184186
}
185187

186188
switch (true) {
187189
case conditions['1m']:
188190
return '1m'
191+
case conditions['5m']:
192+
return '5m'
189193
case conditions['10m']:
190194
return '10m'
191195
case conditions['30m']:

0 commit comments

Comments
 (0)