From 2520718afbf96efe0e5e711072171cf20a148f79 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 28 Apr 2025 18:40:53 +0530 Subject: [PATCH] feat: use new qb in logs explorer --- .../CodeMirrorWhereClause.styles.scss | 156 +++++++- .../CodeMirrorWhereClause.tsx | 337 +++++++++++------- frontend/src/container/Home/Home2.tsx | 11 - .../LogExplorerQuerySection/index.tsx | 3 + .../ToolbarActions/LeftToolbarActions.tsx | 20 +- frontend/src/pages/HomePage/HomePage.tsx | 4 +- frontend/src/pages/LogsExplorer/utils.tsx | 1 + 7 files changed, 370 insertions(+), 162 deletions(-) delete mode 100644 frontend/src/container/Home/Home2.tsx diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss index f4d6e141d833..331c03e7abce 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.styles.scss @@ -4,7 +4,7 @@ display: flex; flex-direction: column; padding: 16px; - gap: 16px; + gap: 8px; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; @@ -42,10 +42,12 @@ border-radius: 4px; border: 1px solid var(--bg-slate-200, #1d212d); + font-family: 'Space Mono', monospace !important; ul { width: 100% !important; max-width: 100% !important; + font-family: 'Space Mono', monospace !important; &::-webkit-scrollbar { width: 0.3rem; @@ -71,6 +73,8 @@ align-items: center !important; gap: 8px !important; + font-family: 'Space Mono', monospace !important; + .cm-completionIcon { display: none !important; } @@ -87,9 +91,9 @@ } .cm-line { - line-height: 1.8 !important; + line-height: 24px !important; background: var(--bg-ink-300) !important; - + font-family: 'Space Mono', monospace !important; ::-moz-selection { background: var(--bg-ink-100) !important; opacity: 0.5 !important; @@ -124,6 +128,7 @@ flex-direction: column; gap: 8px; margin-bottom: 8px; + margin-top: 16px; .valid, .invalid { @@ -196,22 +201,109 @@ } } - .query-examples { - padding: 12px; - background-color: var(--bg-vanilla-100); - border: 1px solid var(--bg-vanilla-300); - border-radius: 4px; + .code-mirror-card { + .ant-card-body { + padding: 8px; + } + } - ul { - margin-top: 8px; - margin-bottom: 0; - padding-left: 16px; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 8px; + .query-text-preview-title { + font-size: 13px; + color: var(--bg-vanilla-100); + background-color: var(--bg-robin-500); + padding: 2px 6px; + border-radius: 2px; + margin-right: 4px; + } - li { - margin-bottom: 4px; + .query-text-preview { + font-family: 'Space Mono', monospace; + font-size: 13px; + color: var(--bg-vanilla-200); + padding: 2px 6px; + font-style: italic; + } + + .query-examples-card { + background-color: var(--bg-ink-400); + border: 1px solid var(--bg-slate-200); + + .ant-card-body { + padding: 0; + } + + .query-examples { + .ant-collapse-header { + padding: 8px 16px !important; + color: var(--bg-vanilla-300) !important; + font-weight: 500; + } + + .ant-collapse-content { + background-color: transparent !important; + } + + .query-examples-list { + display: flex; + flex-direction: row; + gap: 8px; + flex-wrap: wrap; + } + + .query-example-tag { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px 12px; + background-color: var(--bg-ink-400); + border: 1px solid var(--bg-slate-200); + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; + outline: none; + + &:hover { + background-color: var(--bg-ink-300); + border-color: var(--bg-robin-500); + } + + &:focus-visible { + outline: 2px solid var(--bg-robin-500); + outline-offset: 2px; + } + + .query-example-content { + display: flex; + align-items: center; + gap: 8px; + } + + .query-example-label { + font-weight: 500; + color: var(--bg-vanilla-300); + font-size: 13px; + } + + .query-example-query { + font-family: 'Space Mono', monospace; + font-size: 12px; + color: var(--bg-vanilla-200); + background-color: var(--bg-ink-300); + padding: 2px 6px; + border-radius: 2px; + } + + .query-example-description { + font-size: 12px; + color: var(--bg-vanilla-200); + opacity: 0.8; + } + } + + .query-example-content { + display: inline-flex; + + cursor: pointer; } } } @@ -247,10 +339,36 @@ } } - .query-examples { + .query-examples-card { background-color: var(--bg-ink-400); border-color: var(--bg-slate-500); - color: var(--bg-vanilla-100); + + .ant-collapse-header { + color: var(--bg-vanilla-100) !important; + } + + .query-example-tag { + background-color: var(--bg-ink-400); + border-color: var(--bg-slate-500); + + &:hover { + background-color: var(--bg-ink-300); + border-color: var(--bg-robin-500); + } + + .query-example-label { + color: var(--bg-vanilla-100); + } + + .query-example-query { + color: var(--bg-vanilla-100); + background-color: var(--bg-ink-300); + } + + .query-example-description { + color: var(--bg-vanilla-100); + } + } } } } diff --git a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx index 1ca10354805b..7a748b9afc9f 100644 --- a/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx +++ b/frontend/src/components/QueryBuilderV2/CodeMirrorWhereClause/CodeMirrorWhereClause.tsx @@ -14,8 +14,8 @@ import { } from '@codemirror/autocomplete'; 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, Typography } from 'antd'; +import CodeMirror, { EditorView } from '@uiw/react-codemirror'; +import { Card, Collapse, Space, Typography } from 'antd'; import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion'; import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -32,58 +32,100 @@ import { } from 'utils/antlrQueryUtils'; const { Text } = Typography; +const { Panel } = Collapse; -function collapseSpacesOutsideStrings(): Extension { - return EditorView.inputHandler.of((view, from, to, text) => { - // Get the current line text - const { state } = view; - const line = state.doc.lineAt(from); - - // Find the position within the line - const before = line.text.slice(0, from - line.from); - const after = line.text.slice(to - line.from); - - const fullText = before + text + after; - - let insideString = false; - let escaped = false; - let processed = ''; - - for (let i = 0; i < fullText.length; i++) { - const char = fullText[i]; - - if (char === '"' && !escaped) { - insideString = !insideString; - } - if (char === '\\' && !escaped) { - escaped = true; - } else { - escaped = false; - } - - if (!insideString && char === ' ' && processed.endsWith(' ')) { - // Collapse multiple spaces outside strings - // Skip this space - } else { - processed += char; - } - } - - // Only dispatch if the processed text differs - if (processed !== fullText) { - view.dispatch({ - changes: { - from: line.from, - to: line.to, - insert: processed, - }, - }); - return true; - } +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 +const stopEventsExtension = EditorView.domEventHandlers({ + keydown: (event) => { + event.stopPropagation(); + // Optionally: event.preventDefault(); + return false; // Important for CM to know you handled it + }, + input: (event) => { + event.stopPropagation(); return false; - }); -} + }, +}); function CodeMirrorWhereClause(): JSX.Element { const [query, setQuery] = useState(''); @@ -328,35 +370,42 @@ function CodeMirrorWhereClause(): JSX.Element { 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 ; + const handleExampleClick = (exampleQuery: string): void => { + // If there's an existing query, append the example with AND + const newQuery = query ? `${query} AND ${exampleQuery}` : exampleQuery; + setQuery(newQuery); + handleQueryChange(newQuery); }; + // 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 ; + // }; + function myCompletions(context: CompletionContext): CompletionResult | null { const word = context.matchBefore(/\w*/); if (word?.from === word?.to && !context.explicit) return null; @@ -552,7 +601,7 @@ function CodeMirrorWhereClause(): JSX.Element { return (
- + - - {query && ( - <> - - - Query: - {query} - - - )} - - {query && ( - <> - - -
-
- Status: -
- {validation.isValid ? ( - - Valid - - ) : ( - - Invalid - - )} -
-
- -
- {validation.errors.map((error) => ( -
-
- {error.line}:{error.column} -
- -
{error.message}
-
- ))} -
-
- - )}
- {queryContext && ( + {query && ( + + + searchExpr + {query} + + +
+
+ Status: +
+ {validation.isValid ? ( + + Valid + + ) : ( + + Invalid + + )} +
+
+ +
+ {validation.errors.map((error) => ( +
+
+ {error.line}:{error.column} +
+ +
{error.message}
+
+ ))} +
+
+
+ )} + + + + +
+ {queryExamples.map((example) => ( +
handleExampleClick(example.query)} + role="button" + tabIndex={0} + onKeyDown={(e): void => { + if (e.key === 'Enter' || e.key === ' ') { + handleExampleClick(example.query); + } + }} + > + +
+ ))} +
+
+
+
+ + {/* {queryContext && (
@@ -640,7 +722,6 @@ function CodeMirrorWhereClause(): JSX.Element { {renderContextBadge()} - {/* Display the key-operator-value triplet when available */} {queryContext.keyToken && ( Key: @@ -664,7 +745,7 @@ function CodeMirrorWhereClause(): JSX.Element {
- )} + )} */}
); } diff --git a/frontend/src/container/Home/Home2.tsx b/frontend/src/container/Home/Home2.tsx deleted file mode 100644 index cc76425678d4..000000000000 --- a/frontend/src/container/Home/Home2.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2'; - -function Home2(): JSX.Element { - return ( -
- -
- ); -} - -export default Home2; diff --git a/frontend/src/container/LogExplorerQuerySection/index.tsx b/frontend/src/container/LogExplorerQuerySection/index.tsx index c49990861f0c..7ccd9f9e6e5e 100644 --- a/frontend/src/container/LogExplorerQuerySection/index.tsx +++ b/frontend/src/container/LogExplorerQuerySection/index.tsx @@ -1,5 +1,6 @@ import './LogsExplorerQuerySection.styles.scss'; +import QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2'; import { initialQueriesMap, OPERATORS, @@ -107,6 +108,8 @@ function LogExplorerQuerySection({ version="v3" // setting this to v3 as we this is rendered in logs explorer /> )} + + {selectedView === SELECTED_VIEWS.QUERY_BUILDER_V2 && } ); } diff --git a/frontend/src/container/QueryBuilder/components/ToolbarActions/LeftToolbarActions.tsx b/frontend/src/container/QueryBuilder/components/ToolbarActions/LeftToolbarActions.tsx index 691f53da589b..bb9a75f8fd2c 100644 --- a/frontend/src/container/QueryBuilder/components/ToolbarActions/LeftToolbarActions.tsx +++ b/frontend/src/container/QueryBuilder/components/ToolbarActions/LeftToolbarActions.tsx @@ -3,7 +3,7 @@ import './ToolbarActions.styles.scss'; import { FilterOutlined } from '@ant-design/icons'; import { Button, Switch, Tooltip, Typography } from 'antd'; import cx from 'classnames'; -import { Atom, SquareMousePointer, Terminal } from 'lucide-react'; +import { Atom, Binoculars, SquareMousePointer, Terminal } from 'lucide-react'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; interface LeftToolbarActionsProps { @@ -19,7 +19,7 @@ interface LeftToolbarActionsProps { const activeTab = 'active-tab'; const actionBtn = 'action-btn'; export const queryBuilder = 'query-builder'; - +export const queryBuilderV2 = 'query-builder-v2'; export default function LeftToolbarActions({ items, selectedView, @@ -81,6 +81,22 @@ export default function LeftToolbarActions({ )} + + + +
diff --git a/frontend/src/pages/HomePage/HomePage.tsx b/frontend/src/pages/HomePage/HomePage.tsx index 0dcf58fbbf87..2d2e9bb1c773 100644 --- a/frontend/src/pages/HomePage/HomePage.tsx +++ b/frontend/src/pages/HomePage/HomePage.tsx @@ -1,7 +1,7 @@ -import Home2 from 'container/Home/Home2'; +import Home from 'container/Home/Home'; function HomePage(): JSX.Element { - return ; + return ; } export default HomePage; diff --git a/frontend/src/pages/LogsExplorer/utils.tsx b/frontend/src/pages/LogsExplorer/utils.tsx index f140f495b4ba..31c551a02c60 100644 --- a/frontend/src/pages/LogsExplorer/utils.tsx +++ b/frontend/src/pages/LogsExplorer/utils.tsx @@ -21,6 +21,7 @@ export enum SELECTED_VIEWS { SEARCH = 'search', QUERY_BUILDER = 'query-builder', CLICKHOUSE = 'clickhouse', + QUERY_BUILDER_V2 = 'query-builder-v2', } export const LogsQuickFiltersConfig: IQuickFiltersConfig[] = [