diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 1ff012f3fee..e93907503a2 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -1,38 +1,40 @@ import { css, cx } from '@emotion/css'; import { capitalize, groupBy } from 'lodash'; -import { useCallback, useEffect, useState, useRef, useMemo } from 'react'; import * as React from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { usePrevious, useUnmount } from 'react-use'; import { - SplitOpen, - LogRowModel, - LogsMetaItem, - DataFrame, AbsoluteTimeRange, - GrafanaTheme2, - LoadingState, - TimeZone, - RawTimeRange, - DataQueryResponse, - LogRowContextOptions, - EventBus, - ExplorePanelsState, - TimeRange, - LogsDedupStrategy, - LogsSortOrder, CoreApp, - LogsDedupDescription, - rangeUtil, - ExploreLogsPanelState, + DataFrame, DataHoverClearEvent, DataHoverEvent, - serializeStateToUrlParam, - urlUtil, + DataQueryResponse, + EventBus, + ExploreLogsPanelState, + ExplorePanelsState, + FieldConfigSource, + GrafanaTheme2, + LoadingState, LogLevel, + LogRowContextOptions, + LogRowModel, + LogsDedupDescription, + LogsDedupStrategy, + LogsMetaItem, + LogsSortOrder, + PanelData, + rangeUtil, + RawTimeRange, + serializeStateToUrlParam, shallowCompare, + SplitOpen, + TimeRange, + TimeZone, + urlUtil, } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; +import { t, Trans } from '@grafana/i18n'; import { config, reportInteraction } from '@grafana/runtime'; import { DataQuery, DataTopic } from '@grafana/schema'; import { @@ -47,6 +49,7 @@ import { Themeable2, withTheme2, } from '@grafana/ui'; +import { replaceVariables } from '@grafana-plugins/loki/querybuilder/parsingUtils'; import store from 'app/core/store'; import { createAndCopyShortLink, getLogsPermalinkRange } from 'app/core/utils/shortLinks'; import { ControlledLogRows } from 'app/features/logs/components/ControlledLogRows'; @@ -56,15 +59,17 @@ import { LogRowContextModal } from 'app/features/logs/components/log-context/Log import { LogLineContext } from 'app/features/logs/components/panel/LogLineContext'; import { LogList, LogListOptions } from 'app/features/logs/components/panel/LogList'; import { isDedupStrategy, isLogsSortOrder } from 'app/features/logs/components/panel/LogListContext'; -import { LogLevelColor, dedupLogRows } from 'app/features/logs/logsModel'; +import { dedupLogRows, LogLevelColor } from 'app/features/logs/logsModel'; import { getLogLevelFromKey, getLogLevelInfo } from 'app/features/logs/utils'; import { LokiQueryDirection } from 'app/plugins/datasource/loki/dataquery.gen'; import { isLokiQuery } from 'app/plugins/datasource/loki/queryUtils'; import { GetFieldLinksFn } from 'app/plugins/panel/logs/types'; +import { Options } from 'app/plugins/panel/logstable/panelcfg.gen'; import { getState } from 'app/store/store'; import { ExploreItemState } from 'app/types/explore'; import { useDispatch } from 'app/types/store'; +import { LogsTable } from '../../../plugins/panel/logstable/LogsTable'; import { contentOutlineTrackPinAdded, contentOutlineTrackPinClicked, @@ -80,7 +85,7 @@ import { changeQueries, runQueries } from '../state/query'; import { LogsFeedback } from './LogsFeedback'; import { LogsMetaRow } from './LogsMetaRow'; import LogsNavigation from './LogsNavigation'; -import { LogsTableWrap, getLogsTableHeight } from './LogsTableWrap'; +import { getLogsTableHeight, LogsTableWrap } from './LogsTableWrap'; import { LogsVolumePanelList } from './LogsVolumePanelList'; import { SETTING_KEY_ROOT, SETTINGS_KEYS, visualisationTypeKey } from './utils/logs'; import { getExploreBaseUrl } from './utils/url'; @@ -127,8 +132,9 @@ interface Props extends Themeable2 { range: TimeRange; onClickFilterString?: (value: string, refId?: string) => void; onClickFilterOutString?: (value: string, refId?: string) => void; - loadMoreLogs?(range: AbsoluteTimeRange): void; onPinLineCallback?: () => void; + + loadMoreLogs?(range: AbsoluteTimeRange): void; } export type LogsVisualisationType = 'table' | 'logs'; @@ -764,6 +770,14 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { setFilterLevels(levels.map((level) => getLogLevelFromKey(level))); }, []); + // @todo ff + const enableNewLogsTable = true; + const panelData: PanelData = { + state: loading ? LoadingState.Loading : LoadingState.Done, + series: props.logsFrames ?? [], + timeRange: props.range, + }; + return ( <> {(!config.featureToggles.newLogsPanel || !config.featureToggles.newLogContext) && getRowContext && contextRow && ( @@ -989,24 +1003,55 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => {
{!config.featureToggles.logsPanelControls && visualisationType === 'table' && hasData && (
- {/* Width should be full width minus logs navigation and padding */} - + {/* @todo add flag*/} + {enableNewLogsTable && ( + + )} + + {!enableNewLogsTable && ( + + )}
)} {(!config.featureToggles.newLogsPanel || visualisationType === 'table') && @@ -1014,6 +1059,10 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { hasData && (
{ loading: boolean; logsMeta?: LogsMetaItem[]; @@ -33,6 +45,7 @@ export interface ControlledLogRowsProps extends Omit { onLogOptionsChange?: (option: LogListOptions, value: string | boolean | string[]) => void; range: TimeRange; filterLevels?: LogLevel[]; + fieldConfig: FieldConfigSource; /** Props added for Table **/ visualisationType: LogsVisualisationType; @@ -74,10 +87,35 @@ export const ControlledLogRows = forwardRef { + const dataFrames = rest.logsTableFrames ?? []; + const panelData: PanelData = { + state: rest.loading ? LoadingState.Loading : LoadingState.Done, + series: dataFrames, + timeRange: rest.timeRange, + }; + + const eventBus = getAppEvents(); + + const onCellFilterAdded = (filter: AdHocFilterItem) => { + const { value, key, operator } = filter; + const { onClickFilterLabel, onClickFilterOutLabel } = rest; + if (!onClickFilterLabel || !onClickFilterOutLabel) { + return; + } + if (operator === FILTER_FOR_OPERATOR) { + onClickFilterLabel(key, value, dataFrames[0]); + } + + if (operator === FILTER_OUT_OPERATOR) { + onClickFilterOutLabel(key, value, dataFrames[0]); + } + }; + return ( {rest.visualisationType === 'logs' && ( - + + )} + {rest.visualisationType === 'table' && rest.updatePanelState && ( + + + )} - {rest.visualisationType === 'table' && rest.updatePanelState && } ); } diff --git a/public/app/features/logs/components/LogRows.tsx b/public/app/features/logs/components/LogRows.tsx index e1556290167..846ce619d28 100644 --- a/public/app/features/logs/components/LogRows.tsx +++ b/public/app/features/logs/components/LogRows.tsx @@ -10,6 +10,7 @@ import { DataFrame, LogRowContextOptions, TimeRange, + FieldConfigSource, } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { DataQuery } from '@grafana/schema'; @@ -73,6 +74,7 @@ export interface Props { logRowMenuIconsAfter?: ReactNode[]; scrollElement: HTMLDivElement | null; renderPreview?: boolean; + fieldConfig?: FieldConfigSource; } export type PopoverStateType = { diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 3d89ba1d105..1f23d10669f 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -653,6 +653,7 @@ export const LogsPanel = ({ sortOrder={sortOrder} > { setScrollElement(scrollElement); }} + fieldConfig={fieldConfig} visualisationType="logs" loading={infiniteScrolling} loadMoreLogs={enableInfiniteScrolling ? loadMoreLogs : undefined} diff --git a/public/app/plugins/panel/logstable/LogsTable.tsx b/public/app/plugins/panel/logstable/LogsTable.tsx index 501e2e92a52..9add863aade 100644 --- a/public/app/plugins/panel/logstable/LogsTable.tsx +++ b/public/app/plugins/panel/logstable/LogsTable.tsx @@ -5,6 +5,7 @@ import { lastValueFrom } from 'rxjs'; import { applyFieldOverrides, DataFrame, + Field, FieldConfigSource, GrafanaTheme2, PanelProps, @@ -25,13 +26,7 @@ import { parseLogsFrame, } from '../../../features/logs/logsFrame'; import { isSetDisplayedFields } from '../logs/types'; -import { - AdHocFilterItem, - FILTER_FOR_OPERATOR, - FILTER_OUT_OPERATOR, - TableFilterActionCallback, - TablePanel, -} from '../table/TablePanel'; +import { TablePanel } from '../table/TablePanel'; import type { Options as TableOptions } from '../table/panelcfg.gen'; import type { Options as LogsTableOptions } from './panelcfg.gen'; @@ -125,39 +120,11 @@ export const LogsTable = ({ const handleTableOnFieldConfigChange = useCallback( (fieldConfig: FieldConfigSource) => { - console.log('onTableOnFieldConfigChange', fieldConfig); onFieldConfigChange(fieldConfig); }, [onFieldConfigChange] ); - const handleOnCellFilterAdded: TableFilterActionCallback = (filter: AdHocFilterItem) => { - const { value, key, operator } = filter; - if (operator === FILTER_FOR_OPERATOR) { - onClickFilterLabel(key, value); - } - - if (operator === FILTER_OUT_OPERATOR) { - onClickFilterOutLabel(key, value); - } - }; - - /** - * @todo - * Used by Logs details. - */ - const onClickFilterLabel = (key: string, value: string | number, frame?: DataFrame) => { - console.log('onClickFilterLabel', key, value, frame); - }; - - /** - * @todo - * Used by Logs details. - */ - const onClickFilterOutLabel = (key: string, value: string | number, frame?: DataFrame) => { - console.log('onClickFilterOutLabel', key, value, frame); - }; - /** * Extract fields transform */ @@ -169,18 +136,32 @@ export const LogsTable = ({ }; extractFields().then((frame) => { - setExtractedFrame( - applyFieldOverrides({ - data: frame, - fieldConfig, - replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()), - theme: config.theme2, - timeZone: timeZone, - dataLinkPostProcessor: dataLinksContext.dataLinkPostProcessor, - }) - ); + const extractedFrames = applyFieldOverrides({ + data: frame, + fieldConfig: fieldConfig, + replaceVariables: replaceVariables ?? getTemplateSrv().replace.bind(getTemplateSrv()), + theme: config.theme2, + timeZone: timeZone, + dataLinkPostProcessor: dataLinksContext.dataLinkPostProcessor, + }); + + for (let frameIndex = 0; frameIndex < extractedFrames.length; frameIndex++) { + const frame = extractedFrames[frameIndex]; + for (const [, field] of frame.fields.entries()) { + field.config = { + ...field.config, + filterable: field.config?.filterable ?? doesFieldSupportAdHocFiltering(field), + custom: { + ...field.config.custom, + inspect: field.config?.custom?.inspect ?? true, + }, + }; + } + } + + setExtractedFrame(extractedFrames); }); - }, [dataLinksContext.dataLinkPostProcessor, fieldConfig, timeZone, unTransformedDataFrame]); + }, [dataLinksContext.dataLinkPostProcessor, fieldConfig, replaceVariables, timeZone, unTransformedDataFrame]); /** * Organize fields transform @@ -267,7 +248,6 @@ export const LogsTable = ({ onFieldConfigChange={handleTableOnFieldConfigChange} replaceVariables={replaceVariables} onChangeTimeRange={onChangeTimeRange} - onCellFilterAdded={handleOnCellFilterAdded} />
@@ -293,6 +273,10 @@ function displayedFieldsToColumns(displayedFields: string[], logsFrame: LogsFram return columns; } +function doesFieldSupportAdHocFiltering(field: Field): boolean { + return true; +} + const getStyles = (theme: GrafanaTheme2, sidebarWidth: number, height: number, width: number) => { return { tableWrapper: css({ diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index 77a8a7e6ed5..2ac469eddb8 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -24,16 +24,7 @@ import { getActions } from '../../../features/actions/utils'; import { hasDeprecatedParentRowIndex, migrateFromParentRowIndexToNestedFrames } from './migrations'; import { Options } from './panelcfg.gen'; -/** @todo get yelled at by dataviz */ -export const FILTER_FOR_OPERATOR = '='; -export const FILTER_OUT_OPERATOR = '!='; -export type AdHocFilterOperator = typeof FILTER_FOR_OPERATOR | typeof FILTER_OUT_OPERATOR; -export type AdHocFilterItem = { key: string; value: string; operator: AdHocFilterOperator }; -export type TableFilterActionCallback = (item: AdHocFilterItem) => void; - -interface Props extends PanelProps { - onCellFilterAdded?: TableFilterActionCallback; -} +interface Props extends PanelProps {} export function TablePanel(props: Props) { const { data, height, width, options, fieldConfig, id, timeRange, replaceVariables, transparent } = props; @@ -86,7 +77,7 @@ export function TablePanel(props: Props) { initialSortBy={options.sortBy} onSortByChange={(sortBy) => onSortByChange(sortBy, props)} onColumnResize={(displayName, resizedWidth) => onColumnResize(displayName, resizedWidth, props)} - onCellFilterAdded={props.onCellFilterAdded ?? panelContext.onAddAdHocFilter} + onCellFilterAdded={panelContext.onAddAdHocFilter} frozenColumns={options.frozenColumns?.left} enablePagination={options.enablePagination} cellHeight={options.cellHeight}