import { acceptUntrustedSql } from '@supabase/pg-meta' import { useQuery } from '@tanstack/react-query' import { useParams } from 'common' import { X } from 'lucide-react' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { DEPRECATED_REPORTS } from '../Reports.constants' import { ChartBlock } from './ChartBlock' import { DeprecatedChartBlock } from './DeprecatedChartBlock' import { ChartConfig } from '@/components/interfaces/SQLEditor/UtilityPanel/ChartConfig' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { DEFAULT_CHART_CONFIG, QueryBlock } from '@/components/ui/QueryBlock/QueryBlock' import { AnalyticsInterval } from '@/data/analytics/constants' import { useContentIdQuery } from '@/data/content/content-id-query' import { usePrimaryDatabase } from '@/data/read-replicas/replicas-query' import { executeSql } from '@/data/sql/execute-sql-query' import { sqlKeys } from '@/data/sql/keys' import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector' import type { Dashboards, SqlSnippets } from '@/types' interface ReportBlockProps { item: Dashboards.Chart startDate: string endDate: string interval: AnalyticsInterval disableUpdate: boolean isRefreshing: boolean onRemoveChart: ({ metric }: { metric: { key: string } }) => void onUpdateChart: ({ chart, chartConfig, }: { chart?: Partial chartConfig?: Partial }) => void } export const ReportBlock = ({ item, startDate, endDate, interval, disableUpdate, isRefreshing, onRemoveChart, onUpdateChart, }: ReportBlockProps) => { const { ref: projectRef } = useParams() const state = useDatabaseSelectorStateSnapshot() const [isWriteQuery, setIsWriteQuery] = useState(false) const isSnippet = item.attribute.startsWith('snippet_') const { data, error: contentError, isPending: isLoadingContent, } = useContentIdQuery( { projectRef, id: item.id }, { enabled: isSnippet && !!item.id, refetchOnWindowFocus: false, refetchOnMount: false, refetchIntervalInBackground: false, retry: (failureCount: number, error) => { if (error.code === 404 || failureCount >= 2) return false return true }, } ) const sql = isSnippet ? (data?.content as SqlSnippets.Content)?.unchecked_sql : undefined const chartConfig = { ...DEFAULT_CHART_CONFIG, ...(item.chartConfig ?? {}) } const isDeprecatedChart = DEPRECATED_REPORTS.includes(item.attribute) const snippetMissing = contentError?.message.includes('Content not found') const { database: primaryDatabase } = usePrimaryDatabase({ projectRef }) const readOnlyConnectionString = primaryDatabase?.connection_string_read_only const postgresConnectionString = primaryDatabase?.connectionString const { data: queryResult, error: executeSqlError, isPending: executeSqlLoading, refetch, } = useQuery({ queryKey: sqlKeys.query(projectRef, [ item.id, sql, readOnlyConnectionString, postgresConnectionString, ]), queryFn: async () => { if (!projectRef || !sql) return null const connectionString = readOnlyConnectionString ?? postgresConnectionString if (!connectionString) { toast.error('Unable to establish a database connection for this project.') return null } 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), }) }, enabled: !isLoadingContent && contentError == null, refetchOnWindowFocus: false, }) const rows = queryResult?.result useEffect(() => { if (executeSqlError) { const errorMessage = String(executeSqlError).toLowerCase() const isReadOnlyError = errorMessage.includes('read-only transaction') || errorMessage.includes('permission denied') || errorMessage.includes('must be owner') if (isReadOnlyError) { setIsWriteQuery(true) } } }, [executeSqlError]) useEffect(() => { if (isRefreshing) { refetch() } }, [isRefreshing, refetch]) return ( <> {isSnippet ? ( } className="w-7 h-7" onClick={() => onRemoveChart({ metric: { key: item.attribute } })} tooltip={{ content: { side: 'bottom', text: 'Remove chart' } }} /> } onExecute={(_queryType) => { refetch() }} onUpdateChartConfig={onUpdateChart} onRemoveChart={() => onRemoveChart({ metric: { key: item.attribute } })} disabled={isLoadingContent || snippetMissing || !sql} /> ) : isDeprecatedChart ? ( } className="w-7 h-7" onClick={() => onRemoveChart({ metric: { key: item.attribute } })} tooltip={{ content: { side: 'bottom', text: 'Remove chart' } }} /> ) : null } /> ) : ( } className="w-7 h-7" onClick={() => onRemoveChart({ metric: { key: item.attribute } })} tooltip={{ content: { side: 'bottom', text: 'Remove chart' } }} /> ) : null } onUpdateChartConfig={onUpdateChart} /> )} ) }