mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Logs Panel: Table UI - Multiple dataframes (queries) (#77589)
* Add dropdown to logs table UI to allow users to select which dataFrame to visualize in the table
This commit is contained in:
parent
a01f8c5b42
commit
1dec96ebe7
@ -51,6 +51,8 @@ export interface ExploreLogsPanelState {
|
||||
id?: string;
|
||||
columns?: Record<number, string>;
|
||||
visualisationType?: 'table' | 'logs';
|
||||
// Used for logs table visualisation, contains the refId of the dataFrame that is currently visualized
|
||||
refId?: string;
|
||||
}
|
||||
|
||||
export interface SplitOpenOptions<T extends AnyQuery = AnyQuery> {
|
||||
|
@ -183,6 +183,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
|
||||
...state.panelsState.logs,
|
||||
columns: logsPanelState.columns ?? this.props.panelState?.logs?.columns,
|
||||
visualisationType: logsPanelState.visualisationType ?? this.state.visualisationType,
|
||||
refId: logsPanelState.refId ?? this.props.panelState?.logs?.refId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ const getComponent = (partialProps?: Partial<ComponentProps<typeof LogsTable>>,
|
||||
to: toUtc('2019-01-01 16:00:00'),
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
}}
|
||||
logsFrames={[logs ?? testDataFrame]}
|
||||
dataFrame={logs ?? testDataFrame}
|
||||
{...partialProps}
|
||||
/>
|
||||
);
|
||||
@ -121,7 +121,7 @@ describe('LogsTable', () => {
|
||||
|
||||
it('should render extracted labels as columns (elastic)', async () => {
|
||||
setup({
|
||||
logsFrames: [getMockElasticFrame()],
|
||||
dataFrame: getMockElasticFrame(),
|
||||
columnsWithMeta: {
|
||||
counter: { active: true, percentOfLinesWithLabel: 3 },
|
||||
level: { active: true, percentOfLinesWithLabel: 3 },
|
||||
|
@ -26,7 +26,7 @@ import { getFieldLinksForExplore } from '../utils/links';
|
||||
import { fieldNameMeta } from './LogsTableWrap';
|
||||
|
||||
interface Props {
|
||||
logsFrames: DataFrame[];
|
||||
dataFrame: DataFrame;
|
||||
width: number;
|
||||
timeZone: string;
|
||||
splitOpen: SplitOpen;
|
||||
@ -39,12 +39,9 @@ interface Props {
|
||||
}
|
||||
|
||||
export function LogsTable(props: Props) {
|
||||
const { timeZone, splitOpen, range, logsSortOrder, width, logsFrames, columnsWithMeta } = props;
|
||||
const { timeZone, splitOpen, range, logsSortOrder, width, dataFrame, columnsWithMeta } = props;
|
||||
const [tableFrame, setTableFrame] = useState<DataFrame | undefined>(undefined);
|
||||
|
||||
// Only a single frame (query) is supported currently
|
||||
const logFrameRaw = logsFrames ? logsFrames[0] : undefined;
|
||||
|
||||
const prepareTableFrame = useCallback(
|
||||
(frame: DataFrame): DataFrame => {
|
||||
if (!frame.length) {
|
||||
@ -99,15 +96,13 @@ export function LogsTable(props: Props) {
|
||||
useEffect(() => {
|
||||
const prepare = async () => {
|
||||
// Parse the dataframe to a logFrame
|
||||
const logsFrame = logFrameRaw ? parseLogsFrame(logFrameRaw) : undefined;
|
||||
const logsFrame = dataFrame ? parseLogsFrame(dataFrame) : undefined;
|
||||
|
||||
if (!logFrameRaw || !logsFrame) {
|
||||
if (!logsFrame) {
|
||||
setTableFrame(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let dataFrame = logFrameRaw;
|
||||
|
||||
// create extract JSON transformation for every field that is `json.RawMessage`
|
||||
const transformations: Array<DataTransformerConfig | CustomTransformOperator> = extractFields(dataFrame);
|
||||
|
||||
@ -139,7 +134,7 @@ export function LogsTable(props: Props) {
|
||||
}
|
||||
};
|
||||
prepare();
|
||||
}, [columnsWithMeta, logFrameRaw, logsSortOrder, prepareTableFrame]);
|
||||
}, [columnsWithMeta, dataFrame, logsSortOrder, prepareTableFrame]);
|
||||
|
||||
if (!tableFrame) {
|
||||
return null;
|
||||
@ -152,11 +147,11 @@ export function LogsTable(props: Props) {
|
||||
return;
|
||||
}
|
||||
if (operator === FILTER_FOR_OPERATOR) {
|
||||
onClickFilterLabel(key, value);
|
||||
onClickFilterLabel(key, value, dataFrame.refId);
|
||||
}
|
||||
|
||||
if (operator === FILTER_OUT_OPERATOR) {
|
||||
onClickFilterOutLabel(key, value);
|
||||
onClickFilterOutLabel(key, value, dataFrame.refId);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
@ -8,11 +8,12 @@ import {
|
||||
GrafanaTheme2,
|
||||
Labels,
|
||||
LogsSortOrder,
|
||||
SelectableValue,
|
||||
SplitOpen,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime/src';
|
||||
import { Themeable2 } from '@grafana/ui/';
|
||||
import { InlineField, Select, Themeable2 } from '@grafana/ui/';
|
||||
|
||||
import { parseLogsFrame } from '../../logs/logsFrame';
|
||||
|
||||
@ -44,6 +45,7 @@ type fieldNameMetaStore = Record<fieldName, fieldNameMeta>;
|
||||
|
||||
export function LogsTableWrap(props: Props) {
|
||||
const { logsFrames } = props;
|
||||
|
||||
// Save the normalized cardinality of each label
|
||||
const [columnsWithMeta, setColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined);
|
||||
|
||||
@ -51,7 +53,13 @@ export function LogsTableWrap(props: Props) {
|
||||
const [filteredColumnsWithMeta, setFilteredColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined);
|
||||
|
||||
const height = getTableHeight();
|
||||
const dataFrame = logsFrames[0];
|
||||
|
||||
// The current dataFrame containing the refId of the current query
|
||||
const [currentDataFrame, setCurrentDataFrame] = useState<DataFrame>(
|
||||
logsFrames.find((f) => f.refId === props?.panelState?.refId) ?? logsFrames[0]
|
||||
);
|
||||
// The refId of the current frame being displayed
|
||||
const currentFrameRefId = currentDataFrame.refId;
|
||||
|
||||
const getColumnsFromProps = useCallback(
|
||||
(fieldNames: fieldNameMetaStore) => {
|
||||
@ -100,11 +108,11 @@ export function LogsTableWrap(props: Props) {
|
||||
*/
|
||||
useEffect(() => {
|
||||
// If the data frame is empty, there's nothing to viz, it could mean the user has unselected all columns
|
||||
if (!dataFrame.length) {
|
||||
if (!currentDataFrame.length) {
|
||||
return;
|
||||
}
|
||||
const numberOfLogLines = dataFrame ? dataFrame.length : 0;
|
||||
const logsFrame = parseLogsFrame(dataFrame);
|
||||
const numberOfLogLines = currentDataFrame ? currentDataFrame.length : 0;
|
||||
const logsFrame = parseLogsFrame(currentDataFrame);
|
||||
const labels = logsFrame?.getLogFrameLabelsAsLabels();
|
||||
|
||||
const otherFields = [];
|
||||
@ -197,7 +205,7 @@ export function LogsTableWrap(props: Props) {
|
||||
setColumnsWithMeta(pendingLabelState);
|
||||
|
||||
// The panel state is updated when the user interacts with the multi-select sidebar
|
||||
}, [dataFrame, getColumnsFromProps]);
|
||||
}, [currentDataFrame, getColumnsFromProps]);
|
||||
|
||||
if (!columnsWithMeta) {
|
||||
return null;
|
||||
@ -259,6 +267,7 @@ export function LogsTableWrap(props: Props) {
|
||||
// Only include active filters
|
||||
.filter((key) => pendingLabelState[key]?.active)
|
||||
),
|
||||
refId: currentDataFrame.refId,
|
||||
visualisationType: 'table',
|
||||
};
|
||||
|
||||
@ -300,34 +309,69 @@ export function LogsTableWrap(props: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const onFrameSelectorChange = (value: SelectableValue<string>) => {
|
||||
const matchingDataFrame = logsFrames.find((frame) => frame.refId === value.value);
|
||||
if (matchingDataFrame) {
|
||||
setCurrentDataFrame(logsFrames.find((frame) => frame.refId === value.value) ?? logsFrames[0]);
|
||||
}
|
||||
props.updatePanelState({ refId: value.value });
|
||||
};
|
||||
|
||||
const sidebarWidth = 220;
|
||||
const totalWidth = props.width;
|
||||
const tableWidth = totalWidth - sidebarWidth;
|
||||
const styles = getStyles(props.theme, height, sidebarWidth);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<section className={styles.sidebar}>
|
||||
<LogsColumnSearch onChange={onSearchInputChange} />
|
||||
<LogsTableMultiSelect
|
||||
toggleColumn={toggleColumn}
|
||||
filteredColumnsWithMeta={filteredColumnsWithMeta}
|
||||
<>
|
||||
<div>
|
||||
{logsFrames.length > 1 && (
|
||||
<div>
|
||||
<InlineField
|
||||
label="Select query"
|
||||
htmlFor="explore_logs_table_frame_selector"
|
||||
labelWidth={22}
|
||||
tooltip="Select a query to visualize in the table."
|
||||
>
|
||||
<Select
|
||||
inputId={'explore_logs_table_frame_selector'}
|
||||
aria-label={'Select query by name'}
|
||||
value={currentFrameRefId}
|
||||
options={logsFrames.map((frame) => {
|
||||
return {
|
||||
label: frame.refId,
|
||||
value: frame.refId,
|
||||
};
|
||||
})}
|
||||
onChange={onFrameSelectorChange}
|
||||
/>
|
||||
</InlineField>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.wrapper}>
|
||||
<section className={styles.sidebar}>
|
||||
<LogsColumnSearch onChange={onSearchInputChange} />
|
||||
<LogsTableMultiSelect
|
||||
toggleColumn={toggleColumn}
|
||||
filteredColumnsWithMeta={filteredColumnsWithMeta}
|
||||
columnsWithMeta={columnsWithMeta}
|
||||
/>
|
||||
</section>
|
||||
<LogsTable
|
||||
onClickFilterLabel={props.onClickFilterLabel}
|
||||
onClickFilterOutLabel={props.onClickFilterOutLabel}
|
||||
logsSortOrder={props.logsSortOrder}
|
||||
range={props.range}
|
||||
splitOpen={props.splitOpen}
|
||||
timeZone={props.timeZone}
|
||||
width={tableWidth}
|
||||
dataFrame={currentDataFrame}
|
||||
columnsWithMeta={columnsWithMeta}
|
||||
height={height}
|
||||
/>
|
||||
</section>
|
||||
<LogsTable
|
||||
onClickFilterLabel={props.onClickFilterLabel}
|
||||
onClickFilterOutLabel={props.onClickFilterOutLabel}
|
||||
logsSortOrder={props.logsSortOrder}
|
||||
range={props.range}
|
||||
splitOpen={props.splitOpen}
|
||||
timeZone={props.timeZone}
|
||||
width={tableWidth}
|
||||
logsFrames={logsFrames}
|
||||
columnsWithMeta={columnsWithMeta}
|
||||
height={height}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -347,9 +391,6 @@ function getStyles(theme: GrafanaTheme2, height: number, width: number) {
|
||||
width: width,
|
||||
paddingRight: theme.spacing(1.5),
|
||||
}),
|
||||
|
||||
labelCount: css({}),
|
||||
checkbox: css({}),
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user