feat: query search component clean up

This commit is contained in:
Yunus M 2025-06-14 21:46:08 +05:30
parent fbbbd9883a
commit cf247b98c8
3 changed files with 307 additions and 339 deletions

View File

@ -0,0 +1,154 @@
# QuerySearch Component Documentation
## Overview
The QuerySearch component is a sophisticated query builder interface that allows users to construct complex search queries with real-time validation and autocomplete functionality.
## Dependencies
```typescript
// Core UI
import { Card, Collapse, Space, Tag, Typography } from 'antd';
// Code Editor
import {
autocompletion,
CompletionContext,
CompletionResult,
startCompletion,
} from '@codemirror/autocomplete';
import { javascript } from '@codemirror/lang-javascript';
import { ViewPlugin, ViewUpdate } from '@codemirror/view';
import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, { EditorView, Extension } from '@uiw/react-codemirror';
// Custom Hooks and Utilities
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import { queryOperatorSuggestions, validateQuery } from 'utils/antlrQueryUtils';
import { getQueryContextAtCursor } from 'utils/queryContextUtils';
```
## Key Features
1. Real-time query validation
2. Context-aware autocompletion
3. Support for various query operators (=, !=, IN, LIKE, etc.)
4. Support for complex conditions with AND/OR operators
5. Support for functions (HAS, HASANY, HASALL, HASNONE)
6. Support for parentheses and nested conditions
7. Query examples for common use cases
## State Management
```typescript
const [query, setQuery] = useState<string>('');
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
const [activeKey, setActiveKey] = useState<string>('');
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
const [queryContext, setQueryContext] = useState<IQueryContext | null>(null);
const [validation, setValidation] = useState<IValidationResult>({...});
const [editingMode, setEditingMode] = useState<'key' | 'operator' | 'value' | 'conjunction' | 'function' | 'parenthesis' | 'bracketList' | null>(null);
```
## Core Functions
### 1. Autocomplete Handler
```typescript
function myCompletions(context: CompletionContext): CompletionResult | null {
// Handles autocomplete suggestions based on context
// Supports different contexts: key, operator, value, function, etc.
}
```
### 2. Value Suggestions Fetcher
```typescript
const fetchValueSuggestions = useCallback(
async (key: string): Promise<void> => {
// Fetches value suggestions for a given key
// Handles loading states and error cases
},
[activeKey, isLoadingSuggestions],
);
```
### 3. Query Change Handler
```typescript
const handleQueryChange = useCallback(async (newQuery: string) => {
// Updates query and validates it
// Handles validation errors
}, []);
```
## Query Context Types
1. Key context: When editing a field name
2. Operator context: When selecting an operator
3. Value context: When entering a value
4. Conjunction context: When using AND/OR
5. Function context: When using functions
6. Parenthesis context: When using parentheses
7. Bracket list context: When using IN operator
## Example Queries
```typescript
const queryExamples = [
{ label: 'Basic Query', query: "status = 'error'" },
{ label: 'Multiple Conditions', query: "status = 'error' AND service = 'frontend'" },
{ label: 'IN Operator', query: "status IN ['error', 'warning']" },
{ label: 'Function Usage', query: "HAS(service, 'frontend')" },
{ label: 'Numeric Comparison', query: 'duration > 1000' },
// ... more examples
];
```
## Performance Optimizations
1. Uses `useCallback` for memoized functions
2. Tracks component mount state to prevent updates after unmount
3. Debounces suggestion fetching
4. Caches key suggestions
## Error Handling
```typescript
try {
const validationResponse = validateQuery(newQuery);
setValidation(validationResponse);
} catch (error) {
setValidation({
isValid: false,
message: 'Failed to process query',
errors: [error as IDetailedError],
});
}
```
## Usage Example
```typescript
<QuerySearch />
```
## Styling
- Uses SCSS for styling
- Custom classes for different components
- Theme integration with CodeMirror
## Best Practices
1. Always validate queries before submission
2. Handle loading states appropriately
3. Provide clear error messages
4. Use appropriate operators for different data types
5. Consider performance implications of complex queries
## Common Issues and Solutions
1. Query validation errors
- Check syntax and operator usage
- Verify data types match operator requirements
2. Performance issues
- Optimize suggestion fetching
- Cache frequently used values
3. UI/UX issues
- Ensure clear error messages
- Provide helpful suggestions
- Show appropriate loading states
## Future Improvements
1. Add more query examples
2. Enhance error messages
3. Improve performance for large datasets
4. Add more operator support
5. Enhance UI/UX features

