grafana/public/app/features/logs/components/ControlledLogRows.tsx

232 lines
6.8 KiB
TypeScript
Raw Normal View History

import { css } from '@emotion/css';
import { useEffect, useMemo, useRef, forwardRef, useImperativeHandle, useCallback } from 'react';
import {
AbsoluteTimeRange,
CoreApp,
DataFrame,
EventBusSrv,
ExploreLogsPanelState,
LogsMetaItem,
LogsSortOrder,
SplitOpen,
TimeRange,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { LogsVisualisationType } from '../../explore/Logs/Logs';
import { ControlledLogsTable } from './ControlledLogsTable';
import { InfiniteScroll } from './InfiniteScroll';
import { LogRows, Props } from './LogRows';
import { LogListControlOptions } from './panel/LogList';
import { LogListContextProvider, useLogListContext } from './panel/LogListContext';
import { LogListControls } from './panel/LogListControls';
import { ScrollToLogsEvent } from './panel/virtualization';
export interface ControlledLogRowsProps extends Omit<Props, 'scrollElement'> {
hasUnescapedContent?: boolean;
loading: boolean;
logsMeta?: LogsMetaItem[];
loadMoreLogs?: (range: AbsoluteTimeRange) => void;
logOptionsStorageKey?: string;
onLogOptionsChange?: (option: keyof LogListControlOptions, value: string | boolean | string[]) => void;
range: TimeRange;
/** Props added for Table **/
visualisationType: LogsVisualisationType;
splitOpen?: SplitOpen;
panelState?: ExploreLogsPanelState;
updatePanelState?: (panelState: Partial<ExploreLogsPanelState>) => void;
datasourceType?: string;
width?: number;
logsTableFrames?: DataFrame[];
}
export type LogRowsComponentProps = Omit<
ControlledLogRowsProps,
'app' | 'dedupStrategy' | 'showLabels' | 'showTime' | 'logsSortOrder' | 'prettifyLogMessage' | 'wrapLogMessage'
>;
export const ControlledLogRows = forwardRef<HTMLDivElement | null, ControlledLogRowsProps>(
(
{
deduplicatedRows,
dedupStrategy,
hasUnescapedContent,
showLabels,
showTime,
logsMeta,
logOptionsStorageKey,
logsSortOrder,
prettifyLogMessage,
onLogOptionsChange,
wrapLogMessage,
...rest
}: ControlledLogRowsProps,
ref
) => {
return (
<LogListContextProvider
app={rest.app || CoreApp.Unknown}
displayedFields={[]}
dedupStrategy={dedupStrategy}
New Logs Panel: Add Log Details support (#105609) * Log list: add onclick listener * LogListContext: add basic details support * LogLineDetails: create component * Address lint issues * Log Details: make resizable and store size * LogListModel: add sampled and error support * LogDetails: pass more required props * LogLineContext: add interactive callbacks support * LogLineDetails: pass interactive callbacks * LogList: pass displayedFields callbacks * LogLine: move click listener * LogLineMenu: support showing details * LogLine: move onclick listener * LogListContext: remove displayedFields intermediation * i18n * LogListContext: abstract details shown function * LogLine: visually show expanded lines * LogDetails: remove min width for labels * LogLineDetails: add close button * LogList: add extra wrapper to get width * LogLineDetails: update logs size on resize * virtualization: update to new width reference * LogLine: check overflow on every re-render * LogList: debug virtualization when resizing * LogLineDetails: make it scrollable * LogListContext: make detailsWidth not undefined * Update tests with new attributes * LogLine: update collapsed state with container changes * LogLine: move cursor property to clickable styles * LogList: fix height recalculation when display options change * Logs: fix feature toggles support * Logs: more feature toggles adjustments * Lint * LogLine: support duplicates, hasError, and isSampled * Logs: debug feature flag combinations * i18n * Prettier * New Logs Panel: generate storage key for dashboards * Explore Logs: fix filtered levels * Logs Sample: integrate new panel * LogLine: fix unwrapped logs * Fix test * Update test * Logs panel: update test * Prettier * LogLine: update tests * LogLineMenu: update test * LogList: update unit test * processing: update test * virtualization: update unit test
2025-05-21 01:28:35 +08:00
enableLogDetails={false}
New Logs Panel: font size selector and Log Details size improvments (#106376) * LogList: create font size option * LogList: prevent option fontSize bouncing * LogListContext: fix stored container size bigger than container * LogList: render smaller font size * virtualization: adjust to variable font size * virtualization: strip white characters of at the start successive long lines * LogList: add font size to log size cache * LogList: use getters instead of fixed constants * LogLine: prevent unnecessary overflow calls * virtualization: strip ansi color codes before measuring * LogListDetails: adjust size on resize and give logs panel a min width * LogsPanel: add showControls as a dashboard option * virtualization: update test * virtualization: add small test case * processing: update font size * LogListControls: update test * Extract translations * Logs Panel: enable controls by default * LogListContext: update mock * ControlledLogRows: add missing prop * LogLine: remove height ref * LogList: dont touch the debounced function on successive calls * LogLine: update test * LogsPanel: make controls default to false again * LogsPanel: make controls default to false again * LogLineDetails: fix height resizing and make close button sticky * LogLine: memo log component * LogLineDetails: fix close button position * New Logs Panel: Add Popover Menu support (#106394) * LogList: add popover menu support * LogList: test popover menu * Chore: remove unnecessary optional chain op * LogLinedDetails: fix close button position with and without scroll
2025-06-10 17:59:01 +08:00
fontSize="default"
hasUnescapedContent={hasUnescapedContent}
logOptionsStorageKey={logOptionsStorageKey}
logs={deduplicatedRows ?? []}
logsMeta={logsMeta}
prettifyJSON={prettifyLogMessage}
showControls
showTime={showTime}
showUniqueLabels={showLabels}
sortOrder={logsSortOrder || LogsSortOrder.Descending}
onLogOptionsChange={onLogOptionsChange}
wrapLogMessage={wrapLogMessage}
>
{rest.visualisationType === 'logs' && (
<LogRowsComponent ref={ref} {...rest} deduplicatedRows={deduplicatedRows} />
)}
{rest.visualisationType === 'table' && rest.updatePanelState && <ControlledLogsTable {...rest} />}
</LogListContextProvider>
);
}
);
ControlledLogRows.displayName = 'ControlledLogRows';
const LogRowsComponent = forwardRef<HTMLDivElement | null, LogRowsComponentProps>(
(
{
loading,
loadMoreLogs,
deduplicatedRows = [],
range,
scrollIntoView: scrollIntoViewProp,
...rest
}: LogRowsComponentProps,
ref
) => {
const {
app,
dedupStrategy,
filterLevels,
forceEscape,
prettifyJSON,
sortOrder,
showTime,
showUniqueLabels,
wrapLogMessage,
} = useLogListContext();
const eventBus = useMemo(() => new EventBusSrv(), []);
const scrollElementRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) =>
handleScrollToEvent(e, scrollElementRef.current)
);
return () => subscription.unsubscribe();
}, [eventBus]);
useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(ref, () => scrollElementRef.current);
const filteredLogs = useMemo(
() =>
filterLevels.length === 0
? deduplicatedRows
: deduplicatedRows.filter((log) => filterLevels.includes(log.logLevel)),
[filterLevels, deduplicatedRows]
);
const scrollElementClassName = useMemo(() => {
if (ref) {
return styles.forwardedScrollableLogRows;
}
return config.featureToggles.logsInfiniteScrolling ? styles.scrollableLogRows : styles.logRows;
}, [ref]);
const scrollIntoView = useCallback(
(element: HTMLElement) => {
if (scrollIntoViewProp) {
scrollIntoViewProp(element);
return;
}
if (scrollElementRef.current) {
scrollElementRef.current.scroll({
behavior: 'smooth',
top: scrollElementRef.current.scrollTop + element.getBoundingClientRect().top - window.innerHeight / 2,
});
}
},
[scrollIntoViewProp]
);
return (
<div className={styles.logRowsContainer}>
<LogListControls eventBus={eventBus} />
<div ref={scrollElementRef} className={scrollElementClassName}>
<InfiniteScroll
loading={loading}
loadMoreLogs={loadMoreLogs}
range={range}
timeZone={rest.timeZone}
rows={filteredLogs}
scrollElement={scrollElementRef.current}
sortOrder={sortOrder}
>
<LogRows
{...rest}
app={app}
dedupStrategy={dedupStrategy}
deduplicatedRows={filteredLogs}
forceEscape={forceEscape}
logRows={filteredLogs}
logsSortOrder={sortOrder}
scrollElement={scrollElementRef.current}
prettifyLogMessage={Boolean(prettifyJSON)}
scrollIntoView={scrollIntoView}
showLabels={Boolean(showUniqueLabels)}
showTime={showTime}
wrapLogMessage={wrapLogMessage}
/>
</InfiniteScroll>
</div>
</div>
);
}
);
LogRowsComponent.displayName = 'LogRowsComponent';
function handleScrollToEvent(event: ScrollToLogsEvent, scrollElement: HTMLDivElement | null) {
if (event.payload.scrollTo === 'top') {
scrollElement?.scrollTo(0, 0);
} else if (scrollElement) {
scrollElement.scrollTo(0, scrollElement.scrollHeight);
}
}
const styles = {
scrollableLogRows: css({
overflowY: 'auto',
width: '100%',
maxHeight: '80vh',
}),
forwardedScrollableLogRows: css({
overflowY: 'auto',
width: '100%',
maxHeight: '100%',
}),
logRows: css({
overflowX: 'scroll',
overflowY: 'visible',
width: '100%',
}),
logRowsContainer: css({
display: 'flex',
flexDirection: 'row-reverse',
height: '100%',
}),
};