mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
parent
ea25cc5d43
commit
2f25ce6fa6
5 changed files with 178 additions and 128 deletions
5
.changeset/large-pumas-yawn.md
Normal file
5
.changeset/large-pumas-yawn.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: laggy performance across app
|
||||
|
|
@ -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(
|
||||
() => <SourceSchemaPreview source={inputSourceObj} variant="text" />,
|
||||
[inputSourceObj],
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex direction="column" h="100vh" style={{ overflow: 'hidden' }}>
|
||||
<Head>
|
||||
|
|
@ -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 source={inputSourceObj} variant="text" />
|
||||
}
|
||||
sourceSchemaPreview={sourceSchemaPreview}
|
||||
/>
|
||||
<Menu withArrow position="bottom-start">
|
||||
<Menu.Target>
|
||||
|
|
@ -1358,7 +1396,7 @@ function DBSearchPage() {
|
|||
</Group>
|
||||
<Box style={{ minWidth: 100, flexGrow: 1 }}>
|
||||
<SQLInlineEditorControlled
|
||||
tableConnection={tcFromSource(inputSourceObj)}
|
||||
tableConnection={inputSourceTableConnection}
|
||||
control={control}
|
||||
name="select"
|
||||
defaultValue={inputSourceObj?.defaultTableSelectExpression}
|
||||
|
|
@ -1372,7 +1410,7 @@ function DBSearchPage() {
|
|||
</Box>
|
||||
<Box style={{ maxWidth: 400, width: '20%' }}>
|
||||
<SQLInlineEditorControlled
|
||||
tableConnection={tcFromSource(inputSourceObj)}
|
||||
tableConnection={inputSourceTableConnection}
|
||||
control={control}
|
||||
name="orderBy"
|
||||
defaultValue={defaultOrderBy}
|
||||
|
|
@ -1834,16 +1872,7 @@ function DBSearchPage() {
|
|||
dbSqlRowTableConfig &&
|
||||
analysisMode === 'results' && (
|
||||
<DBSqlRowTableWithSideBar
|
||||
context={{
|
||||
onPropertyAddClick: searchFilters.setFilterValue,
|
||||
displayedColumns,
|
||||
toggleColumn,
|
||||
generateSearchUrl,
|
||||
dbSqlRowTableConfig,
|
||||
isChildModalOpen: isDrawerChildModalOpen,
|
||||
setChildModalOpen: setDrawerChildModalOpen,
|
||||
source: searchedSource,
|
||||
}}
|
||||
context={rowTableContext}
|
||||
config={dbSqlRowTableConfig}
|
||||
sourceId={searchedConfig.source}
|
||||
onSidebarOpen={onSidebarOpen}
|
||||
|
|
|
|||
|
|
@ -618,7 +618,10 @@ export const RawLogTable = memo(
|
|||
const rowVirtualizer = useVirtualizer({
|
||||
count: _rows.length,
|
||||
// count: hasNextPage ? allRows.length + 1 : allRows.length,
|
||||
getScrollElement: () => tableContainerRef.current,
|
||||
getScrollElement: useCallback(
|
||||
() => tableContainerRef.current,
|
||||
[tableContainerRef],
|
||||
),
|
||||
estimateSize: useCallback(() => 23, []),
|
||||
overscan: 30,
|
||||
paddingEnd: 20,
|
||||
|
|
@ -897,6 +900,7 @@ export const RawLogTable = memo(
|
|||
>
|
||||
<div className={styles.fieldTextContainer}>
|
||||
<DBRowTableFieldWithPopover
|
||||
key={cell.id}
|
||||
cellValue={cellValue}
|
||||
wrapLinesEnabled={wrapLinesEnabled}
|
||||
tableContainerRef={tableContainerRef}
|
||||
|
|
|
|||
|
|
@ -192,6 +192,13 @@ const createStyleTheme = (allowMultiline: boolean = false) =>
|
|||
},
|
||||
});
|
||||
|
||||
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 (
|
||||
<Paper
|
||||
flex="auto"
|
||||
|
|
@ -393,65 +448,15 @@ export default function SQLInlineEditor({
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
theme={colorScheme === 'dark' ? 'dark' : 'light'}
|
||||
onFocus={() => {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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<T>(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<CustomStorageChangeDetail>(
|
||||
'customStorage',
|
||||
{
|
||||
detail: {
|
||||
key,
|
||||
instanceId,
|
||||
const setValue = useCallback(
|
||||
(value: SetStateAction<T>) => {
|
||||
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<CustomStorageChangeDetail>(
|
||||
'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<T>(type: string | undefined) {
|
||||
const key = `${QUERY_LOCAL_STORAGE.KEY}.${type}`;
|
||||
const [queryHistory, _setQueryHistory] = useLocalStorage<string[]>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue