diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts index ea196a1fa03..f16e2d98ed9 100644 --- a/packages/grafana-data/src/types/explore.ts +++ b/packages/grafana-data/src/types/explore.ts @@ -51,6 +51,8 @@ export interface ExploreLogsPanelState { id?: string; columns?: Record; visualisationType?: 'table' | 'logs'; + // Used for logs table visualisation, contains the refId of the dataFrame that is currently visualized + refId?: string; } export interface SplitOpenOptions { diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 692e0976eba..0d44edaad47 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -183,6 +183,7 @@ class UnthemedLogs extends PureComponent { ...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, }) ); } diff --git a/public/app/features/explore/Logs/LogsTable.test.tsx b/public/app/features/explore/Logs/LogsTable.test.tsx index 93de51e84a4..06ba89a83ea 100644 --- a/public/app/features/explore/Logs/LogsTable.test.tsx +++ b/public/app/features/explore/Logs/LogsTable.test.tsx @@ -65,7 +65,7 @@ const getComponent = (partialProps?: Partial>, 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 }, diff --git a/public/app/features/explore/Logs/LogsTable.tsx b/public/app/features/explore/Logs/LogsTable.tsx index 08c39ca56f6..808b747df26 100644 --- a/public/app/features/explore/Logs/LogsTable.tsx +++ b/public/app/features/explore/Logs/LogsTable.tsx @@ -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(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 = 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); } }; diff --git a/public/app/features/explore/Logs/LogsTableWrap.tsx b/public/app/features/explore/Logs/LogsTableWrap.tsx index 5f7a2ff987b..4fbc12ebbc7 100644 --- a/public/app/features/explore/Logs/LogsTableWrap.tsx +++ b/public/app/features/explore/Logs/LogsTableWrap.tsx @@ -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; export function LogsTableWrap(props: Props) { const { logsFrames } = props; + // Save the normalized cardinality of each label const [columnsWithMeta, setColumnsWithMeta] = useState(undefined); @@ -51,7 +53,13 @@ export function LogsTableWrap(props: Props) { const [filteredColumnsWithMeta, setFilteredColumnsWithMeta] = useState(undefined); const height = getTableHeight(); - const dataFrame = logsFrames[0]; + + // The current dataFrame containing the refId of the current query + const [currentDataFrame, setCurrentDataFrame] = useState( + 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) => { + 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 ( -
-
- - +
+ {logsFrames.length > 1 && ( +
+ +