|
|
|
@ -16,8 +16,8 @@ export interface Props extends Themeable2 { |
|
|
|
|
disableActions: boolean; |
|
|
|
|
wrapLogMessage?: boolean; |
|
|
|
|
isLabel?: boolean; |
|
|
|
|
onClickFilterLabel?: (key: string, value: string) => void; |
|
|
|
|
onClickFilterOutLabel?: (key: string, value: string) => void; |
|
|
|
|
onClickFilterLabel?: (key: string, value: string, refId?: string) => void; |
|
|
|
|
onClickFilterOutLabel?: (key: string, value: string, refId?: string) => void; |
|
|
|
|
links?: Array<LinkModel<Field>>; |
|
|
|
|
getStats: () => LogLabelStatsModel[] | null; |
|
|
|
|
displayedFields?: string[]; |
|
|
|
@ -25,7 +25,7 @@ export interface Props extends Themeable2 { |
|
|
|
|
onClickHideField?: (key: string) => void; |
|
|
|
|
row: LogRowModel; |
|
|
|
|
app?: CoreApp; |
|
|
|
|
isFilterLabelActive?: (key: string, value: string) => Promise<boolean>; |
|
|
|
|
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface State { |
|
|
|
@ -134,9 +134,9 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
isFilterLabelActive = async () => { |
|
|
|
|
const { isFilterLabelActive, parsedKeys, parsedValues } = this.props; |
|
|
|
|
const { isFilterLabelActive, parsedKeys, parsedValues, row } = this.props; |
|
|
|
|
if (isFilterLabelActive) { |
|
|
|
|
return await isFilterLabelActive(parsedKeys[0], parsedValues[0]); |
|
|
|
|
return await isFilterLabelActive(parsedKeys[0], parsedValues[0], row.dataFrame?.refId); |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
}; |
|
|
|
@ -144,7 +144,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
filterLabel = () => { |
|
|
|
|
const { onClickFilterLabel, parsedKeys, parsedValues, row } = this.props; |
|
|
|
|
if (onClickFilterLabel) { |
|
|
|
|
onClickFilterLabel(parsedKeys[0], parsedValues[0]); |
|
|
|
|
onClickFilterLabel(parsedKeys[0], parsedValues[0], row.dataFrame?.refId); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
reportInteraction('grafana_explore_logs_log_details_filter_clicked', { |
|
|
|
@ -157,7 +157,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
filterOutLabel = () => { |
|
|
|
|
const { onClickFilterOutLabel, parsedKeys, parsedValues, row } = this.props; |
|
|
|
|
if (onClickFilterOutLabel) { |
|
|
|
|
onClickFilterOutLabel(parsedKeys[0], parsedValues[0]); |
|
|
|
|
onClickFilterOutLabel(parsedKeys[0], parsedValues[0], row.dataFrame?.refId); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
reportInteraction('grafana_explore_logs_log_details_filter_clicked', { |
|
|
|
@ -250,6 +250,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
onClickFilterLabel, |
|
|
|
|
onClickFilterOutLabel, |
|
|
|
|
disableActions, |
|
|
|
|
row, |
|
|
|
|
} = this.props; |
|
|
|
|
const { showFieldsStats, fieldStats, fieldCount } = this.state; |
|
|
|
|
const styles = getStyles(theme); |
|
|
|
@ -257,6 +258,8 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
const singleKey = parsedKeys == null ? false : parsedKeys.length === 1; |
|
|
|
|
const singleVal = parsedValues == null ? false : parsedValues.length === 1; |
|
|
|
|
const hasFilteringFunctionality = !disableActions && onClickFilterLabel && onClickFilterOutLabel; |
|
|
|
|
const refIdTooltip = |
|
|
|
|
config.featureToggles.toggleLabelsInLogsUI && row.dataFrame?.refId ? ` in query ${row.dataFrame?.refId}` : ''; |
|
|
|
|
|
|
|
|
|
const isMultiParsedValueWithNoContent = |
|
|
|
|
!singleVal && parsedValues != null && !parsedValues.every((val) => val === ''); |
|
|
|
@ -275,18 +278,22 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
<div className={styles.buttonRow}> |
|
|
|
|
{hasFilteringFunctionality && ( |
|
|
|
|
<> |
|
|
|
|
{config.featureToggles.toggleLabelsInLogsUI && ( |
|
|
|
|
{config.featureToggles.toggleLabelsInLogsUI ? ( |
|
|
|
|
// If we are using the new label toggling, we want to use the async icon button
|
|
|
|
|
<AsyncIconButton |
|
|
|
|
name="search-plus" |
|
|
|
|
onClick={this.filterLabel} |
|
|
|
|
isActive={this.isFilterLabelActive} |
|
|
|
|
tooltipSuffix={refIdTooltip} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
{!config.featureToggles.toggleLabelsInLogsUI && ( |
|
|
|
|
) : ( |
|
|
|
|
<IconButton name="search-plus" onClick={this.filterLabel} tooltip="Filter for value" /> |
|
|
|
|
)} |
|
|
|
|
<IconButton name="search-minus" tooltip="Filter out value" onClick={this.filterOutLabel} /> |
|
|
|
|
<IconButton |
|
|
|
|
name="search-minus" |
|
|
|
|
tooltip={`Filter out value${refIdTooltip}`} |
|
|
|
|
onClick={this.filterOutLabel} |
|
|
|
|
/> |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
{!disableActions && displayedFields && toggleFieldButton} |
|
|
|
@ -350,10 +357,12 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { |
|
|
|
|
interface AsyncIconButtonProps extends Pick<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> { |
|
|
|
|
name: IconName; |
|
|
|
|
isActive(): Promise<boolean>; |
|
|
|
|
tooltipSuffix: string; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const AsyncIconButton = ({ isActive, ...rest }: AsyncIconButtonProps) => { |
|
|
|
|
const AsyncIconButton = ({ isActive, tooltipSuffix, ...rest }: AsyncIconButtonProps) => { |
|
|
|
|
const [active, setActive] = useState(false); |
|
|
|
|
const tooltip = active ? 'Remove filter' : 'Filter for value'; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* We purposely want to run this on every render to allow the active state to be updated |
|
|
|
@ -361,13 +370,7 @@ const AsyncIconButton = ({ isActive, ...rest }: AsyncIconButtonProps) => { |
|
|
|
|
*/ |
|
|
|
|
isActive().then(setActive); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<IconButton |
|
|
|
|
{...rest} |
|
|
|
|
variant={active ? 'primary' : undefined} |
|
|
|
|
tooltip={active ? 'Remove filter' : 'Filter for value'} |
|
|
|
|
/> |
|
|
|
|
); |
|
|
|
|
return <IconButton {...rest} variant={active ? 'primary' : undefined} tooltip={tooltip + tooltipSuffix} />; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export const LogDetailsRow = withTheme2(UnThemedLogDetailsRow); |
|
|
|
|