|
|
@ -1,6 +1,6 @@ |
|
|
|
import { css } from '@emotion/css'; |
|
|
|
import { css } from '@emotion/css'; |
|
|
|
import { debounce } from 'lodash'; |
|
|
|
import { debounce } from 'lodash'; |
|
|
|
import React, { useState, useEffect, useCallback } from 'react'; |
|
|
|
import React, { useCallback, useEffect, useState } from 'react'; |
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
|
import { |
|
|
|
DataFrame, |
|
|
|
DataFrame, |
|
|
@ -8,11 +8,12 @@ import { |
|
|
|
GrafanaTheme2, |
|
|
|
GrafanaTheme2, |
|
|
|
Labels, |
|
|
|
Labels, |
|
|
|
LogsSortOrder, |
|
|
|
LogsSortOrder, |
|
|
|
|
|
|
|
SelectableValue, |
|
|
|
SplitOpen, |
|
|
|
SplitOpen, |
|
|
|
TimeRange, |
|
|
|
TimeRange, |
|
|
|
} from '@grafana/data'; |
|
|
|
} from '@grafana/data'; |
|
|
|
import { reportInteraction } from '@grafana/runtime/src'; |
|
|
|
import { reportInteraction } from '@grafana/runtime/src'; |
|
|
|
import { Themeable2 } from '@grafana/ui/'; |
|
|
|
import { InlineField, Select, Themeable2 } from '@grafana/ui/'; |
|
|
|
|
|
|
|
|
|
|
|
import { parseLogsFrame } from '../../logs/logsFrame'; |
|
|
|
import { parseLogsFrame } from '../../logs/logsFrame'; |
|
|
|
|
|
|
|
|
|
|
@ -44,6 +45,7 @@ type fieldNameMetaStore = Record<fieldName, fieldNameMeta>; |
|
|
|
|
|
|
|
|
|
|
|
export function LogsTableWrap(props: Props) { |
|
|
|
export function LogsTableWrap(props: Props) { |
|
|
|
const { logsFrames } = props; |
|
|
|
const { logsFrames } = props; |
|
|
|
|
|
|
|
|
|
|
|
// Save the normalized cardinality of each label
|
|
|
|
// Save the normalized cardinality of each label
|
|
|
|
const [columnsWithMeta, setColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined); |
|
|
|
const [columnsWithMeta, setColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined); |
|
|
|
|
|
|
|
|
|
|
@ -51,7 +53,13 @@ export function LogsTableWrap(props: Props) { |
|
|
|
const [filteredColumnsWithMeta, setFilteredColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined); |
|
|
|
const [filteredColumnsWithMeta, setFilteredColumnsWithMeta] = useState<fieldNameMetaStore | undefined>(undefined); |
|
|
|
|
|
|
|
|
|
|
|
const height = getTableHeight(); |
|
|
|
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( |
|
|
|
const getColumnsFromProps = useCallback( |
|
|
|
(fieldNames: fieldNameMetaStore) => { |
|
|
|
(fieldNames: fieldNameMetaStore) => { |
|
|
@ -100,11 +108,11 @@ export function LogsTableWrap(props: Props) { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
// If the data frame is empty, there's nothing to viz, it could mean the user has unselected all columns
|
|
|
|
// 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; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
const numberOfLogLines = dataFrame ? dataFrame.length : 0; |
|
|
|
const numberOfLogLines = currentDataFrame ? currentDataFrame.length : 0; |
|
|
|
const logsFrame = parseLogsFrame(dataFrame); |
|
|
|
const logsFrame = parseLogsFrame(currentDataFrame); |
|
|
|
const labels = logsFrame?.getLogFrameLabelsAsLabels(); |
|
|
|
const labels = logsFrame?.getLogFrameLabelsAsLabels(); |
|
|
|
|
|
|
|
|
|
|
|
const otherFields = []; |
|
|
|
const otherFields = []; |
|
|
@ -197,7 +205,7 @@ export function LogsTableWrap(props: Props) { |
|
|
|
setColumnsWithMeta(pendingLabelState); |
|
|
|
setColumnsWithMeta(pendingLabelState); |
|
|
|
|
|
|
|
|
|
|
|
// The panel state is updated when the user interacts with the multi-select sidebar
|
|
|
|
// The panel state is updated when the user interacts with the multi-select sidebar
|
|
|
|
}, [dataFrame, getColumnsFromProps]); |
|
|
|
}, [currentDataFrame, getColumnsFromProps]); |
|
|
|
|
|
|
|
|
|
|
|
if (!columnsWithMeta) { |
|
|
|
if (!columnsWithMeta) { |
|
|
|
return null; |
|
|
|
return null; |
|
|
@ -259,6 +267,7 @@ export function LogsTableWrap(props: Props) { |
|
|
|
// Only include active filters
|
|
|
|
// Only include active filters
|
|
|
|
.filter((key) => pendingLabelState[key]?.active) |
|
|
|
.filter((key) => pendingLabelState[key]?.active) |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
refId: currentDataFrame.refId, |
|
|
|
visualisationType: 'table', |
|
|
|
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 sidebarWidth = 220; |
|
|
|
const totalWidth = props.width; |
|
|
|
const totalWidth = props.width; |
|
|
|
const tableWidth = totalWidth - sidebarWidth; |
|
|
|
const tableWidth = totalWidth - sidebarWidth; |
|
|
|
const styles = getStyles(props.theme, height, sidebarWidth); |
|
|
|
const styles = getStyles(props.theme, height, sidebarWidth); |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div className={styles.wrapper}> |
|
|
|
<> |
|
|
|
<section className={styles.sidebar}> |
|
|
|
<div> |
|
|
|
<LogsColumnSearch onChange={onSearchInputChange} /> |
|
|
|
{logsFrames.length > 1 && ( |
|
|
|
<LogsTableMultiSelect |
|
|
|
<div> |
|
|
|
toggleColumn={toggleColumn} |
|
|
|
<InlineField |
|
|
|
filteredColumnsWithMeta={filteredColumnsWithMeta} |
|
|
|
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} |
|
|
|
columnsWithMeta={columnsWithMeta} |
|
|
|
|
|
|
|
height={height} |
|
|
|
/> |
|
|
|
/> |
|
|
|
</section> |
|
|
|
</div> |
|
|
|
<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> |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -347,9 +391,6 @@ function getStyles(theme: GrafanaTheme2, height: number, width: number) { |
|
|
|
width: width, |
|
|
|
width: width, |
|
|
|
paddingRight: theme.spacing(1.5), |
|
|
|
paddingRight: theme.spacing(1.5), |
|
|
|
}), |
|
|
|
}), |
|
|
|
|
|
|
|
|
|
|
|
labelCount: css({}), |
|
|
|
|
|
|
|
checkbox: css({}), |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|