Logs Table: Add new Controls component to Explore (#103467)
* Add logs options to Table --------- Co-authored-by: Matias Chomicki <matyax@gmail.com>
This commit is contained in:
co-authored by
Matias Chomicki
parent
71c66acb2d
commit
369dc28ca2
@@ -213,7 +213,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const [contextOpen, setContextOpen] = useState<boolean>(false);
|
||||
const [contextRow, setContextRow] = useState<LogRowModel | undefined>(undefined);
|
||||
const [pinLineButtonTooltipTitle, setPinLineButtonTooltipTitle] = useState<PopoverContent>(PINNED_LOGS_MESSAGE);
|
||||
const [visualisationType, setVisualisationType] = useState<LogsVisualisationType | undefined>(
|
||||
const [visualisationType, setVisualisationType] = useState<LogsVisualisationType>(
|
||||
panelState?.logs?.visualisationType ?? getDefaultVisualisationType()
|
||||
);
|
||||
const logsContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -229,7 +229,8 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const logLevelsRef = useRef<LogLevel[] | null>(null);
|
||||
|
||||
const tableHeight = getLogsTableHeight();
|
||||
const styles = getStyles(theme, wrapLogMessage, tableHeight);
|
||||
const setWrapperLineWrapStyles = wrapLogMessage || visualisationType === 'table';
|
||||
const styles = getStyles(theme, setWrapperLineWrapStyles, tableHeight);
|
||||
const hasData = logRows && logRows.length > 0;
|
||||
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
|
||||
|
||||
@@ -1007,7 +1008,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className={cx(styles.logsSection, visualisationType === 'table' ? styles.logsTable : undefined)}>
|
||||
{visualisationType === 'table' && hasData && (
|
||||
{!config.featureToggles.logsPanelControls && visualisationType === 'table' && hasData && (
|
||||
<div className={styles.logRows} data-testid="logRowsTable">
|
||||
{/* Width should be full width minus logs navigation and padding */}
|
||||
<LogsTableWrap
|
||||
@@ -1026,9 +1027,16 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{config.featureToggles.logsPanelControls && visualisationType === 'logs' && hasData && (
|
||||
{config.featureToggles.logsPanelControls && hasData && (
|
||||
<div className={styles.logRowsWrapper} data-testid="logRows">
|
||||
<ControlledLogRows
|
||||
logsTableFrames={props.logsFrames}
|
||||
width={width}
|
||||
updatePanelState={updatePanelState}
|
||||
panelState={panelState?.logs}
|
||||
datasourceType={props.datasourceType}
|
||||
splitOpen={splitOpen}
|
||||
visualisationType={visualisationType}
|
||||
loading={loading}
|
||||
loadMoreLogs={infiniteScrollAvailable ? loadMoreLogs : undefined}
|
||||
range={props.range}
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { AbsoluteTimeRange, CoreApp, EventBusSrv, LogsMetaItem, LogsSortOrder, TimeRange } from '@grafana/data';
|
||||
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';
|
||||
@@ -11,16 +24,25 @@ import { LogListContextProvider, useLogListContext } from './panel/LogListContex
|
||||
import { LogListControls } from './panel/LogListControls';
|
||||
import { ScrollToLogsEvent } from './panel/virtualization';
|
||||
|
||||
interface ControlledLogRowsProps extends Omit<Props, 'scrollElement'> {
|
||||
export interface ControlledLogRowsProps extends Omit<Props, 'scrollElement'> {
|
||||
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 | undefined;
|
||||
updatePanelState: (panelState: Partial<ExploreLogsPanelState>) => void;
|
||||
datasourceType?: string;
|
||||
width: number;
|
||||
logsTableFrames: DataFrame[] | undefined;
|
||||
}
|
||||
|
||||
type LogRowsComponentProps = Omit<
|
||||
export type LogRowsComponentProps = Omit<
|
||||
ControlledLogRowsProps,
|
||||
'app' | 'dedupStrategy' | 'showLabels' | 'showTime' | 'logsSortOrder' | 'prettifyLogMessage' | 'wrapLogMessage'
|
||||
>;
|
||||
@@ -54,7 +76,8 @@ export const ControlledLogRows = ({
|
||||
onLogOptionsChange={onLogOptionsChange}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
>
|
||||
<LogRowsComponent {...rest} deduplicatedRows={deduplicatedRows} />
|
||||
{rest.visualisationType === 'logs' && <LogRowsComponent {...rest} deduplicatedRows={deduplicatedRows} />}
|
||||
{rest.visualisationType === 'table' && <ControlledLogsTable {...rest} />}
|
||||
</LogListContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { EventBusSrv, GrafanaTheme2 } from '@grafana/data';
|
||||
import { useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { LogsTableWrap } from '../../explore/Logs/LogsTableWrap';
|
||||
|
||||
import { LogRowsComponentProps } from './ControlledLogRows';
|
||||
import { useLogListContext } from './panel/LogListContext';
|
||||
import { LogListControls } from './panel/LogListControls';
|
||||
|
||||
export const ControlledLogsTable = ({
|
||||
loading,
|
||||
loadMoreLogs,
|
||||
deduplicatedRows = [],
|
||||
range,
|
||||
splitOpen,
|
||||
onClickFilterLabel,
|
||||
onClickFilterOutLabel,
|
||||
panelState,
|
||||
datasourceType,
|
||||
updatePanelState,
|
||||
width,
|
||||
logsTableFrames,
|
||||
visualisationType,
|
||||
...rest
|
||||
}: LogRowsComponentProps) => {
|
||||
const { sortOrder } = useLogListContext();
|
||||
const eventBus = useMemo(() => new EventBusSrv(), []);
|
||||
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
return (
|
||||
<div className={styles.logRowsContainer}>
|
||||
<div className={styles.logRows} data-testid="logRowsTable">
|
||||
{/* Width should be full width minus logs navigation and padding */}
|
||||
<LogsTableWrap
|
||||
logsSortOrder={sortOrder}
|
||||
range={range}
|
||||
splitOpen={splitOpen}
|
||||
timeZone={rest.timeZone}
|
||||
width={width - 45}
|
||||
logsFrames={logsTableFrames ?? []}
|
||||
onClickFilterLabel={onClickFilterLabel}
|
||||
onClickFilterOutLabel={onClickFilterOutLabel}
|
||||
panelState={panelState}
|
||||
theme={theme}
|
||||
updatePanelState={updatePanelState}
|
||||
datasourceType={datasourceType}
|
||||
/>
|
||||
</div>
|
||||
<LogListControls eventBus={eventBus} visualisationType={visualisationType} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
logRows: css({
|
||||
overflowY: 'visible',
|
||||
width: '100%',
|
||||
}),
|
||||
logRowsContainer: css({
|
||||
display: 'flex',
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -2,19 +2,13 @@ import { css } from '@emotion/css';
|
||||
import { capitalize } from 'lodash';
|
||||
import { MouseEvent, useCallback, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
CoreApp,
|
||||
EventBus,
|
||||
GrafanaTheme2,
|
||||
LogLevel,
|
||||
LogsDedupDescription,
|
||||
LogsDedupStrategy,
|
||||
LogsSortOrder,
|
||||
} from '@grafana/data';
|
||||
import { CoreApp, EventBus, LogLevel, LogsDedupDescription, LogsDedupStrategy, LogsSortOrder } from '@grafana/data';
|
||||
import { GrafanaTheme2 } from '@grafana/data/';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { Dropdown, IconButton, Menu, useStyles2 } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
|
||||
import { LogsVisualisationType } from '../../../explore/Logs/Logs';
|
||||
import { DownloadFormat } from '../../utils';
|
||||
|
||||
import { useLogListContext } from './LogListContext';
|
||||
@@ -22,6 +16,7 @@ import { ScrollToLogsEvent } from './virtualization';
|
||||
|
||||
type Props = {
|
||||
eventBus: EventBus;
|
||||
visualisationType?: LogsVisualisationType;
|
||||
};
|
||||
|
||||
const DEDUP_OPTIONS = [
|
||||
@@ -40,7 +35,7 @@ const FILTER_LEVELS: LogLevel[] = [
|
||||
LogLevel.critical,
|
||||
];
|
||||
|
||||
export const LogListControls = ({ eventBus }: Props) => {
|
||||
export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const {
|
||||
app,
|
||||
@@ -204,14 +199,16 @@ export const LogListControls = ({ eventBus }: Props) => {
|
||||
|
||||
return (
|
||||
<div className={styles.navContainer}>
|
||||
<IconButton
|
||||
name="arrow-down"
|
||||
className={styles.controlButton}
|
||||
variant="secondary"
|
||||
onClick={onScrollToBottomClick}
|
||||
tooltip={t('logs.logs-controls.scroll-bottom', 'Scroll to bottom')}
|
||||
size="lg"
|
||||
/>
|
||||
{visualisationType === 'logs' && (
|
||||
<IconButton
|
||||
name="arrow-down"
|
||||
className={styles.controlButton}
|
||||
variant="secondary"
|
||||
onClick={onScrollToBottomClick}
|
||||
tooltip={t('logs.logs-controls.scroll-bottom', 'Scroll to bottom')}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
{!inDashboard ? (
|
||||
<>
|
||||
<IconButton
|
||||
@@ -225,87 +222,95 @@ export const LogListControls = ({ eventBus }: Props) => {
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
<Dropdown overlay={deduplicationMenu} placement="auto-end">
|
||||
<IconButton
|
||||
name={'filter'}
|
||||
className={dedupStrategy !== LogsDedupStrategy.none ? styles.controlButtonActive : styles.controlButton}
|
||||
tooltip={t('logs.logs-controls.deduplication', 'Deduplication')}
|
||||
size="lg"
|
||||
/>
|
||||
</Dropdown>
|
||||
<Dropdown overlay={filterLevelsMenu} placement="auto-end">
|
||||
<IconButton
|
||||
name={'gf-logs'}
|
||||
className={filterLevels && filterLevels.length > 0 ? styles.controlButtonActive : styles.controlButton}
|
||||
tooltip={t('logs.logs-controls.display-level', 'Display levels')}
|
||||
size="lg"
|
||||
/>
|
||||
</Dropdown>
|
||||
<IconButton
|
||||
name="clock-nine"
|
||||
aria-pressed={showTime}
|
||||
className={showTime ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onShowTimestampsClick}
|
||||
tooltip={
|
||||
showTime
|
||||
? t('logs.logs-controls.hide-timestamps', 'Hide timestamps')
|
||||
: t('logs.logs-controls.show-timestamps', 'Show timestamps')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
{showUniqueLabels !== undefined && (
|
||||
<IconButton
|
||||
name="tag-alt"
|
||||
aria-pressed={showUniqueLabels}
|
||||
className={showUniqueLabels ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onShowUniqueLabelsClick}
|
||||
tooltip={
|
||||
showUniqueLabels
|
||||
? t('logs.logs-controls.hide-unique-labels', 'Hide unique labels')
|
||||
: t('logs.logs-controls.show-unique-labels', 'Show unique labels')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
name="wrap-text"
|
||||
className={wrapLogMessage ? styles.controlButtonActive : styles.controlButton}
|
||||
aria-pressed={wrapLogMessage}
|
||||
onClick={onWrapLogMessageClick}
|
||||
tooltip={
|
||||
wrapLogMessage
|
||||
? t('logs.logs-controls.unwrap-lines', 'Unwrap lines')
|
||||
: t('logs.logs-controls.wrap-lines', 'Wrap lines')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
{prettifyJSON !== undefined && (
|
||||
<IconButton
|
||||
name="brackets-curly"
|
||||
aria-pressed={prettifyJSON}
|
||||
className={prettifyJSON ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onSetPrettifyJSONClick}
|
||||
tooltip={
|
||||
prettifyJSON
|
||||
? t('logs.logs-controls.disable-prettify-json', 'Collapse JSON logs')
|
||||
: t('logs.logs-controls.prettify-json', 'Expand JSON logs')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
{syntaxHighlighting !== undefined && (
|
||||
<IconButton
|
||||
name="brackets-curly"
|
||||
className={syntaxHighlighting ? styles.controlButtonActive : styles.controlButton}
|
||||
aria-pressed={syntaxHighlighting}
|
||||
onClick={onSyntaxHightlightingClick}
|
||||
tooltip={
|
||||
syntaxHighlighting
|
||||
? t('logs.logs-controls.disable-highlighting', 'Disable highlighting')
|
||||
: t('logs.logs-controls.enable-highlighting', 'Enable highlighting')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
{visualisationType === 'logs' && (
|
||||
<>
|
||||
<Dropdown overlay={deduplicationMenu} placement="auto-end">
|
||||
<IconButton
|
||||
name={'filter'}
|
||||
className={
|
||||
dedupStrategy !== LogsDedupStrategy.none ? styles.controlButtonActive : styles.controlButton
|
||||
}
|
||||
tooltip={t('logs.logs-controls.deduplication', 'Deduplication')}
|
||||
size="lg"
|
||||
/>
|
||||
</Dropdown>
|
||||
<Dropdown overlay={filterLevelsMenu} placement="auto-end">
|
||||
<IconButton
|
||||
name={'gf-logs'}
|
||||
className={
|
||||
filterLevels && filterLevels.length > 0 ? styles.controlButtonActive : styles.controlButton
|
||||
}
|
||||
tooltip={t('logs.logs-controls.display-level', 'Display levels')}
|
||||
size="lg"
|
||||
/>
|
||||
</Dropdown>
|
||||
<IconButton
|
||||
name="clock-nine"
|
||||
aria-pressed={showTime}
|
||||
className={showTime ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onShowTimestampsClick}
|
||||
tooltip={
|
||||
showTime
|
||||
? t('logs.logs-controls.hide-timestamps', 'Hide timestamps')
|
||||
: t('logs.logs-controls.show-timestamps', 'Show timestamps')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
{showUniqueLabels !== undefined && (
|
||||
<IconButton
|
||||
name="tag-alt"
|
||||
aria-pressed={showUniqueLabels}
|
||||
className={showUniqueLabels ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onShowUniqueLabelsClick}
|
||||
tooltip={
|
||||
showUniqueLabels
|
||||
? t('logs.logs-controls.hide-unique-labels', 'Hide unique labels')
|
||||
: t('logs.logs-controls.show-unique-labels', 'Show unique labels')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
name="wrap-text"
|
||||
className={wrapLogMessage ? styles.controlButtonActive : styles.controlButton}
|
||||
aria-pressed={wrapLogMessage}
|
||||
onClick={onWrapLogMessageClick}
|
||||
tooltip={
|
||||
wrapLogMessage
|
||||
? t('logs.logs-controls.unwrap-lines', 'Unwrap lines')
|
||||
: t('logs.logs-controls.wrap-lines', 'Wrap lines')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
{prettifyJSON !== undefined && (
|
||||
<IconButton
|
||||
name="brackets-curly"
|
||||
aria-pressed={prettifyJSON}
|
||||
className={prettifyJSON ? styles.controlButtonActive : styles.controlButton}
|
||||
onClick={onSetPrettifyJSONClick}
|
||||
tooltip={
|
||||
prettifyJSON
|
||||
? t('logs.logs-controls.disable-prettify-json', 'Collapse JSON logs')
|
||||
: t('logs.logs-controls.prettify-json', 'Expand JSON logs')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
{syntaxHighlighting !== undefined && (
|
||||
<IconButton
|
||||
name="brackets-curly"
|
||||
className={syntaxHighlighting ? styles.controlButtonActive : styles.controlButton}
|
||||
aria-pressed={syntaxHighlighting}
|
||||
onClick={onSyntaxHightlightingClick}
|
||||
tooltip={
|
||||
syntaxHighlighting
|
||||
? t('logs.logs-controls.disable-highlighting', 'Disable highlighting')
|
||||
: t('logs.logs-controls.enable-highlighting', 'Enable highlighting')
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!config.exploreHideLogsDownload && (
|
||||
<>
|
||||
@@ -332,15 +337,17 @@ export const LogListControls = ({ eventBus }: Props) => {
|
||||
/>
|
||||
</Dropdown>
|
||||
)}
|
||||
<IconButton
|
||||
name="arrow-up"
|
||||
data-testid="scrollToTop"
|
||||
className={styles.scrollToTopButton}
|
||||
variant="secondary"
|
||||
onClick={onScrollToTopClick}
|
||||
tooltip={t('logs.logs-controls.scroll-top', 'Scroll to top')}
|
||||
size="lg"
|
||||
/>
|
||||
{visualisationType === 'logs' && (
|
||||
<IconButton
|
||||
name="arrow-up"
|
||||
data-testid="scrollToTop"
|
||||
className={styles.scrollToTopButton}
|
||||
variant="secondary"
|
||||
onClick={onScrollToTopClick}
|
||||
tooltip={t('logs.logs-controls.scroll-top', 'Scroll to top')}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user