From 5e5feb5c0071f5a5893a2f1e82b844b4dd2d8784 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Fri, 6 Mar 2026 09:40:53 +0100 Subject: [PATCH] feat(recipe-studio, validators): tweak OXC validator with lint suppression support and improve error normalization logic --- .../data_recipe/local_callable_validators.py | 4 + .../data_recipe/oxc-validator/validate.mjs | 19 +- .../components/executions/executions-view.tsx | 248 ++++++++++-------- 3 files changed, 155 insertions(+), 116 deletions(-) diff --git a/studio/backend/core/data_recipe/local_callable_validators.py b/studio/backend/core/data_recipe/local_callable_validators.py index 7beec7684d..3d95e68265 100644 --- a/studio/backend/core/data_recipe/local_callable_validators.py +++ b/studio/backend/core/data_recipe/local_callable_validators.py @@ -267,6 +267,7 @@ def _run_oxc_batch( "error_count": 1, "error_message": "Invalid OXC result entry.", "severity": None, + "code": None, "labels": [], "codeframe": None, "warning_count": 0, @@ -277,6 +278,7 @@ def _run_oxc_batch( error_count_raw = item.get("error_count") message_raw = item.get("error_message") severity_raw = item.get("severity") + code_raw = item.get("code") labels_raw = item.get("labels") codeframe_raw = item.get("codeframe") warning_count_raw = item.get("warning_count") @@ -286,6 +288,7 @@ def _run_oxc_batch( "error_count": int(error_count_raw) if isinstance(error_count_raw, int) else 0, "error_message": str(message_raw or ""), "severity": str(severity_raw) if isinstance(severity_raw, str) else None, + "code": str(code_raw) if isinstance(code_raw, str) else None, "labels": labels_raw if isinstance(labels_raw, list) else [], "codeframe": str(codeframe_raw) if isinstance(codeframe_raw, str) else None, "warning_count": int(warning_count_raw) @@ -303,6 +306,7 @@ def _fallback_results(row_count: int, message: str) -> list[dict[str, Any]]: "error_count": 1, "error_message": message, "severity": None, + "code": None, "labels": [], "codeframe": None, "warning_count": 0, diff --git a/studio/backend/core/data_recipe/oxc-validator/validate.mjs b/studio/backend/core/data_recipe/oxc-validator/validate.mjs index b2efa7df1f..7d2f206ce0 100644 --- a/studio/backend/core/data_recipe/oxc-validator/validate.mjs +++ b/studio/backend/core/data_recipe/oxc-validator/validate.mjs @@ -16,6 +16,7 @@ const VALIDATION_MODES = new Set(["syntax", "lint", "syntax+lint"]); const CODE_SHAPES = new Set(["auto", "module", "snippet"]); const SNIPPET_PREFIX = "(() => {\n"; const SNIPPET_SUFFIX = "\n})();\nexport {};\n"; +const OXLINT_SUPPRESSED_RULES = ["no-unused-vars", "no-new-array"]; const TOOL_DIR = dirname(fileURLToPath(import.meta.url)); function mapLang(value) { @@ -112,6 +113,7 @@ function remapDiagnosticOffsets(diagnostic, offset) { function normalizeParserError(error) { if (typeof error === "string") { return { + code: null, message: error.trim() || "Unknown parser error", severity: null, labels: [], @@ -120,12 +122,14 @@ function normalizeParserError(error) { } if (!error || typeof error !== "object") { return { + code: null, message: "Unknown parser error", severity: null, labels: [], codeframe: null, }; } + const code = typeof error.code === "string" ? error.code : null; const message = String(error.message || error.reason || "").trim() || "Unknown parser error"; const severity = typeof error.severity === "string" ? error.severity : null; const labels = Array.isArray(error.labels) @@ -146,6 +150,7 @@ function normalizeParserError(error) { : []; const codeframe = typeof error.codeframe === "string" ? error.codeframe : null; return { + code, message, severity, labels, @@ -190,6 +195,7 @@ function normalizeLintDiagnostic(diagnostic) { const code = typeof diagnostic.code === "string" ? diagnostic.code : null; return { + code, message: code ? `${code}: ${message}` : message, severity, labels, @@ -203,6 +209,7 @@ function makeResult({ warningCount = 0, message = "", severity = null, + code = null, labels = [], codeframe = null, }) { @@ -212,6 +219,7 @@ function makeResult({ warning_count: Number.isInteger(warningCount) ? warningCount : 0, error_message: String(message || ""), severity: typeof severity === "string" ? severity : null, + code: typeof code === "string" ? code : null, labels: Array.isArray(labels) ? labels : [], codeframe: typeof codeframe === "string" ? codeframe : null, }; @@ -225,6 +233,7 @@ function syntaxResultFromErrors(errors) { warningCount: 0, message: errors.slice(0, 3).map((error) => error.message).join(" | "), severity: first ? first.severity : null, + code: first ? first.code : null, labels: first ? first.labels : [], codeframe: first ? first.codeframe : null, }); @@ -377,7 +386,13 @@ function runLintBatch(entries) { } const oxlintBin = join(TOOL_DIR, "node_modules", ".bin", "oxlint"); - const exec = spawnSync(oxlintBin, ["--format", "json", tempDir], { + const oxlintArgs = [ + ...OXLINT_SUPPRESSED_RULES.flatMap((rule) => ["-A", rule]), + "--format", + "json", + tempDir, + ]; + const exec = spawnSync(oxlintBin, oxlintArgs, { encoding: "utf8", cwd: TOOL_DIR, }); @@ -452,6 +467,7 @@ function runLintBatch(entries) { .map((diag) => diag.message) .join(" | "), severity: top ? top.severity : null, + code: top ? top.code : null, labels: top ? top.labels : [], codeframe: top ? top.codeframe : null, }), @@ -555,4 +571,3 @@ main().catch((error) => { process.stderr.write(String(error?.stack || error)); process.exit(1); }); - diff --git a/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx b/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx index e9a2a12f85..8fe9a0d225 100644 --- a/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx +++ b/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx @@ -284,9 +284,32 @@ export function ExecutionsView({ return formatDuration(selectedExecution.createdAt, selectedExecution.finishedAt); }, [selectedExecution]); const showSummaryCards = selectedExecution?.status === "completed"; - const showProgressPanel = - selectedExecution?.status === "completed" || - (selectedExecution ? isExecutionInProgress(selectedExecution.status) : false); + const hasProgressSnapshot = Boolean( + selectedExecution?.progress && + (typeof selectedExecution.progress.done === "number" || + typeof selectedExecution.progress.total === "number" || + typeof selectedExecution.progress.percent === "number" || + typeof selectedExecution.progress.rate === "number" || + typeof selectedExecution.progress.eta_sec === "number"), + ) || Boolean( + selectedExecution?.column_progress && + (typeof selectedExecution.column_progress.done === "number" || + typeof selectedExecution.column_progress.total === "number" || + typeof selectedExecution.column_progress.percent === "number"), + ) || Boolean( + selectedExecution?.batch && + (typeof selectedExecution.batch.idx === "number" || + typeof selectedExecution.batch.total === "number"), + ); + const selectedStatus = selectedExecution?.status ?? null; + const isSelectedExecutionInProgress = selectedStatus + ? isExecutionInProgress(selectedStatus) + : false; + const showProgressPanel = Boolean(selectedExecution) && ( + selectedStatus === "completed" || + isSelectedExecutionInProgress || + hasProgressSnapshot + ); const progressComplete = selectedExecution?.status === "completed"; const progressPercent = selectedExecution?.progress?.percent ?? (progressComplete ? 100 : 0); const batchTotal = selectedExecution?.batch?.total ?? null; @@ -400,118 +423,115 @@ export function ExecutionsView({ )} - {(selectedExecution.status === "completed" || - isExecutionInProgress(selectedExecution.status)) && ( - -
- - Overview - Columns - Data - Raw - - {canCancel && ( - - )} -
- - { - const element = event.currentTarget; - const distanceFromBottom = - element.scrollHeight - element.scrollTop - element.clientHeight; - shouldStickTerminalToBottomRef.current = - distanceFromBottom <= TERMINAL_STICKY_BOTTOM_THRESHOLD_PX; - }} - /> - - - - - - { + +
+ + Overview + Columns + Data + Raw + + {canCancel && ( + + )} +
+ + { + const element = event.currentTarget; + const distanceFromBottom = + element.scrollHeight - element.scrollTop - element.clientHeight; + shouldStickTerminalToBottomRef.current = + distanceFromBottom <= TERMINAL_STICKY_BOTTOM_THRESHOLD_PX; + }} + /> + + + + + + { + const selectedId = selectedExecution.id; + setHiddenDatasetColumnsByExecution((current) => { + const currentColumns = current[selectedId] ?? []; + return { + ...current, + [selectedId]: updater(currentColumns), + }; + }); + }} + onPrevPage={() => { + if (selectedExecution.kind === "preview") { const selectedId = selectedExecution.id; - setHiddenDatasetColumnsByExecution((current) => { - const currentColumns = current[selectedId] ?? []; - return { - ...current, - [selectedId]: updater(currentColumns), - }; - }); - }} - onPrevPage={() => { - if (selectedExecution.kind === "preview") { - const selectedId = selectedExecution.id; - setPreviewDatasetPageByExecution((current) => ({ - ...current, - [selectedId]: Math.max(1, currentDatasetPage - 1), - })); - return; - } - onLoadDatasetPage(selectedExecution.id, currentDatasetPage - 1); - }} - onNextPage={() => { - if (selectedExecution.kind === "preview") { - const selectedId = selectedExecution.id; - setPreviewDatasetPageByExecution((current) => ({ - ...current, - [selectedId]: Math.min(totalPages, currentDatasetPage + 1), - })); - return; - } - onLoadDatasetPage(selectedExecution.id, currentDatasetPage + 1); - }} - onToggleRowExpanded={(rowId) => { - setExpandedDatasetRowsByExecution((current) => { - const rows = current[selectedExecution.id] ?? {}; - return { - ...current, - [selectedExecution.id]: { - ...rows, - [rowId]: !rows[rowId], - }, - }; - }); - }} - /> - - - - -
- )} + setPreviewDatasetPageByExecution((current) => ({ + ...current, + [selectedId]: Math.max(1, currentDatasetPage - 1), + })); + return; + } + onLoadDatasetPage(selectedExecution.id, currentDatasetPage - 1); + }} + onNextPage={() => { + if (selectedExecution.kind === "preview") { + const selectedId = selectedExecution.id; + setPreviewDatasetPageByExecution((current) => ({ + ...current, + [selectedId]: Math.min(totalPages, currentDatasetPage + 1), + })); + return; + } + onLoadDatasetPage(selectedExecution.id, currentDatasetPage + 1); + }} + onToggleRowExpanded={(rowId) => { + setExpandedDatasetRowsByExecution((current) => { + const rows = current[selectedExecution.id] ?? {}; + return { + ...current, + [selectedExecution.id]: { + ...rows, + [rowId]: !rows[rowId], + }, + }; + }); + }} + /> +
+ + + +
)}