Skip to content

Commit 78b9df9

Browse files
authored
Merge pull request #3947 from RedisInsight/fe/bugfix/RI-5681-search-and-query
#RI-6217 - fix suggestions
2 parents 50090f7 + ee3d1d4 commit 78b9df9

File tree

12 files changed

+203
-283
lines changed

12 files changed

+203
-283
lines changed

redisinsight/ui/src/pages/search/utils/query.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,8 @@ export const findArgByToken = (list: SearchCommand[], arg: string): Maybe<Search
457457
? cArg.arguments?.some((oneOfArg: SearchCommand) => oneOfArg?.token?.toLowerCase() === arg?.toLowerCase())
458458
: cArg.arguments?.[0]?.token?.toLowerCase() === arg.toLowerCase()))
459459

460-
export const isCompositeArgument = (arg: string, prevArg?: string) =>
461-
COMPOSITE_ARGS.includes([prevArg?.toUpperCase(), arg?.toUpperCase()].join(' '))
460+
export const isCompositeArgument = (arg: string, prevArg?: string, args: string[] = []) =>
461+
args.includes([prevArg?.toUpperCase(), arg?.toUpperCase()].join(' '))
462462

463463
export const generateDetail = (command: Maybe<SearchCommand>) => {
464464
if (!command) return ''

redisinsight/ui/src/pages/workbench/components/query/Query/Query.tsx

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@ import cx from 'classnames'
55
import MonacoEditor, { monaco as monacoEditor } from 'react-monaco-editor'
66
import { useParams } from 'react-router-dom'
77

8-
import {
9-
Theme,
10-
MonacoLanguage,
11-
DSLNaming,
12-
IRedisCommand,
13-
} from 'uiSrc/constants'
8+
import { DSLNaming, ICommandTokenType, IRedisCommand, MonacoLanguage, Theme, } from 'uiSrc/constants'
149
import {
1510
actionTriggerParameterHints,
1611
createSyntaxWidget,
1712
decoration,
18-
findArgIndexByCursor,
1913
findCompleteQuery,
2014
getMonacoAction,
2115
IMonacoQuery,
@@ -28,35 +22,26 @@ import { ThemeContext } from 'uiSrc/contexts/themeContext'
2822
import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands'
2923
import { IEditorMount, ISnippetController } from 'uiSrc/pages/workbench/interfaces'
3024
import { CommandExecutionUI, RedisResponseBuffer } from 'uiSrc/slices/interfaces'
31-
import { RunQueryMode, ResultsMode } from 'uiSrc/slices/interfaces/workbench'
25+
import { ResultsMode, RunQueryMode } from 'uiSrc/slices/interfaces/workbench'
3226
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
3327
import { stopProcessing, workbenchResultsSelector } from 'uiSrc/slices/workbench/wb-results'
3428
import DedicatedEditor from 'uiSrc/components/monaco-editor/components/dedicated-editor'
3529
import { QueryActions, QueryTutorials } from 'uiSrc/components/query'
3630

37-
import {
38-
addOwnTokenToArgs,
39-
} from 'uiSrc/pages/workbench/utils/query'
31+
import { addOwnTokenToArgs, findCurrentArgument, } from 'uiSrc/pages/workbench/utils/query'
4032
import { getRange, getRediSearchSignutureProvider, } from 'uiSrc/pages/workbench/utils/monaco'
4133
import { CursorContext } from 'uiSrc/pages/workbench/types'
42-
import {
43-
asSuggestionsRef,
44-
getCommandsSuggestions,
45-
isIndexComplete
46-
} from 'uiSrc/pages/workbench/utils/suggestions'
47-
import {
48-
COMMANDS_TO_GET_INDEX_INFO,
49-
EmptySuggestionsIds,
50-
} from 'uiSrc/pages/workbench/constants'
34+
import { asSuggestionsRef, getCommandsSuggestions, isIndexComplete } from 'uiSrc/pages/workbench/utils/suggestions'
35+
import { COMMANDS_TO_GET_INDEX_INFO, COMPOSITE_ARGS, EmptySuggestionsIds, } from 'uiSrc/pages/workbench/constants'
5136
import { useDebouncedEffect } from 'uiSrc/services'
5237
import { fetchRedisearchInfoAction } from 'uiSrc/slices/browser/redisearch'
5338
import { findSuggestionsByArg } from 'uiSrc/pages/workbench/utils/searchSuggestions'
5439
import {
55-
aroundQuotesRegExp,
5640
argInQuotesRegExp,
41+
aroundQuotesRegExp,
42+
options,
5743
SYNTAX_CONTEXT_ID,
5844
SYNTAX_WIDGET_ID,
59-
options,
6045
TUTORIALS
6146
} from './constants'
6247
import styles from './styles.module.scss'
@@ -343,7 +328,7 @@ const Query = (props: Props) => {
343328
return
344329
}
345330

346-
const command = findCompleteQuery(model, e.position, REDIS_COMMANDS_SPEC, REDIS_COMMANDS_ARRAY)
331+
const command = findCompleteQuery(model, e.position, REDIS_COMMANDS_SPEC, REDIS_COMMANDS_ARRAY, COMPOSITE_ARGS)
347332
handleSuggestions(editor, command)
348333
handleDslSyntax(e, command)
349334
}
@@ -427,33 +412,30 @@ const Query = (props: Props) => {
427412
return
428413
}
429414

430-
const queryArgIndex = command.info?.arguments?.findIndex((arg) => arg.dsl) || -1
431-
const cursorPosition = command.commandCursorPosition || 0
432-
const { allArgs } = command || {}
433-
if (!allArgs.length || queryArgIndex < 0) {
415+
const isContainsDSL = command.info?.arguments?.some((arg) => arg.dsl)
416+
if (!isContainsDSL) {
434417
isWidgetEscaped.current = false
435418
return
436419
}
437420

438-
const argIndex = findArgIndexByCursor(allArgs, command.fullQuery, cursorPosition)
439-
if (argIndex === null) {
440-
isWidgetEscaped.current = false
441-
return
442-
}
443-
444-
const queryArg = allArgs[argIndex]
445-
const argDSL = command.info?.arguments?.[argIndex]?.dsl || ''
421+
const [beforeOffsetArgs, [currentOffsetArg]] = command.args
422+
const foundArg = findCurrentArgument([{
423+
...command.info,
424+
type: ICommandTokenType.Block,
425+
token: command.name,
426+
arguments: command.info?.arguments
427+
}], beforeOffsetArgs)
446428

447-
if (queryArgIndex === argIndex && argInQuotesRegExp.test(queryArg)) {
429+
const DSL = foundArg?.stopArg?.dsl
430+
if (DSL && argInQuotesRegExp.test(currentOffsetArg)) {
448431
if (isWidgetEscaped.current) return
449-
const lang = DSLNaming[argDSL] ?? null
432+
433+
const lang = DSLNaming[DSL] ?? null
450434
lang && showSyntaxWidget(editor, e.position, lang)
451-
selectedArg.current = queryArg
452-
syntaxCommand.current = {
453-
...command,
454-
lang: argDSL,
455-
argToReplace: queryArg
456-
}
435+
selectedArg.current = currentOffsetArg
436+
syntaxCommand.current = { ...command, lang: DSL, argToReplace: currentOffsetArg }
437+
} else {
438+
isWidgetEscaped.current = false
457439
}
458440
}
459441

redisinsight/ui/src/pages/workbench/data/supported_commands.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -714,14 +714,10 @@
714714
"optional": true
715715
},
716716
{
717-
"name": "queryword",
718-
"type": "pure-token",
717+
"name": "query",
718+
"type": "token",
719719
"token": "QUERY",
720720
"expression": true
721-
},
722-
{
723-
"name": "query",
724-
"type": "string"
725721
}
726722
],
727723
"since": "2.2.0",

