Log Line Details: Header options and inline icons improvements (#114479)

* LogLineDetailsHeader: introduce divider

* LogLineDetails: improve icons spacing

* useKeyBindings: close sidebar details with escape
This commit is contained in:
Matias Chomicki
2025-12-09 17:15:05 +01:00
committed by GitHub
parent 6e4de0f81c
commit 3197892094
3 changed files with 75 additions and 47 deletions
@@ -267,54 +267,56 @@ export const LogLineDetailsField = ({
<div className={styles.row}>
{!disableActions && (
<div className={styles.actions}>
{onClickFilterLabel && fieldSupportsFilters && (
<AsyncIconButton
name="search-plus"
onClick={filterLabel}
// We purposely want to pass a new function on every render to allow the active state to be updated when log details remains open between updates.
isActive={labelFilterActive}
tooltipSuffix={refIdTooltip}
/>
)}
{onClickFilterOutLabel && fieldSupportsFilters && (
<div className={styles.actionIcons}>
{onClickFilterLabel && fieldSupportsFilters && (
<AsyncIconButton
name="search-plus"
onClick={filterLabel}
// We purposely want to pass a new function on every render to allow the active state to be updated when log details remains open between updates.
isActive={labelFilterActive}
tooltipSuffix={refIdTooltip}
/>
)}
{onClickFilterOutLabel && fieldSupportsFilters && (
<IconButton
name="search-minus"
tooltip={
app === CoreApp.Explore && log.dataFrame?.refId
? t('logs.log-line-details.fields.filter-out-query', 'Filter out value in query {{query}}', {
query: log.dataFrame?.refId,
})
: t('logs.log-line-details.fields.filter-out', 'Filter out value')
}
onClick={filterOutLabel}
/>
)}
{singleKey && displayedFields.includes(keys[0]) && (
<IconButton
variant="primary"
tooltip={t('logs.log-line-details.fields.toggle-field-button.hide-this-field', 'Hide this field')}
name="eye"
onClick={hideField}
/>
)}
{singleKey && !displayedFields.includes(keys[0]) && (
<IconButton
tooltip={t(
'logs.log-line-details.fields.toggle-field-button.field-instead-message',
'Show this field instead of the message'
)}
name="eye"
onClick={showField}
/>
)}
<IconButton
name="search-minus"
tooltip={
app === CoreApp.Explore && log.dataFrame?.refId
? t('logs.log-line-details.fields.filter-out-query', 'Filter out value in query {{query}}', {
query: log.dataFrame?.refId,
})
: t('logs.log-line-details.fields.filter-out', 'Filter out value')
}
onClick={filterOutLabel}
variant={showFieldsStats ? 'primary' : 'secondary'}
name="signal"
tooltip={t('logs.log-line-details.fields.adhoc-statistics', 'Ad-hoc statistics')}
className={styles.statsIcon}
disabled={!singleKey}
onClick={showStats}
/>
)}
{singleKey && displayedFields.includes(keys[0]) && (
<IconButton
variant="primary"
tooltip={t('logs.log-line-details.fields.toggle-field-button.hide-this-field', 'Hide this field')}
name="eye"
onClick={hideField}
/>
)}
{singleKey && !displayedFields.includes(keys[0]) && (
<IconButton
tooltip={t(
'logs.log-line-details.fields.toggle-field-button.field-instead-message',
'Show this field instead of the message'
)}
name="eye"
onClick={showField}
/>
)}
<IconButton
variant={showFieldsStats ? 'primary' : 'secondary'}
name="signal"
tooltip={t('logs.log-line-details.fields.adhoc-statistics', 'Ad-hoc statistics')}
className="stats-button"
disabled={!singleKey}
onClick={showStats}
/>
</div>
</div>
)}
<div className={styles.label}>
@@ -388,6 +390,15 @@ const getFieldStyles = (theme: GrafanaTheme2) => ({
actions: css({
whiteSpace: 'nowrap',
}),
actionIcons: css({
display: 'flex',
justifyContent: 'space-between',
paddingRight: 2,
}),
statsIcon: css({
margin: 0,
paddingRight: 4,
}),
label: css({
paddingRight: theme.spacing(1),
overflowWrap: 'break-word',
@@ -235,6 +235,7 @@ export const LogLineDetailsHeader = ({ focusLogLine, log, search, onSearch }: Pr
tabIndex={0}
/>
)}
<div className={`${styles.divider} ${styles.dividerMargin}`} />
<IconButton
name={detailsMode === 'inline' ? 'web-section' : 'gf-layout-simple'}
tooltip={
@@ -244,9 +245,11 @@ export const LogLineDetailsHeader = ({ focusLogLine, log, search, onSearch }: Pr
}
onClick={toggleDetailsMode}
/>
<div className={styles.divider} />
<IconButton
name="times"
tooltip={t('logs.log-line-details.close', 'Close log details')}
variant="primary"
onClick={closeDetails}
/>
</div>
@@ -280,6 +283,7 @@ const getStyles = (theme: GrafanaTheme2, mode: LogLineDetailsMode, wrapLogMessag
display: 'flex',
gap: theme.spacing(1),
paddingLeft: theme.spacing(1),
alignContent: 'center',
}),
copyLogButton: css({
padding: 0,
@@ -293,4 +297,12 @@ const getStyles = (theme: GrafanaTheme2, mode: LogLineDetailsMode, wrapLogMessag
componentWrapper: css({
padding: theme.spacing(0, 1, 1, 1),
}),
divider: css({
width: 1,
borderRight: `solid 1px ${theme.colors.border.medium}`,
height: theme.spacing(2.25),
}),
dividerMargin: css({
marginRight: theme.spacing(0.5),
}),
});
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { useLogDetailsContext } from './LogDetailsContext';
import { useLogListSearchContext } from './LogListSearchContext';
/**
@@ -11,6 +12,7 @@ import { useLogListSearchContext } from './LogListSearchContext';
export const useKeyBindings = () => {
const { hideSearch, searchVisible, showSearch } = useLogListSearchContext();
const { showDetails, detailsMode, closeDetails } = useLogDetailsContext();
useEffect(() => {
function handleToggleSearch(event: KeyboardEvent) {
@@ -24,10 +26,13 @@ export const useKeyBindings = () => {
if (event.key === 'Escape' && searchVisible) {
hideSearch();
}
if (event.key === 'Escape' && showDetails.length > 0 && detailsMode === 'sidebar') {
closeDetails();
}
}
document.addEventListener('keydown', handleToggleSearch);
return () => {
document.removeEventListener('keydown', handleToggleSearch);
};
});
}, [closeDetails, detailsMode, hideSearch, searchVisible, showDetails.length, showSearch]);
};