From 711af444dcd0ef44f22e403c5bf6ce8f09d451ae Mon Sep 17 00:00:00 2001 From: Yunus M Date: Sun, 27 Apr 2025 15:34:55 +0530 Subject: [PATCH] feat: update context to recognise conjunction operator --- .../CodeMirrorWhereClause.styles.scss | 147 +++++++++++++- .../CodeMirrorWhereClause.tsx | 185 +++++++++++++----- frontend/src/types/antlrQueryTypes.ts | 2 + frontend/src/utils/antlrQueryUtils.ts | 18 +- 4 files changed, 298 insertions(+), 54 deletions(-) diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss index 0376e9088f77..bad10aa3fe35 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss @@ -1,16 +1,149 @@ .code-mirror-where-clause { width: 100%; height: 100%; + display: flex; + flex-direction: column; + padding: 16px; + gap: 16px; + font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', sans-serif; - .query-context { - background-color: #f0f0f0; - padding: 10px; - border-radius: 5px; + .cm-editor { + border: 1px solid var(--bg-vanilla-300); + border-radius: 4px; + overflow: hidden; + margin-bottom: 4px; + + &:focus-within { + border-color: var(--bg-robin-500); + box-shadow: 0 0 0 2px rgba(63, 94, 204, 0.2); + } } .cursor-position { - background-color: #f0f0f0; - padding: 10px; - border-radius: 5px; + font-size: 12px; + color: var(--bg-ink-200); + padding: 6px; + background-color: var(--bg-vanilla-200); + border-radius: 4px; + display: inline-flex; + align-items: center; + margin-bottom: 8px; + } + + .query-validation { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + + .valid, + .invalid { + display: inline-flex; + align-items: center; + padding: 4px 8px; + border-radius: 4px; + font-weight: 500; + font-size: 12px; + } + + .valid { + background-color: rgba(39, 174, 96, 0.1); + color: #27ae60; + } + + .invalid { + background-color: rgba(235, 87, 87, 0.1); + color: #eb5757; + } + } + + .query-context { + padding: 12px; + background-color: var(--bg-vanilla-200); + border-radius: 4px; + border-left: 3px solid var(--bg-robin-500); + + h3 { + margin-top: 0; + margin-bottom: 8px; + font-size: 14px; + font-weight: 600; + color: var(--bg-ink-300); + } + + .context-details { + display: flex; + flex-wrap: wrap; + gap: 12px; + + p { + margin: 0; + font-size: 13px; + + strong { + color: var(--bg-ink-300); + margin-right: 4px; + } + } + } + } + + .query-examples { + padding: 12px; + background-color: var(--bg-vanilla-100); + border: 1px solid var(--bg-vanilla-300); + border-radius: 4px; + + ul { + margin-top: 8px; + margin-bottom: 0; + padding-left: 16px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 8px; + + li { + margin-bottom: 4px; + } + } + } +} + +/* Dark mode support */ +:global(.darkMode) { + .code-mirror-where-clause { + .cm-editor { + border-color: var(--bg-slate-500); + background-color: var(--bg-ink-400); + } + + .cursor-position { + background-color: var(--bg-ink-400); + color: var(--bg-vanilla-100); + } + + .query-context { + background-color: var(--bg-ink-400); + color: var(--bg-vanilla-100); + + h3 { + color: var(--bg-vanilla-100); + } + + .context-details { + p { + strong { + color: var(--bg-vanilla-200); + } + } + } + } + + .query-examples { + background-color: var(--bg-ink-400); + border-color: var(--bg-slate-500); + color: var(--bg-vanilla-100); + } } } diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx index 96d98d0c3eec..688eb76c820e 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx @@ -2,13 +2,19 @@ import './CodeMirrorWhereClause.styles.scss'; +import { + CheckCircleFilled, + CloseCircleFilled, + InfoCircleOutlined, + QuestionCircleOutlined, +} from '@ant-design/icons'; import CodeMirror, { EditorView } from '@uiw/react-codemirror'; -import { Typography } from 'antd'; +import { Badge, Card, Divider, Space, Tooltip, Typography } from 'antd'; import { useCallback, useEffect, useRef, useState } from 'react'; import { IQueryContext, IValidationResult } from 'types/antlrQueryTypes'; import { getQueryContextAtCursor, validateQuery } from 'utils/antlrQueryUtils'; -const { Text } = Typography; +const { Text, Title } = Typography; function CodeMirrorWhereClause(): JSX.Element { const [query, setQuery] = useState(''); @@ -74,82 +80,169 @@ function CodeMirrorWhereClause(): JSX.Element { } }, [query, cursorPos]); - useEffect(() => { - console.log('cursorPos', cursorPos); - }, [cursorPos]); - const handleChange = (value: string): void => { - console.log('value', value); setQuery(value); handleQueryChange(value); }; + const renderContextBadge = (): JSX.Element | null => { + if (!queryContext) return null; + + let color = 'black'; + let text = 'Unknown'; + + if (queryContext.isInKey) { + color = 'blue'; + text = 'Key'; + } else if (queryContext.isInOperator) { + color = 'purple'; + text = 'Operator'; + } else if (queryContext.isInValue) { + color = 'green'; + text = 'Value'; + } else if (queryContext.isInFunction) { + color = 'orange'; + text = 'Function'; + } else if (queryContext.isInConjunction) { + color = 'magenta'; + text = 'Conjunction'; + } else if (queryContext.isInParenthesis) { + color = 'grey'; + text = 'Parenthesis'; + } + + return ( + + ); + }; + return (
- + Where Clause} + extra={ + + + + } + > + -
- Cursor at Line: {cursorPos.line}, Ch: {cursorPos.ch} -
+ + + + Line: {cursorPos.line}, Position: {cursorPos.ch} + + + + + +
+ Status: +
+ {validation.isValid ? ( + <> + Valid + + ) : ( + <> + Invalid + + )} +
+ {validation.message && ( + + + + )} +
+
{queryContext && ( -
-

Current Context

+
-

- Token: {queryContext.currentToken} -

-

- Type: {queryContext.tokenType} -

-

- Context:{' '} - {queryContext.isInValue - ? 'Value' - : queryContext.isInKey - ? 'Key' - : queryContext.isInOperator - ? 'Operator' - : queryContext.isInFunction - ? 'Function' - : 'Unknown'} -

+ + + + Token: + + + {queryContext.currentToken || '-'} + + + + + Type: + + {queryContext.tokenType || '-'} + + + + Context: + + {renderContextBadge()} + +
-
+ )} -
- Examples: + +
Query Examples
  • - status = 'error' + + status = 'error' +
  • - + service = 'frontend' AND level = 'error'
  • - message LIKE '%timeout%' + + message LIKE '%timeout%' +
  • - duration {'>'} 1000 + + duration {'>'} 1000 +
  • - tags IN ['prod', 'frontend'] + + tags IN ['prod', 'frontend'] +
  • - + NOT (status = 'error' OR level = 'error')
