Joshen/fe 3624 queryblock causes client side crash if rendering many rows (#47021)

## Context

Having a custom report block on the project home page with a SQL that
returns a large set of results (e.g > 100k rows) causes a client side
crash with "Maximum call stack size exceeded"
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/e4bb5b73-e114-4687-9d0b-a7bff328167c"
/>

This is happening due to an array spread in `computeYAxisWidth` in
`Math.max` - which am hence opting to use a `reduce` instead to mitigate
the problem.

Am also opting to apply the same autolimit logic in the SQL editor into
the `QueryBlock` here, so that we don't unnecessarily fetch a large
dataset in this UI. Added a UI indicator as well if auto limit has been
applied (So this also overlaps into dashboard scalability too)
<img width="1383" height="465" alt="image"
src="https://github.com/user-attachments/assets/08b66398-f3b8-49ce-b4a4-23c91510bd54"
/>



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

## Summary by CodeRabbit

* **New Features**
* Report query blocks now feature automatic SQL limiting functionality,
which restricts query results to a maximum of 100 rows when enabled
* When active, query result blocks display an informational notice to
users, clearly indicating the row restriction that has been applied

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-06-17 17:52:03 +08:00
1 parent f4c3e26fd7
commit 42ec3c4960
3 files changed
+21 -3

No files matched your search

@@ -5,6 +5,7 @@ import { X } from 'lucide-react'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import { checkIfAppendLimitRequired, suffixWithLimit } from '../../SQLEditor/SQLEditor.utils'
import { BURSTABLE_IO_METRIC_KEYS, DEPRECATED_REPORTS } from '../Reports.constants'
import { ChartBlock } from './ChartBlock'
import { DeprecatedChartBlock } from './DeprecatedChartBlock'
@@ -78,7 +79,10 @@ export const ReportBlock = ({
}
)
const autoLimit = 100
const sql = isSnippet ? (data?.content as SqlSnippets.Content)?.unchecked_sql : undefined
const { appendAutoLimit } = checkIfAppendLimitRequired(sql ?? '', autoLimit)
const chartConfig = { ...DEFAULT_CHART_CONFIG, ...(item.chartConfig ?? {}) }
const isDeprecatedChart = DEPRECATED_REPORTS.includes(item.attribute)
const snippetMissing = contentError?.message.includes('Content not found')
@@ -98,6 +102,7 @@ export const ReportBlock = ({
sql,
readOnlyConnectionString,
postgresConnectionString,
autoLimit,
]),
queryFn: async () => {
if (!projectRef || !sql) return null
@@ -109,13 +114,15 @@ export const ReportBlock = ({
return null
}
const formattedSql = suffixWithLimit(acceptUntrustedSql(sql), autoLimit)
return executeSql({
projectRef,
connectionString,
// acceptUntrustedSql is usually not allowed in an auto-run position,
// but in this case we are explicitly allowing it because adding a block
// to a report is an explicit user action.
sql: acceptUntrustedSql(sql),
sql: formattedSql,
})
},
enabled: !isLoadingContent && contentError == null,
@@ -180,6 +187,7 @@ export const ReportBlock = ({
onUpdateChartConfig={onUpdateChart}
onRemoveChart={() => onRemoveChart({ metric: { key: item.attribute } })}
disabled={isLoadingContent || snippetMissing || !sql}
autoLimit={appendAutoLimit}
/>
) : isUnavailableBurstChart ? (
<UnavailableChartBlock
@@ -50,6 +50,7 @@ export interface QueryBlockProps {
blockWriteQueries?: boolean
/** Render the chart tooltip in a portal so it isn't clipped by overflow-hidden ancestors (e.g. report cards). */
portalTooltip?: boolean
autoLimit?: boolean
onExecute?: (queryType: 'select' | 'mutation') => void
onRemoveChart?: () => void
onUpdateChartConfig?: ({ chartConfig }: { chartConfig: Partial<ChartConfig> }) => void
@@ -73,6 +74,7 @@ export const QueryBlock = ({
disabled = false,
blockWriteQueries = false,
portalTooltip = false,
autoLimit = false,
onExecute,
onRemoveChart,
onUpdateChartConfig,
@@ -390,6 +392,11 @@ export const QueryBlock = ({
)}
>
<Results rows={results} />
{autoLimit && (
<p className="text-xs font-mono px-2 py-1 border-t text-foreground-light">
Limited to only 100 rows
</p>
)}
</div>
)
)}
@@ -31,8 +31,11 @@ export const computeYAxisWidth = (
): number => {
if (isLogScale) return 52
if (isPercentage) return Math.max(36, (3 + 1) * 8) // max tick is "100"
const maxMagnitude =
data.length > 0 ? Math.max(...data.map((d) => Math.abs(Number(d[key]) || 0))) : 0
const maxMagnitude = data.reduce((max, d) => {
const magnitude = Math.abs(Number(d[key]) || 0)
return magnitude > max ? magnitude : max
}, 0)
return Math.max(36, (formatYAxisTick(maxMagnitude).length + 1) * 8)
}