import { Code, Play } from 'lucide-react' import { ReactNode, useEffect, useMemo, useState } from 'react' import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts' import { toast } from 'sonner' import { useParams } from 'common' import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' import Results from 'components/interfaces/SQLEditor/UtilityPanel/Results' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' import { Parameter, parseParameters, processParameterizedSql } from 'lib/sql-parameters' import { Button, ChartContainer, ChartTooltip, ChartTooltipContent, cn, CodeBlock, SQL_ICON, } from 'ui' import { Admonition } from 'ui-patterns' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import { containsUnknownFunction, isReadOnlySelect } from '../AIAssistantPanel/AIAssistant.utils' import { ButtonTooltip } from '../ButtonTooltip' import { BlockViewConfiguration } from './BlockViewConfiguration' import { EditQueryButton } from './EditQueryButton' import { ParametersPopover } from './ParametersPopover' import { getCumulativeResults } from './QueryBlock.utils' const DEFAULT_CHART_CONFIG: ChartConfig = { type: 'bar', cumulative: false, xKey: '', yKey: '', showLabels: false, showGrid: false, } interface QueryBlockProps { id?: string label: string sql: string chartConfig?: ChartConfig maxHeight?: number parameterValues?: Record actions?: ReactNode // Any other actions specific to the parent to be rendered in the header isChart?: boolean isLoading?: boolean runQuery?: boolean lockColumns?: boolean onSetParameter?: (params: Parameter[]) => void onUpdateChartConfig?: (config: ChartConfig) => void } // [Joshen ReportsV2] JFYI we may adjust this in subsequent PRs when we implement this into Reports V2 // First iteration here is just to make this work with the AI Assistant first export const QueryBlock = ({ id, label, sql, chartConfig = DEFAULT_CHART_CONFIG, maxHeight = 250, parameterValues: extParameterValues, actions, isChart = false, isLoading = false, runQuery = false, lockColumns = false, onSetParameter, onUpdateChartConfig, }: QueryBlockProps) => { const { ref } = useParams() const { project } = useProjectContext() const [showSql, setShowSql] = useState(!isChart) const [view, setView] = useState<'table' | 'chart'>(isChart ? 'chart' : 'table') // [Joshen] Thinking cumulative could just be a UI state here to prevent unnecessary re-rendering const [cumulative, setCumulative] = useState(false) const [queryResult, setQueryResult] = useState() const [parameterValues, setParameterValues] = useState>({}) const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>() const { xKey, yKey } = chartConfig const showChart = isChart && (queryResult ?? []).length > 0 && !!xKey && !!yKey && view === 'chart' const parameters = useMemo(() => { if (!sql) return [] return parseParameters(sql) }, [sql]) const combinedParameterValues = { ...extParameterValues, ...parameterValues } const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({ onSuccess: (data) => setQueryResult(data.result), }) const handleExecute = () => { if (!sql || isLoading) return if (!isReadOnlySelect(sql)) { const hasUnknownFunctions = containsUnknownFunction(sql) return setShowWarning(hasUnknownFunctions ? 'hasUnknownFunctions' : 'hasWriteOperation') } try { const processedSql = processParameterizedSql(sql, combinedParameterValues) execute({ projectRef: ref, connectionString: project?.connectionString, sql: processedSql, }) } catch (error: any) { toast.error(`Failed to execute query: ${error.message}`) } } // Run once on mount to parse parameters and notify parent useEffect(() => { if (!!sql && onSetParameter) { const params = parseParameters(sql) onSetParameter(params) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [sql]) useEffect(() => { if (!!sql && !isLoading && runQuery && isReadOnlySelect(sql) && !!project) { handleExecute() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [sql, isLoading, runQuery, project]) return (

{label}

{/* QueryBlock actions */}
} onClick={() => setShowSql(!showSql)} tooltip={{ content: { side: 'bottom', text: showSql ? 'Hide query' : 'Show query' }, }} /> {queryResult && ( <> {/* [Joshen ReportsV2] Won't see this just yet as this is intended for Reports V2 */} {parameters.length > 0 && ( )} {isChart && ( { if (onUpdateChartConfig) onUpdateChartConfig(config) setCumulative(config.cumulative) }} /> )} )} } loading={isExecuting || isLoading} disabled={isLoading} onClick={handleExecute} tooltip={{ content: { side: 'bottom', className: 'max-w-56 text-center', text: isExecuting ? (

{`Query is running. You may cancel ongoing queries via the [SQL Editor](/project/${ref}/sql?viewOngoingQueries=true).`}

) : ( 'Run query' ), }, }} /> {actions}
{showWarning && (

{showWarning === 'hasWriteOperation' ? 'This query contains write operations.' : 'This query involves running a function.'}{' '} Are you sure you want to execute it?

Make sure you are not accidentally removing something important.

)} {/* QueryBlock output */} {showSql && (
code]:m-0 [&>code>span]:text-foreground' )} />
)} {isExecuting && queryResult === undefined && (
)} {showChart ? (
} />
) : ( <> {queryResult && (
)} )}
) }