import dayjs from 'dayjs' import { Code, Play } from 'lucide-react' import { DragEvent, ReactNode, useEffect, useMemo, useRef, useState } from 'react' import { Bar, BarChart, CartesianGrid, Cell, Tooltip, XAxis, YAxis } from 'recharts' import { Badge, Button, ChartContainer, ChartTooltipContent, cn } from 'ui' import { CodeBlock } from 'ui-patterns/CodeBlock' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { ButtonTooltip } from '../ButtonTooltip' import { CHART_COLORS } from '../Charts/Charts.constants' import { SqlWarningAdmonition } from '../SqlWarningAdmonition' import { BlockViewConfiguration } from './BlockViewConfiguration' import { EditQueryButton } from './EditQueryButton' import { checkHasNonPositiveValues, computeYAxisWidth, formatLogTick, formatYAxisTick, getCumulativeResults, } from './QueryBlock.utils' import { ReportBlockContainer } from '@/components/interfaces/Reports/ReportBlock/ReportBlockContainer' import { ChartConfig } from '@/components/interfaces/SQLEditor/UtilityPanel/ChartConfig' import Results from '@/components/interfaces/SQLEditor/UtilityPanel/Results' export const DEFAULT_CHART_CONFIG: ChartConfig = { type: 'bar', cumulative: false, xKey: '', yKey: '', showLabels: false, showGrid: false, logScale: false, view: 'table', } export interface QueryBlockProps { id?: string label: string sql?: string isWriteQuery?: boolean chartConfig?: ChartConfig actions?: ReactNode results?: any[] errorText?: string isExecuting?: boolean initialHideSql?: boolean draggable?: boolean disabled?: boolean blockWriteQueries?: boolean onExecute?: (queryType: 'select' | 'mutation') => void onRemoveChart?: () => void onUpdateChartConfig?: ({ chartConfig }: { chartConfig: Partial }) => void onDragStart?: (e: DragEvent) => 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, actions, results, errorText, isWriteQuery = false, isExecuting = false, initialHideSql = false, draggable = false, disabled = false, blockWriteQueries = false, onExecute, onRemoveChart, onUpdateChartConfig, onDragStart, }: QueryBlockProps) => { const [chartSettings, setChartSettings] = useState(chartConfig) const { xKey, yKey, view = 'table', logScale = false } = chartSettings const [showSql, setShowSql] = useState(!results && !initialHideSql) const [focusDataIndex, setFocusDataIndex] = useState() const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>() const prevIsWriteQuery = useRef(isWriteQuery) useEffect(() => { if (!prevIsWriteQuery.current && isWriteQuery) { setShowWarning('hasWriteOperation') } if (!isWriteQuery && showWarning === 'hasWriteOperation') { setShowWarning(undefined) } prevIsWriteQuery.current = isWriteQuery }, [isWriteQuery, showWarning]) useEffect(() => { setChartSettings(chartConfig) }, [chartConfig]) const formattedQueryResult = useMemo(() => { return results?.map((row) => { return Object.fromEntries( Object.entries(row).map(([key, value]) => { if (key === yKey) return [key, Number(value)] return [key, value] }) ) }) }, [results, yKey]) const chartData = chartSettings.cumulative ? getCumulativeResults({ rows: formattedQueryResult ?? [] }, chartSettings) : formattedQueryResult const hasNonPositiveValues = useMemo(() => { if (!logScale || !yKey || !chartData?.length) return false return checkHasNonPositiveValues(chartData, yKey) }, [logScale, yKey, chartData]) const effectiveLogScale = logScale && !hasNonPositiveValues const yAxisWidth = computeYAxisWidth(chartData ?? [], yKey ?? '', { isLogScale: effectiveLogScale, }) const getDateFormat = (key: any) => { const value = chartData?.[0]?.[key] || '' if (typeof value === 'number') return 'number' if (dayjs(value).isValid()) return 'date' return 'string' } const xKeyDateFormat = getDateFormat(xKey) const hasResults = Array.isArray(results) && results.length > 0 const runSelect = () => { if (!sql || disabled || isExecuting) return if (isWriteQuery) { setShowWarning('hasWriteOperation') return } onExecute?.('select') } const runMutation = () => { if (!sql || disabled || isExecuting) return setShowWarning(undefined) onExecute?.('mutation') } return ( ) => onDragStart?.(e)} loading={isExecuting} label={label} badge={isWriteQuery && Write} actions={ <> {!disabled && ( <> } onClick={() => setShowSql(!showSql)} tooltip={{ content: { side: 'bottom', text: showSql ? 'Hide query' : 'Show query' }, }} /> {hasResults && ( { if (onUpdateChartConfig) onUpdateChartConfig({ chartConfig: { view: nextView } }) setChartSettings({ ...chartSettings, view: nextView }) }} updateChartConfig={(config) => { if (onUpdateChartConfig) onUpdateChartConfig({ chartConfig: config }) setChartSettings(config) }} /> )} } loading={isExecuting} disabled={isExecuting || disabled || !sql} onClick={runSelect} tooltip={{ content: { side: 'bottom', className: 'max-w-56 text-center', text: isExecuting ? 'Query is running. Check the SQL Editor to manage running queries.' : 'Run query', }, }} /> )} {actions} } > {!!showWarning && !blockWriteQueries && ( setShowWarning(undefined)} onConfirm={runMutation} disabled={!sql} {...(showWarning !== 'hasWriteOperation' ? { message: 'Run this query now and send the results to the Assistant? ', subMessage: 'We will execute the query and provide the result rows back to the Assistant to continue the conversation.', cancelLabel: 'Skip', confirmLabel: 'Run & send', } : {})} /> )} {showSql && (
code]:m-0 [&>code>span]:text-foreground' )} />
)} {isExecuting && !results && (
)} {view === 'chart' && results !== undefined ? ( <> {(results ?? []).length === 0 ? (

No results returned from query

) : !xKey || !yKey ? (

Select columns for the X and Y axes

) : (
{hasNonPositiveValues && (

Log scale is unavailable because the data contains zero or negative values.

)} { if (e.activeTooltipIndex !== focusDataIndex) { setFocusDataIndex(e.activeTooltipIndex) } }} onMouseLeave={() => setFocusDataIndex(undefined)} > xKeyDateFormat === 'date' ? dayjs(value).format('MMM D YYYY HH:mm') : value } /> xKeyDateFormat === 'date' ? dayjs(value).format('MMM D YYYY HH:mm') : String(value) } /> } /> {chartData?.map((_: any, index: number) => ( ))}
)} ) : ( <> {isWriteQuery && blockWriteQueries ? (

SQL query is not read-only and cannot be rendered

Queries that involve any mutation will not be run in reports

{!!onRemoveChart && ( )}
) : !isExecuting && !!errorText ? (
ERROR: {errorText}
) : ( results && (
) )} )}
) }