feat: update context to recognise conjunction operator

This commit is contained in:
Yunus M 2025-04-27 15:34:55 +05:30 committed by ahrefabhi
parent 2c193747d3
commit 711af444dc
4 changed files with 298 additions and 54 deletions

View File

@ -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);
}
}
}

View File

@ -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<string>('');
@ -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 (
<Badge
color={color}
text={text}
style={{
color: 'black',
}}
/>
);
};
return (
<div className="code-mirror-where-clause">
<CodeMirror
value={query}
onChange={handleChange}
onUpdate={handleUpdate}
placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')"
/>
<Card
size="small"
title={<Title level={5}>Where Clause</Title>}
extra={
<Tooltip title="Write a query to filter your data">
<QuestionCircleOutlined />
</Tooltip>
}
>
<CodeMirror
value={query}
theme="dark"
onChange={handleChange}
onUpdate={handleUpdate}
placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')"
/>
<div className="cursor-position">
Cursor at Line: {cursorPos.line}, Ch: {cursorPos.ch}
</div>
<Space className="cursor-position" size={4}>
<InfoCircleOutlined />
<Text style={{ color: 'black' }}>
Line: {cursorPos.line}, Position: {cursorPos.ch}
</Text>
</Space>
<Divider style={{ margin: '8px 0' }} />
<div className="query-validation">
<Text>Status:</Text>
<div className={validation.isValid ? 'valid' : 'invalid'}>
{validation.isValid ? (
<>
<CheckCircleFilled /> Valid
</>
) : (
<>
<CloseCircleFilled /> Invalid
</>
)}
</div>
{validation.message && (
<Tooltip title={validation.message}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
)}
</div>
</Card>
{queryContext && (
<div className="query-context">
<h3>Current Context</h3>
<Card size="small" title="Current Context" className="query-context">
<div className="context-details">
<p>
<strong>Token:</strong> {queryContext.currentToken}
</p>
<p>
<strong>Type:</strong> {queryContext.tokenType}
</p>
<p>
<strong>Context:</strong>{' '}
{queryContext.isInValue
? 'Value'
: queryContext.isInKey
? 'Key'
: queryContext.isInOperator
? 'Operator'
: queryContext.isInFunction
? 'Function'
: 'Unknown'}
</p>
<Space direction="vertical" size={4}>
<Space>
<Text strong style={{ color: 'black' }}>
Token:
</Text>
<Text code style={{ color: 'black' }}>
{queryContext.currentToken || '-'}
</Text>
</Space>
<Space>
<Text strong style={{ color: 'black' }}>
Type:
</Text>
<Text style={{ color: 'black' }}>{queryContext.tokenType || '-'}</Text>
</Space>
<Space>
<Text strong style={{ color: 'black' }}>
Context:
</Text>
{renderContextBadge()}
</Space>
</Space>
</div>
</div>
</Card>
)}
<div className="query-examples">
<Text type="secondary">Examples:</Text>
<Card
size="small"
title="Query Examples"
className="query-examples"
style={{
backgroundColor: 'var(--bg-vanilla-100)',
color: 'black',
}}
>
<div className="query-examples-list">Query Examples</div>
<ul>
<li>
<Text code>status = &apos;error&apos;</Text>
<Text code style={{ color: 'black' }}>
status = &apos;error&apos;
</Text>
</li>
<li>
<Text code>
<Text code style={{ color: 'black' }}>
service = &apos;frontend&apos; AND level = &apos;error&apos;
</Text>
</li>
<li>
<Text code>message LIKE &apos;%timeout%&apos;</Text>
<Text code style={{ color: 'black' }}>
message LIKE &apos;%timeout%&apos;
</Text>
</li>
<li>
<Text code>duration {'>'} 1000</Text>
<Text code style={{ color: 'black' }}>
duration {'>'} 1000
</Text>
</li>
<li>
<Text code>tags IN [&apos;prod&apos;, &apos;frontend&apos;]</Text>
<Text code style={{ color: 'black' }}>
tags IN [&apos;prod&apos;, &apos;frontend&apos;]
</Text>
</li>
<li>
<Text code>
<Text code style={{ color: 'black' }}>
NOT (status = &apos;error&apos; OR level = &apos;error&apos;)
</Text>
</li>
</ul>
</div>
</Card>
</div>
);
}

View File

@ -22,6 +22,8 @@ export interface IQueryContext {
isInKey: boolean;
isInOperator: boolean;
isInFunction: boolean;
isInConjunction?: boolean;
isInParenthesis?: boolean;
}
export interface IDetailedError {

View File

@ -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,
};
}
}