diff --git a/packages/otelbin/src/components/monaco-editor/Editor.tsx b/packages/otelbin/src/components/monaco-editor/Editor.tsx index cb12ade6..1c165a98 100644 --- a/packages/otelbin/src/components/monaco-editor/Editor.tsx +++ b/packages/otelbin/src/components/monaco-editor/Editor.tsx @@ -25,6 +25,7 @@ import { PanelLeftOpen } from "lucide-react"; import { IconButton } from "~/components/icon-button"; import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/tooltip"; import { track } from "@vercel/analytics"; +import { useServerSideValidation } from "../validation/useServerSideValidation"; const firaCode = Fira_Code({ display: "swap", @@ -43,6 +44,7 @@ export default function Editor({ locked, setLocked }: { locked: boolean; setLock const [{ config }, getLink] = useUrlState([editorBinding]); const [currentConfig, setCurrentConfig] = useState(config); const clerk = useClerk(); + const serverSideValidationResult = useServerSideValidation(); const onWidthChange = useCallback((newWidth: number) => { localStorage.setItem("width", String(newWidth)); @@ -60,11 +62,16 @@ export default function Editor({ locked, setLocked }: { locked: boolean; setLock const totalValidationErrors = useMemo((): IError => { if (editorRef && monacoRef) { - return validateOtelCollectorConfigurationAndSetMarkers(currentConfig, editorRef, monacoRef); + return validateOtelCollectorConfigurationAndSetMarkers( + currentConfig, + editorRef, + monacoRef, + serverSideValidationResult + ); } else { return {}; } - }, [currentConfig, editorRef, monacoRef]); + }, [currentConfig, editorRef, monacoRef, serverSideValidationResult]); const isValidConfig = totalValidationErrors.jsYamlError == null && (totalValidationErrors.ajvErrors?.length ?? 0) === 0; diff --git a/packages/otelbin/src/components/monaco-editor/ValidationErrorConsole.tsx b/packages/otelbin/src/components/monaco-editor/ValidationErrorConsole.tsx index b829ce9f..4816fff8 100644 --- a/packages/otelbin/src/components/monaco-editor/ValidationErrorConsole.tsx +++ b/packages/otelbin/src/components/monaco-editor/ValidationErrorConsole.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from "react"; import { ChevronDown, XCircle, AlertTriangle } from "lucide-react"; import { type NextFont } from "next/dist/compiled/@next/font"; -import { useServerSideValidation } from "../validation/useServerSideValidation"; export interface IAjvError { message: string; @@ -18,20 +17,27 @@ export interface IJsYamlError { reason: string | null; } +export interface IServerSideError { + message: string; + error: string; + line: number | null; + path?: string[]; +} + export interface IError { jsYamlError?: IJsYamlError; ajvErrors?: IAjvError[]; customErrors?: string[]; customWarnings?: string[]; + serverSideError?: IServerSideError; } export default function ValidationErrorConsole({ errors, font }: { errors?: IError; font: NextFont }) { - const serverSideValidationResult = useServerSideValidation(); const errorCount = (errors?.ajvErrors?.length ?? 0) + (errors?.jsYamlError != null ? 1 : 0) + (errors?.customErrors?.length ?? 0) + - (serverSideValidationResult.result?.error ? 1 : 0); + (errors?.serverSideError?.error ? 1 : 0); const warningsCount = errors?.customWarnings?.length ?? 0; const [isOpenErrorConsole, setIsOpenErrorConsole] = useState(false); @@ -71,14 +77,7 @@ export default function ValidationErrorConsole({ errors, font }: { errors?: IErr errors.customWarnings.map((warning: string, index: number) => { return ; })} - {serverSideValidationResult.result?.error && ( - - )} + {errors?.serverSideError?.error && } {errors?.ajvErrors && errors.ajvErrors?.length > 0 && errors.ajvErrors.map((error: IAjvError, index: number) => { @@ -107,7 +106,7 @@ export function ErrorMessage({ }: { ajvError?: IAjvError; jsYamlError?: IJsYamlError; - serverSideError?: string; + serverSideError?: IServerSideError; customErrors?: string; customWarnings?: string; font: NextFont; @@ -140,7 +139,9 @@ export function ErrorMessage({ )} {serverSideError ? (
-