-
+
); } diff --git a/frontend/src/types/antlrQueryTypes.ts b/frontend/src/types/antlrQueryTypes.ts index 22e0e5302015..cf35a6565945 100644 --- a/frontend/src/types/antlrQueryTypes.ts +++ b/frontend/src/types/antlrQueryTypes.ts @@ -22,6 +22,8 @@ export interface IQueryContext { isInKey: boolean; isInOperator: boolean; isInFunction: boolean; + isInConjunction?: boolean; + isInParenthesis?: boolean; } export interface IDetailedError { diff --git a/frontend/src/utils/antlrQueryUtils.ts b/frontend/src/utils/antlrQueryUtils.ts index 8d9054f2c876..a23282b7f45c 100644 --- a/frontend/src/utils/antlrQueryUtils.ts +++ b/frontend/src/utils/antlrQueryUtils.ts @@ -1,7 +1,6 @@ /* eslint-disable sonarjs/no-collapsible-if */ /* eslint-disable no-continue */ /* eslint-disable sonarjs/cognitive-complexity */ -/* eslint-disable max-classes-per-file */ import { CharStreams, CommonTokenStream } from 'antlr4'; import FilterQueryLexer from 'parser/FilterQueryLexer'; import FilterQueryParser from 'parser/FilterQueryParser'; @@ -273,9 +272,22 @@ export function getQueryContextAtCursor( isInKey: false, isInOperator: false, isInFunction: false, + isInConjunction: false, + isInParenthesis: false, }; } + // Determine if the current token is a conjunction (AND or OR) + const isInConjunction = [FilterQueryLexer.AND, FilterQueryLexer.OR].includes( + currentToken.type, + ); + + // Determine if the current token is a parenthesis + const isInParenthesis = [ + FilterQueryLexer.LPAREN, + FilterQueryLexer.RPAREN, + ].includes(currentToken.type); + // Determine the context based on the token type const isInValue = [ FilterQueryLexer.QUOTED_TEXT, @@ -322,6 +334,8 @@ export function getQueryContextAtCursor( isInKey, isInOperator, isInFunction, + isInConjunction, + isInParenthesis, }; } catch (error) { console.error('Error in getQueryContextAtCursor:', error); @@ -335,6 +349,8 @@ export function getQueryContextAtCursor( isInKey: false, isInOperator: false, isInFunction: false, + isInConjunction: false, + isInParenthesis: false, }; } }