diff --git a/public/app/features/explore/Logs/PopoverMenu.tsx b/public/app/features/explore/Logs/PopoverMenu.tsx
index 9c435bee1d7..5d5895ed5c4 100644
--- a/public/app/features/explore/Logs/PopoverMenu.tsx
+++ b/public/app/features/explore/Logs/PopoverMenu.tsx
@@ -14,6 +14,7 @@ interface PopoverMenuProps {
y: number;
onClickFilterString?: (value: string, refId?: string) => void;
onClickFilterOutString?: (value: string, refId?: string) => void;
+ onClickSearchString?: (text: string) => void;
onDisable: () => void;
row: LogRowModel;
close: () => void;
@@ -24,6 +25,7 @@ export const PopoverMenu = ({
y,
onClickFilterString,
onClickFilterOutString,
+ onClickSearchString,
selection,
row,
close,
@@ -50,7 +52,7 @@ export const PopoverMenu = ({
props.onDisable();
}, [props, row.datasourceType, selection.length]);
- const supported = onClickFilterString || onClickFilterOutString;
+ const supported = onClickFilterString || onClickFilterOutString || onClickSearchString;
if (!supported) {
return null;
@@ -89,6 +91,17 @@ export const PopoverMenu = ({
/>
)}
+ {onClickSearchString && (
+ {
+ onClickSearchString(selection);
+ close();
+ track('search_text', selection.length, row.datasourceType);
+ }}
+ />
+ )}
+
diff --git a/public/app/features/logs/components/panel/LogList.tsx b/public/app/features/logs/components/panel/LogList.tsx
index 3c51af0342b..8cfbaee1665 100644
--- a/public/app/features/logs/components/panel/LogList.tsx
+++ b/public/app/features/logs/components/panel/LogList.tsx
@@ -280,6 +280,7 @@ const LogListComponent = ({
wrapLogMessage,
} = useLogListContext();
const { detailsMode, showDetails, toggleDetails } = useLogDetailsContext();
+ const { setSearch, showSearch } = useLogListSearchContext();
const [processedLogs, setProcessedLogs] = useState([]);
const [listHeight, setListHeight] = useState(getListHeight(containerElement, app));
const theme = useTheme2();
@@ -441,6 +442,14 @@ const LogListComponent = ({
[debouncedScrollToItem, filteredLogs]
);
+ const onClickSearchString = useCallback(
+ (search: string) => {
+ showSearch();
+ setSearch(search);
+ },
+ [setSearch, showSearch]
+ );
+
const logLevels = useMemo(() => getLevelsFromLogs(processedLogs), [processedLogs]);
if (!containerElement || listHeight == null) {
@@ -471,6 +480,7 @@ const LogListComponent = ({
{...popoverState.popoverMenuCoordinates}
onClickFilterString={onClickFilterString}
onClickFilterOutString={onClickFilterOutString}
+ onClickSearchString={onClickSearchString}
onDisable={onDisablePopoverMenu}
/>
)}
diff --git a/public/app/features/logs/components/panel/LogListSearch.tsx b/public/app/features/logs/components/panel/LogListSearch.tsx
index 3cdd1cbbe3d..7cae9f36d5e 100644
--- a/public/app/features/logs/components/panel/LogListSearch.tsx
+++ b/public/app/features/logs/components/panel/LogListSearch.tsx
@@ -18,19 +18,11 @@ interface Props {
export const LOG_LIST_SEARCH_HEIGHT = 48;
export const LogListSearch = ({ listRef, logs }: Props) => {
- const {
- hideSearch,
- filterLogs,
- matchingUids,
- setMatchingUids,
- setSearch: setContextSearch,
- searchVisible,
- toggleFilterLogs,
- } = useLogListSearchContext();
+ const { hideSearch, filterLogs, matchingUids, search, setMatchingUids, setSearch, searchVisible, toggleFilterLogs } =
+ useLogListSearchContext();
const { displayedFields, noInteractions } = useLogListContext();
- const [search, setSearch] = useState('');
const [currentResult, setCurrentResult] = useState(null);
- const inputRef = useRef('');
+ const inputRef = useRef(null);
const searchUsedRef = useRef(false);
const styles = useStyles2(getStyles);
@@ -43,16 +35,15 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
const handleChange = useCallback(
(e: ChangeEvent) => {
- inputRef.current = e.target.value;
startTransition(() => {
- setSearch(inputRef.current);
+ setSearch(inputRef.current?.value ?? '');
});
if (!searchUsedRef.current && !noInteractions) {
reportInteraction('logs_log_list_search_used');
searchUsedRef.current = true;
}
},
- [noInteractions]
+ [noInteractions, setSearch]
);
const prevResult = useCallback(() => {
@@ -78,19 +69,27 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
setCurrentResult(null);
return;
}
- if (!currentResult) {
+ if (currentResult === null) {
setCurrentResult(0);
- listRef?.scrollToItem(logs.indexOf(matches[0]), 'center');
+ // No need to filter if we're only showing matching logs, otherwise scroll to the first result.
+ if (!filterLogs) {
+ listRef?.scrollToItem(logs.indexOf(matches[0]), 'center');
+ }
}
- }, [currentResult, listRef, logs, matches]);
+ }, [currentResult, filterLogs, listRef, logs, matches]);
useEffect(() => {
if (!searchVisible) {
- setSearch('');
- setContextSearch(undefined);
setMatchingUids(null);
}
- }, [searchVisible, setContextSearch, setMatchingUids]);
+ }, [searchVisible, setMatchingUids]);
+
+ useEffect(() => {
+ if (!inputRef.current || !search) {
+ return;
+ }
+ inputRef.current.value = search;
+ }, [search]);
useEffect(() => {
const newMatchingUids = matches.map((log) => log.uid);
@@ -104,13 +103,12 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
.forEach((log) => log.setCurrentSearch(undefined));
}
- setContextSearch(search ? search : undefined);
if (!sameLogs) {
setMatchingUids(newMatchingUids.length ? newMatchingUids : null);
} else if (!matches.length) {
setMatchingUids(null);
}
- }, [logs, matches, matchingUids, search, setContextSearch, setMatchingUids]);
+ }, [logs, matches, matchingUids, search, setMatchingUids]);
if (!searchVisible) {
return null;
@@ -126,6 +124,7 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
onChange={handleChange}
autoFocus
placeholder={t('logs.log-list-search.input-placeholder', 'Search in logs')}
+ ref={inputRef}
suffix={suffix}
/>
@@ -141,22 +140,22 @@ export const LogListSearch = ({ listRef, logs }: Props) => {
onClick={prevResult}
disabled={!matches || !matches.length}
name="angle-up"
- aria-label={t('logs.log-list-search.prev', 'Previous result')}
+ tooltip={t('logs.log-list-search.prev', 'Previous result')}
/>
-
+
);
};
diff --git a/public/app/features/logs/components/panel/LogListSearchContext.tsx b/public/app/features/logs/components/panel/LogListSearchContext.tsx
index ff1548a56cf..22be0c251a6 100644
--- a/public/app/features/logs/components/panel/LogListSearchContext.tsx
+++ b/public/app/features/logs/components/panel/LogListSearchContext.tsx
@@ -7,7 +7,7 @@ export interface LogListSearchContextData {
search?: string;
searchVisible?: boolean;
setMatchingUids: (matches: string[] | null) => void;
- setSearch: (search: string | undefined) => void;
+ setSearch: (search: string) => void;
showSearch: () => void;
toggleFilterLogs: () => void;
}
@@ -33,13 +33,14 @@ export const useLogListSearchContext = (): LogListSearchContextData => {
};
export const LogListSearchContextProvider = ({ children }: { children: ReactNode }) => {
- const [search, setSearch] = useState(undefined);
+ const [search, setSearch] = useState('');
const [searchVisible, setSearchVisible] = useState(false);
const [matchingUids, setMatchingUids] = useState(null);
const [filterLogs, setFilterLogs] = useState(false);
const hideSearch = useCallback(() => {
setSearchVisible(false);
+ setSearch('');
}, []);
const showSearch = useCallback(() => {
diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts
index dcb391c8e07..9132c2b7e6e 100644
--- a/public/app/features/logs/components/panel/processing.ts
+++ b/public/app/features/logs/components/panel/processing.ts
@@ -336,7 +336,7 @@ function countNewLines(log: string, limit = Infinity) {
let count = 0;
for (let i = 0; i < log.length; ++i) {
// No need to iterate further
- if (count > Infinity) {
+ if (count > limit) {
return count;
}
if (log[i] === '\n') {
diff --git a/public/app/features/logs/components/panel/useKeyBindings.ts b/public/app/features/logs/components/panel/useKeyBindings.ts
index 6a75dcfcf2b..21776b15bb7 100644
--- a/public/app/features/logs/components/panel/useKeyBindings.ts
+++ b/public/app/features/logs/components/panel/useKeyBindings.ts
@@ -15,7 +15,7 @@ export const useKeyBindings = () => {
const { showDetails, detailsMode, closeDetails } = useLogDetailsContext();
useEffect(() => {
- function handleToggleSearch(event: KeyboardEvent) {
+ function handleOpenSearch(event: KeyboardEvent) {
const isMac = navigator.userAgent.includes('Mac');
const isFKey = event.key === 'f' || event.key === 'F';
@@ -23,6 +23,8 @@ export const useKeyBindings = () => {
showSearch();
return;
}
+ }
+ function handleClose(event: KeyboardEvent) {
if (event.key === 'Escape' && searchVisible) {
hideSearch();
}
@@ -30,9 +32,11 @@ export const useKeyBindings = () => {
closeDetails();
}
}
- document.addEventListener('keydown', handleToggleSearch);
+ document.addEventListener('keydown', handleOpenSearch);
+ document.addEventListener('keyup', handleClose);
return () => {
- document.removeEventListener('keydown', handleToggleSearch);
+ document.removeEventListener('keydown', handleOpenSearch);
+ document.removeEventListener('keyup', handleClose);
};
}, [closeDetails, detailsMode, hideSearch, searchVisible, showDetails.length, showSearch]);
};
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index f62b26211f6..c5fc2f1d935 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -10181,7 +10181,8 @@
"copy": "Copy selection",
"disable-menu": "Disable menu",
"line-contains": "Add as line contains filter",
- "line-contains-not": "Add as line does not contain filter"
+ "line-contains-not": "Add as line does not contain filter",
+ "search-text": "Search in results"
},
"show-log-attributes": "Display log attributes for OTel logs",
"timestamp-format": "Timestamp resolution",