Skip to content

Commit aed6b0a

Browse files
committed
feat: add deployment customizations for live tail, autocomplete, team access, and otel export
- move Google SSO button above email/password form on login page - replace Fuse.js with substring includes() for autocomplete filtering - remove autocomplete pagination; show top N results filtered per-keystroke - keep AUTOCOMPLETE_MIN_CHARS threshold and AUTOCOMPLETE_SUGGESTIONS_LIMIT cap
1 parent f7c13b8 commit aed6b0a

2 files changed

Lines changed: 54 additions & 122 deletions

File tree

packages/app/src/AuthPage.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,21 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
155155
<Stack gap="xl">
156156
<Paper p={34} shadow="md" radius="md">
157157
<Stack gap="lg">
158+
{action === 'login' && config.GOOGLE_SSO_ENABLED && (
159+
<>
160+
<Button
161+
component="a"
162+
href="/api/login/google"
163+
variant="secondary"
164+
size="md"
165+
leftSection={<IconBrandGoogle size={18} />}
166+
data-test-id="google-sso-button"
167+
>
168+
Sign in with Google
169+
</Button>
170+
<Divider label="or" labelPosition="center" />
171+
</>
172+
)}
158173
<TextInput
159174
label="Email"
160175
size="md"
@@ -215,21 +230,6 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
215230
? 'Register'
216231
: 'Login'}
217232
</Button>
218-
{action === 'login' && config.GOOGLE_SSO_ENABLED && (
219-
<>
220-
<Divider label="or" labelPosition="center" />
221-
<Button
222-
component="a"
223-
href="/api/login/google"
224-
variant="secondary"
225-
size="md"
226-
leftSection={<IconBrandGoogle size={18} />}
227-
data-test-id="google-sso-button"
228-
>
229-
Sign in with Google
230-
</Button>
231-
</>
232-
)}
233233
</Stack>
234234
</Paper>
235235

packages/app/src/components/SearchInput/AutocompleteInput.tsx

Lines changed: 39 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
1+
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
22
import cx from 'classnames';
3-
import Fuse from 'fuse.js';
43
import { Popover, Textarea, UnstyledButton } from '@mantine/core';
54

