@burdenoff/microfe-bigconsole 2026.804.2 → 2026.805.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.
- package/dist/bigconsole/AdminRoot.js +21 -16
- package/dist/bigconsole/AdminRoot.js.map +1 -1
- package/dist/bigconsole/BigConsoleRoot.js +44 -39
- package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js +115 -100
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js.map +1 -1
- package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js +100 -98
- package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js +164 -161
- package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkCard.js +20 -20
- package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
- package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js +216 -49
- package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js.map +1 -1
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js +96 -90
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetMenu.js +87 -76
- package/dist/bigconsole/components/widgets/WidgetMenu.js.map +1 -1
- package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js +120 -118
- package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +113 -104
- package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardViewPage.js +208 -199
- package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +376 -325
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js +135 -125
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkBuilderPage.js +182 -172
- package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkViewPage.js +233 -198
- package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +281 -268
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserBuilderPage.js +238 -223
- package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserViewPage.js +162 -144
- package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineBuilderPage.js +212 -204
- package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineViewPage.js +191 -183
- package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelinesPage.js +128 -103
- package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowViewPage.js +238 -217
- package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js +116 -102
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/dist/bigconsole/utils/csv.js +91 -0
- package/dist/bigconsole/utils/csv.js.map +1 -0
- package/dist/constants/permissions.js +83 -0
- package/dist/constants/permissions.js.map +1 -0
- package/package.json +2 -2
|
@@ -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';\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 “Run Test” 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// RBAC\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\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 // Permissions\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_READ);\n // The Save action needs update (edit mode) or create (new mode).\n const canSave = isEdit\n ? hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_UPDATE)\n : hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_CREATE);\n const savePermission = isEdit ? BIGCONSOLE_PERMISSIONS.PARSER_UPDATE : BIGCONSOLE_PERMISSIONS.PARSER_CREATE;\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 (wait for the parser fetch in edit mode and the permission load)\n if ((loading && isEdit) || permissionsLoading) {\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 // Read gate — editing an existing parser requires read access\n if (isEdit && !canRead) {\n return <AccessDenied title=\"Access Denied\" description=\"You don't have permission to view this parser.\" />;\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 <PermissionButton permission={BIGCONSOLE_PERMISSIONS.PARSER_TEST} deniedBehavior=\"disable\">\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 </PermissionButton>\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 “Run Test” 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 <PermissionButton permission={savePermission} deniedBehavior=\"disable\">\n <Button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!isFormValid || isSaving || !canSave}\n variant=\"default\"\n >\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 </PermissionButton>\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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"}
|