redisinsight/ui/src/pages/workbench/utils/query.ts

Lines changed: 10 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,10 @@
11
/* eslint-disable no-continue */
22

3-
import { isNumber, toNumber } from 'lodash'
3+
import { findLastIndex, isNumber, toNumber } from 'lodash'
44
import { generateArgsNames, Maybe, Nullable } from 'uiSrc/utils'
55
import { CommandProvider, IRedisCommand, IRedisCommandTree, ICommandTokenType } from 'uiSrc/constants'
6-
import { COMPOSITE_ARGS } from 'uiSrc/pages/workbench/constants'
76
import { ArgName, FoundCommandArgument } from '../types'
87

9-
export const splitQueryByArgs = (query: string, position: number = 0) => {
10-
const args: [string[], string[]] = [[], []]
11-
let arg = ''
12-
let inQuotes = false
13-
let escapeNextChar = false
14-
let quoteChar = ''
15-
let isCursorInQuotes = false
16-
let lastArg = ''
17-
let argLeftOffset = 0
18-
let argRightOffset = 0
19-
20-
const pushToProperTuple = (isAfterOffset: boolean, arg: string) => {
21-
lastArg = arg
22-
isAfterOffset ? args[1].push(arg) : args[0].push(arg)
23-
}
24-
25-
const updateLastArgument = (isAfterOffset: boolean, arg: string) => {
26-
const argsBySide = args[isAfterOffset ? 1 : 0]
27-
argsBySide[argsBySide.length - 1] = `${argsBySide[argsBySide.length - 1]} ${arg}`
28-
}
29-
30-
const updateArgOffsets = (left: number, right: number) => {
31-
argLeftOffset = left
32-
argRightOffset = right
33-
}
34-
35-
for (let i = 0; i < query.length; i++) {
36-
const char = query[i]
37-
const isAfterOffset = i >= position + (inQuotes ? -1 : 0)
38-
39-
if (escapeNextChar) {
40-
arg += char
41-
escapeNextChar = !quoteChar
42-
} else if (char === '\\') {
43-
escapeNextChar = true
44-
} else if (inQuotes) {
45-
if (char === quoteChar) {
46-
inQuotes = false
47-
const argWithChat = arg + char
48-
49-
if (isAfterOffset && !argLeftOffset) {
50-
updateArgOffsets(i - arg.length, i + 1)
51-
}
52-
53-
if (isCompositeArgument(argWithChat, lastArg)) {
54-
updateLastArgument(isAfterOffset, argWithChat)
55-
} else {
56-
pushToProperTuple(isAfterOffset, argWithChat)
57-
}
58-
59-
arg = ''
60-
} else {
61-
arg += char
62-
}
63-
} else if (char === '"' || char === "'") {
64-
inQuotes = true
65-
quoteChar = char
66-
arg += char
67-
} else if (char === ' ' || char === '\n') {
68-
if (arg.length > 0) {
69-
if (isAfterOffset && !argLeftOffset) {
70-
updateArgOffsets(i - arg.length, i)
71-
}
72-
73-
if (isCompositeArgument(arg, lastArg)) {
74-
updateLastArgument(isAfterOffset, arg)
75-
} else {
76-
pushToProperTuple(isAfterOffset, arg)
77-
}
78-
79-
arg = ''
80-
}
81-
} else {
82-
arg += char
83-
}
84-
85-
if (i === position - 1) isCursorInQuotes = inQuotes
86-
}
87-
88-
if (arg.length > 0) {
89-
if (!argLeftOffset) updateArgOffsets(query.length - arg.length, query.length)
90-
pushToProperTuple(true, arg)
91-
}
92-
93-
const cursor = {
94-
isCursorInQuotes,
95-
prevCursorChar: query[position - 1]?.trim() || '',
96-
nextCursorChar: query[position]?.trim() || '',
97-
argLeftOffset,
98-
argRightOffset
99-
}
100-
101-
return { args, cursor }
102-
}
103-
1048
export const findCurrentArgument = (
1059
args: IRedisCommand[],
10610
prev: string[],
@@ -340,7 +244,7 @@ export const getArgumentSuggestions = (
340244
const isBlockHasParent = current?.arguments?.some(({ name }) => parent?.name && name === parent?.name)
341245
const foundParent = isBlockHasParent ? { ...parent, parent: current } : (parent || current)
342246

343-
const isBlockComplete = !stopArgument && current?.name === lastArgument?.name
247+
const isBlockComplete = !stopArgument && isPrevArgWasMandatory
344248
const beforeMandatoryOptionalArgs = getAllRestArguments(foundParent, lastArgument, untilTokenArgs, isBlockComplete)
345249
const requiredArgsLength = restNotFilledArgs.filter((arg) => !arg.optional).length
346250

@@ -395,8 +299,14 @@ export const getAllRestArguments = (
395299
skipLevel = false
396300
) => {
397301
const appendArgs: Array<IRedisCommand[]> = []
398-
const currentLvlNextArgs = removeNotSuggestedArgs(
302+
303+
const currentToken = current?.type === ICommandTokenType.Block ? current?.arguments?.[0].token : current?.token
304+
const lastTokenIndex = findLastIndex(
399305
untilTokenArgs,
306+
(arg) => arg?.toLowerCase() === currentToken?.toLowerCase()
307+
)
308+
const currentLvlNextArgs = removeNotSuggestedArgs(
309+
untilTokenArgs.slice(lastTokenIndex > 0 ? lastTokenIndex : 0),
400310
getRestArguments(current, stopArgument)
401311
)
402312

@@ -464,10 +374,7 @@ export const findArgByToken = (list: IRedisCommand[], arg: string): Maybe<IRedis
464374
list.find((cArg) =>
465375
(cArg.type === ICommandTokenType.OneOf
466376
? cArg.arguments?.some((oneOfArg: IRedisCommand) => oneOfArg?.token?.toLowerCase() === arg?.toLowerCase())
467-
: cArg.arguments?.[0]?.token?.toLowerCase() === arg.toLowerCase()))
468-
469-
export const isCompositeArgument = (arg: string, prevArg?: string) =>
470-
COMPOSITE_ARGS.includes([prevArg?.toUpperCase(), arg?.toUpperCase()].join(' '))
377+
: cArg.arguments?.[0]?.token?.toLowerCase() === arg?.toLowerCase()))
471378

472379
export const generateDetail = (command: Maybe<IRedisCommand>) => {
473380
if (!command) return ''

redisinsight/ui/src/pages/workbench/utils/searchSuggestions.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { monaco as monacoEditor } from 'react-monaco-editor'
22
import { isNumber } from 'lodash'
3-
import { IMonacoQuery, Nullable } from 'uiSrc/utils'
3+
import { IMonacoQuery, Nullable, splitQueryByArgs } from 'uiSrc/utils'
44
import { CursorContext, FoundCommandArgument } from 'uiSrc/pages/workbench/types'
5-
import { findCurrentArgument, splitQueryByArgs } from 'uiSrc/pages/workbench/utils/query'
5+
import { findCurrentArgument } from 'uiSrc/pages/workbench/utils/query'
66
import { IRedisCommand } from 'uiSrc/constants'
77
import {
88
asSuggestionsRef,
@@ -49,28 +49,27 @@ export const findSuggestionsByArg = (
4949
return handleFieldSuggestions(additionData.fields || [], foundArg, cursorContext.range)
5050
}
5151

52+
if (foundArg?.stopArg?.token && !foundArg?.isBlocked) {
53+
return handleCommonSuggestions(
54+
command.fullQuery,
55+
foundArg,
56+
allArgs,
57+
additionData.fields || [],
58+
cursorContext,
59+
isEscaped
60+
)
61+
}
62+
63+
const { indexes, fields } = additionData
5264
switch (foundArg?.stopArg?.name) {
5365
case DefinedArgumentName.index: {
54-
return handleIndexSuggestions(
55-
additionData.indexes || [],
56-
command,
57-
foundArg,
58-
currentOffsetArg,
59-
cursorContext.range
60-
)
66+
return handleIndexSuggestions(indexes, command, foundArg, currentOffsetArg, cursorContext)
6167
}
6268
case DefinedArgumentName.query: {
6369
return handleQuerySuggestions(foundArg)
6470
}
6571
default: {
66-
return handleCommonSuggestions(
67-
command.fullQuery,
68-
foundArg,
69-
allArgs,
70-
additionData.fields || [],
71-
cursorContext,
72-
isEscaped
73-
)
72+
return handleCommonSuggestions(command.fullQuery, foundArg, allArgs, fields, cursorContext, isEscaped)
7473
}
7574
}
7675
}
@@ -88,11 +87,11 @@ const handleFieldSuggestions = (
8887
}
8988

9089
const handleIndexSuggestions = (
91-
indexes: any[],
90+
indexes: any[] = [],
9291
command: IMonacoQuery,
9392
foundArg: FoundCommandArgument,
9493
currentOffsetArg: Nullable<string>,
95-
range: monacoEditor.IRange
94+
cursorContext: CursorContext
9695
) => {
9796
const isIndex = indexes.length > 0
9897
const helpWidget = { isOpen: isIndex, parent: command.info, currentArg: foundArg?.stopArg }
@@ -109,7 +108,7 @@ const handleIndexSuggestions = (
109108
helpWidget.isOpen = !!currentOffsetArg
110109

111110
return {
112-
suggestions: asSuggestionsRef(!currentOffsetArg ? getNoIndexesSuggestion(range) : [], true),
111+
suggestions: asSuggestionsRef(!currentOffsetArg ? getNoIndexesSuggestion(cursorContext.range) : [], true),
113112
helpWidget
114113
}
115114
}
@@ -127,7 +126,7 @@ const handleIndexSuggestions = (
127126
&& currentCommand?.arguments?.[argumentIndex + 1]?.name === DefinedArgumentName.query
128127

129128
return {
130-
suggestions: asSuggestionsRef(getIndexesSuggestions(indexes, range, isNextArgQuery)),
129+
suggestions: asSuggestionsRef(getIndexesSuggestions(indexes, cursorContext.range, isNextArgQuery)),
131130
helpWidget
132131
}
133132
}
@@ -171,11 +170,13 @@ const handleCommonSuggestions = (
171170
value: string,
172171
foundArg: Nullable<FoundCommandArgument>,
173172
allArgs: string[],
174-
fields: any[],
173+
fields: any[] = [],
175174
cursorContext: CursorContext,
176175
isEscaped: boolean
177176
) => {
178-
if (foundArg?.stopArg?.expression) return handleExpressionSuggestions(value, foundArg, cursorContext)
177+
if (foundArg?.stopArg?.expression && foundArg.isBlocked) {
178+
return handleExpressionSuggestions(value, foundArg, cursorContext)
179+
}
179180

180181
const { prevCursorChar, nextCursorChar, isCursorInQuotes } = cursorContext
181182
const shouldHideSuggestions = isCursorInQuotes || nextCursorChar || (prevCursorChar && isEscaped)

0 commit comments

Comments
 (0)