@burdenoff/microfe-bigconsole 2026.731.8 → 2026.801.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/bigconsole/AdminRoutes.js +1 -1
  2. package/dist/bigconsole/AdminRoutes.js.map +1 -1
  3. package/dist/bigconsole/BigConsoleRoot.js +1 -1
  4. package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
  5. package/dist/bigconsole/BigConsoleRoutes.js +1 -1
  6. package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
  7. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js +1 -1
  8. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js.map +1 -1
  9. package/dist/bigconsole/components/embed/EmbedErrorBoundary.js +1 -1
  10. package/dist/bigconsole/components/embed/EmbedErrorBoundary.js.map +1 -1
  11. package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js +1 -1
  12. package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js.map +1 -1
  13. package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +7 -7
  14. package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -1
  15. package/dist/bigconsole/components/embed/widgets/KpiComparisonEmbed.js +20 -14
  16. package/dist/bigconsole/components/embed/widgets/KpiComparisonEmbed.js.map +1 -1
  17. package/dist/bigconsole/components/embed/widgets/ListEmbed.js +8 -8
  18. package/dist/bigconsole/components/embed/widgets/ListEmbed.js.map +1 -1
  19. package/dist/bigconsole/components/embed/widgets/MetricCardEmbed.js +17 -9
  20. package/dist/bigconsole/components/embed/widgets/MetricCardEmbed.js.map +1 -1
  21. package/dist/bigconsole/components/embed/widgets/TableEmbed.js +34 -22
  22. package/dist/bigconsole/components/embed/widgets/TableEmbed.js.map +1 -1
  23. package/dist/bigconsole/components/embed/widgets/TextEmbed.js +3 -3
  24. package/dist/bigconsole/components/embed/widgets/TextEmbed.js.map +1 -1
  25. package/dist/bigconsole/components/embed/widgets/shared.js +48 -3
  26. package/dist/bigconsole/components/embed/widgets/shared.js.map +1 -1
  27. package/dist/bigconsole/contexts/NavigationContext.js +1 -1
  28. package/dist/bigconsole/contexts/NavigationContext.js.map +1 -1
  29. package/dist/bigconsole/hooks/useFilterUrlSync.js +1 -1
  30. package/dist/bigconsole/hooks/useFilterUrlSync.js.map +1 -1
  31. package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
  32. package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
  33. package/dist/bigconsole/pages/DashboardViewPage.js +1 -1
  34. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  35. package/dist/bigconsole/pages/DataParsersPage.js +1 -1
  36. package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
  37. package/dist/bigconsole/pages/DataSinkBuilderPage.js +1 -1
  38. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  39. package/dist/bigconsole/pages/DataSinkViewPage.js +1 -1
  40. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  41. package/dist/bigconsole/pages/ParserBuilderPage.js +1 -1
  42. package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
  43. package/dist/bigconsole/pages/ParserViewPage.js +1 -1
  44. package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
  45. package/dist/bigconsole/pages/PipelineBuilderPage.js +1 -1
  46. package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
  47. package/dist/bigconsole/pages/PipelineViewPage.js +1 -1
  48. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  49. package/dist/bigconsole/pages/WorkflowViewPage.js +1 -1
  50. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  51. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"DataSinkViewPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinkViewPage.tsx"],"sourcesContent":["/**\n * DataSink View Page\n *\n * Page for viewing DataSink details, data preview, and actions.\n */\n\nimport { memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { formatDistanceToNow } from 'date-fns';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDataSinkSubscription } from '../hooks/useDataSinkSubscription';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { DataSinkDataViewer } from '../components/datasink/DataSinkDataViewer';\nimport { DataSinkTableViewer } from '../components/datasink/DataSinkTableViewer';\nimport { LinkedWorkflowsPanel } from '../components/datasink/LinkedWorkflowsPanel';\nimport { LinkedParsersPanel } from '../components/datasink/LinkedParsersPanel';\nimport { WorkflowStatusPanel } from '../components/datasink/WorkflowStatusPanel';\nimport { ExportDataSinkDialog } from '../components/datasink/ExportDataSinkDialog';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, IllustratedEmptyState, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Edit,\n RefreshCw,\n Trash2,\n Database,\n Loader2,\n AlertCircle,\n Eye,\n EyeOff,\n Download,\n Grid3X3,\n Code,\n} from 'lucide-react';\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const DataSinkViewPage = memo(function DataSinkViewPage() {\n const { dataSinkId } = useParams<{ dataSinkId: string; consoleId: string }>();\n const navigate = useAppNavigate();\n\n // Local state\n const [showData, setShowData] = useState(false);\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataLoading, setDataLoading] = useState(false);\n const [dataViewMode, setDataViewMode] = useState<'table' | 'json'>('table');\n // v3.0: server-side pagination for array payloads (avoids loading millions of rows).\n const [pageSize, setPageSize] = useState(100);\n const [pageOffset, setPageOffset] = useState(0);\n const [dataTotalCount, setDataTotalCount] = useState<number | null>(null);\n const [dataHasMore, setDataHasMore] = useState(false);\n\n // Store\n const dataSink = useDataSinkStore((state) => (dataSinkId ? state.dataSinks.get(dataSinkId) : undefined));\n\n // Operations\n const { loading, error, fetchDataSink, getDataSinkData, refreshDataSink, deleteDataSink } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Subscribe to updates\n useDataSinkSubscription({\n dataSinkId,\n enabled: !!dataSinkId,\n });\n\n // Load DataSink on mount - always fetch full data to get workflowLinks\n useEffect(() => {\n if (dataSinkId) {\n fetchDataSink(dataSinkId);\n }\n }, [dataSinkId, fetchDataSink]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n navigate(`/datasinks`);\n }, [navigate]);\n\n // Handle edit\n const handleEdit = useCallback(() => {\n navigate(`/datasinks/${dataSinkId}/edit`);\n }, [navigate, dataSinkId]);\n\n // Handle refresh\n const handleRefresh = useCallback(async () => {\n if (!dataSinkId) return;\n setIsRefreshing(true);\n try {\n await refreshDataSink({ id: dataSinkId });\n } catch (err) {\n } finally {\n setIsRefreshing(false);\n }\n }, [dataSinkId, refreshDataSink]);\n\n // Handle delete\n const handleDelete = useCallback(async () => {\n if (!dataSinkId) return;\n const native = await nativeConfirm({\n title: 'Delete DataSink?',\n message: 'Are you sure you want to delete this DataSink?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm('Are you sure you want to delete this DataSink?') : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n try {\n await deleteDataSink(dataSinkId);\n if (isNative()) void nativeNotify('success');\n navigate(`/datasinks`);\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n }\n }, [dataSinkId, deleteDataSink, navigate]);\n\n // Load one page of the sink's data (v3.0: server-side pagination).\n const loadDataPage = useCallback(\n async (offset: number, size: number) => {\n if (!dataSinkId) return;\n setDataLoading(true);\n try {\n const result = await getDataSinkData({ id: dataSinkId }, { limit: size, offset });\n if (result) {\n setDataPreview((result.raw as Record<string, unknown>) || {});\n // totalCount is non-null only for array payloads being paginated.\n setDataTotalCount(typeof result.totalCount === 'number' ? result.totalCount : null);\n setDataHasMore(Boolean(result.hasMore));\n setPageOffset(offset);\n }\n } catch {\n // handled by the hook's error state\n } finally {\n setDataLoading(false);\n }\n },\n [dataSinkId, getDataSinkData]\n );\n\n // Handle show data (toggles the preview; loads the first page).\n const handleShowData = useCallback(async () => {\n if (!dataSinkId) return;\n if (showData) {\n setShowData(false);\n return;\n }\n await loadDataPage(0, pageSize);\n setShowData(true);\n }, [dataSinkId, showData, loadDataPage, pageSize]);\n\n const handlePageSizeChange = useCallback(\n (size: number) => {\n setPageSize(size);\n void loadDataPage(0, size);\n },\n [loadDataPage]\n );\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // Loading state\n if (loading && !dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-status-error-text text-body-sm font-medium mb-2\">Failed to load DataSink</p>\n <p className=\"text-text-tertiary text-body-xs mb-6\">{error.message}</p>\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back\n </Button>\n </div>\n </div>\n );\n }\n\n // Not found state\n if (!dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"DataSink not found\"\n description=\"The DataSink you're looking for doesn't exist or has been deleted.\"\n action={\n <Button onClick={handleBack} variant=\"default\" size=\"default\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back to DataSinks\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-card-padding space-y-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4\">\n <div className=\"flex-1\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to DataSinks\n </Button>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{dataSink.name || 'Untitled'}</h1>\n <DataSinkStatusBadge status={dataSink.status} />\n </div>\n <p className=\"text-body-sm text-text-tertiary font-mono mt-1\">{dataSink.key}</p>\n {dataSink.description && <p className=\"text-body-sm text-text-secondary mt-2\">{dataSink.description}</p>}\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2 flex-wrap\">\n <Button onClick={handleRefresh} disabled={isRefreshing} variant=\"secondary\" size=\"default\">\n <RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />\n {isRefreshing ? 'Refreshing...' : 'Refresh'}\n </Button>\n <Button onClick={handleExport} variant=\"secondary\" size=\"default\">\n <Download className=\"w-4 h-4 mr-2\" />\n Export\n </Button>\n <Button onClick={handleEdit} variant=\"secondary\" size=\"default\">\n <Edit className=\"w-4 h-4 mr-2\" />\n Edit\n </Button>\n <Button onClick={handleDelete} disabled={isDeleting} variant=\"destructive\" size=\"default\">\n <Trash2 className=\"w-4 h-4 mr-2\" />\n {isDeleting ? 'Deleting...' : 'Delete'}\n </Button>\n </div>\n </div>\n\n <PagePurpose>\n A data sink is a structured store that holds your processed data and serves it to dashboards. From here you can\n inspect its schema and stats, preview the cached rows, refresh it from its linked workflow, or export it.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"bigconsole-datasink-view\"\n steps={[\n {\n id: 'preview',\n label: 'Preview the data',\n description: 'Open the cached rows to confirm the sink holds what you expect.',\n onClick: handleShowData,\n },\n {\n id: 'refresh',\n label: 'Refresh from source',\n description: 'Re-run the linked workflow to pull the latest data.',\n onClick: handleRefresh,\n },\n {\n id: 'build-dashboard',\n label: 'Visualize it on a dashboard',\n description: 'Add widgets that query this sink to see your data live.',\n onClick: () => navigate('/dashboards'),\n },\n {\n id: 'export',\n label: 'Export the data',\n description: 'Download the contents for use elsewhere.',\n onClick: handleExport,\n },\n ]}\n />\n\n {/* Details Grid */}\n <div className=\"grid gap-6 md:grid-cols-2\">\n {/* Info Panel — primary emphasis zone */}\n <GlassCard treatment=\"glass\" glow>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"space-y-4 text-body-sm\">\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd>\n <DataSinkStatusBadge status={dataSink.status} />\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Cache TTL</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.ttl ? `${dataSink.ttl}s` : 'None'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Refresh Type</dt>\n <dd className=\"text-text-primary font-medium capitalize\">{dataSink.refresh?.type || 'Manual'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.sizeBytes ? `${(dataSink.sizeBytes / 1024).toFixed(2)} KB` : '0 KB'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Version</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.version || 1}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Last Refreshed</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.lastRefreshedAt\n ? formatDistanceToNow(new Date(dataSink.lastRefreshedAt), { addSuffix: true })\n : 'Never'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Created</dt>\n <dd className=\"text-text-primary font-medium\">\n {formatDistanceToNow(new Date(dataSink.createdAt), { addSuffix: true })}\n </dd>\n </div>\n </dl>\n </CardContent>\n </GlassCard>\n\n {/* Workflow Status Panel */}\n {dataSink.fluidGridsWorkflowId && (\n <WorkflowStatusPanel\n workflowId={dataSink.fluidGridsWorkflowId}\n executionId={dataSink.fluidGridsWorkflowExecutionId || undefined}\n lastRefreshedAt={dataSink.lastRefreshedAt || undefined}\n setupState={dataSink.setupState}\n status={dataSink.status}\n onRerunWorkflow={handleRefresh}\n disabled={isRefreshing}\n isRerunning={isRefreshing}\n />\n )}\n\n {/* Linked Workflows */}\n <LinkedWorkflowsPanel workflowLinks={dataSink.workflowLinks} />\n\n {/* Linked Parsers */}\n <LinkedParsersPanel dataSinkKey={dataSink.key} />\n </div>\n\n {/* Data Preview Section */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <Database className=\"w-4 h-4 text-text-secondary\" />\n Data Preview\n </CardTitle>\n <div className=\"flex items-center gap-2\">\n {showData && (\n <div className=\"flex items-center border border-border-default rounded-md overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setDataViewMode('table')}\n className={`p-1.5 ${dataViewMode === 'table' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"Table view\"\n >\n <Grid3X3 className=\"w-4 h-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDataViewMode('json')}\n className={`p-1.5 ${dataViewMode === 'json' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"JSON view\"\n >\n <Code className=\"w-4 h-4\" />\n </button>\n </div>\n )}\n <Button\n onClick={handleShowData}\n disabled={dataLoading}\n variant={showData ? 'secondary' : 'default'}\n size=\"sm\"\n >\n {dataLoading ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Loading...\n </>\n ) : showData ? (\n <>\n <EyeOff className=\"w-4 h-4 mr-2\" />\n Hide Data\n </>\n ) : (\n <>\n <Eye className=\"w-4 h-4 mr-2\" />\n Show Data\n </>\n )}\n </Button>\n </div>\n </CardHeader>\n\n {showData && dataPreview && (\n <CardContent>\n {dataViewMode === 'table' ? (\n <DataSinkTableViewer data={dataPreview} maxHeight=\"400px\" />\n ) : (\n <DataSinkDataViewer data={dataPreview} maxHeight=\"400px\" />\n )}\n {/* v3.0: pagination controls — shown only for array payloads. */}\n {dataTotalCount !== null && (\n <div className=\"flex items-center justify-between gap-4 mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-center gap-2 text-body-sm text-text-secondary\">\n <span>Rows per page</span>\n <select\n aria-label=\"Rows per page\"\n className=\"bg-bg-canvas border border-border-default rounded px-2 py-0.5 text-text-primary\"\n value={pageSize}\n onChange={(e) => handlePageSizeChange(Number(e.target.value))}\n >\n {[100, 500, 1000].map((n) => (\n <option key={n} value={n}>\n {n}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-4 text-body-sm text-text-secondary\">\n <span>\n {dataTotalCount === 0\n ? '0 rows'\n : `${pageOffset + 1}–${Math.min(pageOffset + pageSize, dataTotalCount)} of ${dataTotalCount}`}\n </span>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={pageOffset === 0 || dataLoading}\n onClick={() => void loadDataPage(Math.max(0, pageOffset - pageSize), pageSize)}\n >\n Previous\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={!dataHasMore || dataLoading}\n onClick={() => void loadDataPage(pageOffset + pageSize, pageSize)}\n >\n Next\n </Button>\n </div>\n </div>\n )}\n </CardContent>\n )}\n\n {!showData && (\n <CardContent className=\"text-center p-8\">\n <Database className=\"w-10 h-10 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-body-sm text-text-secondary\">Click &quot;Show Data&quot; to preview the cached data.</p>\n </CardContent>\n )}\n </Card>\n\n {/* Export DataSink Dialog */}\n <ExportDataSinkDialog\n isOpen={isExportDialogOpen}\n dataSinkId={dataSinkId || null}\n dataSinkName={dataSink.name || dataSink.key}\n onClose={handleExportClose}\n />\n </div>\n );\n});\n\nexport default DataSinkViewPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataSinkViewPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinkViewPage.tsx"],"sourcesContent":["/**\n * DataSink View Page\n *\n * Page for viewing DataSink details, data preview, and actions.\n */\n\nimport { memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { formatDistanceToNow } from 'date-fns';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDataSinkSubscription } from '../hooks/useDataSinkSubscription';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { DataSinkDataViewer } from '../components/datasink/DataSinkDataViewer';\nimport { DataSinkTableViewer } from '../components/datasink/DataSinkTableViewer';\nimport { LinkedWorkflowsPanel } from '../components/datasink/LinkedWorkflowsPanel';\nimport { LinkedParsersPanel } from '../components/datasink/LinkedParsersPanel';\nimport { WorkflowStatusPanel } from '../components/datasink/WorkflowStatusPanel';\nimport { ExportDataSinkDialog } from '../components/datasink/ExportDataSinkDialog';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, IllustratedEmptyState, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Edit,\n RefreshCw,\n Trash2,\n Database,\n Loader2,\n AlertCircle,\n Eye,\n EyeOff,\n Download,\n Grid3X3,\n Code,\n} from 'lucide-react';\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const DataSinkViewPage = memo(function DataSinkViewPage() {\n const { dataSinkId } = useParams<{ dataSinkId: string; consoleId: string }>();\n const navigate = useAppNavigate();\n\n // Local state\n const [showData, setShowData] = useState(false);\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataLoading, setDataLoading] = useState(false);\n const [dataViewMode, setDataViewMode] = useState<'table' | 'json'>('table');\n // v3.0: server-side pagination for array payloads (avoids loading millions of rows).\n const [pageSize, setPageSize] = useState(100);\n const [pageOffset, setPageOffset] = useState(0);\n const [dataTotalCount, setDataTotalCount] = useState<number | null>(null);\n const [dataHasMore, setDataHasMore] = useState(false);\n\n // Store\n const dataSink = useDataSinkStore((state) => (dataSinkId ? state.dataSinks.get(dataSinkId) : undefined));\n\n // Operations\n const { loading, error, fetchDataSink, getDataSinkData, refreshDataSink, deleteDataSink } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Subscribe to updates\n useDataSinkSubscription({\n dataSinkId,\n enabled: !!dataSinkId,\n });\n\n // Load DataSink on mount - always fetch full data to get workflowLinks\n useEffect(() => {\n if (dataSinkId) {\n fetchDataSink(dataSinkId);\n }\n }, [dataSinkId, fetchDataSink]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n navigate(`/datasinks`);\n }, [navigate]);\n\n // Handle edit\n const handleEdit = useCallback(() => {\n navigate(`/datasinks/${dataSinkId}/edit`);\n }, [navigate, dataSinkId]);\n\n // Handle refresh\n const handleRefresh = useCallback(async () => {\n if (!dataSinkId) return;\n setIsRefreshing(true);\n try {\n await refreshDataSink({ id: dataSinkId });\n } catch (err) {\n } finally {\n setIsRefreshing(false);\n }\n }, [dataSinkId, refreshDataSink]);\n\n // Handle delete\n const handleDelete = useCallback(async () => {\n if (!dataSinkId) return;\n const native = await nativeConfirm({\n title: 'Delete DataSink?',\n message: 'Are you sure you want to delete this DataSink?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm('Are you sure you want to delete this DataSink?') : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n try {\n await deleteDataSink(dataSinkId);\n if (isNative()) void nativeNotify('success');\n navigate(`/datasinks`);\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n }\n }, [dataSinkId, deleteDataSink, navigate]);\n\n // Load one page of the sink's data (v3.0: server-side pagination).\n const loadDataPage = useCallback(\n async (offset: number, size: number) => {\n if (!dataSinkId) return;\n setDataLoading(true);\n try {\n const result = await getDataSinkData({ id: dataSinkId }, { limit: size, offset });\n if (result) {\n setDataPreview((result.raw as Record<string, unknown>) || {});\n // totalCount is non-null only for array payloads being paginated.\n setDataTotalCount(typeof result.totalCount === 'number' ? result.totalCount : null);\n setDataHasMore(Boolean(result.hasMore));\n setPageOffset(offset);\n }\n } catch {\n // handled by the hook's error state\n } finally {\n setDataLoading(false);\n }\n },\n [dataSinkId, getDataSinkData]\n );\n\n // Handle show data (toggles the preview; loads the first page).\n const handleShowData = useCallback(async () => {\n if (!dataSinkId) return;\n if (showData) {\n setShowData(false);\n return;\n }\n await loadDataPage(0, pageSize);\n setShowData(true);\n }, [dataSinkId, showData, loadDataPage, pageSize]);\n\n const handlePageSizeChange = useCallback(\n (size: number) => {\n setPageSize(size);\n void loadDataPage(0, size);\n },\n [loadDataPage]\n );\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // Loading state\n if (loading && !dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-status-error-text text-body-sm font-medium mb-2\">Failed to load DataSink</p>\n <p className=\"text-text-tertiary text-body-xs mb-6\">{error.message}</p>\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back\n </Button>\n </div>\n </div>\n );\n }\n\n // Not found state\n if (!dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"DataSink not found\"\n description=\"The DataSink you're looking for doesn't exist or has been deleted.\"\n action={\n <Button onClick={handleBack} variant=\"default\" size=\"default\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back to DataSinks\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-card-padding space-y-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4\">\n <div className=\"flex-1\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to DataSinks\n </Button>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{dataSink.name || 'Untitled'}</h1>\n <DataSinkStatusBadge status={dataSink.status} />\n </div>\n <p className=\"text-body-sm text-text-tertiary font-mono mt-1\">{dataSink.key}</p>\n {dataSink.description && <p className=\"text-body-sm text-text-secondary mt-2\">{dataSink.description}</p>}\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2 flex-wrap\">\n <Button onClick={handleRefresh} disabled={isRefreshing} variant=\"secondary\" size=\"default\">\n <RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />\n {isRefreshing ? 'Refreshing...' : 'Refresh'}\n </Button>\n <Button onClick={handleExport} variant=\"secondary\" size=\"default\">\n <Download className=\"w-4 h-4 mr-2\" />\n Export\n </Button>\n <Button onClick={handleEdit} variant=\"secondary\" size=\"default\">\n <Edit className=\"w-4 h-4 mr-2\" />\n Edit\n </Button>\n <Button onClick={handleDelete} disabled={isDeleting} variant=\"destructive\" size=\"default\">\n <Trash2 className=\"w-4 h-4 mr-2\" />\n {isDeleting ? 'Deleting...' : 'Delete'}\n </Button>\n </div>\n </div>\n\n <PagePurpose>\n A data sink is a structured store that holds your processed data and serves it to dashboards. From here you can\n inspect its schema and stats, preview the cached rows, refresh it from its linked workflow, or export it.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"bigconsole-datasink-view\"\n steps={[\n {\n id: 'preview',\n label: 'Preview the data',\n description: 'Open the cached rows to confirm the sink holds what you expect.',\n onClick: handleShowData,\n },\n {\n id: 'refresh',\n label: 'Refresh from source',\n description: 'Re-run the linked workflow to pull the latest data.',\n onClick: handleRefresh,\n },\n {\n id: 'build-dashboard',\n label: 'Visualize it on a dashboard',\n description: 'Add widgets that query this sink to see your data live.',\n onClick: () => navigate('/dashboards'),\n },\n {\n id: 'export',\n label: 'Export the data',\n description: 'Download the contents for use elsewhere.',\n onClick: handleExport,\n },\n ]}\n />\n\n {/* Details Grid */}\n <div className=\"grid gap-6 md:grid-cols-2\">\n {/* Info Panel — primary emphasis zone */}\n <GlassCard treatment=\"glass\" glow>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"space-y-4 text-body-sm\">\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd>\n <DataSinkStatusBadge status={dataSink.status} />\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Cache TTL</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.ttl ? `${dataSink.ttl}s` : 'None'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Refresh Type</dt>\n <dd className=\"text-text-primary font-medium capitalize\">{dataSink.refresh?.type || 'Manual'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.sizeBytes ? `${(dataSink.sizeBytes / 1024).toFixed(2)} KB` : '0 KB'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Version</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.version || 1}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Last Refreshed</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.lastRefreshedAt\n ? formatDistanceToNow(new Date(dataSink.lastRefreshedAt), { addSuffix: true })\n : 'Never'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Created</dt>\n <dd className=\"text-text-primary font-medium\">\n {formatDistanceToNow(new Date(dataSink.createdAt), { addSuffix: true })}\n </dd>\n </div>\n </dl>\n </CardContent>\n </GlassCard>\n\n {/* Workflow Status Panel */}\n {dataSink.fluidGridsWorkflowId && (\n <WorkflowStatusPanel\n workflowId={dataSink.fluidGridsWorkflowId}\n executionId={dataSink.fluidGridsWorkflowExecutionId || undefined}\n lastRefreshedAt={dataSink.lastRefreshedAt || undefined}\n setupState={dataSink.setupState}\n status={dataSink.status}\n onRerunWorkflow={handleRefresh}\n disabled={isRefreshing}\n isRerunning={isRefreshing}\n />\n )}\n\n {/* Linked Workflows */}\n <LinkedWorkflowsPanel workflowLinks={dataSink.workflowLinks} />\n\n {/* Linked Parsers */}\n <LinkedParsersPanel dataSinkKey={dataSink.key} />\n </div>\n\n {/* Data Preview Section */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <Database className=\"w-4 h-4 text-text-secondary\" />\n Data Preview\n </CardTitle>\n <div className=\"flex items-center gap-2\">\n {showData && (\n <div className=\"flex items-center border border-border-default rounded-md overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setDataViewMode('table')}\n className={`p-1.5 ${dataViewMode === 'table' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"Table view\"\n >\n <Grid3X3 className=\"w-4 h-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDataViewMode('json')}\n className={`p-1.5 ${dataViewMode === 'json' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"JSON view\"\n >\n <Code className=\"w-4 h-4\" />\n </button>\n </div>\n )}\n <Button\n onClick={handleShowData}\n disabled={dataLoading}\n variant={showData ? 'secondary' : 'default'}\n size=\"sm\"\n >\n {dataLoading ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Loading...\n </>\n ) : showData ? (\n <>\n <EyeOff className=\"w-4 h-4 mr-2\" />\n Hide Data\n </>\n ) : (\n <>\n <Eye className=\"w-4 h-4 mr-2\" />\n Show Data\n </>\n )}\n </Button>\n </div>\n </CardHeader>\n\n {showData && dataPreview && (\n <CardContent>\n {dataViewMode === 'table' ? (\n <DataSinkTableViewer data={dataPreview} maxHeight=\"400px\" />\n ) : (\n <DataSinkDataViewer data={dataPreview} maxHeight=\"400px\" />\n )}\n {/* v3.0: pagination controls — shown only for array payloads. */}\n {dataTotalCount !== null && (\n <div className=\"flex items-center justify-between gap-4 mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-center gap-2 text-body-sm text-text-secondary\">\n <span>Rows per page</span>\n <select\n aria-label=\"Rows per page\"\n className=\"bg-bg-canvas border border-border-default rounded px-2 py-0.5 text-text-primary\"\n value={pageSize}\n onChange={(e) => handlePageSizeChange(Number(e.target.value))}\n >\n {[100, 500, 1000].map((n) => (\n <option key={n} value={n}>\n {n}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-4 text-body-sm text-text-secondary\">\n <span>\n {dataTotalCount === 0\n ? '0 rows'\n : `${pageOffset + 1}–${Math.min(pageOffset + pageSize, dataTotalCount)} of ${dataTotalCount}`}\n </span>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={pageOffset === 0 || dataLoading}\n onClick={() => void loadDataPage(Math.max(0, pageOffset - pageSize), pageSize)}\n >\n Previous\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={!dataHasMore || dataLoading}\n onClick={() => void loadDataPage(pageOffset + pageSize, pageSize)}\n >\n Next\n </Button>\n </div>\n </div>\n )}\n </CardContent>\n )}\n\n {!showData && (\n <CardContent className=\"text-center p-8\">\n <Database className=\"w-10 h-10 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-body-sm text-text-secondary\">Click &quot;Show Data&quot; to preview the cached data.</p>\n </CardContent>\n )}\n </Card>\n\n {/* Export DataSink Dialog */}\n <ExportDataSinkDialog\n isOpen={isExportDialogOpen}\n dataSinkId={dataSinkId || null}\n dataSinkName={dataSink.name || dataSink.key}\n onClose={handleExportClose}\n />\n </div>\n );\n});\n\nexport default DataSinkViewPage;\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import ee from "../hooks/useDataSinkOperations.js";
5
5
  import te from "../hooks/useParserOperations.js";
6
6
  import ne from "../components/datasink/CacheTTLSelector.js";
7
7
  import { memo as n, useCallback as r, useEffect as i, useMemo as a, useRef as re, useState as o } from "react";
8
- import { useParams as ie } from "react-router-dom";
8
+ import { useParams as ie } from "react-router";
9
9
  import { AlertCircle as s, ArrowLeft as ae, CheckCircle2 as oe, ChevronDown as se, ChevronRight as ce, Code as le, Download as ue, Eye as de, FileCode2 as fe, LayoutDashboard as pe, Loader2 as c, Play as l, Save as me, Settings as he, X as ge } from "lucide-react";
10
10
  import { Button as u, CardContent as _e, CardHeader as ve, CardTitle as ye, GlassCard as be, PagePurpose as xe, Stepper as Se } from "@burdenoff/fe-libs/ui";
11
11
  import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"ParserBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/ParserBuilderPage.tsx"],"sourcesContent":["/**\n * Parser Builder Page\n *\n * Step wizard for creating and editing Parsers with JQ or JSONata expressions.\n * Required fields: consoleId, name, type, expression, inputSinkKey, outputKey\n *\n * Steps: 1) Details 2) Source & Type 3) Expression & Test 4) Cache & Review.\n * One step is shown at a time; a top Stepper allows quick-nav. See the reference\n * mock at bigconsole-app/public/parser-mockup-v2.html.\n */\n\nimport { memo, useState, useMemo, useEffect, useCallback, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../contexts';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDashboardOperations } from '../hooks/useDashboardOperations';\nimport { CacheTTLSelector } from '../components/datasink/CacheTTLSelector';\nimport type { ParserType } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, PagePurpose, Stepper } from '@burdenoff/fe-libs/ui';\nimport type { StepperStep } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Save,\n Play,\n Loader2,\n AlertCircle,\n CheckCircle2,\n FileCode2,\n Code,\n Settings,\n ChevronDown,\n ChevronRight,\n Eye,\n LayoutDashboard,\n Download,\n X,\n} from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface FormState {\n name: string;\n outputKey: string;\n description: string;\n type: ParserType;\n expression: string;\n inputSinkKey: string;\n // `null` represents \"no caching / not set\" (matches Parser.ttl: number | null\n // on the backend and CacheTTLSelector's value prop). Using number-only here\n // would force a collapse to 0/900 that silently loses user intent on edit.\n ttl: number | null;\n}\n\ninterface FormErrors {\n name?: string;\n outputKey?: string;\n expression?: string;\n inputSinkKey?: string;\n ttl?: string;\n}\n\n/** Metadata about a loaded DataSink sample (BOFF-5518 Part A). */\ninterface SampleMeta {\n total: number | null;\n hasMore: boolean | null;\n sinkName: string;\n}\n\n// ============================================================================\n// HELPERS / CONSTANTS\n// ============================================================================\n\n// Rows fetched for a DataSink preview / \"Load Sample\" (BOFF-5518). We never pull\n// the full payload into the browser — a small slice is enough to author + test a\n// parser, and keeps both the download and the subsequent test request small.\nconst PREVIEW_LIMIT = 25;\n\n// Default console ID for standalone parser creation (must match DataParsersPage)\nconst DEFAULT_CONSOLE_ID = 'default';\n\nconst STEP_KEYS = ['details', 'source', 'expression', 'review'] as const;\ntype StepKey = (typeof STEP_KEYS)[number];\n\nconst STEPS: StepperStep[] = [\n { key: 'details', label: 'Details', description: 'Name & scope' },\n { key: 'source', label: 'Source & Type', description: 'Engine & data' },\n { key: 'expression', label: 'Expression & Test', description: 'Transform & verify' },\n { key: 'review', label: 'Cache & Review', description: 'Finish up' },\n];\n\n/**\n * Generate a key from name (slug format)\n */\nfunction generateKey(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .substring(0, 64);\n}\n\n/**\n * Validate form data\n */\nfunction validateForm(form: FormState): FormErrors {\n const errors: FormErrors = {};\n\n if (!form.name.trim()) {\n errors.name = 'Name is required';\n } else if (form.name.length < 2) {\n errors.name = 'Name must be at least 2 characters';\n }\n\n if (!form.outputKey.trim()) {\n errors.outputKey = 'Output key is required';\n } else if (!/^[a-z0-9_]+$/.test(form.outputKey)) {\n errors.outputKey = 'Output key can only contain lowercase letters, numbers, and underscores';\n }\n\n if (!form.expression.trim()) {\n errors.expression = 'Expression is required';\n }\n\n if (!form.inputSinkKey.trim()) {\n errors.inputSinkKey = 'Source DataSink is required';\n }\n\n if (form.ttl != null && form.ttl > 0 && (form.ttl < 30 || form.ttl > 86400)) {\n errors.ttl = 'Cache duration must be between 30 and 86400 seconds';\n }\n\n return errors;\n}\n\n// Parser type descriptions (JQ and JSONata only)\nconst PARSER_TYPE_INFO: Record<\n ParserType,\n { name: string; description: string; placeholder: string; example: string }\n> = {\n jq: {\n name: 'JQ',\n description: 'Lightweight JSON processor - fast and efficient for simple transformations',\n placeholder: '.data | map({name: .name, value: .count})',\n example: '.data | map(.name) - Extract names from data array',\n },\n jsonata: {\n name: 'JSONata',\n description: 'Query and transformation language - supports complex queries and calculations',\n placeholder: '$sum(data[*].value)',\n example: '$sum(data.values) - Calculate sum of values',\n },\n};\n\n// Shared input styling\nconst inputBase =\n 'w-full px-3 py-2.5 text-sm text-text-primary bg-bg-surface border rounded-md placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg transition-all';\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const ParserBuilderPage = memo(function ParserBuilderPage() {\n const { parserId, consoleId: urlConsoleId } = useParams<{\n parserId?: string;\n consoleId?: string;\n }>();\n const navigate = useAppNavigate();\n\n // Wizard step\n const [activeStep, setActiveStep] = useState(0);\n\n // State for selected dashboard (when creating from global route)\n const [selectedDashboardId, setSelectedDashboardId] = useState<string>(DEFAULT_CONSOLE_ID);\n\n // Use URL consoleId, or selected dashboard, or fall back to default\n const consoleId = urlConsoleId || selectedDashboardId || DEFAULT_CONSOLE_ID;\n\n const isEdit = parserId !== 'new' && !!parserId;\n\n // Show dashboard selector when NOT on dashboard-scoped route (both create and edit)\n const showDashboardSelector = !urlConsoleId;\n\n // Form state\n const [form, setForm] = useState<FormState>({\n name: '',\n outputKey: '',\n description: '',\n type: 'jq',\n expression: '',\n inputSinkKey: '',\n // 15 minutes (Recommended preset). A 0 default made CacheTTLSelector\n // auto-select \"Custom\" with an invalid value, blocking form submission.\n ttl: 900,\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n // Test panel state\n const [testInput, setTestInput] = useState(\n '{\\n \"data\": [\\n {\"name\": \"Item 1\", \"count\": 10},\\n {\"name\": \"Item 2\", \"count\": 20}\\n ]\\n}'\n );\n const [testOutput, setTestOutput] = useState('');\n const [testError, setTestError] = useState<string | null>(null);\n const [isTesting, setIsTesting] = useState(false);\n // Set when a reference-mode test ran against a truncated sample (BOFF-5518) —\n // surfaces \"tested first N of M rows\" so a partial-sample result is visible.\n const [testMeta, setTestMeta] = useState<{ sampledRows: number | null; totalRows: number | null } | null>(null);\n\n // Inferred test mode (BOFF-5518): once a sample is LOADED from a DataSink the\n // Input JSON is locked (read-only) — that's \"reference mode\". Typing into an\n // empty box is \"sample mode\". Locking prevents load-then-edit of a large\n // payload; the user must Clear to edit.\n const [inputLocked, setInputLocked] = useState(false);\n const [sampleMeta, setSampleMeta] = useState<SampleMeta | null>(null);\n const [loadingSample, setLoadingSample] = useState(false);\n // Mirrors the selected sink so an in-flight preview/sample load can detect a\n // mid-flight source change and drop its (now stale) result.\n const inputSinkKeyRef = useRef(form.inputSinkKey);\n useEffect(() => {\n inputSinkKeyRef.current = form.inputSinkKey;\n }, [form.inputSinkKey]);\n\n // Data preview state\n const [showDataPreview, setShowDataPreview] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataPreviewLoading, setDataPreviewLoading] = useState(false);\n const [dataPreviewError, setDataPreviewError] = useState<string | null>(null);\n const [dataPreviewMeta, setDataPreviewMeta] = useState<SampleMeta | null>(null);\n\n // Operations\n const { loading, error, parsers, fetchParser, createParser, updateParser, testParserExpression } =\n useParserOperations({\n consoleId: consoleId || '',\n skipFetch: true,\n });\n\n const { dataSinks, fetchDataSinks, getDataSinkData } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Fetch dashboards for the dashboard selector (only when showing selector)\n const { dashboards, fetchDashboards, loading: dashboardsLoading } = useDashboardOperations();\n\n // Get existing parser if editing\n const existingParser = isEdit ? parsers.find((p) => p.id === parserId) : null;\n\n // Load data on mount\n useEffect(() => {\n fetchDataSinks();\n if (isEdit && parserId) {\n fetchParser(parserId);\n }\n // Fetch dashboards for the selector (only when showing selector)\n if (showDashboardSelector) {\n fetchDashboards();\n }\n }, [isEdit, parserId, fetchParser, fetchDataSinks, showDashboardSelector, fetchDashboards]);\n\n // Populate form with existing data when editing\n useEffect(() => {\n if (existingParser) {\n setForm({\n name: existingParser.name || '',\n outputKey: existingParser.outputKey || '',\n description: existingParser.description || '',\n type: existingParser.type,\n expression: existingParser.expression || '',\n inputSinkKey: existingParser.inputSinkKey || '',\n // Preserve the stored ttl exactly as saved — `null` and `0` both mean\n // \"no caching\" and are legitimate user choices. The BOFF-2241 default\n // (900) applies only to the *initial* Create-form state, never to\n // hydrating an existing parser.\n ttl: existingParser.ttl ?? null,\n });\n setKeyManuallyEdited(true);\n // Set the dashboard scope from existing parser's consoleId\n if (existingParser.consoleId) {\n setSelectedDashboardId(existingParser.consoleId);\n }\n }\n }, [existingParser]);\n\n const selectedSinkName = useMemo(() => {\n const sink = dataSinks.find((ds) => ds.key === form.inputSinkKey);\n return sink?.name || form.inputSinkKey;\n }, [dataSinks, form.inputSinkKey]);\n\n // Auto-generate key from name\n const handleNameChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n outputKey: keyManuallyEdited ? prev.outputKey : generateKey(name),\n }));\n setErrors((prev) => ({ ...prev, name: undefined }));\n },\n [keyManuallyEdited]\n );\n\n // Handle output key change\n const handleOutputKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n const outputKey = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '');\n setForm((prev) => ({ ...prev, outputKey }));\n setKeyManuallyEdited(true);\n setErrors((prev) => ({ ...prev, outputKey: undefined }));\n }, []);\n\n // Handle description change\n const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, description: e.target.value }));\n }, []);\n\n // Handle type change\n const handleTypeChange = useCallback((type: ParserType) => {\n setForm((prev) => ({ ...prev, type }));\n // Clear test output when type changes\n setTestOutput('');\n setTestError(null);\n }, []);\n\n // Handle expression change\n const handleExpressionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, expression: e.target.value }));\n setErrors((prev) => ({ ...prev, expression: undefined }));\n }, []);\n\n // Handle input sink change — reset any loaded sample (it belonged to the old sink)\n const handleInputSinkChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n setForm((prev) => ({ ...prev, inputSinkKey: e.target.value }));\n setErrors((prev) => ({ ...prev, inputSinkKey: undefined }));\n setShowDataPreview(false);\n setDataPreview(null);\n setDataPreviewMeta(null);\n setDataPreviewError(null);\n // Reset any loaded reference sample — it belonged to the previous sink.\n // Without this, a locked (reference-mode) Input JSON would keep showing the\n // old sink's rows while Run Test sends the NEW dataSinkKey — a silent\n // data/display mismatch. A locked sample is cleared; hand-typed sample JSON\n // (unlocked) is left alone since it isn't tied to a specific sink.\n if (inputLocked) {\n setTestInput('');\n setInputLocked(false);\n }\n setSampleMeta(null);\n setTestOutput('');\n setTestError(null);\n setTestMeta(null);\n },\n [inputLocked]\n );\n\n // Handle TTL change\n const handleTTLChange = useCallback((ttl: number | null) => {\n setForm((prev) => ({ ...prev, ttl }));\n setErrors((prev) => ({ ...prev, ttl: undefined }));\n }, []);\n\n // Handle dashboard selection change\n const handleDashboardChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {\n setSelectedDashboardId(e.target.value);\n }, []);\n\n // Handle test input typing (only possible when NOT locked → sample mode)\n const handleTestInputChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setTestInput(e.target.value);\n }, []);\n\n // Load a SMALL sample from the selected datasink into the Input JSON and lock\n // it (reference mode). BOFF-5518 Part A: only PREVIEW_LIMIT rows are fetched,\n // never the full payload.\n const handleLoadSampleData = useCallback(async () => {\n const requestedKey = form.inputSinkKey;\n if (!requestedKey) return;\n setLoadingSample(true);\n try {\n const result = await getDataSinkData({ key: requestedKey }, { limit: PREVIEW_LIMIT });\n // Ignore a result that arrived after the user switched sources mid-flight —\n // otherwise we'd lock the input to a stale sample while the form points at a\n // different sink (a reference-mode Run Test would then mismatch).\n if (inputSinkKeyRef.current !== requestedKey) return;\n if (result?.raw !== undefined && result?.raw !== null) {\n setTestInput(JSON.stringify(result.raw, null, 2));\n setInputLocked(true);\n setSampleMeta({\n total: result.totalCount ?? null,\n hasMore: result.hasMore ?? null,\n sinkName: selectedSinkName,\n });\n setTestOutput('');\n setTestError(null);\n setTestMeta(null);\n } else {\n // getDataSinkData returns null (not throws) on failure; surface it so the\n // click isn't a silent no-op after the spinner stops.\n setTestError('Could not load a sample from the selected DataSink.');\n }\n } catch (err) {\n if (inputSinkKeyRef.current !== requestedKey) return;\n setTestError(err instanceof Error ? err.message : 'Failed to load sample data');\n } finally {\n setLoadingSample(false);\n }\n }, [form.inputSinkKey, getDataSinkData, selectedSinkName]);\n\n // Clear the loaded sample → unlock for manual editing (back to sample mode).\n const handleClearInput = useCallback(() => {\n setTestInput('');\n setInputLocked(false);\n setSampleMeta(null);\n setTestMeta(null);\n setTestOutput('');\n setTestError(null);\n }, []);\n\n // Handle data preview toggle (step 2) — also a small slice only.\n const handleToggleDataPreview = useCallback(async () => {\n if (showDataPreview) {\n setShowDataPreview(false);\n return;\n }\n\n if (!form.inputSinkKey) {\n setDataPreviewError('Please select a source DataSink first');\n return;\n }\n\n setShowDataPreview(true);\n setDataPreviewLoading(true);\n setDataPreviewError(null);\n\n const requestedKey = form.inputSinkKey;\n try {\n const result = await getDataSinkData({ key: requestedKey }, { limit: PREVIEW_LIMIT });\n // Drop a stale result if the source changed while this was in flight.\n if (inputSinkKeyRef.current !== requestedKey) return;\n if (result?.raw !== undefined && result?.raw !== null) {\n setDataPreview(result.raw as Record<string, unknown>);\n setDataPreviewMeta({\n total: result.totalCount ?? null,\n hasMore: result.hasMore ?? null,\n sinkName: selectedSinkName,\n });\n } else {\n setDataPreviewError('No data available in selected DataSink');\n }\n } catch (err) {\n if (inputSinkKeyRef.current !== requestedKey) return;\n setDataPreviewError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setDataPreviewLoading(false);\n }\n }, [showDataPreview, form.inputSinkKey, getDataSinkData, selectedSinkName]);\n\n // Handle test expression\n const handleTestExpression = useCallback(async () => {\n if (!form.expression.trim()) {\n setTestError('Expression is required');\n return;\n }\n\n // BOFF-5518: reference mode (a sample was loaded → box locked) sends only the\n // expression + DataSink key; the backend evaluates server-side, no data blob.\n // Sample mode sends the (hand-typed) JSON as before.\n let input: {\n type: ParserType;\n expression: string;\n sampleData?: unknown;\n dataSinkKey?: string;\n sampleLimit?: number;\n };\n if (inputLocked) {\n input = {\n type: form.type,\n expression: form.expression,\n dataSinkKey: form.inputSinkKey,\n sampleLimit: PREVIEW_LIMIT,\n };\n } else {\n let parsedInput: unknown;\n try {\n parsedInput = JSON.parse(testInput);\n } catch {\n setTestError('Input JSON is not valid JSON');\n return;\n }\n input = { type: form.type, expression: form.expression, sampleData: parsedInput };\n }\n\n setIsTesting(true);\n setTestError(null);\n setTestOutput('');\n setTestMeta(null);\n\n try {\n const result = await testParserExpression(input);\n\n if (result) {\n if (result.success) {\n setTestOutput(JSON.stringify(result.output, null, 2));\n if (result.sampled) {\n setTestMeta({ sampledRows: result.sampledRows ?? null, totalRows: result.totalRows ?? null });\n }\n } else {\n setTestError(result.error || 'Test failed');\n }\n } else {\n // The hook returns null (not throws) when the GraphQL call fails —\n // surface it instead of stopping the spinner with no output/error.\n setTestError('Test request failed — please try again.');\n }\n } catch (err) {\n setTestError(err instanceof Error ? err.message : 'Test failed');\n } finally {\n setIsTesting(false);\n }\n }, [form.type, form.expression, form.inputSinkKey, testInput, inputLocked, testParserExpression]);\n\n // Handle back navigation (leave the builder)\n const handleLeave = useCallback(() => {\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers`);\n } else {\n navigate('/parsers');\n }\n }, [navigate, urlConsoleId]);\n\n // Handle form submission\n const handleSubmit = useCallback(async () => {\n setSubmitError(null);\n\n // Validate form\n const validationErrors = validateForm(form);\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n // Jump to the first step whose field is invalid so the error is visible\n // (the offending field lives on an earlier step, not the Review step).\n if (validationErrors.name || validationErrors.outputKey) {\n setActiveStep(0);\n } else if (validationErrors.inputSinkKey) {\n setActiveStep(1);\n } else if (validationErrors.expression) {\n setActiveStep(2);\n }\n return;\n }\n\n setIsSaving(true);\n try {\n if (isEdit && existingParser) {\n // Update existing (type is immutable, not included in update)\n const result = await updateParser(\n existingParser.id,\n {\n name: form.name,\n description: form.description || undefined,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n // Pass `null` through so the backend distinguishes an explicit\n // \"clear caching\" from an unset field (InputMaybe<Int> = Int | null).\n ttl: form.ttl,\n },\n existingParser.version\n );\n if (!result) {\n throw new Error('Failed to update parser');\n }\n } else {\n // Create new - include all required fields\n const result = await createParser({\n consoleId: consoleId || '',\n name: form.name,\n type: form.type,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n description: form.description || undefined,\n ttl: form.ttl ?? undefined,\n });\n if (!result) {\n throw new Error('Failed to create parser');\n }\n }\n handleLeave();\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : 'Failed to save parser');\n } finally {\n setIsSaving(false);\n }\n }, [form, isEdit, existingParser, createParser, updateParser, consoleId, handleLeave]);\n\n // Full-form validity (gates the final Create action)\n const isFormValid = useMemo(() => {\n return (\n form.name.trim().length >= 2 &&\n form.expression.trim().length > 0 &&\n form.inputSinkKey.trim().length > 0 &&\n form.outputKey.trim().length > 0 &&\n /^[a-z0-9_]+$/.test(form.outputKey)\n );\n }, [form.name, form.expression, form.inputSinkKey, form.outputKey]);\n\n // Per-step validity — gates the \"Next\" button so problems surface in context.\n const canAdvance = useCallback(\n (step: number): boolean => {\n const key: StepKey = STEP_KEYS[step];\n switch (key) {\n case 'details':\n return (\n form.name.trim().length >= 2 && form.outputKey.trim().length > 0 && /^[a-z0-9_]+$/.test(form.outputKey)\n );\n case 'source':\n return form.inputSinkKey.trim().length > 0;\n case 'expression':\n return form.expression.trim().length > 0;\n default:\n return true;\n }\n },\n [form.name, form.outputKey, form.inputSinkKey, form.expression]\n );\n\n const goToStep = useCallback(\n (step: number) => {\n const target = Math.max(0, Math.min(step, STEPS.length - 1));\n // Backward navigation is always allowed; a forward jump requires every\n // intermediate step to be valid (defense-in-depth even though the Stepper\n // no longer offers skip-ahead) so you can't land on an empty Review step.\n if (target > activeStep) {\n for (let s = activeStep; s < target; s++) {\n if (!canAdvance(s)) return;\n }\n }\n setActiveStep(target);\n },\n [activeStep, canAdvance]\n );\n const goNext = useCallback(() => {\n setActiveStep((s) => Math.min(s + 1, STEPS.length - 1));\n }, []);\n const goPrev = useCallback(() => {\n setActiveStep((s) => Math.max(s - 1, 0));\n }, []);\n\n // Loading state\n if (loading && isEdit) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-status-error-text text-body-sm font-medium mb-2\">Failed to load Parser</p>\n <p className=\"text-text-tertiary text-body-xs\">{error.message}</p>\n </div>\n </div>\n );\n }\n\n const typeInfo = PARSER_TYPE_INFO[form.type];\n const isLastStep = activeStep === STEPS.length - 1;\n\n return (\n <div className=\"min-h-screen bg-bg-canvas\">\n <div className=\"max-w-4xl mx-auto p-card-padding pb-10\">\n {/* Header */}\n <div className=\"mb-6\">\n <Button onClick={handleLeave} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to Parsers\n </Button>\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{isEdit ? 'Edit Parser' : 'Create Parser'}</h1>\n <p className=\"text-body-sm text-text-secondary mt-1\">Transform data using JQ or JSONata expressions.</p>\n </div>\n\n <PagePurpose className=\"mb-6\">\n A parser maps raw input into structured fields using a JQ or JSONata expression. Write the expression, run it\n against a sample to confirm the output, and save it once it produces the shape you need — then reuse it across\n pipelines to normalise any source.\n </PagePurpose>\n\n {/* Stepper — click a step to jump */}\n <div className=\"mb-6\">\n {/* No allowSkipAhead: the Stepper only lets you revisit steps you've\n already reached (idx <= active). Forward progress goes through Next,\n which is gated by per-step validation — so you can't land on an\n empty Review before filling earlier steps. */}\n <Stepper steps={STEPS} active={activeStep} onStepClick={goToStep} />\n </div>\n\n {/* Error Alert */}\n {submitError && (\n <div className=\"mb-6 p-4 bg-status-error-bg/10 border border-status-error-border/30 rounded-lg flex items-start gap-3\">\n <AlertCircle className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">Failed to save Parser</p>\n <p className=\"text-xs text-status-error-text/80 mt-1\">{submitError}</p>\n </div>\n </div>\n )}\n\n <GlassCard treatment=\"glass\" glow>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n {activeStep === 0 && <FileCode2 className=\"w-4 h-4 text-action-primary-bg\" />}\n {activeStep === 1 && <Code className=\"w-4 h-4 text-action-primary-bg\" />}\n {activeStep === 2 && <Play className=\"w-4 h-4 text-action-primary-bg\" />}\n {activeStep === 3 && <Settings className=\"w-4 h-4 text-action-primary-bg\" />}\n </div>\n <div>\n <p className=\"text-xs text-text-tertiary\">\n Step {activeStep + 1} of {STEPS.length}\n </p>\n <CardTitle className=\"text-base\">{STEPS[activeStep].label}</CardTitle>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* ============ STEP 1: DETAILS ============ */}\n {activeStep === 0 && (\n <div className=\"space-y-5\">\n <div className=\"grid gap-5 sm:grid-cols-2\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={form.name}\n onChange={handleNameChange}\n placeholder=\"e.g., User Analytics Parser\"\n className={`${inputBase} ${errors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {errors.name && <p className=\"text-xs text-status-error-text mt-1.5\">{errors.name}</p>}\n </div>\n\n {/* Output Key */}\n <div>\n <label htmlFor=\"outputKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Output Key <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"outputKey\"\n type=\"text\"\n value={form.outputKey}\n onChange={handleOutputKeyChange}\n placeholder=\"user_analytics_parser\"\n disabled={isEdit}\n className={`${inputBase} font-mono ${\n errors.outputKey ? 'border-status-error-border' : 'border-border-default'\n } disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60`}\n />\n {errors.outputKey && <p className=\"text-xs text-status-error-text mt-1.5\">{errors.outputKey}</p>}\n {!isEdit && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Auto-generated from name. Customize if needed.\n </p>\n )}\n </div>\n </div>\n\n {/* Description */}\n <div>\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Description\n </label>\n <textarea\n id=\"description\"\n value={form.description}\n onChange={handleDescriptionChange}\n placeholder=\"Describe what this parser does...\"\n rows={2}\n className={`${inputBase} border-border-default resize-y`}\n />\n </div>\n\n {/* Dashboard Selector */}\n {showDashboardSelector && (\n <div>\n <label htmlFor=\"dashboard\" className=\"block text-sm font-medium text-text-primary mb-2\">\n <span className=\"flex items-center gap-2\">\n <LayoutDashboard className=\"w-4 h-4\" />\n Dashboard Scope\n </span>\n </label>\n <select\n id=\"dashboard\"\n value={selectedDashboardId}\n onChange={handleDashboardChange}\n disabled={dashboardsLoading || isEdit}\n className={`${inputBase} border-border-default disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60`}\n >\n <option value=\"default\">Global (available everywhere)</option>\n {dashboards.map((dashboard) => (\n <option key={dashboard.id} value={dashboard.id}>\n {dashboard.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n {isEdit\n ? 'Dashboard scope cannot be changed after creation.'\n : selectedDashboardId === 'default'\n ? 'Global parsers are available across all dashboards.'\n : 'This parser will only be available in the selected dashboard.'}\n </p>\n </div>\n )}\n </div>\n )}\n\n {/* ============ STEP 2: SOURCE & TYPE ============ */}\n {activeStep === 1 && (\n <div className=\"space-y-5\">\n {/* Parser Type */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-3\">\n Parser Type <span className=\"text-status-error-text\">*</span>\n </label>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {(Object.keys(PARSER_TYPE_INFO) as ParserType[]).map((type) => {\n const info = PARSER_TYPE_INFO[type];\n return (\n <label\n key={type}\n className={`flex items-start gap-3 p-3 border rounded-md cursor-pointer transition-colors ${\n form.type === type\n ? 'border-action-primary-bg bg-action-primary-bg/5'\n : 'border-border-default hover:bg-bg-sunken'\n }`}\n >\n <input\n type=\"radio\"\n name=\"parserType\"\n value={type}\n checked={form.type === type}\n onChange={() => handleTypeChange(type)}\n className=\"mt-1 w-4 h-4 text-action-primary-bg\"\n />\n <span>\n <span className=\"text-sm font-medium text-text-primary\">{info.name}</span>\n <p className=\"text-xs text-text-secondary mt-0.5\">{info.description}</p>\n </span>\n </label>\n );\n })}\n </div>\n </div>\n\n {/* Source DataSink */}\n <div>\n <label htmlFor=\"inputSinkKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Source DataSink <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n id=\"inputSinkKey\"\n value={form.inputSinkKey}\n onChange={handleInputSinkChange}\n className={`${inputBase} ${\n errors.inputSinkKey ? 'border-status-error-border' : 'border-border-default'\n }`}\n >\n <option value=\"\">Select a DataSink...</option>\n {dataSinks.map((ds) => (\n <option key={ds.id} value={ds.key}>\n {ds.name || ds.key}\n </option>\n ))}\n </select>\n {errors.inputSinkKey && (\n <p className=\"text-xs text-status-error-text mt-1.5\">{errors.inputSinkKey}</p>\n )}\n {dataSinks.length === 0 && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n No DataSinks yet — create one first, then return here to build a parser for it.\n </p>\n )}\n\n {/* Data Preview */}\n {form.inputSinkKey && (\n <div className=\"mt-3\">\n <button\n type=\"button\"\n onClick={handleToggleDataPreview}\n className=\"inline-flex items-center gap-2 text-sm text-action-primary-bg hover:text-action-primary-bgHover transition-colors\"\n >\n {showDataPreview ? <ChevronDown className=\"w-4 h-4\" /> : <ChevronRight className=\"w-4 h-4\" />}\n <Eye className=\"w-4 h-4\" />\n Show Data Preview\n </button>\n\n {showDataPreview && (\n <div className=\"mt-3 border border-border-default rounded-md overflow-hidden\">\n {dataPreviewLoading ? (\n <div className=\"p-4 flex items-center justify-center\">\n <Loader2 className=\"w-5 h-5 text-text-secondary animate-spin\" />\n <span className=\"ml-2 text-sm text-text-secondary\">Loading sample…</span>\n </div>\n ) : dataPreviewError ? (\n <div className=\"p-4 bg-status-error-bg/5\">\n <p className=\"text-sm text-status-error-text\">{dataPreviewError}</p>\n </div>\n ) : dataPreview ? (\n <>\n <pre className=\"p-3 text-xs font-mono bg-bg-sunken text-text-primary overflow-auto max-h-48\">\n {JSON.stringify(dataPreview, null, 2)}\n </pre>\n {dataPreviewMeta && (\n <p className=\"px-3 py-2 text-xs text-text-tertiary border-t border-border-default\">\n Previewing a sample\n {dataPreviewMeta.total != null\n ? ` (first ${Math.min(PREVIEW_LIMIT, dataPreviewMeta.total)} of ${dataPreviewMeta.total} rows)`\n : ''}\n {' · '}\n {dataPreviewMeta.sinkName}\n </p>\n )}\n </>\n ) : (\n <div className=\"p-4 text-sm text-text-secondary text-center\">No data available</div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* ============ STEP 3: EXPRESSION & TEST ============ */}\n {activeStep === 2 && (\n <div className=\"space-y-5\">\n {/* Expression */}\n <div>\n <label htmlFor=\"expression\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Expression <span className=\"text-status-error-text\">*</span>\n </label>\n <textarea\n id=\"expression\"\n value={form.expression}\n onChange={handleExpressionChange}\n placeholder={typeInfo.placeholder}\n rows={8}\n className={`${inputBase} font-mono resize-y ${\n errors.expression ? 'border-status-error-border' : 'border-border-default'\n }`}\n />\n {errors.expression && <p className=\"text-xs text-status-error-text mt-1.5\">{errors.expression}</p>}\n <p className=\"text-xs text-text-tertiary mt-1.5\">Example: {typeInfo.example}</p>\n </div>\n\n {/* Run Test */}\n <Button\n type=\"button\"\n onClick={handleTestExpression}\n disabled={isTesting || !form.expression.trim()}\n variant=\"default\"\n className=\"w-full\"\n >\n {isTesting ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Testing...\n </>\n ) : (\n <>\n <Play className=\"w-4 h-4 mr-2\" />\n Run Test\n </>\n )}\n </Button>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n {/* Input JSON */}\n <div>\n <div className=\"flex items-center justify-between mb-2\">\n <label className=\"text-sm font-medium text-text-primary\">\n Input JSON\n <span\n className={`ml-2 text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded border align-middle ${\n inputLocked\n ? 'text-action-primary-bg bg-action-primary-bg/10 border-action-primary-bg/30'\n : 'text-text-secondary bg-bg-sunken border-border-default'\n }`}\n >\n {inputLocked ? 'Reference mode' : 'Sample mode'}\n </span>\n </label>\n <div className=\"flex items-center gap-2\">\n {form.inputSinkKey && !inputLocked && (\n <button\n type=\"button\"\n onClick={handleLoadSampleData}\n disabled={loadingSample}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-semibold text-action-primary-bg border border-action-primary-bg rounded-md hover:bg-action-primary-bg/10 transition-colors disabled:opacity-50\"\n >\n {loadingSample ? (\n <Loader2 className=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <Download className=\"w-3.5 h-3.5\" />\n )}\n Load Sample\n </button>\n )}\n {inputLocked && (\n <button\n type=\"button\"\n onClick={handleClearInput}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-semibold text-text-primary border border-border-default rounded-md hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"w-3.5 h-3.5\" />\n Clear\n </button>\n )}\n </div>\n </div>\n <textarea\n value={testInput}\n onChange={handleTestInputChange}\n readOnly={inputLocked}\n placeholder=\"Type sample JSON to test against it — or Load a sample from the source…\"\n rows={8}\n className={`${inputBase} min-h-[220px] text-xs font-mono leading-relaxed resize-y border-border-default ${\n inputLocked ? 'bg-bg-sunken opacity-80 cursor-not-allowed' : ''\n }`}\n />\n {inputLocked && sampleMeta ? (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Loaded a small sample\n {sampleMeta.total != null\n ? ` (first ${Math.min(PREVIEW_LIMIT, sampleMeta.total)} of ${sampleMeta.total} rows)`\n : ''}{' '}\n from {sampleMeta.sinkName} · locked — press Clear to edit.\n </p>\n ) : (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Type sample JSON to test against it, or Load a sample from the source.\n </p>\n )}\n </div>\n\n {/* Output */}\n <div>\n <div className=\"flex items-center justify-between mb-2\">\n <label className=\"text-sm font-medium text-text-primary\">\n {testError ? 'Error Output' : 'Output'}\n </label>\n {(testOutput || testError) &&\n (testError ? (\n <span className=\"text-xs px-2 py-1 bg-status-error-bg/10 text-status-error-text rounded flex items-center gap-1\">\n <AlertCircle className=\"w-3 h-3\" /> Failed\n </span>\n ) : (\n <span className=\"text-xs px-2 py-1 bg-status-success-bg/10 text-status-success-text rounded flex items-center gap-1\">\n <CheckCircle2 className=\"w-3 h-3\" /> Passed\n </span>\n ))}\n </div>\n {testOutput || testError ? (\n <pre\n className={`w-full px-3 py-2.5 text-xs font-mono leading-relaxed border rounded-md overflow-auto min-h-[220px] max-h-[220px] resize-y ${\n testError\n ? 'bg-status-error-bg/5 text-status-error-text border-status-error-border/30'\n : 'bg-status-success-bg/5 text-text-primary border-status-success-border/30'\n }`}\n >\n {testError || testOutput}\n </pre>\n ) : (\n <div className=\"min-h-[220px] p-3 bg-bg-sunken rounded-md border border-border-default\">\n <p className=\"text-xs text-text-secondary leading-relaxed\">\n <strong>Tip:</strong> write your expression and click &ldquo;Run Test&rdquo; to see the\n transformation.\n </p>\n </div>\n )}\n {testMeta && !testError && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Tested a sample\n {testMeta.sampledRows != null && testMeta.totalRows != null\n ? ` (first ${testMeta.sampledRows} of ${testMeta.totalRows} rows)`\n : ''}\n — a saved parser runs over the full source, so results may differ.\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* ============ STEP 4: CACHE & REVIEW ============ */}\n {activeStep === 3 && (\n <div className=\"space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Cache Duration</label>\n <p className=\"text-xs text-text-tertiary mb-3\">How long should data be cached before refreshing?</p>\n <CacheTTLSelector value={form.ttl} onChange={handleTTLChange} error={errors.ttl} hideLabel />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">Review</label>\n <div className=\"border border-border-default rounded-lg overflow-hidden\">\n {(\n [\n ['Name', form.name || '—'],\n ['Output Key', form.outputKey || '—'],\n [\n 'Scope',\n showDashboardSelector\n ? selectedDashboardId === 'default'\n ? 'Global'\n : dashboards.find((d) => d.id === selectedDashboardId)?.name || selectedDashboardId\n : 'Dashboard',\n ],\n ['Type', PARSER_TYPE_INFO[form.type].name],\n ['Source', selectedSinkName || '—'],\n ] as const\n ).map(([k, v]) => (\n <div\n key={k}\n className=\"flex items-center justify-between gap-3 px-4 py-2.5 text-sm border-b border-border-default last:border-b-0\"\n >\n <span className=\"text-text-secondary\">{k}</span>\n <span className=\"font-medium text-text-primary text-right break-words\">{v}</span>\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n </CardContent>\n </GlassCard>\n\n {/* Footer nav */}\n <div className=\"flex items-center justify-between gap-4 pt-6 mt-6 border-t border-border-default\">\n {activeStep === 0 ? (\n <Button type=\"button\" onClick={handleLeave} variant=\"secondary\">\n Cancel\n </Button>\n ) : (\n <Button type=\"button\" onClick={goPrev} variant=\"secondary\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back\n </Button>\n )}\n\n {isLastStep ? (\n <Button type=\"button\" onClick={handleSubmit} disabled={!isFormValid || isSaving} variant=\"default\">\n {isSaving ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Saving...\n </>\n ) : (\n <>\n <Save className=\"w-4 h-4 mr-2\" />\n {isEdit ? 'Save Changes' : 'Create Parser'}\n </>\n )}\n </Button>\n ) : (\n <Button type=\"button\" onClick={goNext} disabled={!canAdvance(activeStep)} variant=\"default\">\n Next\n <ChevronRight className=\"w-4 h-4 ml-2\" />\n </Button>\n )}\n </div>\n </div>\n </div>\n );\n});\n\nexport default ParserBuilderPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ParserBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/ParserBuilderPage.tsx"],"sourcesContent":["/**\n * Parser Builder Page\n *\n * Step wizard for creating and editing Parsers with JQ or JSONata expressions.\n * Required fields: consoleId, name, type, expression, inputSinkKey, outputKey\n *\n * Steps: 1) Details 2) Source & Type 3) Expression & Test 4) Cache & Review.\n * One step is shown at a time; a top Stepper allows quick-nav. See the reference\n * mock at bigconsole-app/public/parser-mockup-v2.html.\n */\n\nimport { memo, useState, useMemo, useEffect, useCallback, useRef } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDashboardOperations } from '../hooks/useDashboardOperations';\nimport { CacheTTLSelector } from '../components/datasink/CacheTTLSelector';\nimport type { ParserType } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, PagePurpose, Stepper } from '@burdenoff/fe-libs/ui';\nimport type { StepperStep } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Save,\n Play,\n Loader2,\n AlertCircle,\n CheckCircle2,\n FileCode2,\n Code,\n Settings,\n ChevronDown,\n ChevronRight,\n Eye,\n LayoutDashboard,\n Download,\n X,\n} from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface FormState {\n name: string;\n outputKey: string;\n description: string;\n type: ParserType;\n expression: string;\n inputSinkKey: string;\n // `null` represents \"no caching / not set\" (matches Parser.ttl: number | null\n // on the backend and CacheTTLSelector's value prop). Using number-only here\n // would force a collapse to 0/900 that silently loses user intent on edit.\n ttl: number | null;\n}\n\ninterface FormErrors {\n name?: string;\n outputKey?: string;\n expression?: string;\n inputSinkKey?: string;\n ttl?: string;\n}\n\n/** Metadata about a loaded DataSink sample (BOFF-5518 Part A). */\ninterface SampleMeta {\n total: number | null;\n hasMore: boolean | null;\n sinkName: string;\n}\n\n// ============================================================================\n// HELPERS / CONSTANTS\n// ============================================================================\n\n// Rows fetched for a DataSink preview / \"Load Sample\" (BOFF-5518). We never pull\n// the full payload into the browser — a small slice is enough to author + test a\n// parser, and keeps both the download and the subsequent test request small.\nconst PREVIEW_LIMIT = 25;\n\n// Default console ID for standalone parser creation (must match DataParsersPage)\nconst DEFAULT_CONSOLE_ID = 'default';\n\nconst STEP_KEYS = ['details', 'source', 'expression', 'review'] as const;\ntype StepKey = (typeof STEP_KEYS)[number];\n\nconst STEPS: StepperStep[] = [\n { key: 'details', label: 'Details', description: 'Name & scope' },\n { key: 'source', label: 'Source & Type', description: 'Engine & data' },\n { key: 'expression', label: 'Expression & Test', description: 'Transform & verify' },\n { key: 'review', label: 'Cache & Review', description: 'Finish up' },\n];\n\n/**\n * Generate a key from name (slug format)\n */\nfunction generateKey(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .substring(0, 64);\n}\n\n/**\n * Validate form data\n */\nfunction validateForm(form: FormState): FormErrors {\n const errors: FormErrors = {};\n\n if (!form.name.trim()) {\n errors.name = 'Name is required';\n } else if (form.name.length < 2) {\n errors.name = 'Name must be at least 2 characters';\n }\n\n if (!form.outputKey.trim()) {\n errors.outputKey = 'Output key is required';\n } else if (!/^[a-z0-9_]+$/.test(form.outputKey)) {\n errors.outputKey = 'Output key can only contain lowercase letters, numbers, and underscores';\n }\n\n if (!form.expression.trim()) {\n errors.expression = 'Expression is required';\n }\n\n if (!form.inputSinkKey.trim()) {\n errors.inputSinkKey = 'Source DataSink is required';\n }\n\n if (form.ttl != null && form.ttl > 0 && (form.ttl < 30 || form.ttl > 86400)) {\n errors.ttl = 'Cache duration must be between 30 and 86400 seconds';\n }\n\n return errors;\n}\n\n// Parser type descriptions (JQ and JSONata only)\nconst PARSER_TYPE_INFO: Record<\n ParserType,\n { name: string; description: string; placeholder: string; example: string }\n> = {\n jq: {\n name: 'JQ',\n description: 'Lightweight JSON processor - fast and efficient for simple transformations',\n placeholder: '.data | map({name: .name, value: .count})',\n example: '.data | map(.name) - Extract names from data array',\n },\n jsonata: {\n name: 'JSONata',\n description: 'Query and transformation language - supports complex queries and calculations',\n placeholder: '$sum(data[*].value)',\n example: '$sum(data.values) - Calculate sum of values',\n },\n};\n\n// Shared input styling\nconst inputBase =\n 'w-full px-3 py-2.5 text-sm text-text-primary bg-bg-surface border rounded-md placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg transition-all';\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const ParserBuilderPage = memo(function ParserBuilderPage() {\n const { parserId, consoleId: urlConsoleId } = useParams<{\n parserId?: string;\n consoleId?: string;\n }>();\n const navigate = useAppNavigate();\n\n // Wizard step\n const [activeStep, setActiveStep] = useState(0);\n\n // State for selected dashboard (when creating from global route)\n const [selectedDashboardId, setSelectedDashboardId] = useState<string>(DEFAULT_CONSOLE_ID);\n\n // Use URL consoleId, or selected dashboard, or fall back to default\n const consoleId = urlConsoleId || selectedDashboardId || DEFAULT_CONSOLE_ID;\n\n const isEdit = parserId !== 'new' && !!parserId;\n\n // Show dashboard selector when NOT on dashboard-scoped route (both create and edit)\n const showDashboardSelector = !urlConsoleId;\n\n // Form state\n const [form, setForm] = useState<FormState>({\n name: '',\n outputKey: '',\n description: '',\n type: 'jq',\n expression: '',\n inputSinkKey: '',\n // 15 minutes (Recommended preset). A 0 default made CacheTTLSelector\n // auto-select \"Custom\" with an invalid value, blocking form submission.\n ttl: 900,\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n // Test panel state\n const [testInput, setTestInput] = useState(\n '{\\n \"data\": [\\n {\"name\": \"Item 1\", \"count\": 10},\\n {\"name\": \"Item 2\", \"count\": 20}\\n ]\\n}'\n );\n const [testOutput, setTestOutput] = useState('');\n const [testError, setTestError] = useState<string | null>(null);\n const [isTesting, setIsTesting] = useState(false);\n // Set when a reference-mode test ran against a truncated sample (BOFF-5518) —\n // surfaces \"tested first N of M rows\" so a partial-sample result is visible.\n const [testMeta, setTestMeta] = useState<{ sampledRows: number | null; totalRows: number | null } | null>(null);\n\n // Inferred test mode (BOFF-5518): once a sample is LOADED from a DataSink the\n // Input JSON is locked (read-only) — that's \"reference mode\". Typing into an\n // empty box is \"sample mode\". Locking prevents load-then-edit of a large\n // payload; the user must Clear to edit.\n const [inputLocked, setInputLocked] = useState(false);\n const [sampleMeta, setSampleMeta] = useState<SampleMeta | null>(null);\n const [loadingSample, setLoadingSample] = useState(false);\n // Mirrors the selected sink so an in-flight preview/sample load can detect a\n // mid-flight source change and drop its (now stale) result.\n const inputSinkKeyRef = useRef(form.inputSinkKey);\n useEffect(() => {\n inputSinkKeyRef.current = form.inputSinkKey;\n }, [form.inputSinkKey]);\n\n // Data preview state\n const [showDataPreview, setShowDataPreview] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataPreviewLoading, setDataPreviewLoading] = useState(false);\n const [dataPreviewError, setDataPreviewError] = useState<string | null>(null);\n const [dataPreviewMeta, setDataPreviewMeta] = useState<SampleMeta | null>(null);\n\n // Operations\n const { loading, error, parsers, fetchParser, createParser, updateParser, testParserExpression } =\n useParserOperations({\n consoleId: consoleId || '',\n skipFetch: true,\n });\n\n const { dataSinks, fetchDataSinks, getDataSinkData } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Fetch dashboards for the dashboard selector (only when showing selector)\n const { dashboards, fetchDashboards, loading: dashboardsLoading } = useDashboardOperations();\n\n // Get existing parser if editing\n const existingParser = isEdit ? parsers.find((p) => p.id === parserId) : null;\n\n // Load data on mount\n useEffect(() => {\n fetchDataSinks();\n if (isEdit && parserId) {\n fetchParser(parserId);\n }\n // Fetch dashboards for the selector (only when showing selector)\n if (showDashboardSelector) {\n fetchDashboards();\n }\n }, [isEdit, parserId, fetchParser, fetchDataSinks, showDashboardSelector, fetchDashboards]);\n\n // Populate form with existing data when editing\n useEffect(() => {\n if (existingParser) {\n setForm({\n name: existingParser.name || '',\n outputKey: existingParser.outputKey || '',\n description: existingParser.description || '',\n type: existingParser.type,\n expression: existingParser.expression || '',\n inputSinkKey: existingParser.inputSinkKey || '',\n // Preserve the stored ttl exactly as saved — `null` and `0` both mean\n // \"no caching\" and are legitimate user choices. The BOFF-2241 default\n // (900) applies only to the *initial* Create-form state, never to\n // hydrating an existing parser.\n ttl: existingParser.ttl ?? null,\n });\n setKeyManuallyEdited(true);\n // Set the dashboard scope from existing parser's consoleId\n if (existingParser.consoleId) {\n setSelectedDashboardId(existingParser.consoleId);\n }\n }\n }, [existingParser]);\n\n const selectedSinkName = useMemo(() => {\n const sink = dataSinks.find((ds) => ds.key === form.inputSinkKey);\n return sink?.name || form.inputSinkKey;\n }, [dataSinks, form.inputSinkKey]);\n\n // Auto-generate key from name\n const handleNameChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n outputKey: keyManuallyEdited ? prev.outputKey : generateKey(name),\n }));\n setErrors((prev) => ({ ...prev, name: undefined }));\n },\n [keyManuallyEdited]\n );\n\n // Handle output key change\n const handleOutputKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n const outputKey = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '');\n setForm((prev) => ({ ...prev, outputKey }));\n setKeyManuallyEdited(true);\n setErrors((prev) => ({ ...prev, outputKey: undefined }));\n }, []);\n\n // Handle description change\n const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, description: e.target.value }));\n }, []);\n\n // Handle type change\n const handleTypeChange = useCallback((type: ParserType) => {\n setForm((prev) => ({ ...prev, type }));\n // Clear test output when type changes\n setTestOutput('');\n setTestError(null);\n }, []);\n\n // Handle expression change\n const handleExpressionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, expression: e.target.value }));\n setErrors((prev) => ({ ...prev, expression: undefined }));\n }, []);\n\n // Handle input sink change — reset any loaded sample (it belonged to the old sink)\n const handleInputSinkChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n setForm((prev) => ({ ...prev, inputSinkKey: e.target.value }));\n setErrors((prev) => ({ ...prev, inputSinkKey: undefined }));\n setShowDataPreview(false);\n setDataPreview(null);\n setDataPreviewMeta(null);\n setDataPreviewError(null);\n // Reset any loaded reference sample — it belonged to the previous sink.\n // Without this, a locked (reference-mode) Input JSON would keep showing the\n // old sink's rows while Run Test sends the NEW dataSinkKey — a silent\n // data/display mismatch. A locked sample is cleared; hand-typed sample JSON\n // (unlocked) is left alone since it isn't tied to a specific sink.\n if (inputLocked) {\n setTestInput('');\n setInputLocked(false);\n }\n setSampleMeta(null);\n setTestOutput('');\n setTestError(null);\n setTestMeta(null);\n },\n [inputLocked]\n );\n\n // Handle TTL change\n const handleTTLChange = useCallback((ttl: number | null) => {\n setForm((prev) => ({ ...prev, ttl }));\n setErrors((prev) => ({ ...prev, ttl: undefined }));\n }, []);\n\n // Handle dashboard selection change\n const handleDashboardChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {\n setSelectedDashboardId(e.target.value);\n }, []);\n\n // Handle test input typing (only possible when NOT locked → sample mode)\n const handleTestInputChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setTestInput(e.target.value);\n }, []);\n\n // Load a SMALL sample from the selected datasink into the Input JSON and lock\n // it (reference mode). BOFF-5518 Part A: only PREVIEW_LIMIT rows are fetched,\n // never the full payload.\n const handleLoadSampleData = useCallback(async () => {\n const requestedKey = form.inputSinkKey;\n if (!requestedKey) return;\n setLoadingSample(true);\n try {\n const result = await getDataSinkData({ key: requestedKey }, { limit: PREVIEW_LIMIT });\n // Ignore a result that arrived after the user switched sources mid-flight —\n // otherwise we'd lock the input to a stale sample while the form points at a\n // different sink (a reference-mode Run Test would then mismatch).\n if (inputSinkKeyRef.current !== requestedKey) return;\n if (result?.raw !== undefined && result?.raw !== null) {\n setTestInput(JSON.stringify(result.raw, null, 2));\n setInputLocked(true);\n setSampleMeta({\n total: result.totalCount ?? null,\n hasMore: result.hasMore ?? null,\n sinkName: selectedSinkName,\n });\n setTestOutput('');\n setTestError(null);\n setTestMeta(null);\n } else {\n // getDataSinkData returns null (not throws) on failure; surface it so the\n // click isn't a silent no-op after the spinner stops.\n setTestError('Could not load a sample from the selected DataSink.');\n }\n } catch (err) {\n if (inputSinkKeyRef.current !== requestedKey) return;\n setTestError(err instanceof Error ? err.message : 'Failed to load sample data');\n } finally {\n setLoadingSample(false);\n }\n }, [form.inputSinkKey, getDataSinkData, selectedSinkName]);\n\n // Clear the loaded sample → unlock for manual editing (back to sample mode).\n const handleClearInput = useCallback(() => {\n setTestInput('');\n setInputLocked(false);\n setSampleMeta(null);\n setTestMeta(null);\n setTestOutput('');\n setTestError(null);\n }, []);\n\n // Handle data preview toggle (step 2) — also a small slice only.\n const handleToggleDataPreview = useCallback(async () => {\n if (showDataPreview) {\n setShowDataPreview(false);\n return;\n }\n\n if (!form.inputSinkKey) {\n setDataPreviewError('Please select a source DataSink first');\n return;\n }\n\n setShowDataPreview(true);\n setDataPreviewLoading(true);\n setDataPreviewError(null);\n\n const requestedKey = form.inputSinkKey;\n try {\n const result = await getDataSinkData({ key: requestedKey }, { limit: PREVIEW_LIMIT });\n // Drop a stale result if the source changed while this was in flight.\n if (inputSinkKeyRef.current !== requestedKey) return;\n if (result?.raw !== undefined && result?.raw !== null) {\n setDataPreview(result.raw as Record<string, unknown>);\n setDataPreviewMeta({\n total: result.totalCount ?? null,\n hasMore: result.hasMore ?? null,\n sinkName: selectedSinkName,\n });\n } else {\n setDataPreviewError('No data available in selected DataSink');\n }\n } catch (err) {\n if (inputSinkKeyRef.current !== requestedKey) return;\n setDataPreviewError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setDataPreviewLoading(false);\n }\n }, [showDataPreview, form.inputSinkKey, getDataSinkData, selectedSinkName]);\n\n // Handle test expression\n const handleTestExpression = useCallback(async () => {\n if (!form.expression.trim()) {\n setTestError('Expression is required');\n return;\n }\n\n // BOFF-5518: reference mode (a sample was loaded → box locked) sends only the\n // expression + DataSink key; the backend evaluates server-side, no data blob.\n // Sample mode sends the (hand-typed) JSON as before.\n let input: {\n type: ParserType;\n expression: string;\n sampleData?: unknown;\n dataSinkKey?: string;\n sampleLimit?: number;\n };\n if (inputLocked) {\n input = {\n type: form.type,\n expression: form.expression,\n dataSinkKey: form.inputSinkKey,\n sampleLimit: PREVIEW_LIMIT,\n };\n } else {\n let parsedInput: unknown;\n try {\n parsedInput = JSON.parse(testInput);\n } catch {\n setTestError('Input JSON is not valid JSON');\n return;\n }\n input = { type: form.type, expression: form.expression, sampleData: parsedInput };\n }\n\n setIsTesting(true);\n setTestError(null);\n setTestOutput('');\n setTestMeta(null);\n\n try {\n const result = await testParserExpression(input);\n\n if (result) {\n if (result.success) {\n setTestOutput(JSON.stringify(result.output, null, 2));\n if (result.sampled) {\n setTestMeta({ sampledRows: result.sampledRows ?? null, totalRows: result.totalRows ?? null });\n }\n } else {\n setTestError(result.error || 'Test failed');\n }\n } else {\n // The hook returns null (not throws) when the GraphQL call fails —\n // surface it instead of stopping the spinner with no output/error.\n setTestError('Test request failed — please try again.');\n }\n } catch (err) {\n setTestError(err instanceof Error ? err.message : 'Test failed');\n } finally {\n setIsTesting(false);\n }\n }, [form.type, form.expression, form.inputSinkKey, testInput, inputLocked, testParserExpression]);\n\n // Handle back navigation (leave the builder)\n const handleLeave = useCallback(() => {\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers`);\n } else {\n navigate('/parsers');\n }\n }, [navigate, urlConsoleId]);\n\n // Handle form submission\n const handleSubmit = useCallback(async () => {\n setSubmitError(null);\n\n // Validate form\n const validationErrors = validateForm(form);\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n // Jump to the first step whose field is invalid so the error is visible\n // (the offending field lives on an earlier step, not the Review step).\n if (validationErrors.name || validationErrors.outputKey) {\n setActiveStep(0);\n } else if (validationErrors.inputSinkKey) {\n setActiveStep(1);\n } else if (validationErrors.expression) {\n setActiveStep(2);\n }\n return;\n }\n\n setIsSaving(true);\n try {\n if (isEdit && existingParser) {\n // Update existing (type is immutable, not included in update)\n const result = await updateParser(\n existingParser.id,\n {\n name: form.name,\n description: form.description || undefined,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n // Pass `null` through so the backend distinguishes an explicit\n // \"clear caching\" from an unset field (InputMaybe<Int> = Int | null).\n ttl: form.ttl,\n },\n existingParser.version\n );\n if (!result) {\n throw new Error('Failed to update parser');\n }\n } else {\n // Create new - include all required fields\n const result = await createParser({\n consoleId: consoleId || '',\n name: form.name,\n type: form.type,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n description: form.description || undefined,\n ttl: form.ttl ?? undefined,\n });\n if (!result) {\n throw new Error('Failed to create parser');\n }\n }\n handleLeave();\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : 'Failed to save parser');\n } finally {\n setIsSaving(false);\n }\n }, [form, isEdit, existingParser, createParser, updateParser, consoleId, handleLeave]);\n\n // Full-form validity (gates the final Create action)\n const isFormValid = useMemo(() => {\n return (\n form.name.trim().length >= 2 &&\n form.expression.trim().length > 0 &&\n form.inputSinkKey.trim().length > 0 &&\n form.outputKey.trim().length > 0 &&\n /^[a-z0-9_]+$/.test(form.outputKey)\n );\n }, [form.name, form.expression, form.inputSinkKey, form.outputKey]);\n\n // Per-step validity — gates the \"Next\" button so problems surface in context.\n const canAdvance = useCallback(\n (step: number): boolean => {\n const key: StepKey = STEP_KEYS[step];\n switch (key) {\n case 'details':\n return (\n form.name.trim().length >= 2 && form.outputKey.trim().length > 0 && /^[a-z0-9_]+$/.test(form.outputKey)\n );\n case 'source':\n return form.inputSinkKey.trim().length > 0;\n case 'expression':\n return form.expression.trim().length > 0;\n default:\n return true;\n }\n },\n [form.name, form.outputKey, form.inputSinkKey, form.expression]\n );\n\n const goToStep = useCallback(\n (step: number) => {\n const target = Math.max(0, Math.min(step, STEPS.length - 1));\n // Backward navigation is always allowed; a forward jump requires every\n // intermediate step to be valid (defense-in-depth even though the Stepper\n // no longer offers skip-ahead) so you can't land on an empty Review step.\n if (target > activeStep) {\n for (let s = activeStep; s < target; s++) {\n if (!canAdvance(s)) return;\n }\n }\n setActiveStep(target);\n },\n [activeStep, canAdvance]\n );\n const goNext = useCallback(() => {\n setActiveStep((s) => Math.min(s + 1, STEPS.length - 1));\n }, []);\n const goPrev = useCallback(() => {\n setActiveStep((s) => Math.max(s - 1, 0));\n }, []);\n\n // Loading state\n if (loading && isEdit) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-status-error-text text-body-sm font-medium mb-2\">Failed to load Parser</p>\n <p className=\"text-text-tertiary text-body-xs\">{error.message}</p>\n </div>\n </div>\n );\n }\n\n const typeInfo = PARSER_TYPE_INFO[form.type];\n const isLastStep = activeStep === STEPS.length - 1;\n\n return (\n <div className=\"min-h-screen bg-bg-canvas\">\n <div className=\"max-w-4xl mx-auto p-card-padding pb-10\">\n {/* Header */}\n <div className=\"mb-6\">\n <Button onClick={handleLeave} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to Parsers\n </Button>\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{isEdit ? 'Edit Parser' : 'Create Parser'}</h1>\n <p className=\"text-body-sm text-text-secondary mt-1\">Transform data using JQ or JSONata expressions.</p>\n </div>\n\n <PagePurpose className=\"mb-6\">\n A parser maps raw input into structured fields using a JQ or JSONata expression. Write the expression, run it\n against a sample to confirm the output, and save it once it produces the shape you need — then reuse it across\n pipelines to normalise any source.\n </PagePurpose>\n\n {/* Stepper — click a step to jump */}\n <div className=\"mb-6\">\n {/* No allowSkipAhead: the Stepper only lets you revisit steps you've\n already reached (idx <= active). Forward progress goes through Next,\n which is gated by per-step validation — so you can't land on an\n empty Review before filling earlier steps. */}\n <Stepper steps={STEPS} active={activeStep} onStepClick={goToStep} />\n </div>\n\n {/* Error Alert */}\n {submitError && (\n <div className=\"mb-6 p-4 bg-status-error-bg/10 border border-status-error-border/30 rounded-lg flex items-start gap-3\">\n <AlertCircle className=\"w-5 h-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">Failed to save Parser</p>\n <p className=\"text-xs text-status-error-text/80 mt-1\">{submitError}</p>\n </div>\n </div>\n )}\n\n <GlassCard treatment=\"glass\" glow>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n {activeStep === 0 && <FileCode2 className=\"w-4 h-4 text-action-primary-bg\" />}\n {activeStep === 1 && <Code className=\"w-4 h-4 text-action-primary-bg\" />}\n {activeStep === 2 && <Play className=\"w-4 h-4 text-action-primary-bg\" />}\n {activeStep === 3 && <Settings className=\"w-4 h-4 text-action-primary-bg\" />}\n </div>\n <div>\n <p className=\"text-xs text-text-tertiary\">\n Step {activeStep + 1} of {STEPS.length}\n </p>\n <CardTitle className=\"text-base\">{STEPS[activeStep].label}</CardTitle>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* ============ STEP 1: DETAILS ============ */}\n {activeStep === 0 && (\n <div className=\"space-y-5\">\n <div className=\"grid gap-5 sm:grid-cols-2\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={form.name}\n onChange={handleNameChange}\n placeholder=\"e.g., User Analytics Parser\"\n className={`${inputBase} ${errors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {errors.name && <p className=\"text-xs text-status-error-text mt-1.5\">{errors.name}</p>}\n </div>\n\n {/* Output Key */}\n <div>\n <label htmlFor=\"outputKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Output Key <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"outputKey\"\n type=\"text\"\n value={form.outputKey}\n onChange={handleOutputKeyChange}\n placeholder=\"user_analytics_parser\"\n disabled={isEdit}\n className={`${inputBase} font-mono ${\n errors.outputKey ? 'border-status-error-border' : 'border-border-default'\n } disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60`}\n />\n {errors.outputKey && <p className=\"text-xs text-status-error-text mt-1.5\">{errors.outputKey}</p>}\n {!isEdit && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Auto-generated from name. Customize if needed.\n </p>\n )}\n </div>\n </div>\n\n {/* Description */}\n <div>\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Description\n </label>\n <textarea\n id=\"description\"\n value={form.description}\n onChange={handleDescriptionChange}\n placeholder=\"Describe what this parser does...\"\n rows={2}\n className={`${inputBase} border-border-default resize-y`}\n />\n </div>\n\n {/* Dashboard Selector */}\n {showDashboardSelector && (\n <div>\n <label htmlFor=\"dashboard\" className=\"block text-sm font-medium text-text-primary mb-2\">\n <span className=\"flex items-center gap-2\">\n <LayoutDashboard className=\"w-4 h-4\" />\n Dashboard Scope\n </span>\n </label>\n <select\n id=\"dashboard\"\n value={selectedDashboardId}\n onChange={handleDashboardChange}\n disabled={dashboardsLoading || isEdit}\n className={`${inputBase} border-border-default disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60`}\n >\n <option value=\"default\">Global (available everywhere)</option>\n {dashboards.map((dashboard) => (\n <option key={dashboard.id} value={dashboard.id}>\n {dashboard.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n {isEdit\n ? 'Dashboard scope cannot be changed after creation.'\n : selectedDashboardId === 'default'\n ? 'Global parsers are available across all dashboards.'\n : 'This parser will only be available in the selected dashboard.'}\n </p>\n </div>\n )}\n </div>\n )}\n\n {/* ============ STEP 2: SOURCE & TYPE ============ */}\n {activeStep === 1 && (\n <div className=\"space-y-5\">\n {/* Parser Type */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-3\">\n Parser Type <span className=\"text-status-error-text\">*</span>\n </label>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {(Object.keys(PARSER_TYPE_INFO) as ParserType[]).map((type) => {\n const info = PARSER_TYPE_INFO[type];\n return (\n <label\n key={type}\n className={`flex items-start gap-3 p-3 border rounded-md cursor-pointer transition-colors ${\n form.type === type\n ? 'border-action-primary-bg bg-action-primary-bg/5'\n : 'border-border-default hover:bg-bg-sunken'\n }`}\n >\n <input\n type=\"radio\"\n name=\"parserType\"\n value={type}\n checked={form.type === type}\n onChange={() => handleTypeChange(type)}\n className=\"mt-1 w-4 h-4 text-action-primary-bg\"\n />\n <span>\n <span className=\"text-sm font-medium text-text-primary\">{info.name}</span>\n <p className=\"text-xs text-text-secondary mt-0.5\">{info.description}</p>\n </span>\n </label>\n );\n })}\n </div>\n </div>\n\n {/* Source DataSink */}\n <div>\n <label htmlFor=\"inputSinkKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Source DataSink <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n id=\"inputSinkKey\"\n value={form.inputSinkKey}\n onChange={handleInputSinkChange}\n className={`${inputBase} ${\n errors.inputSinkKey ? 'border-status-error-border' : 'border-border-default'\n }`}\n >\n <option value=\"\">Select a DataSink...</option>\n {dataSinks.map((ds) => (\n <option key={ds.id} value={ds.key}>\n {ds.name || ds.key}\n </option>\n ))}\n </select>\n {errors.inputSinkKey && (\n <p className=\"text-xs text-status-error-text mt-1.5\">{errors.inputSinkKey}</p>\n )}\n {dataSinks.length === 0 && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n No DataSinks yet — create one first, then return here to build a parser for it.\n </p>\n )}\n\n {/* Data Preview */}\n {form.inputSinkKey && (\n <div className=\"mt-3\">\n <button\n type=\"button\"\n onClick={handleToggleDataPreview}\n className=\"inline-flex items-center gap-2 text-sm text-action-primary-bg hover:text-action-primary-bgHover transition-colors\"\n >\n {showDataPreview ? <ChevronDown className=\"w-4 h-4\" /> : <ChevronRight className=\"w-4 h-4\" />}\n <Eye className=\"w-4 h-4\" />\n Show Data Preview\n </button>\n\n {showDataPreview && (\n <div className=\"mt-3 border border-border-default rounded-md overflow-hidden\">\n {dataPreviewLoading ? (\n <div className=\"p-4 flex items-center justify-center\">\n <Loader2 className=\"w-5 h-5 text-text-secondary animate-spin\" />\n <span className=\"ml-2 text-sm text-text-secondary\">Loading sample…</span>\n </div>\n ) : dataPreviewError ? (\n <div className=\"p-4 bg-status-error-bg/5\">\n <p className=\"text-sm text-status-error-text\">{dataPreviewError}</p>\n </div>\n ) : dataPreview ? (\n <>\n <pre className=\"p-3 text-xs font-mono bg-bg-sunken text-text-primary overflow-auto max-h-48\">\n {JSON.stringify(dataPreview, null, 2)}\n </pre>\n {dataPreviewMeta && (\n <p className=\"px-3 py-2 text-xs text-text-tertiary border-t border-border-default\">\n Previewing a sample\n {dataPreviewMeta.total != null\n ? ` (first ${Math.min(PREVIEW_LIMIT, dataPreviewMeta.total)} of ${dataPreviewMeta.total} rows)`\n : ''}\n {' · '}\n {dataPreviewMeta.sinkName}\n </p>\n )}\n </>\n ) : (\n <div className=\"p-4 text-sm text-text-secondary text-center\">No data available</div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* ============ STEP 3: EXPRESSION & TEST ============ */}\n {activeStep === 2 && (\n <div className=\"space-y-5\">\n {/* Expression */}\n <div>\n <label htmlFor=\"expression\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Expression <span className=\"text-status-error-text\">*</span>\n </label>\n <textarea\n id=\"expression\"\n value={form.expression}\n onChange={handleExpressionChange}\n placeholder={typeInfo.placeholder}\n rows={8}\n className={`${inputBase} font-mono resize-y ${\n errors.expression ? 'border-status-error-border' : 'border-border-default'\n }`}\n />\n {errors.expression && <p className=\"text-xs text-status-error-text mt-1.5\">{errors.expression}</p>}\n <p className=\"text-xs text-text-tertiary mt-1.5\">Example: {typeInfo.example}</p>\n </div>\n\n {/* Run Test */}\n <Button\n type=\"button\"\n onClick={handleTestExpression}\n disabled={isTesting || !form.expression.trim()}\n variant=\"default\"\n className=\"w-full\"\n >\n {isTesting ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Testing...\n </>\n ) : (\n <>\n <Play className=\"w-4 h-4 mr-2\" />\n Run Test\n </>\n )}\n </Button>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n {/* Input JSON */}\n <div>\n <div className=\"flex items-center justify-between mb-2\">\n <label className=\"text-sm font-medium text-text-primary\">\n Input JSON\n <span\n className={`ml-2 text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded border align-middle ${\n inputLocked\n ? 'text-action-primary-bg bg-action-primary-bg/10 border-action-primary-bg/30'\n : 'text-text-secondary bg-bg-sunken border-border-default'\n }`}\n >\n {inputLocked ? 'Reference mode' : 'Sample mode'}\n </span>\n </label>\n <div className=\"flex items-center gap-2\">\n {form.inputSinkKey && !inputLocked && (\n <button\n type=\"button\"\n onClick={handleLoadSampleData}\n disabled={loadingSample}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-semibold text-action-primary-bg border border-action-primary-bg rounded-md hover:bg-action-primary-bg/10 transition-colors disabled:opacity-50\"\n >\n {loadingSample ? (\n <Loader2 className=\"w-3.5 h-3.5 animate-spin\" />\n ) : (\n <Download className=\"w-3.5 h-3.5\" />\n )}\n Load Sample\n </button>\n )}\n {inputLocked && (\n <button\n type=\"button\"\n onClick={handleClearInput}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-semibold text-text-primary border border-border-default rounded-md hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"w-3.5 h-3.5\" />\n Clear\n </button>\n )}\n </div>\n </div>\n <textarea\n value={testInput}\n onChange={handleTestInputChange}\n readOnly={inputLocked}\n placeholder=\"Type sample JSON to test against it — or Load a sample from the source…\"\n rows={8}\n className={`${inputBase} min-h-[220px] text-xs font-mono leading-relaxed resize-y border-border-default ${\n inputLocked ? 'bg-bg-sunken opacity-80 cursor-not-allowed' : ''\n }`}\n />\n {inputLocked && sampleMeta ? (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Loaded a small sample\n {sampleMeta.total != null\n ? ` (first ${Math.min(PREVIEW_LIMIT, sampleMeta.total)} of ${sampleMeta.total} rows)`\n : ''}{' '}\n from {sampleMeta.sinkName} · locked — press Clear to edit.\n </p>\n ) : (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Type sample JSON to test against it, or Load a sample from the source.\n </p>\n )}\n </div>\n\n {/* Output */}\n <div>\n <div className=\"flex items-center justify-between mb-2\">\n <label className=\"text-sm font-medium text-text-primary\">\n {testError ? 'Error Output' : 'Output'}\n </label>\n {(testOutput || testError) &&\n (testError ? (\n <span className=\"text-xs px-2 py-1 bg-status-error-bg/10 text-status-error-text rounded flex items-center gap-1\">\n <AlertCircle className=\"w-3 h-3\" /> Failed\n </span>\n ) : (\n <span className=\"text-xs px-2 py-1 bg-status-success-bg/10 text-status-success-text rounded flex items-center gap-1\">\n <CheckCircle2 className=\"w-3 h-3\" /> Passed\n </span>\n ))}\n </div>\n {testOutput || testError ? (\n <pre\n className={`w-full px-3 py-2.5 text-xs font-mono leading-relaxed border rounded-md overflow-auto min-h-[220px] max-h-[220px] resize-y ${\n testError\n ? 'bg-status-error-bg/5 text-status-error-text border-status-error-border/30'\n : 'bg-status-success-bg/5 text-text-primary border-status-success-border/30'\n }`}\n >\n {testError || testOutput}\n </pre>\n ) : (\n <div className=\"min-h-[220px] p-3 bg-bg-sunken rounded-md border border-border-default\">\n <p className=\"text-xs text-text-secondary leading-relaxed\">\n <strong>Tip:</strong> write your expression and click &ldquo;Run Test&rdquo; to see the\n transformation.\n </p>\n </div>\n )}\n {testMeta && !testError && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Tested a sample\n {testMeta.sampledRows != null && testMeta.totalRows != null\n ? ` (first ${testMeta.sampledRows} of ${testMeta.totalRows} rows)`\n : ''}\n — a saved parser runs over the full source, so results may differ.\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* ============ STEP 4: CACHE & REVIEW ============ */}\n {activeStep === 3 && (\n <div className=\"space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Cache Duration</label>\n <p className=\"text-xs text-text-tertiary mb-3\">How long should data be cached before refreshing?</p>\n <CacheTTLSelector value={form.ttl} onChange={handleTTLChange} error={errors.ttl} hideLabel />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">Review</label>\n <div className=\"border border-border-default rounded-lg overflow-hidden\">\n {(\n [\n ['Name', form.name || '—'],\n ['Output Key', form.outputKey || '—'],\n [\n 'Scope',\n showDashboardSelector\n ? selectedDashboardId === 'default'\n ? 'Global'\n : dashboards.find((d) => d.id === selectedDashboardId)?.name || selectedDashboardId\n : 'Dashboard',\n ],\n ['Type', PARSER_TYPE_INFO[form.type].name],\n ['Source', selectedSinkName || '—'],\n ] as const\n ).map(([k, v]) => (\n <div\n key={k}\n className=\"flex items-center justify-between gap-3 px-4 py-2.5 text-sm border-b border-border-default last:border-b-0\"\n >\n <span className=\"text-text-secondary\">{k}</span>\n <span className=\"font-medium text-text-primary text-right break-words\">{v}</span>\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n </CardContent>\n </GlassCard>\n\n {/* Footer nav */}\n <div className=\"flex items-center justify-between gap-4 pt-6 mt-6 border-t border-border-default\">\n {activeStep === 0 ? (\n <Button type=\"button\" onClick={handleLeave} variant=\"secondary\">\n Cancel\n </Button>\n ) : (\n <Button type=\"button\" onClick={goPrev} variant=\"secondary\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back\n </Button>\n )}\n\n {isLastStep ? (\n <Button type=\"button\" onClick={handleSubmit} disabled={!isFormValid || isSaving} variant=\"default\">\n {isSaving ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Saving...\n </>\n ) : (\n <>\n <Save className=\"w-4 h-4 mr-2\" />\n {isEdit ? 'Save Changes' : 'Create Parser'}\n </>\n )}\n </Button>\n ) : (\n <Button type=\"button\" onClick={goNext} disabled={!canAdvance(activeStep)} variant=\"default\">\n Next\n <ChevronRight className=\"w-4 h-4 ml-2\" />\n </Button>\n )}\n </div>\n </div>\n </div>\n );\n});\n\nexport default ParserBuilderPage;\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { isNative as i, nativeCopyText as a, nativeImpact as ne, nativeNotify as
9
9
  import ie from "../components/parser/ExpressionEditor.js";
10
10
  import ae from "../components/parser/ExportParserDialog.js";
11
11
  import { memo as s, useCallback as c, useEffect as l, useState as u } from "react";
12
- import { useParams as d } from "react-router-dom";
12
+ import { useParams as d } from "react-router";
13
13
  import { AlertCircle as f, AlertTriangle as oe, ArrowDown as se, ArrowLeft as p, Check as ce, Copy as le, Database as ue, Download as de, Edit as fe, FileCode2 as pe, Loader2 as m, Play as h, Trash2 as g } from "lucide-react";
14
14
  import { Badge as _, Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Dialog as me, DialogContent as he, DialogDescription as ge, DialogFooter as _e, DialogHeader as ve, DialogTitle as ye, GlassCard as C, IllustratedEmptyState as w, NextSteps as T, PagePurpose as E } from "@burdenoff/fe-libs/ui";
15
15
  import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";