diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss index a9617c75325e..db2f77b9beac 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss @@ -18,6 +18,89 @@ border-color: var(--bg-robin-500); background-color: var(--bg-ink-400); } + + .cm-content { + border-radius: 2px; + border: 1px solid var(--Slate-400, #1d212d); + background: var(--Ink-300, #16181d); + } + + .cm-tooltip-autocomplete { + background: var(--bg-ink-300) !important; + border-radius: 2px !important; + font-size: 12px !important; + font-weight: 500 !important; + margin-top: -2px !important; + min-width: 400px !important; + position: relative !important; + top: 0px !important; + left: 0px !important; + + border-radius: 4px; + border: 1px solid var(--bg-slate-200, #1d212d); + + ul { + width: 100% !important; + max-width: 100% !important; + + &::-webkit-scrollbar { + width: 0.3rem; + } + &::-webkit-scrollbar-corner { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: rgb(136, 136, 136); + border-radius: 0.625rem; + } + &::-webkit-scrollbar-track { + background: transparent; + } + + li { + width: 100% !important; + max-width: 100% !important; + line-height: 24px !important; + padding: 4px 8px !important; + + display: flex !important; + align-items: center !important; + gap: 8px !important; + + .cm-completionIcon { + display: none !important; + } + + &[aria-selected='true'] { + background-color: rgba(78, 116, 248, 0.7) !important; + } + } + } + } + + .cm-gutters { + display: none !important; + } + + .cm-line { + line-height: 1.8 !important; + background: var(--bg-ink-300) !important; + + ::-moz-selection { + background: var(--bg-ink-100) !important; + opacity: 0.5 !important; + } + + ::selection { + background: var(--bg-ink-100) !important; + opacity: 0.5 !important; + } + } + + .cm-selectionBackground { + background: var(--bg-ink-100) !important; + opacity: 0.5 !important; + } } .cursor-position { @@ -29,11 +112,12 @@ display: inline-flex; align-items: center; margin-bottom: 8px; + margin-top: 8px; } .query-validation { display: flex; - align-items: center; + flex-direction: column; gap: 8px; margin-bottom: 8px; @@ -56,20 +140,39 @@ background-color: rgba(235, 87, 87, 0.1); color: #eb5757; } + + .query-validation-status { + display: flex; + align-items: center; + gap: 8px; + } + + .query-validation-errors { + display: flex; + flex-direction: column; + gap: 8px; + + .query-validation-error { + display: flex; + flex-direction: row; + gap: 16px; + + font-size: 12px; + font-family: 'Space Mono', monospace !important; + color: var(--bg-cherry-500); + } + } } .query-context { padding: 12px; - background-color: var(--bg-vanilla-200); + background-color: var(--bg-ink-400); border-radius: 4px; border-left: 3px solid var(--bg-robin-500); + color: var(--bg-ink-300) !important; - h3 { - margin-top: 0; - margin-bottom: 8px; - font-size: 14px; - font-weight: 600; - color: var(--bg-ink-300); + .ant-card-head { + color: var(--bg-vanilla-300) !important; } .context-details { @@ -82,7 +185,7 @@ font-size: 13px; strong { - color: var(--bg-ink-300); + color: var(--bg-vanilla-300); margin-right: 4px; } } diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx index 0aded7a50cba..99991cb5086d 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx @@ -5,12 +5,7 @@ import './CodeMirrorWhereClause.styles.scss'; -import { - CheckCircleFilled, - CloseCircleFilled, - InfoCircleOutlined, - QuestionCircleOutlined, -} from '@ant-design/icons'; +import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import { autocompletion, CompletionContext, @@ -20,12 +15,15 @@ import { import { javascript } from '@codemirror/lang-javascript'; import { copilot } from '@uiw/codemirror-theme-copilot'; import CodeMirror, { EditorView, Extension } from '@uiw/react-codemirror'; -import { Badge, Card, Divider, Space, Tooltip, Typography } from 'antd'; +import { Badge, Card, Divider, Space, Typography } from 'antd'; import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion'; import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions'; -// import { useGetQueryKeyValueSuggestions } from 'hooks/querySuggestions/useGetQueryKeyValueSuggestions'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { IQueryContext, IValidationResult } from 'types/antlrQueryTypes'; +import { + IDetailedError, + IQueryContext, + IValidationResult, +} from 'types/antlrQueryTypes'; import { QueryKeySuggestionsProps } from 'types/api/querySuggestions/types'; import { getQueryContextAtCursor, @@ -33,7 +31,7 @@ import { validateQuery, } from 'utils/antlrQueryUtils'; -const { Text, Title } = Typography; +const { Text } = Typography; function collapseSpacesOutsideStrings(): Extension { return EditorView.inputHandler.of((view, from, to, text) => { @@ -244,7 +242,7 @@ function CodeMirrorWhereClause(): JSX.Element { setValidation({ isValid: false, message: 'Failed to process query', - errors: [error instanceof Error ? error.message : 'Unknown error'], + errors: [error as IDetailedError], }); } }, []); @@ -289,15 +287,7 @@ function CodeMirrorWhereClause(): JSX.Element { // text = 'Parenthesis'; // } - return ( - - ); + return ; }; function myCompletions(context: CompletionContext): CompletionResult | null { @@ -380,33 +370,6 @@ function CodeMirrorWhereClause(): JSX.Element { }; } - const customTheme = EditorView.theme({ - '&': { - fontFamily: '"Space Mono", monospace', // Change to any font - fontSize: '13px', // Set font size - lineHeight: '1.8', // Set line height - margin: '8px 0px', - }, - '.cm-line': { - lineHeight: '2.2', // Set line height - }, - '.cm-gutters': { - lineHeight: '1.8', // Set line height - display: 'none', - }, - '.cm-content': { - lineHeight: '2.2', // Set line height - borderRadius: '2px', - border: '1px solid var(--bg-ink-400) !important', - background: 'var(--bg-ink-400) !important', - padding: '0px', - }, - '.cm-focused': { - border: '1px solid var(--bg-robin-500) !important', - background: 'var(--bg-ink-400) !important', - }, - }); - // Add back the generateOptions function and useEffect const generateOptions = (data: any): any[] => Object.values(data.keys).flatMap((items: any) => @@ -440,15 +403,7 @@ function CodeMirrorWhereClause(): JSX.Element { return (
- Where Clause} - extra={ - - - - } - > + - - - - Line: {cursorPos.line}, Position: {cursorPos.ch} - - + {query && ( + <> + + + Query: + {query} + + + )} - + {query && ( + <> + -
- Status: -
- {validation.isValid ? ( - <> - Valid - - ) : ( - <> - Invalid - - )} -
- {validation.message && ( - - - - )} -
+
+
+ Status: +
+ {validation.isValid ? ( + + Valid + + ) : ( + + Invalid + + )} +
+
+ +
+ {validation.errors.map((error) => ( +
+
+ {error.line}:{error.column} +
+ +
{error.message}
+
+ ))} +
+
+ + )}
{queryContext && ( @@ -508,107 +479,43 @@ function CodeMirrorWhereClause(): JSX.Element {
- - Token: - - - {queryContext.currentToken || '-'} - + Token: + {queryContext.currentToken || '-'} - - Type: - - {queryContext.tokenType || '-'} + Type: + {queryContext.tokenType || '-'} - - Context: - + Context: {renderContextBadge()} {/* Display the key-operator-value triplet when available */} {queryContext.keyToken && ( - - Key: - - - {queryContext.keyToken} - + Key: + {queryContext.keyToken} )} {queryContext.operatorToken && ( - - Operator: - - - {queryContext.operatorToken} - + Operator: + {queryContext.operatorToken} )} {queryContext.valueToken && ( - - Value: - - - {queryContext.valueToken} - + Value: + {queryContext.valueToken} )}
)} - - -
Query Examples
-
    -
  • - - status = 'error' - -
  • -
  • - - service = 'frontend' AND level = 'error' - -
  • -
  • - - message LIKE '%timeout%' - -
  • -
  • - - duration {'>'} 1000 - -
  • -
  • - - tags IN ['prod', 'frontend'] - -
  • -
  • - - NOT (status = 'error' OR level = 'error') - -
  • -
-
); } diff --git a/frontend/src/components/QueryBuilderV2/WhereClause/WhereClause.tsx b/frontend/src/components/QueryBuilderV2/WhereClause/WhereClause.tsx index 14a2c9cae249..f778e970b52a 100644 --- a/frontend/src/components/QueryBuilderV2/WhereClause/WhereClause.tsx +++ b/frontend/src/components/QueryBuilderV2/WhereClause/WhereClause.tsx @@ -37,7 +37,13 @@ function QueryWhereClause(): JSX.Element { setValidation({ isValid: false, message: 'Failed to process query', - errors: [error instanceof Error ? error.message : 'Unknown error'], + errors: [ + { + message: error instanceof Error ? error.message : 'Unknown error', + line: 0, + column: 0, + }, + ], }); } finally { setIsLoading(false); @@ -79,7 +85,7 @@ function QueryWhereClause(): JSX.Element { {queryContext && (
-

Current Context

+ Current Context

Token: {queryContext.currentToken} diff --git a/frontend/src/types/antlrQueryTypes.ts b/frontend/src/types/antlrQueryTypes.ts index 1ecfc30a9b5b..9a23c4f346ff 100644 --- a/frontend/src/types/antlrQueryTypes.ts +++ b/frontend/src/types/antlrQueryTypes.ts @@ -1,7 +1,7 @@ export interface IValidationResult { isValid: boolean; message: string; - errors: string[]; + errors: IDetailedError[]; } export interface IToken { diff --git a/frontend/src/utils/antlrQueryUtils.ts b/frontend/src/utils/antlrQueryUtils.ts index 722ae30a0e97..0d69766ea00e 100644 --- a/frontend/src/utils/antlrQueryUtils.ts +++ b/frontend/src/utils/antlrQueryUtils.ts @@ -92,14 +92,22 @@ class QueryErrorListener { getFormattedErrors(): string[] { return this.errors.map((error) => { - let message = `Line ${error.line}:${error.column} - ${error.message}`; + const { + offendingSymbol, + expectedTokens, + message: errorMessage, + line, + column, + } = error; - if (error.offendingSymbol && error.offendingSymbol !== 'undefined') { - message += `\nOffending symbol: '${error.offendingSymbol}'`; + let message = `Line ${line}:${column} - ${errorMessage}`; + + if (offendingSymbol && offendingSymbol !== 'undefined') { + message += `\n Symbol: '${offendingSymbol}'`; } - if (error.expectedTokens && error.expectedTokens.length > 0) { - message += `\nExpected: ${error.expectedTokens.join(', ')}`; + if (expectedTokens && expectedTokens.length > 0) { + message += `\n Expected: ${expectedTokens.join(', ')}`; } return message; @@ -113,7 +121,15 @@ export const validateQuery = (query: string): IValidationResult => { return { isValid: false, message: 'Query cannot be empty', - errors: ['Query cannot be empty'], + errors: [ + { + message: 'Query cannot be empty', + line: 0, + column: 0, + offendingSymbol: '', + expectedTokens: [], + }, + ], }; } @@ -133,16 +149,14 @@ export const validateQuery = (query: string): IValidationResult => { parser.addErrorListener(errorListener); // Try parsing - const parsedTree = parser.query(); - - console.log('parsedTree', parsedTree); + parser.query(); // Check if any errors were captured if (errorListener.hasErrors()) { return { isValid: false, message: 'Query syntax error', - errors: errorListener.getFormattedErrors(), + errors: errorListener.getErrors(), }; } @@ -154,10 +168,18 @@ export const validateQuery = (query: string): IValidationResult => { } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Invalid query syntax'; + + const detailedError: IDetailedError = { + message: errorMessage, + line: 0, + column: 0, + offendingSymbol: '', + expectedTokens: [], + }; return { isValid: false, message: 'Invalid query syntax', - errors: [errorMessage], + errors: [detailedError], }; } };