65
import { AUTOCOMPLETE_MIN_CHARS, AUTOCOMPLETE_SUGGESTIONS_LIMIT } from '@/config';
@@ -82,8 +81,6 @@ export default function AutocompleteInput({
8281
const [selectedQueryHistoryIndex, setSelectedQueryHistoryIndex] =
8382
useState(-1);
8483

85-
const [page, setPage] = useState(0);
86-
8784
// query search history
8885
const [queryHistory, setQueryHistory] = useQueryHistory(queryHistoryType);
8986
const queryHistoryList = useMemo(() => {
@@ -102,16 +99,6 @@ export default function AutocompleteInput({
10299
queryHistoryList.length > 0 &&
103100
queryHistoryType;
104101

105-
const fuse = useMemo(
106-
() =>
107-
new Fuse(autocompleteOptions ?? [], {
108-
keys: ['value'],
109-
threshold: 0,
110-
ignoreLocation: true,
111-
}),
112-
[autocompleteOptions],
113-
);
114-
115102
const debouncedValue = useDebounce(value ?? '', 200);
116103
const suggestedProperties = useMemo(() => {
117104
const lastToken = getLastToken(debouncedValue);
@@ -121,25 +108,19 @@ export default function AutocompleteInput({
121108
}
122109
if (!lastToken.length) return [];
123110

124-
const fuseTerm = extractFuseSearchTerm(lastToken);
111+
const searchTerm = extractFuseSearchTerm(lastToken);
125112
// bare `field:` (empty value portion) or pure wildcard → show all fetched options
126-
if (!fuseTerm.length) return autocompleteOptions ?? [];
113+
if (!searchTerm.length) return autocompleteOptions ?? [];
127114
// enforce minimum character threshold before filtering
128-
if (fuseTerm.length < AUTOCOMPLETE_MIN_CHARS) return [];
129-
130-
return fuse.search(fuseTerm).map(result => result.item);
131-
}, [debouncedValue, fuse, autocompleteOptions, showSuggestionsOnEmpty]);
115+
if (searchTerm.length < AUTOCOMPLETE_MIN_CHARS) return [];
132116

133-
// Reset pagination and selection whenever the suggestion list changes
134-
useEffect(() => {
135-
setPage(0);
136-
setSelectedAutocompleteIndex(-1);
137-
}, [suggestedProperties]);
117+
const lower = searchTerm.toLowerCase();
118+
return (autocompleteOptions ?? []).filter(opt =>
119+
opt.value.toLowerCase().includes(lower),
120+
);
121+
}, [debouncedValue, autocompleteOptions, showSuggestionsOnEmpty]);
138122

139-
const totalPages = Math.ceil(suggestedProperties.length / pageSize);
140-
const pageStart = page * pageSize;
141-
const pageEnd = Math.min((page + 1) * pageSize, suggestedProperties.length) - 1;
142-
const pagedSuggestions = suggestedProperties.slice(pageStart, pageEnd + 1);
123+
const visibleSuggestions = suggestedProperties.slice(0, pageSize);
143124

144125
const onSelectSearchHistory = (query: string) => {
145126
setSelectedQueryHistoryIndex(-1);
@@ -235,13 +216,13 @@ export default function AutocompleteInput({
235216
// Autocomplete Navigation/Acceptance Keys
236217
if (e.key === 'Tab' && e.target instanceof HTMLTextAreaElement) {
237218
if (
238-
suggestedProperties.length > 0 &&
219+
visibleSuggestions.length > 0 &&
239220
selectedAutocompleteIndex >= 0 &&
240-
selectedAutocompleteIndex < suggestedProperties.length
221+
selectedAutocompleteIndex < visibleSuggestions.length
241222
) {
242223
e.preventDefault();
243224
onAcceptSuggestion(
244-
suggestedProperties[selectedAutocompleteIndex].value,
225+
visibleSuggestions[selectedAutocompleteIndex].value,
245226
);
246227
}
247228
}
@@ -250,13 +231,13 @@ export default function AutocompleteInput({
250231
e.target instanceof HTMLTextAreaElement
251232
) {
252233
if (
253-
suggestedProperties.length > 0 &&
234+
visibleSuggestions.length > 0 &&
254235
selectedAutocompleteIndex >= 0 &&
255-
selectedAutocompleteIndex < suggestedProperties.length
236+
selectedAutocompleteIndex < visibleSuggestions.length
256237
) {
257238
e.preventDefault();
258239
onAcceptSuggestion(
259-
suggestedProperties[selectedAutocompleteIndex].value,
240+
visibleSuggestions[selectedAutocompleteIndex].value,
260241
);
261242
} else {
262243
// Allow shift+enter to still create new lines
@@ -273,29 +254,18 @@ export default function AutocompleteInput({
273254
e.key === 'ArrowDown' &&
274255
e.target instanceof HTMLTextAreaElement
275256
) {
276-
if (suggestedProperties.length > 0) {
277-
const next = Math.min(
278-
selectedAutocompleteIndex + 1,
279-
suggestedProperties.length - 1,
257+
if (visibleSuggestions.length > 0) {
258+
setSelectedAutocompleteIndex(i =>
259+
Math.min(i + 1, visibleSuggestions.length - 1),
280260
);
281-
// Advance page when crossing the page boundary
282-
if (next > pageEnd && page < totalPages - 1) {
283-
setPage(p => p + 1);
284-
}
285-
setSelectedAutocompleteIndex(next);
286261
}
287262
}
288263
if (
289264
e.key === 'ArrowUp' &&
290265
e.target instanceof HTMLTextAreaElement
291266
) {
292-
if (suggestedProperties.length > 0) {
293-
const prev = Math.max(selectedAutocompleteIndex - 1, 0);
294-
// Retreat page when crossing the page boundary
295-
if (prev < pageStart && page > 0) {
296-
setPage(p => p - 1);
297-
}
298-
setSelectedAutocompleteIndex(prev);
267+
if (visibleSuggestions.length > 0) {
268+
setSelectedAutocompleteIndex(i => Math.max(i - 1, 0));
299269
}
300270
}
301271
}}
@@ -317,70 +287,32 @@ export default function AutocompleteInput({
317287
<div className={styles.aboveSuggestions}>{aboveSuggestions}</div>
318288
)}
319289
<div>
320-
{suggestedProperties.length > 0 && (
290+
{visibleSuggestions.length > 0 && (
321291
<div className={styles.suggestionsSection}>
322292
<div className={styles.suggestionsHeaderRow}>
323293
<div className={styles.suggestionsHeader}>
324294
{suggestionsHeader}
325295
</div>
326-
{totalPages > 1 ? (
327-
<div className={styles.pagination}>
328-
<UnstyledButton
329-
className={styles.pageButton}
330-
disabled={page === 0}
331-
onClick={() => {
332-
setPage(p => p - 1);
333-
setSelectedAutocompleteIndex(-1);
334-
}}
335-
aria-label="Previous suggestions"
336-
>
337-
338-
</UnstyledButton>
339-
<span className={styles.pageIndicator}>
340-
{page + 1} / {totalPages}
341-
</span>
342-
<UnstyledButton
343-
className={styles.pageButton}
344-
disabled={page === totalPages - 1}
345-
onClick={() => {
346-
setPage(p => p + 1);
347-
setSelectedAutocompleteIndex(-1);
348-
}}
349-
aria-label="Next suggestions"
350-
>
351-
352-
</UnstyledButton>
296+
{suggestedProperties.length > pageSize && (
297+
<div className={styles.suggestionsLimit}>
298+
(Showing Top {pageSize})
353299
</div>
354-
) : (
355-
suggestedProperties.length > pageSize && (
356-
<div className={styles.suggestionsLimit}>
357-
(Showing Top {pageSize})
358-
</div>
359-
)
360300
)}
361301
</div>
362-
{pagedSuggestions.map(({ value, label }, i) => {
363-
const absoluteIdx = pageStart + i;
364-
return (
365-
<div
366-
className={cx(
367-
styles.suggestionItem,
368-
selectedAutocompleteIndex === absoluteIdx &&
369-
styles.selected,
370-
)}
371-
role="button"
372-
key={value}
373-
onMouseOver={() => {
374-
setSelectedAutocompleteIndex(absoluteIdx);
375-
}}
376-
onClick={() => {
377-
onAcceptSuggestion(value);
378-
}}
379-
>
380-
<span className={styles.suggestionLabel}>{label}</span>
381-
</div>
382-
);
383-
})}
302+
{visibleSuggestions.map(({ value, label }, i) => (
303+
<div
304+
className={cx(
305+
styles.suggestionItem,
306+
selectedAutocompleteIndex === i && styles.selected,
307+
)}
308+
role="button"
309+
key={value}
310+
onMouseOver={() => setSelectedAutocompleteIndex(i)}
311+
onClick={() => onAcceptSuggestion(value)}
312+
>
313+
<span className={styles.suggestionLabel}>{label}</span>
314+
</div>
315+
))}
384316
</div>
385317
)}
386318
</div>

0 commit comments

Comments
 (0)