'use client'; import { useMemo, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { AppCodeEditor } from '@/components/ui/inputs/AppCodeEditor'; import { yamlToJson } from '@/lib/yaml-to-json'; import { useRunSQL } from '@/api/buster_rest/sql/queryRequests'; import { BusterChart } from '@/components/ui/charts/BusterChart'; import { ChartConfigPropsSchema, type ChartConfigProps, type DataResult } from '@buster/server-shared/metrics'; import type { ZodError } from 'zod'; import type { RunSQLResponse } from '@/api/asset_interfaces'; import { useMemoizedFn } from '@/hooks'; import { Button } from '@/components/ui/buttons'; import { Input } from '@/components/ui/inputs'; import { AppSplitter } from '@/components/ui/layouts/AppSplitter'; type YamlifiedConfig = { sql: string; chartConfig: ChartConfigProps; }; const initfile = `name: Top 10 Customers by Lifetime Value\ndescription: Shows the customers who have generated the highest total revenue over their entire relationship with the company\ntimeFrame: All time\nsql: \"SELECT \\n CONCAT(p.firstname, ' ', p.lastname) AS customer_name,\\n clv.metric_clv_all_time::numeric AS lifetime_value\\nFROM postgres.ont_ont.customer_all_time_clv clv\\nJOIN postgres.ont_ont.customer c ON clv.customerid = c.customerid\\nLEFT JOIN postgres.ont_ont.person p ON c.personid = p.businessentityid\\nWHERE p.firstname IS NOT NULL AND p.lastname IS NOT NULL\\nORDER BY clv.metric_clv_all_time::numeric DESC\\nLIMIT 10\\n\"\nchartConfig:\n selectedChartType: bar\n columnLabelFormats:\n customer_name:\n columnType: string\n style: string\n numberSeparatorStyle: null\n replaceMissingDataWith: null\n lifetime_value:\n columnType: number\n style: currency\n numberSeparatorStyle: ','\n minimumFractionDigits: 2\n maximumFractionDigits: 2\n replaceMissingDataWith: 0\n currency: USD\n barAndLineAxis:\n x:\n - customer_name\n y:\n - lifetime_value\n barLayout: horizontal\n`; const initDataSourceId = 'cc3ef3bc-44ec-4a43-8dc4-681cae5c996a'; export default function ChartPlayground() { // State management const [config, setConfig] = useState(initfile); const [dataResponse, setDataResponse] = useState(null); const [dataSourceId, setDataSourceId] = useState(initDataSourceId); // SQL mutation hook const { mutateAsync: runSQLMutation, error: runSQLError, isPending: isRunningSQL, isSuccess: hasRunSQL, reset: resetRunSQL } = useRunSQL(); // Parse YAML config const yamlifiedConfig = useMemo(() => { if (!config.trim()) return null; try { return yamlToJson(config); } catch (error) { return null; } }, [config]); // Parse and validate chart configuration const chartConfigParsed = useMemo(() => { if (!yamlifiedConfig?.chartConfig) { return { data: null, error: null }; } const parsed = ChartConfigPropsSchema.safeParse(yamlifiedConfig.chartConfig); return { data: parsed.success ? parsed.data : null, error: parsed.success ? null : (parsed.error as ZodError) }; }, [yamlifiedConfig]); // Derived values const chartConfig = chartConfigParsed.data; const chartConfigError = chartConfigParsed.error; const data: DataResult = dataResponse?.data || []; const columnMetadata = dataResponse?.data_metadata?.column_metadata || []; const hasSQL = !!yamlifiedConfig?.sql; const hasDataSourceId = !!dataSourceId.trim(); // SQL execution handler const runSQL = useMemoizedFn(async () => { if (!yamlifiedConfig?.sql || !hasDataSourceId) { return; } try { const res = await runSQLMutation({ sql: yamlifiedConfig.sql, data_source_id: dataSourceId }); setDataResponse(res); } catch (error) { // Error is handled by the hook } }); // Status checks const isReadyToRun = hasSQL && hasDataSourceId; const isReadyToChart = chartConfig && data.length > 0 && hasRunSQL; // Setup hotkey for running SQL (meta+enter) useHotkeys( 'meta+enter', (event) => { event.preventDefault(); if (isReadyToRun && !isRunningSQL) { runSQL(); } }, { enabled: isReadyToRun && !isRunningSQL, enableOnContentEditable: true, enableOnFormTags: true } ); // Define the left panel content (code editor and controls) const leftPanelContent = (
setDataSourceId(e.target.value)} />
); // Define the right panel content (chart preview) const rightPanelContent = (
{/* Header */}

Chart Preview

{/* Content Area */}
{/* Error States */} {chartConfigError && (

Chart Configuration Error

              {JSON.stringify(chartConfigError || {}, null, 2)}
            
)} {runSQLError && (

SQL Execution Error

              {JSON.stringify(runSQLError || {}, null, 2)}
            
)} {/* Loading State */} {isRunningSQL && (

Executing SQL Query

Please wait while we process your query...

)} {/* Checklist */} {!(chartConfig && data && hasRunSQL && dataSourceId) && (

Setup Checklist

  • Chart configuration is {chartConfig ? 'ready' : 'missing'}
  • 0 ? 'bg-green-400' : 'bg-yellow-300'}`}>
    0 ? 'text-green-700 line-through' : 'text-amber-700'}`}> Data is {data.length > 0 ? 'available' : 'not available'}
  • SQL has {hasRunSQL ? 'been executed' : 'not been run'}
  • Data Source ID is {dataSourceId ? 'set' : 'not set'}
)} {/* Chart Display */} {chartConfig && data && hasRunSQL && dataSourceId && (

Chart Ready

)}
); return (
); } const DEFAULT_LAYOUT = ['40%', 'auto'];