{`Server-side: ${serverSideError}`}

+

{`${serverSideError.message} - ${serverSideError.error} ${ + (serverSideError.line ?? 0) > 1 ? `(Line ${serverSideError.line})` : "" + }`}

) : ( <> diff --git a/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.test.ts b/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.test.ts index 83d2cb02..dd0e85ea 100644 --- a/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.test.ts +++ b/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.test.ts @@ -228,4 +228,12 @@ describe("findErrorElement", () => { expect(result).toEqual(expectedOutput); }); + + it("with both empty parsed yaml doc and empty error path should return undefined", () => { + const result = findErrorElement([], []); + + const expectedOutput = undefined; + + expect(result).toEqual(expectedOutput); + }); }); diff --git a/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.ts b/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.ts index f2c2f951..7fd5d9d5 100644 --- a/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.ts +++ b/packages/otelbin/src/components/monaco-editor/otelCollectorConfigValidation.ts @@ -20,6 +20,7 @@ import { findLineAndColumn, parseYaml, } from "./parseYaml"; +import type { ValidationState } from "../validation/useServerSideValidation"; type EditorRefType = RefObject; type MonacoRefType = RefObject; @@ -28,7 +29,8 @@ let serviceItemsData: IValidateItem | undefined = {}; export function validateOtelCollectorConfigurationAndSetMarkers( configData: string, editorRef: EditorRefType, - monacoRef: MonacoRefType + monacoRef: MonacoRefType, + serverSideValidationResult?: ValidationState ) { const ajv = new Ajv({ allErrors: true }); // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -47,6 +49,7 @@ export function validateOtelCollectorConfigurationAndSetMarkers( docElements.filter((item: IItem) => item.key?.source === "service")[0], serviceItemsData ); + const serverSideValidationPath = serverSideValidationResult?.result?.path ?? []; try { const jsonData = JsYaml.load(configData); @@ -99,6 +102,23 @@ export function validateOtelCollectorConfigurationAndSetMarkers( } if (!totalErrors.jsYamlError) { customValidate(mainItemsData, serviceItemsData, errorMarkers, totalErrors, configData); + const serverSideErrorElement = findErrorElement(serverSideValidationPath, parsedYamlConfig); + const { line, column } = findLineAndColumn(configData, serverSideErrorElement?.offset); + totalErrors.serverSideError = { + message: serverSideValidationResult?.result?.message ?? "", + error: serverSideValidationResult?.result?.error ?? "", + line: line, + path: serverSideValidationPath, + }; + serverSideValidationPath.length > 0 && + errorMarkers.push({ + startLineNumber: line ?? 0, + endLineNumber: 0, + startColumn: column ?? 0, + endColumn: column ?? 0, + severity: 8, + message: serverSideValidationResult?.result?.message + " - " + serverSideValidationResult?.result?.error, + }); model && monacoRef?.current?.editor.setModelMarkers(model, "json", errorMarkers); } return totalErrors; diff --git a/packages/otelbin/src/components/validation/useServerSideValidation.ts b/packages/otelbin/src/components/validation/useServerSideValidation.ts index 3fefb051..be8b019f 100644 --- a/packages/otelbin/src/components/validation/useServerSideValidation.ts +++ b/packages/otelbin/src/components/validation/useServerSideValidation.ts @@ -10,7 +10,7 @@ import { editorBinding } from "~/components/monaco-editor/editorBinding"; import { useEffect, useMemo, useState } from "react"; import { type ServerSideValidationResult } from "~/types"; -interface ValidationState { +export interface ValidationState { isLoading: boolean; // the config that was/is being validated config: string; diff --git a/packages/otelbin/src/types.ts b/packages/otelbin/src/types.ts index df85df48..4c045a1c 100644 --- a/packages/otelbin/src/types.ts +++ b/packages/otelbin/src/types.ts @@ -21,4 +21,5 @@ export interface Release { export interface ServerSideValidationResult { message: string; error: string; + path?: string[]; }