View File

@ -1,10 +1,3 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable max-classes-per-file */
/* eslint-disable sonarjs/no-collapsible-if */
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-nested-ternary */
import './QuerySearch.styles.scss'; import './QuerySearch.styles.scss';
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
@ -15,7 +8,6 @@ import {
startCompletion, startCompletion,
} from '@codemirror/autocomplete'; } from '@codemirror/autocomplete';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { ViewPlugin, ViewUpdate } from '@codemirror/view';
import { copilot } from '@uiw/codemirror-theme-copilot'; import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, { EditorView, Extension } from '@uiw/react-codemirror'; import CodeMirror, { EditorView, Extension } from '@uiw/react-codemirror';
import { Card, Collapse, Space, Tag, Typography } from 'antd'; import { Card, Collapse, Space, Tag, Typography } from 'antd';
@ -31,89 +23,11 @@ import { QueryKeySuggestionsProps } from 'types/api/querySuggestions/types';
import { queryOperatorSuggestions, validateQuery } from 'utils/antlrQueryUtils'; import { queryOperatorSuggestions, validateQuery } from 'utils/antlrQueryUtils';
import { getQueryContextAtCursor } from 'utils/queryContextUtils'; import { getQueryContextAtCursor } from 'utils/queryContextUtils';
import { queryExamples } from './constants';
const { Text } = Typography; const { Text } = Typography;
const { Panel } = Collapse; const { Panel } = Collapse;
const queryExamples = [
{
label: 'Basic Query',
query: "status = 'error'",
description: 'Find all errors',
},
{
label: 'Multiple Conditions',
query: "status = 'error' AND service = 'frontend'",
description: 'Find errors from frontend service',
},
{
label: 'IN Operator',
query: "status IN ['error', 'warning']",
description: 'Find items with specific statuses',
},
{
label: 'Function Usage',
query: "HAS(service, 'frontend')",
description: 'Use HAS function',
},
{
label: 'Numeric Comparison',
query: 'duration > 1000',
description: 'Find items with duration greater than 1000ms',
},
{
label: 'Range Query',
query: 'duration BETWEEN 100 AND 1000',
description: 'Find items with duration between 100ms and 1000ms',
},
{
label: 'Pattern Matching',
query: "service LIKE 'front%'",
description: 'Find services starting with "front"',
},
{
label: 'Complex Conditions',
query: "(status = 'error' OR status = 'warning') AND service = 'frontend'",
description: 'Find errors or warnings from frontend service',
},
{
label: 'Multiple Functions',
query: "HAS(service, 'frontend') AND HAS(status, 'error')",
description: 'Use multiple HAS functions',
},
{
label: 'NOT Operator',
query: "NOT status = 'success'",
description: 'Find items that are not successful',
},
{
label: 'Array Contains',
query: "tags CONTAINS 'production'",
description: 'Find items with production tag',
},
{
label: 'Regex Pattern',
query: "service REGEXP '^prod-.*'",
description: 'Find services matching regex pattern',
},
{
label: 'Null Check',
query: 'error IS NULL',
description: 'Find items without errors',
},
{
label: 'Multiple Attributes',
query:
"service = 'frontend' AND environment = 'production' AND status = 'error'",
description: 'Find production frontend errors',
},
{
label: 'Nested Conditions',
query:
"(service = 'frontend' OR service = 'backend') AND (status = 'error' OR status = 'warning')",
description: 'Find errors or warnings from frontend or backend',
},
];
// Custom extension to stop events // Custom extension to stop events
const stopEventsExtension = EditorView.domEventHandlers({ const stopEventsExtension = EditorView.domEventHandlers({
keydown: (event) => { keydown: (event) => {
@ -127,29 +41,6 @@ const stopEventsExtension = EditorView.domEventHandlers({
}, },
}); });
// Custom extension to analyze the context at the cursor position
const contextAwarePlugin = (
analyzeContext: (view: EditorView, pos: number) => void,
): ViewPlugin<{ update: (update: ViewUpdate) => void }> =>
ViewPlugin.fromClass(
class {
constructor(view: EditorView) {
this.analyze(view);
}
update(update: ViewUpdate): void {
if (update.selectionSet && !update.docChanged) {
this.analyze(update.view);
}
}
analyze(view: EditorView): void {
const pos = view.state.selection.main.head;
analyzeContext(view, pos);
}
},
);
const disallowMultipleSpaces: Extension = EditorView.inputHandler.of( const disallowMultipleSpaces: Extension = EditorView.inputHandler.of(
(view, from, to, text) => { (view, from, to, text) => {
const currentLine = view.state.doc.lineAt(from); const currentLine = view.state.doc.lineAt(from);
@ -162,6 +53,7 @@ const disallowMultipleSpaces: Extension = EditorView.inputHandler.of(
}, },
); );
// eslint-disable-next-line sonarjs/cognitive-complexity
function QuerySearch(): JSX.Element { function QuerySearch(): JSX.Element {
const [query, setQuery] = useState<string>(''); const [query, setQuery] = useState<string>('');
const [valueSuggestions, setValueSuggestions] = useState<any[]>([ const [valueSuggestions, setValueSuggestions] = useState<any[]>([
@ -257,92 +149,6 @@ function QuerySearch(): JSX.Element {
return value; return value;
}; };
const analyzeContext = useCallback((view: EditorView, pos: number): void => {
// Skip if component unmounted
if (!isMountedRef.current) return;
const doc = view.state.doc.toString();
// Check for spaces around the cursor position for debugging
const isCursorAtSpace = pos < doc.length && doc[pos] === ' ';
const isCursorAfterSpace = pos > 0 && doc[pos - 1] === ' ';
const isCursorAfterToken =
pos > 0 && doc[pos - 1] !== ' ' && doc[pos - 1] !== undefined;
const isCursorBeforeToken =
pos < doc.length && doc[pos] !== ' ' && doc[pos] !== undefined;
// Check brackets around cursor
const isCursorAtOpenBracket =
pos < doc.length && (doc[pos] === '[' || doc[pos] === '(');
const isCursorAfterOpenBracket =
pos > 0 && (doc[pos - 1] === '[' || doc[pos - 1] === '(');
const isCursorAtCloseBracket =
pos < doc.length && (doc[pos] === ']' || doc[pos] === ')');
const isCursorAfterCloseBracket =
pos > 0 && (doc[pos - 1] === ']' || doc[pos - 1] === ')');
// Check if cursor is at transition point (right after a token at the beginning of a space)
const isTransitionPoint = isCursorAtSpace && isCursorAfterToken;
// Get a slice of the text around cursor for context
const sliceStart = Math.max(0, pos - 10);
const sliceEnd = Math.min(doc.length, pos + 10);
const textSlice = doc.substring(sliceStart, sliceEnd);
const cursorPosInSlice = pos - sliceStart;
// Create a visual cursor indicator
const beforeCursor = textSlice.substring(0, cursorPosInSlice);
const afterCursor = textSlice.substring(cursorPosInSlice);
const visualCursor = `${beforeCursor}|${afterCursor}`;
const context = getQueryContextAtCursor(doc, pos);
// Enhanced debug logging with space and pair detection
console.log('Context at cursor:', {
position: pos,
visualCursor,
cursorAtSpace: isCursorAtSpace,
cursorAfterSpace: isCursorAfterSpace,
cursorAfterToken: isCursorAfterToken,
cursorBeforeToken: isCursorBeforeToken,
isTransitionPoint,
bracketInfo: {
cursorAtOpenBracket: isCursorAtOpenBracket,
cursorAfterOpenBracket: isCursorAfterOpenBracket,
cursorAtCloseBracket: isCursorAtCloseBracket,
cursorAfterCloseBracket: isCursorAfterCloseBracket,
isInBracketList: context.isInBracketList,
},
contextType: context.isInKey
? 'Key'
: context.isInOperator
? 'Operator'
: context.isInValue
? 'Value'
: context.isInConjunction
? 'Conjunction'
: context.isInFunction
? 'Function'
: context.isInParenthesis
? 'Parenthesis'
: context.isInBracketList
? 'BracketList'
: 'Unknown',
keyToken: context.keyToken,
operatorToken: context.operatorToken,
valueToken: context.valueToken,
queryPairs: context.queryPairs?.length || 0,
currentPair: context.currentPair
? {
key: context.currentPair.key,
operator: context.currentPair.operator,
value: context.currentPair.value,
isComplete: context.currentPair.isComplete,
}
: null,
});
}, []);
// Add cleanup effect to prevent component updates after unmount // Add cleanup effect to prevent component updates after unmount
useEffect( useEffect(
(): (() => void) => (): void => { (): (() => void) => (): void => {
@ -354,6 +160,7 @@ function QuerySearch(): JSX.Element {
// Use callback to prevent dependency changes on each render // Use callback to prevent dependency changes on each render
const fetchValueSuggestions = useCallback( const fetchValueSuggestions = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
async (key: string): Promise<void> => { async (key: string): Promise<void> => {
if ( if (
!key || !key ||
@ -367,15 +174,12 @@ function QuerySearch(): JSX.Element {
lastKeyRef.current = key; lastKeyRef.current = key;
setActiveKey(key); setActiveKey(key);
console.log('fetching suggestions for key:', key);
// Replace current suggestions with loading indicator
setValueSuggestions([ setValueSuggestions([
{ {
label: 'Loading suggestions...', label: 'Loading suggestions...',
type: 'text', type: 'text',
boost: -99, // Lower boost to appear at the bottom boost: -99,
apply: (): boolean => false, // Prevent selection apply: (): boolean => false,
}, },
]); ]);
@ -428,7 +232,6 @@ function QuerySearch(): JSX.Element {
index === self.findIndex((o) => o.label === option.label), index === self.findIndex((o) => o.label === option.label),
); );
// Only if we're still on the same key
if (lastKeyRef.current === key && isMountedRef.current) { if (lastKeyRef.current === key && isMountedRef.current) {
if (allOptions.length > 0) { if (allOptions.length > 0) {
setValueSuggestions(allOptions); setValueSuggestions(allOptions);
@ -437,8 +240,8 @@ function QuerySearch(): JSX.Element {
{ {
label: 'No suggestions available', label: 'No suggestions available',
type: 'text', type: 'text',
boost: -99, // Lower boost to appear at the bottom boost: -99,
apply: (): boolean => false, // Prevent selection apply: (): boolean => false,
}, },
]); ]);
} }
@ -471,13 +274,9 @@ function QuerySearch(): JSX.Element {
[activeKey, isLoadingSuggestions], [activeKey, isLoadingSuggestions],
); );
// Enhanced update handler to track context changes, including bracket contexts const handleUpdate = useCallback((viewUpdate: { view: EditorView }): void => {
const handleUpdate = useCallback(
(viewUpdate: { view: EditorView }): void => {
// Skip updates if component is unmounted
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
// Store editor reference
if (!editorRef.current) { if (!editorRef.current) {
editorRef.current = viewUpdate.view; editorRef.current = viewUpdate.view;
} }
@ -494,89 +293,30 @@ function QuerySearch(): JSX.Element {
const lastPos = lastPosRef.current; const lastPos = lastPosRef.current;
// Only update if cursor position actually changed
if (newPos.line !== lastPos.line || newPos.ch !== lastPos.ch) { if (newPos.line !== lastPos.line || newPos.ch !== lastPos.ch) {
setCursorPos(newPos); setCursorPos(newPos);
lastPosRef.current = newPos; lastPosRef.current = newPos;
// Detect if cursor is at a space or after a token
const isAtSpace = pos < doc.length && doc[pos] === ' ';
const isAfterToken =
pos > 0 && doc[pos - 1] !== ' ' && doc[pos - 1] !== undefined;
const isTransitionPoint = isAtSpace && isAfterToken;
// Detect brackets around cursor
const isAtOpenBracket =
pos < doc.length && (doc[pos] === '[' || doc[pos] === '(');
const isAfterOpenBracket =
pos > 0 && (doc[pos - 1] === '[' || doc[pos - 1] === '(');
const isAtCloseBracket =
pos < doc.length && (doc[pos] === ']' || doc[pos] === ')');
const isAfterCloseBracket =
pos > 0 && (doc[pos - 1] === ']' || doc[pos - 1] === ')');
// Get context immediately when cursor position changes
if (doc) { if (doc) {
const context = getQueryContextAtCursor(doc, pos); const context = getQueryContextAtCursor(doc, pos);
// Only update context and mode if they've actually changed let newContextType:
// This prevents unnecessary re-renders | 'key'
const previousContextType = queryContext?.isInKey | 'operator'
? 'key' | 'value'
: queryContext?.isInOperator | 'conjunction'
? 'operator' | 'function'
: queryContext?.isInValue | 'parenthesis'
? 'value' | 'bracketList'
: queryContext?.isInConjunction | null = null;
? 'conjunction'
: queryContext?.isInFunction
? 'function'
: queryContext?.isInParenthesis
? 'parenthesis'
: queryContext?.isInBracketList
? 'bracketList'
: null;
const newContextType = context.isInKey if (context.isInKey) newContextType = 'key';
? 'key' else if (context.isInOperator) newContextType = 'operator';
: context.isInOperator else if (context.isInValue) newContextType = 'value';
? 'operator' else if (context.isInConjunction) newContextType = 'conjunction';
: context.isInValue else if (context.isInFunction) newContextType = 'function';
? 'value' else if (context.isInParenthesis) newContextType = 'parenthesis';
: context.isInConjunction else if (context.isInBracketList) newContextType = 'bracketList';
? 'conjunction'
: context.isInFunction
? 'function'
: context.isInParenthesis
? 'parenthesis'
: context.isInBracketList
? 'bracketList'
: null;
// Log context changes for debugging
if (previousContextType !== newContextType) {
console.log(
`Context changed: ${previousContextType || 'none'} -> ${
newContextType || 'none'
}`,
{
position: pos,
isAtSpace,
isAfterToken,
isTransitionPoint,
bracketInfo: {
isAtOpenBracket,
isAfterOpenBracket,
isAtCloseBracket,
isAfterCloseBracket,
isInBracketList: context.isInBracketList,
},
keyToken: context.keyToken,
operatorToken: context.operatorToken,
valueToken: context.valueToken,
},
);
}
setQueryContext(context); setQueryContext(context);
@ -584,9 +324,7 @@ function QuerySearch(): JSX.Element {
setEditingMode(newContextType); setEditingMode(newContextType);
} }
} }
}, }, []);
[queryContext],
);
const handleQueryChange = useCallback(async (newQuery: string) => { const handleQueryChange = useCallback(async (newQuery: string) => {
setQuery(newQuery); setQuery(newQuery);
@ -640,7 +378,8 @@ function QuerySearch(): JSX.Element {
}; };
// Enhanced myCompletions function to better use context including query pairs // Enhanced myCompletions function to better use context including query pairs
function myCompletions(context: CompletionContext): CompletionResult | null { // eslint-disable-next-line sonarjs/cognitive-complexity
function autoSuggestions(context: CompletionContext): CompletionResult | null {
const word = context.matchBefore(/[.\w]*/); const word = context.matchBefore(/[.\w]*/);
if (word?.from === word?.to && !context.explicit) return null; if (word?.from === word?.to && !context.explicit) return null;
@ -667,13 +406,13 @@ function QuerySearch(): JSX.Element {
return null; return null;
} }
// Trigger fetch only if needed if (
if (keyName && (keyName !== activeKey || isLoadingSuggestions)) { keyName &&
// Don't trigger a new fetch if we're already loading for this key (keyName !== activeKey || isLoadingSuggestions) &&
if (!(isLoadingSuggestions && lastKeyRef.current === keyName)) { !(isLoadingSuggestions && lastKeyRef.current === keyName)
) {
fetchValueSuggestions(keyName); fetchValueSuggestions(keyName);
} }
}
// For values in bracket list, just add quotes without enclosing in brackets // For values in bracket list, just add quotes without enclosing in brackets
const processedOptions = valueSuggestions.map((option) => { const processedOptions = valueSuggestions.map((option) => {
@ -804,12 +543,13 @@ function QuerySearch(): JSX.Element {
} }
// Trigger fetch only if needed // Trigger fetch only if needed
if (keyName && (keyName !== activeKey || isLoadingSuggestions)) { if (
// Don't trigger a new fetch if we're already loading for this key keyName &&
if (!(isLoadingSuggestions && lastKeyRef.current === keyName)) { (keyName !== activeKey || isLoadingSuggestions) &&
!(isLoadingSuggestions && lastKeyRef.current === keyName)
) {
fetchValueSuggestions(keyName); fetchValueSuggestions(keyName);
} }
}
// Process options to add appropriate formatting when selected // Process options to add appropriate formatting when selected
const processedOptions = valueSuggestions.map((option) => { const processedOptions = valueSuggestions.map((option) => {
@ -967,9 +707,7 @@ function QuerySearch(): JSX.Element {
} }
}, [queryKeySuggestions]); }, [queryKeySuggestions]);
// Update state when query context changes to trigger suggestion refresh
useEffect(() => { useEffect(() => {
// Skip if we don't have a value context or it hasn't changed
if (!queryContext?.isInValue) return; if (!queryContext?.isInValue) return;
const { keyToken, currentToken } = queryContext; const { keyToken, currentToken } = queryContext;
@ -984,7 +722,6 @@ function QuerySearch(): JSX.Element {
return ( return (
<div className="code-mirror-where-clause"> <div className="code-mirror-where-clause">
{/* Add a context indicator banner */}
{editingMode && ( {editingMode && (
<div className={`context-indicator context-indicator-${editingMode}`}> <div className={`context-indicator context-indicator-${editingMode}`}>
Currently editing: {renderContextBadge()} Currently editing: {renderContextBadge()}
@ -1032,7 +769,7 @@ function QuerySearch(): JSX.Element {
placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')" placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')"
extensions={[ extensions={[
autocompletion({ autocompletion({
override: [myCompletions], override: [autoSuggestions],
defaultKeymap: true, defaultKeymap: true,
closeOnBlur: false, closeOnBlur: false,
activateOnTyping: true, activateOnTyping: true,
@ -1041,9 +778,7 @@ function QuerySearch(): JSX.Element {
javascript({ jsx: false, typescript: false }), javascript({ jsx: false, typescript: false }),
EditorView.lineWrapping, EditorView.lineWrapping,
stopEventsExtension, stopEventsExtension,
contextAwarePlugin(analyzeContext),
disallowMultipleSpaces, disallowMultipleSpaces,
// customTheme,
]} ]}
basicSetup={{ basicSetup={{
lineNumbers: false, lineNumbers: false,
@ -1129,7 +864,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}>
@ -1169,7 +904,7 @@ function QuerySearch(): JSX.Element {
</Space> </Space>
</div> </div>
</Card> </Card>
)} */} )}
</div> </div>
); );
} }

View File

@ -0,0 +1,79 @@
export const queryExamples = [
{
label: 'Basic Query',
query: "status = 'error'",
description: 'Find all errors',
},
{
label: 'Multiple Conditions',
query: "status = 'error' AND service = 'frontend'",
description: 'Find errors from frontend service',
},
{
label: 'IN Operator',
query: "status IN ['error', 'warning']",
description: 'Find items with specific statuses',
},
{
label: 'Function Usage',
query: "HAS(service, 'frontend')",
description: 'Use HAS function',
},
{
label: 'Numeric Comparison',
query: 'duration > 1000',
description: 'Find items with duration greater than 1000ms',
},
{
label: 'Range Query',
query: 'duration BETWEEN 100 AND 1000',
description: 'Find items with duration between 100ms and 1000ms',
},
{
label: 'Pattern Matching',
query: "service LIKE 'front%'",
description: 'Find services starting with "front"',
},
{
label: 'Complex Conditions',
query: "(status = 'error' OR status = 'warning') AND service = 'frontend'",
description: 'Find errors or warnings from frontend service',
},
{
label: 'Multiple Functions',
query: "HAS(service, 'frontend') AND HAS(status, 'error')",
description: 'Use multiple HAS functions',
},
{
label: 'NOT Operator',
query: "NOT status = 'success'",
description: 'Find items that are not successful',
},
{
label: 'Array Contains',
query: "tags CONTAINS 'production'",
description: 'Find items with production tag',
},
{
label: 'Regex Pattern',
query: "service REGEXP '^prod-.*'",
description: 'Find services matching regex pattern',
},
{
label: 'Null Check',
query: 'error IS NULL',
description: 'Find items without errors',
},
{
label: 'Multiple Attributes',
query:
"service = 'frontend' AND environment = 'production' AND status = 'error'",
description: 'Find production frontend errors',
},
{
label: 'Nested Conditions',
query:
"(service = 'frontend' OR service = 'backend') AND (status = 'error' OR status = 'warning')",
description: 'Find errors or warnings from frontend or backend',
},
];