feat: show errors

This commit is contained in:
Yunus M 2025-06-15 01:08:16 +05:30
parent af7f1def55
commit 2dae184976
3 changed files with 164 additions and 98 deletions

View File

@ -6,6 +6,46 @@
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif; 'Helvetica Neue', sans-serif;
.query-where-clause-editor-container {
display: flex;
flex-direction: row;
.query-where-clause-editor {
flex: 1;
min-width: 0;
}
.query-status-container {
width: 32px;
background-color: #121317 !important;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--bg-slate-200);
border-radius: 2px;
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
border-left: none !important;
&.hasErrors {
border-color: var(--bg-cherry-500);
}
}
}
.query-where-clause-editor {
&.hasErrors {
.cm-editor {
.cm-content {
border-color: var(--bg-cherry-500);
border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important;
}
}
}
}
.cm-editor { .cm-editor {
border-radius: 2px; border-radius: 2px;
overflow: hidden; overflow: hidden;
@ -381,9 +421,23 @@
background-color: rgba(235, 47, 150, 0.1); background-color: rgba(235, 47, 150, 0.1);
} }
} }
}
.query-text-preview-container { .query-status-popover {
display: none; .ant-popover-arrow {
display: none !important;
}
.ant-popover-content {
background: linear-gradient(
139deg,
rgba(18, 19, 23, 0.8) 0%,
rgba(18, 19, 23, 0.9) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
margin-top: -6px !important;
} }
} }

View File

