diff --git a/.changeset/large-pumas-yawn.md b/.changeset/large-pumas-yawn.md new file mode 100644 index 00000000..f4435d7a --- /dev/null +++ b/.changeset/large-pumas-yawn.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +fix: laggy performance across app diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index fcf2aef9..709573ed 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -123,6 +123,7 @@ import { SearchConfig } from './types'; import searchPageStyles from '../styles/SearchPage.module.scss'; +const ALLOWED_SOURCE_KINDS = [SourceKind.Log, SourceKind.Trace]; const SearchConfigSchema = z.object({ select: z.string(), source: z.string(), @@ -1075,19 +1076,26 @@ function DBSearchPage() { }; }, [chartConfig, searchedTimeRange]); - const displayedColumns = splitAndTrimWithBracket( - dbSqlRowTableConfig?.select ?? - searchedSource?.defaultTableSelectExpression ?? - '', + const displayedColumns = useMemo( + () => + splitAndTrimWithBracket( + dbSqlRowTableConfig?.select ?? + searchedSource?.defaultTableSelectExpression ?? + '', + ), + [dbSqlRowTableConfig?.select, searchedSource?.defaultTableSelectExpression], ); - const toggleColumn = (column: string) => { - const newSelectArray = displayedColumns.includes(column) - ? displayedColumns.filter(s => s !== column) - : [...displayedColumns, column]; - setValue('select', newSelectArray.join(', ')); - onSubmit(); - }; + const toggleColumn = useCallback( + (column: string) => { + const newSelectArray = displayedColumns.includes(column) + ? displayedColumns.filter(s => s !== column) + : [...displayedColumns, column]; + setValue('select', newSelectArray.join(', ')); + onSubmit(); + }, + [displayedColumns, setValue, onSubmit], + ); const generateSearchUrl = useCallback( ({ @@ -1277,6 +1285,38 @@ function DBSearchPage() { const [isDrawerChildModalOpen, setDrawerChildModalOpen] = useState(false); + const rowTableContext = useMemo( + () => ({ + onPropertyAddClick: searchFilters.setFilterValue, + displayedColumns, + toggleColumn, + generateSearchUrl, + dbSqlRowTableConfig, + isChildModalOpen: isDrawerChildModalOpen, + setChildModalOpen: setDrawerChildModalOpen, + source: searchedSource, + }), + [ + searchFilters.setFilterValue, + searchedSource, + dbSqlRowTableConfig, + displayedColumns, + toggleColumn, + generateSearchUrl, + isDrawerChildModalOpen, + ], + ); + + const inputSourceTableConnection = useMemo( + () => tcFromSource(inputSourceObj), + [inputSourceObj], + ); + + const sourceSchemaPreview = useMemo( + () => , + [inputSourceObj], + ); + return ( @@ -1307,11 +1347,9 @@ function DBSearchPage() { control={control} name="source" onCreate={openNewSourceModal} - allowedSourceKinds={[SourceKind.Log, SourceKind.Trace]} + allowedSourceKinds={ALLOWED_SOURCE_KINDS} data-testid="source-selector" - sourceSchemaPreview={ - - } + sourceSchemaPreview={sourceSchemaPreview} /> @@ -1358,7 +1396,7 @@ function DBSearchPage() { tableContainerRef.current, + getScrollElement: useCallback( + () => tableContainerRef.current, + [tableContainerRef], + ), estimateSize: useCallback(() => 23, []), overscan: 30, paddingEnd: 20, @@ -897,6 +900,7 @@ export const RawLogTable = memo( >
}, }); +const cmBasicSetup = { + lineNumbers: false, + foldGutter: false, + highlightActiveLine: false, + highlightActiveLineGutter: false, +}; + export default function SQLInlineEditor({ tableConnection, tableConnections, @@ -355,6 +362,54 @@ export default function SQLInlineEditor({ ]; }, [parentRef]); + const cmExtensions = useMemo( + () => [ + ...tooltipExt, + createStyleTheme(allowMultiline), + ...(allowMultiline ? [EditorView.lineWrapping] : []), + compartmentRef.current.of( + sql({ + upperCaseKeywords: true, + }), + ), + Prec.highest( + keymap.of([ + { + key: 'Enter', + run: view => { + if (onSubmit == null) { + return false; + } + if (queryHistoryType && ref?.current?.view) { + setQueryHistory(ref?.current?.view.state.doc.toString()); + } + onSubmit(); + return true; + }, + }, + ...(allowMultiline + ? [ + { + key: 'Shift-Enter', + run: () => { + // Allow default behavior (insert new line) + return false; + }, + }, + ] + : []), + ]), + ), + keymap.of([ + { + key: 'Tab', + run: acceptCompletion, + }, + ]), + ], + [allowMultiline, onSubmit, queryHistoryType, setQueryHistory, tooltipExt], + ); + return ( { + onFocus={useCallback(() => { setIsFocused(true); - }} - onBlur={() => { + }, [setIsFocused])} + onBlur={useCallback(() => { setIsFocused(false); - }} - extensions={[ - ...tooltipExt, - createStyleTheme(allowMultiline), - ...(allowMultiline ? [EditorView.lineWrapping] : []), - compartmentRef.current.of( - sql({ - upperCaseKeywords: true, - }), - ), - Prec.highest( - keymap.of([ - { - key: 'Enter', - run: view => { - if (onSubmit == null) { - return false; - } - if (queryHistoryType && ref?.current?.view) { - setQueryHistory(ref?.current?.view.state.doc.toString()); - } - onSubmit(); - return true; - }, - }, - ...(allowMultiline - ? [ - { - key: 'Shift-Enter', - run: () => { - // Allow default behavior (insert new line) - return false; - }, - }, - ] - : []), - ]), - ), - keymap.of([ - { - key: 'Tab', - run: acceptCompletion, - }, - ]), - ]} - onCreateEditor={view => { - updateAutocompleteColumns(view); - }} - basicSetup={{ - lineNumbers: false, - foldGutter: false, - highlightActiveLine: false, - highlightActiveLineGutter: false, - }} + }, [setIsFocused])} + extensions={cmExtensions} + onCreateEditor={updateAutocompleteColumns} + basicSetup={cmBasicSetup} placeholder={placeholder} onClick={() => { if (ref?.current?.view) { diff --git a/packages/app/src/utils.ts b/packages/app/src/utils.ts index af70ab19..abea1219 100644 --- a/packages/app/src/utils.ts +++ b/packages/app/src/utils.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/router'; import { formatDistanceToNowStrict } from 'date-fns'; import numbro from 'numbro'; -import type { MutableRefObject } from 'react'; +import type { MutableRefObject, SetStateAction } from 'react'; import { TSource } from '@hyperdx/common-utils/dist/types'; import { dateRangeToString } from './timeQuery'; @@ -260,55 +260,62 @@ export function useLocalStorage(key: string, initialValue: T) { // Return a wrapped version of useState's setter function that ... // ... persists the new value to localStorage. - const setValue = (value: T | ((prevState: T) => T)) => { - if (typeof window === 'undefined') { - return; - } - try { - // Allow value to be a function so we have same API as useState - const valueToStore = - value instanceof Function ? value(storedValue) : value; - // Save state - setStoredValue(valueToStore); - // Save to local storage - window.localStorage.setItem(key, JSON.stringify(valueToStore)); - // Fire off event so other localStorage hooks listening with the same key - // will update - const event = new CustomEvent( - 'customStorage', - { - detail: { - key, - instanceId, + const setValue = useCallback( + (value: SetStateAction) => { + if (typeof window === 'undefined') { + return; + } + try { + // Allow value to be a function so we have same API as useState + // Save state + setStoredValue(prev => { + const newValue = value instanceof Function ? value(prev) : value; + window.localStorage.setItem(key, JSON.stringify(newValue)); + return newValue; + }); + // Fire off event so other localStorage hooks listening with the same key + // will update + const event = new CustomEvent( + 'customStorage', + { + detail: { + key, + instanceId, + }, }, - }, - ); - window.dispatchEvent(event); - } catch (error) { - // A more advanced implementation would handle the error case - // eslint-disable-next-line no-console - console.log(error); - } - }; + ); + window.dispatchEvent(event); + } catch (error) { + // A more advanced implementation would handle the error case + // eslint-disable-next-line no-console + console.log(error); + } + }, + [instanceId, key], + ); + return [storedValue, setValue] as const; } export function useQueryHistory(type: string | undefined) { const key = `${QUERY_LOCAL_STORAGE.KEY}.${type}`; const [queryHistory, _setQueryHistory] = useLocalStorage(key, []); - const setQueryHistory = (query: string) => { - // do not set up anything if there is no type or empty query - try { - const trimmed = query.trim(); - if (!type || !trimmed) return null; - const deduped = [trimmed, ...queryHistory.filter(q => q !== trimmed)]; - const limited = deduped.slice(0, QUERY_LOCAL_STORAGE.LIMIT); - _setQueryHistory(limited); - } catch (e) { - // eslint-disable-next-line no-console - console.log(`Failed to cache query history, error ${e.message}`); - } - }; + const setQueryHistory = useCallback( + (query: string) => { + // do not set up anything if there is no type or empty query + try { + const trimmed = query.trim(); + if (!type || !trimmed) return null; + const deduped = [trimmed, ...queryHistory.filter(q => q !== trimmed)]; + const limited = deduped.slice(0, QUERY_LOCAL_STORAGE.LIMIT); + _setQueryHistory(limited); + } catch (e) { + // eslint-disable-next-line no-console + console.log(`Failed to cache query history, error ${e.message}`); + } + }, + [_setQueryHistory, queryHistory, type], + ); return [queryHistory, setQueryHistory] as const; }