Skip to content

Commit 7837a62

Browse files
fix: Multiline support for WHERE Input boxes (#1208)
Co-authored-by: Brandon Pereira <brandon-pereira@users.noreply.github.com>
1 parent a8418f6 commit 7837a62

File tree

11 files changed

+369
-50
lines changed

11 files changed

+369
-50
lines changed

.changeset/twenty-squids-argue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/app": patch
3+
---
4+
5+
fix: Multiline support for WHERE Input boxes

packages/app/src/AutocompleteInput.tsx

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1+
import { useEffect, useMemo, useRef, useState } from 'react';
22
import Fuse from 'fuse.js';
33
import { OverlayTrigger } from 'react-bootstrap';
4-
import { TextInput, UnstyledButton } from '@mantine/core';
4+
import { Textarea, UnstyledButton } from '@mantine/core';
55

66
import { useQueryHistory } from '@/utils';
77

@@ -27,7 +27,7 @@ export default function AutocompleteInput({
2727
queryHistoryType,
2828
'data-testid': dataTestId,
2929
}: {
30-
inputRef: React.RefObject<HTMLInputElement>;
30+
inputRef: React.RefObject<HTMLTextAreaElement>;
3131
value?: string;
3232
onChange: (value: string) => void;
3333
onSubmit?: () => void;
@@ -236,14 +236,19 @@ export default function AutocompleteInput({
236236
}}
237237
trigger={[]}
238238
>
239-
<TextInput
239+
<Textarea
240240
ref={inputRef}
241-
type="text"
242-
style={{ flexGrow: 1 }}
243241
placeholder={placeholder}
244-
className="border-0 fs-8"
242+
className="fs-8"
245243
value={value}
246244
size={size}
245+
autosize
246+
minRows={1}
247+
maxRows={4}
248+
style={{
249+
flexGrow: 1,
250+
resize: 'none',
251+
}}
247252
data-testid={dataTestId}
248253
onChange={e => onChange(e.target.value)}
249254
onFocus={() => {
@@ -257,12 +262,12 @@ export default function AutocompleteInput({
257262
setIsSearchInputFocused(false);
258263
}}
259264
onKeyDown={e => {
260-
if (e.key === 'Escape' && e.target instanceof HTMLInputElement) {
265+
if (e.key === 'Escape' && e.target instanceof HTMLTextAreaElement) {
261266
e.target.blur();
262267
}
263268

264269
// Autocomplete Navigation/Acceptance Keys
265-
if (e.key === 'Tab' && e.target instanceof HTMLInputElement) {
270+
if (e.key === 'Tab' && e.target instanceof HTMLTextAreaElement) {
266271
if (
267272
suggestedProperties.length > 0 &&
268273
selectedAutocompleteIndex < suggestedProperties.length &&
@@ -274,23 +279,31 @@ export default function AutocompleteInput({
274279
);
275280
}
276281
}
277-
if (e.key === 'Enter' && e.target instanceof HTMLInputElement) {
282+
if (e.key === 'Enter' && e.target instanceof HTMLTextAreaElement) {
278283
if (
279284
suggestedProperties.length > 0 &&
280285
selectedAutocompleteIndex < suggestedProperties.length &&
281286
selectedAutocompleteIndex >= 0
282287
) {
288+
e.preventDefault();
283289
onAcceptSuggestion(
284290
suggestedProperties[selectedAutocompleteIndex].value,
285291
);
286292
} else {
287-
if (queryHistoryType && value) {
288-
setQueryHistory(value);
293+
// Allow shift+enter to still create new lines
294+
if (!e.shiftKey) {
295+
e.preventDefault();
296+
if (queryHistoryType && value) {
297+
setQueryHistory(value);
298+
}
299+
onSubmit?.();
289300
}
290-
onSubmit?.();
291301
}
292302
}
293-
if (e.key === 'ArrowDown' && e.target instanceof HTMLInputElement) {
303+
if (
304+
e.key === 'ArrowDown' &&
305+
e.target instanceof HTMLTextAreaElement
306+
) {
294307
if (suggestedProperties.length > 0) {
295308
setSelectedAutocompleteIndex(
296309
Math.min(
@@ -301,7 +314,7 @@ export default function AutocompleteInput({
301314
);
302315
}
303316
}
304-
if (e.key === 'ArrowUp' && e.target instanceof HTMLInputElement) {
317+
if (e.key === 'ArrowUp' && e.target instanceof HTMLTextAreaElement) {
305318
if (suggestedProperties.length > 0) {
306319
setSelectedAutocompleteIndex(
307320
Math.max(selectedAutocompleteIndex - 1, 0),
@@ -311,15 +324,15 @@ export default function AutocompleteInput({
311324
}}
312325
rightSectionWidth={ref.current?.clientWidth ?? 'auto'}
313326
rightSection={
314-
<div ref={ref}>
315-
{language != null && onLanguageChange != null && (
327+
language != null && onLanguageChange != null ? (
328+
<div ref={ref}>
316329
<InputLanguageSwitch
317330
showHotkey={showHotkey && isSearchInputFocused}
318331
language={language}
319332
onLanguageChange={onLanguageChange}
320333
/>
321-
)}
322-
</div>
334+
</div>
335+
) : undefined
323336
}
324337
/>
325338
</OverlayTrigger>

packages/app/src/DBDashboardPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
10181018
onSubmit={onSubmit}
10191019
label="GLOBAL WHERE"
10201020
enableHotkey
1021+
allowMultiline={true}
10211022
/>
10221023
) : (
10231024
<SearchInputV2

packages/app/src/DBSearchPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,7 @@ function DBSearchPage() {
14181418
label="WHERE"
14191419
queryHistoryType={QUERY_LOCAL_STORAGE.SEARCH_SQL}
14201420
enableHotkey
1421+
allowMultiline={true}
14211422
/>
14221423
</Box>
14231424
}

packages/app/src/SearchInputV2.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default function SearchInputV2({
5454
field: { onChange, value },
5555
} = useController(props);
5656

57-
const ref = useRef<HTMLInputElement>(null);
57+
const ref = useRef<HTMLTextAreaElement>(null);
5858
const [parsedEnglishQuery, setParsedEnglishQuery] = useState<string>('');
5959

6060
const autoCompleteOptions = useAutoCompleteOptions(

packages/app/src/ServicesDashboardPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ function ServicesDashboardPage() {
968968
language="sql"
969969
label="WHERE"
970970
enableHotkey
971+
allowMultiline={true}
971972
/>
972973
}
973974
luceneInput={

packages/app/src/SessionSubpanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export default function SessionSubpanel({
156156
);
157157

158158
// Event Filter Input =========================
159-
const inputRef = useRef<HTMLInputElement>(null);
159+
const inputRef = useRef<HTMLTextAreaElement>(null);
160160
const [_inputQuery, setInputQuery] = useState<string | undefined>(undefined);
161161
const inputQuery = _inputQuery ?? '';
162162
const [_searchedQuery, setSearchedQuery] = useQueryState('session_q', {

packages/app/src/SessionsPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ export default function SessionsPage() {
462462
language="sql"
463463
label="WHERE"
464464
enableHotkey
465+
allowMultiline={true}
465466
/>
466467
</Box>
467468
}

packages/app/src/components/SQLInlineEditor.tsx

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import {
2-
memo,
3-
RefObject,
4-
useCallback,
5-
useEffect,
6-
useMemo,
7-
useRef,
8-
useState,
9-
} from 'react';
1+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
102
import { useController, UseControllerProps } from 'react-hook-form';
113
import { useHotkeys } from 'react-hotkeys-hook';
124
import {
@@ -122,24 +114,33 @@ type SQLInlineEditorProps = {
122114
additionalSuggestions?: string[];
123115
queryHistoryType?: string;
124116
parentRef?: HTMLElement | null;
117+
allowMultiline?: boolean;
125118
};
126119

127-
const styleTheme = EditorView.baseTheme({
128-
'&.cm-editor.cm-focused': {
129-
outline: '0px solid transparent',
130-
},
131-
'&.cm-editor': {
132-
background: 'transparent !important',
133-
},
134-
'& .cm-tooltip-autocomplete': {
135-
whiteSpace: 'nowrap',
136-
wordWrap: 'break-word',
137-
maxWidth: '100%',
138-
},
139-
'& .cm-scroller': {
140-
overflowX: 'hidden',
141-
},
142-
});
120+
const MAX_EDITOR_HEIGHT = '150px';
121+
122+
const createStyleTheme = (allowMultiline: boolean = false) =>
123+
EditorView.baseTheme({
124+
'&.cm-editor.cm-focused': {
125+
outline: '0px solid transparent',
126+
},
127+
'&.cm-editor': {
128+
background: 'transparent !important',
129+
...(allowMultiline && { maxHeight: MAX_EDITOR_HEIGHT }),
130+
},
131+
'& .cm-tooltip-autocomplete': {
132+
whiteSpace: 'nowrap',
133+
wordWrap: 'break-word',
134+
maxWidth: '100%',
135+
},
136+
'& .cm-scroller': {
137+
overflowX: 'hidden',
138+
...(allowMultiline && {
139+
maxHeight: MAX_EDITOR_HEIGHT,
140+
overflowY: 'auto',
141+
}),
142+
},
143+
});
143144

144145
export default function SQLInlineEditor({
145146
tableConnections,
@@ -158,6 +159,7 @@ export default function SQLInlineEditor({
158159
additionalSuggestions = [],
159160
queryHistoryType,
160161
parentRef,
162+
allowMultiline = false,
161163
}: SQLInlineEditorProps) {
162164
const { data: fields } = useAllFields(tableConnections ?? []);
163165
const filteredFields = useMemo(() => {
@@ -332,7 +334,8 @@ export default function SQLInlineEditor({
332334
}}
333335
extensions={[
334336
...tooltipExt,
335-
styleTheme,
337+
createStyleTheme(allowMultiline),
338+
...(allowMultiline ? [EditorView.lineWrapping] : []),
336339
compartmentRef.current.of(
337340
sql({
338341
upperCaseKeywords: true,
@@ -342,7 +345,7 @@ export default function SQLInlineEditor({
342345
keymap.of([
343346
{
344347
key: 'Enter',
345-
run: () => {
348+
run: view => {
346349
if (onSubmit == null) {
347350
return false;
348351
}
@@ -353,6 +356,17 @@ export default function SQLInlineEditor({
353356
return true;
354357
},
355358
},
359+
...(allowMultiline
360+
? [
361+
{
362+
key: 'Shift-Enter',
363+
run: () => {
364+
// Allow default behavior (insert new line)
365+
return false;
366+
},
367+
},
368+
]
369+
: []),
356370
]),
357371
),
358372
keymap.of([

packages/app/tests/e2e/features/search/search.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ test.describe('Search', { tag: '@search' }, () => {
117117
});
118118
});
119119

120-
//TODO: Add query test using sql
121-
122120
test('Comprehensive Search Workflow - Search, View Results, Navigate Side Panel', async ({
123121
page,
124122
}) => {

0 commit comments

Comments
 (0)