Logs Panel: Table UI - Time range changes not reflecting in table (#78500)

* add hook to update selected dataframe when time range changes
This commit is contained in:
Galen Kistler 2023-11-22 07:15:29 -06:00 committed by GitHub
parent d0b1ceb7d4
commit 392a4342a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 18 deletions

View File

@ -1,4 +1,4 @@
import { css } from '@emotion/css';
import { css, cx } from '@emotion/css';
import { capitalize } from 'lodash';
import memoizeOne from 'memoize-one';
import React, { createRef, PureComponent } from 'react';
@ -59,7 +59,7 @@ import { changePanelState } from '../state/explorePane';
import { LogsMetaRow } from './LogsMetaRow';
import LogsNavigation from './LogsNavigation';
import { LogsTableWrap } from './LogsTableWrap';
import { getLogsTableHeight, LogsTableWrap } from './LogsTableWrap';
import { LogsVolumePanelList } from './LogsVolumePanelList';
import { SETTINGS_KEYS } from './utils/logs';
@ -543,7 +543,8 @@ class UnthemedLogs extends PureComponent<Props, State> {
contextRow,
} = this.state;
const styles = getStyles(theme, wrapLogMessage);
const tableHeight = getLogsTableHeight();
const styles = getStyles(theme, wrapLogMessage, tableHeight);
const hasData = logRows && logRows.length > 0;
const hasUnescapedContent = this.checkUnescapedContent(logRows);
@ -727,7 +728,9 @@ class UnthemedLogs extends PureComponent<Props, State> {
clearDetectedFields={this.clearDetectedFields}
/>
</div>
<div className={styles.logsSection}>
<div
className={cx(styles.logsSection, this.state.visualisationType === 'table' ? styles.logsTable : undefined)}
>
{this.state.visualisationType === 'table' && hasData && (
<div className={styles.logRows} data-testid="logRowsTable">
{/* Width should be full width minus logs navigation and padding */}
@ -821,7 +824,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
export const Logs = withTheme2(UnthemedLogs);
const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean) => {
const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean, tableHeight: number) => {
return {
noData: css`
> * {
@ -858,6 +861,9 @@ const getStyles = (theme: GrafanaTheme2, wrapLogMessage: boolean) => {
flex-direction: row;
justify-content: space-between;
`,
logsTable: css({
maxHeight: `${tableHeight}px`,
}),
logRows: css`
overflow-x: ${scrollableLogsContainer ? 'scroll;' : `${wrapLogMessage ? 'unset' : 'scroll'};`}
overflow-y: visible;

View File

@ -139,6 +139,27 @@ describe('LogsTableWrap', () => {
});
});
it('should update selected dataframe when dataFrames update', async () => {
const initialProps = { logsFrames: [getMockLokiFrameDataPlane(undefined, 3)] };
const render = setup(initialProps);
await waitFor(() => {
const rows = render.getAllByRole('row');
expect(rows.length).toBe(4);
});
render.rerender(
getComponent({
...initialProps,
logsFrames: [getMockLokiFrameDataPlane(undefined, 4)],
})
);
await waitFor(() => {
const rows = render.getAllByRole('row');
expect(rows.length).toBe(5);
});
});
it('search input should search matching columns (dataplane)', async () => {
config.featureToggles.lokiLogsDataplane = true;

View File

@ -52,14 +52,13 @@ export function LogsTableWrap(props: Props) {
// Filtered copy of columnsWithMeta that only includes matching results
const [filteredColumnsWithMeta, setFilteredColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined);
const height = getTableHeight();
const height = getLogsTableHeight();
const panelStateRefId = props?.panelState?.refId;
// 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]
logsFrames.find((f) => f.refId === panelStateRefId) ?? logsFrames[0]
);
// The refId of the current frame being displayed
const currentFrameRefId = currentDataFrame.refId;
const getColumnsFromProps = useCallback(
(fieldNames: fieldNameMetaStore) => {
@ -76,6 +75,16 @@ export function LogsTableWrap(props: Props) {
[props.panelState?.columns]
);
/**
* When logs frame updates (e.g. query|range changes), we need to set the selected frame to state
*/
useEffect(() => {
const newFrame = logsFrames.find((f) => f.refId === panelStateRefId) ?? logsFrames[0];
if (newFrame) {
setCurrentDataFrame(newFrame);
}
}, [logsFrames, panelStateRefId]);
/**
* Keeps the filteredColumnsWithMeta state in sync with the columnsWithMeta state,
* which can be updated by explore browser history state changes
@ -336,7 +345,7 @@ export function LogsTableWrap(props: Props) {
<Select
inputId={'explore_logs_table_frame_selector'}
aria-label={'Select query by name'}
value={currentFrameRefId}
value={currentDataFrame.refId}
options={logsFrames.map((frame) => {
return {
label: frame.refId,
@ -394,7 +403,7 @@ function getStyles(theme: GrafanaTheme2, height: number, width: number) {
};
}
const getTableHeight = () => {
export const getLogsTableHeight = () => {
// Instead of making the height of the table based on the content (like in the table panel itself), let's try to use the vertical space that is available.
// Since this table is in explore, we can expect the user to be running multiple queries that return disparate numbers of rows and labels in the same session
// Also changing the height of the table between queries can be and cause content to jump, so we'll set a minimum height of 500px, and a max based on the innerHeight

View File

@ -52,7 +52,10 @@ export const getMockLokiFrame = (override?: Partial<DataFrame>) => {
};
return { ...testDataFrame, ...override };
};
export const getMockLokiFrameDataPlane = (override?: Partial<DataFrame>): DataFrame => {
export const getMockLokiFrameDataPlane = (override?: Partial<DataFrame>, howManyValues = 3): DataFrame => {
if (howManyValues > 6) {
throw new Error('only 6 or fewer values are supported');
}
const testDataFrame: DataFrame = {
meta: {
type: DataFrameType.LogLines,
@ -72,25 +75,53 @@ export const getMockLokiFrameDataPlane = (override?: Partial<DataFrame>): DataFr
config: {},
name: 'timestamp',
type: FieldType.time,
values: ['2019-01-01 10:00:00', '2019-01-01 11:00:00', '2019-01-01 12:00:00'],
values: [
'2019-01-01 10:00:00',
'2019-01-01 11:00:00',
'2019-01-01 12:00:00',
'2019-01-01 13:00:00',
'2019-01-01 14:00:00',
'2019-01-01 15:00:00',
].slice(0, howManyValues),
},
{
config: {},
name: 'body',
type: FieldType.string,
values: ['log message 1', 'log message 2', 'log message 3'],
values: [
'log message 1',
'log message 2',
'log message 3',
'log message 4',
'log message 5',
'log message 6',
].slice(0, howManyValues),
},
{
config: {},
name: 'tsNs',
type: FieldType.string,
values: ['1697561006608165746', '1697560998869868000', '1697561010006578474'],
values: [
'1697561006608165746',
'1697560998869868000',
'1697561010006578474',
'1697561010006578475',
'1697561010006578476',
'1697561010006578477',
].slice(0, howManyValues),
},
{
config: {},
name: 'id',
type: FieldType.string,
values: ['1697561006608165746_b4cc4b72', '1697560998869868000_eeb96c0f', '1697561010006578474_ad5e2e5a'],
values: [
'1697561006608165746_b4cc4b72',
'1697560998869868000_eeb96c0f',
'1697561010006578474_ad5e2e5a',
'1697561010006578474_ad5e2e5b',
'1697561010006578474_ad5e2e5c',
'1697561010006578474_ad5e2e5d',
].slice(0, howManyValues),
},
{
config: {
@ -103,10 +134,10 @@ export const getMockLokiFrameDataPlane = (override?: Partial<DataFrame>): DataFr
},
name: 'traceID',
type: FieldType.string,
values: ['trace1', 'trace2', 'trace3'],
values: ['trace1', 'trace2', 'trace3', 'trace4', 'trace5', 'trace6'].slice(0, howManyValues),
},
],
length: 3,
length: howManyValues,
};
return { ...testDataFrame, ...override };
};