@ -1,6 +1,6 @@
import './QuerySearch.styles.scss'; import './QuerySearch.styles.scss';
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import { CheckCircleFilled } from '@ant-design/icons';
import { import {
autocompletion, autocompletion,
closeCompletion, closeCompletion,
@ -10,15 +10,18 @@ import {
startCompletion, startCompletion,
} from '@codemirror/autocomplete'; } from '@codemirror/autocomplete';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot'; import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, { import CodeMirror, {
EditorView, EditorView,
Extension, Extension,
keymap, keymap,
} from '@uiw/react-codemirror'; } from '@uiw/react-codemirror';
import { Card, Collapse, Space, Tag, Typography } from 'antd'; import { Button, Card, Collapse, Popover, Tag } from 'antd';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion'; import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions'; import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
import { TriangleAlert } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { import {
IDetailedError, IDetailedError,
@ -31,7 +34,6 @@ import { getQueryContextAtCursor } from 'utils/queryContextUtils';
import { queryExamples } from './constants'; import { queryExamples } from './constants';
const { Text } = Typography;
const { Panel } = Collapse; const { Panel } = Collapse;
// Custom extension to stop events // Custom extension to stop events
@ -336,7 +338,14 @@ function QuerySearch(): JSX.Element {
const handleQueryChange = useCallback(async (newQuery: string) => { const handleQueryChange = useCallback(async (newQuery: string) => {
setQuery(newQuery); setQuery(newQuery);
}, []);
const handleChange = (value: string): void => {
setQuery(value);
handleQueryChange(value);
};
const handleQueryValidation = (newQuery: string): void => {
try { try {
const validationResponse = validateQuery(newQuery); const validationResponse = validateQuery(newQuery);
setValidation(validationResponse); setValidation(validationResponse);
@ -347,11 +356,14 @@ function QuerySearch(): JSX.Element {
errors: [error as IDetailedError], errors: [error as IDetailedError],
}); });
} }
}, []); };
const handleChange = (value: string): void => { const handleBlur = (): void => {
setQuery(value); handleQueryValidation(query);
handleQueryChange(value); setIsFocused(false);
if (editorRef.current) {
closeCompletion(editorRef.current);
}
}; };
const handleExampleClick = (exampleQuery: string): void => { const handleExampleClick = (exampleQuery: string): void => {
@ -854,86 +866,94 @@ function QuerySearch(): JSX.Element {
</div> </div>
)} )}
<CodeMirror <div className="query-where-clause-editor-container">
value={query} <CodeMirror
theme={copilot} value={query}
onChange={handleChange} theme={copilot}
onUpdate={handleUpdate} onChange={handleChange}
extensions={[ onUpdate={handleUpdate}
autocompletion({ className={cx('query-where-clause-editor', {
override: [autoSuggestions], isValid: validation.isValid === true,
defaultKeymap: true, hasErrors: validation.errors.length > 0,
closeOnBlur: true, })}
activateOnTyping: true, extensions={[
maxRenderedOptions: 50, autocompletion({
}), override: [autoSuggestions],
javascript({ jsx: false, typescript: false }), defaultKeymap: true,
EditorView.lineWrapping, closeOnBlur: true,
stopEventsExtension, activateOnTyping: true,
disallowMultipleSpaces, maxRenderedOptions: 50,
keymap.of([ }),
...completionKeymap, javascript({ jsx: false, typescript: false }),
{ EditorView.lineWrapping,
key: 'Escape', stopEventsExtension,
run: closeCompletion, disallowMultipleSpaces,
}, keymap.of([
]), ...completionKeymap,
]} {
placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')" key: 'Escape',
basicSetup={{ run: closeCompletion,
lineNumbers: false, },
}} ]),
onFocus={(): void => { ]}
setIsFocused(true); placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')"
if (editorRef.current) { basicSetup={{
startCompletion(editorRef.current); lineNumbers: false,
} }}
}} onFocus={(): void => {
onBlur={(): void => { setIsFocused(true);
setIsFocused(false); if (editorRef.current) {
if (editorRef.current) { startCompletion(editorRef.current);
closeCompletion(editorRef.current); }
} }}
}} onBlur={handleBlur}
/> />
{query && ( {query && validation.isValid === false && !isFocused && (
<div className="query-text-preview-container"> <div
<Space direction="vertical" size={4}> className={cx('query-status-container', {
<Text className="query-text-preview-title">searchExpr</Text> hasErrors: validation.errors.length > 0,
<Text className="query-text-preview">{query}</Text> })}
</Space> >
<Popover
<div className="query-validation"> placement="bottomRight"
<div className="query-validation-status"> showArrow={false}
<Text>Status:</Text> content={
<div className={validation.isValid ? 'valid' : 'invalid'}> <div className="query-status-content">
{validation.isValid ? ( <div className="query-status-content-header">
<Space> <div className="query-validation">
<CheckCircleFilled /> Valid <div className="query-validation-errors">
</Space> {validation.errors.map((error) => (
) : ( <div key={error.message} className="query-validation-error">
<Space> <div className="query-validation-error">
<CloseCircleFilled /> Invalid {error.line}:{error.column} - {error.message}
</Space> </div>
)} </div>
</div> ))}
</div> </div>
</div>
<div className="query-validation-errors">
{validation.errors.map((error) => (
<div key={error.message} className="query-validation-error">
<div className="query-validation-error-line">
{error.line}:{error.column}
</div> </div>
<div className="query-validation-error-message">{error.message}</div>
</div> </div>
))} }
</div> overlayClassName="query-status-popover"
>
{validation.isValid ? (
<Button
type="text"
icon={<CheckCircleFilled />}
className="periscope-btn ghost"
/>
) : (
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
/>
)}
</Popover>
</div> </div>
</div> )}
)} </div>
{showExamples && ( {showExamples && (
<Card size="small" className="query-examples-card"> <Card size="small" className="query-examples-card">
@ -976,7 +996,7 @@ function QuerySearch(): JSX.Element {
</Card> </Card>
)} )}
{queryContext && ( {/* {queryContext && (
<Card size="small" title="Current Context" className="query-context"> <Card size="small" title="Current Context" className="query-context">
<div className="context-details"> <div className="context-details">
<Space direction="vertical" size={4}> <Space direction="vertical" size={4}>
@ -1016,7 +1036,7 @@ function QuerySearch(): JSX.Element {
</Space> </Space>
</div> </div>
</Card> </Card>
)} )} */}
</div> </div>
); );
} }

View File

@ -119,17 +119,9 @@ export const validateQuery = (query: string): IValidationResult => {
// Empty query is considered invalid // Empty query is considered invalid
if (!query.trim()) { if (!query.trim()) {
return { return {
isValid: false, isValid: true,
message: 'Query cannot be empty', message: 'Query is empty',
errors: [ errors: [],
{
message: 'Query cannot be empty',
line: 0,
column: 0,
offendingSymbol: '',
expectedTokens: [],
},
],
}; };
} }