diff --git a/frontend/src/constants/events.ts b/frontend/src/constants/events.ts index db4ce63d7a..1b6a0e6feb 100644 --- a/frontend/src/constants/events.ts +++ b/frontend/src/constants/events.ts @@ -1,4 +1,5 @@ export enum Events { UPDATE_GRAPH_VISIBILITY_STATE = 'UPDATE_GRAPH_VISIBILITY_STATE', UPDATE_GRAPH_MANAGER_TABLE = 'UPDATE_GRAPH_MANAGER_TABLE', + TABLE_COLUMNS_DATA = 'TABLE_COLUMNS_DATA', } diff --git a/frontend/src/container/GridPanelSwitch/index.tsx b/frontend/src/container/GridPanelSwitch/index.tsx index 511bf449e9..f054fabb5e 100644 --- a/frontend/src/container/GridPanelSwitch/index.tsx +++ b/frontend/src/container/GridPanelSwitch/index.tsx @@ -26,7 +26,12 @@ const GridPanelSwitch = forwardRef< yAxisUnit, thresholds, }, - [PANEL_TYPES.TABLE]: { ...GRID_TABLE_CONFIG, data: panelData, query }, + [PANEL_TYPES.TABLE]: { + ...GRID_TABLE_CONFIG, + data: panelData, + query, + thresholds, + }, [PANEL_TYPES.LIST]: null, [PANEL_TYPES.TRACE]: null, [PANEL_TYPES.EMPTY_WIDGET]: null, diff --git a/frontend/src/container/GridTableComponent/index.tsx b/frontend/src/container/GridTableComponent/index.tsx index e3659c1c95..4b1fab5f8f 100644 --- a/frontend/src/container/GridTableComponent/index.tsx +++ b/frontend/src/container/GridTableComponent/index.tsx @@ -1,20 +1,86 @@ +import { ExclamationCircleFilled } from '@ant-design/icons'; +import { Space, Tooltip } from 'antd'; +import { Events } from 'constants/events'; import { QueryTable } from 'container/QueryTable'; -import { memo } from 'react'; +import { createTableColumnsFromQuery } from 'lib/query/createTableColumnsFromQuery'; +import { memo, ReactNode, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { eventEmitter } from 'utils/getEventEmitter'; import { WrapperStyled } from './styles'; import { GridTableComponentProps } from './types'; +import { findMatchingThreshold } from './utils'; function GridTableComponent({ data, query, + thresholds, ...props }: GridTableComponentProps): JSX.Element { + const { t } = useTranslation(['valueGraph']); + const { columns, dataSource } = useMemo( + () => + createTableColumnsFromQuery({ + query, + queryTableData: data, + }), + [data, query], + ); + + const newColumnData = columns.map((e) => ({ + ...e, + render: (text: string): ReactNode => { + const isNumber = !Number.isNaN(Number(text)); + if (thresholds && isNumber) { + const { hasMultipleMatches, threshold } = findMatchingThreshold( + thresholds, + e.title as string, + Number(text), + ); + + const idx = thresholds.findIndex( + (t) => t.thresholdTableOptions === e.title, + ); + if (idx !== -1) { + return ( +
+ + {text} + {hasMultipleMatches && ( + + + + )} + +
+ ); + } + } + return
{text}
; + }, + })); + + useEffect(() => { + eventEmitter.emit(Events.TABLE_COLUMNS_DATA, { + columns: newColumnData, + dataSource, + }); + }, [dataSource, newColumnData]); + return ( diff --git a/frontend/src/container/GridTableComponent/types.ts b/frontend/src/container/GridTableComponent/types.ts index cd8e446822..4f210eca18 100644 --- a/frontend/src/container/GridTableComponent/types.ts +++ b/frontend/src/container/GridTableComponent/types.ts @@ -1,10 +1,23 @@ import { TableProps } from 'antd'; import { LogsExplorerTableProps } from 'container/LogsExplorerTable/LogsExplorerTable.interfaces'; +import { + ThresholdOperators, + ThresholdProps, +} from 'container/NewWidget/RightContainer/Threshold/types'; import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; -export type GridTableComponentProps = { query: Query } & Pick< - LogsExplorerTableProps, - 'data' -> & +export type GridTableComponentProps = { + query: Query; + thresholds?: ThresholdProps[]; +} & Pick & Omit, 'columns' | 'dataSource'>; + +export type RequiredThresholdProps = Omit< + ThresholdProps, + 'thresholdTableOptions' | 'thresholdOperator' | 'thresholdValue' +> & { + thresholdTableOptions: string; + thresholdOperator: ThresholdOperators; + thresholdValue: number; +}; diff --git a/frontend/src/container/GridTableComponent/utils.ts b/frontend/src/container/GridTableComponent/utils.ts new file mode 100644 index 0000000000..e60cac6a24 --- /dev/null +++ b/frontend/src/container/GridTableComponent/utils.ts @@ -0,0 +1,58 @@ +import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types'; + +// Helper function to evaluate the condition based on the operator +function evaluateCondition( + operator: string | undefined, + value: number, + thresholdValue: number, +): boolean { + switch (operator) { + case '>': + return value > thresholdValue; + case '<': + return value < thresholdValue; + case '>=': + return value >= thresholdValue; + case '<=': + return value <= thresholdValue; + case '==': + return value === thresholdValue; + default: + return false; + } +} + +export function findMatchingThreshold( + thresholds: ThresholdProps[], + label: string, + value: number, +): { + threshold: ThresholdProps; + hasMultipleMatches: boolean; +} { + const matchingThresholds: ThresholdProps[] = []; + let hasMultipleMatches = false; + + thresholds.forEach((threshold) => { + if ( + threshold.thresholdValue !== undefined && + threshold.thresholdTableOptions === label && + evaluateCondition( + threshold.thresholdOperator, + value, + threshold.thresholdValue, + ) + ) { + matchingThresholds.push(threshold); + } + }); + + if (matchingThresholds.length > 1) { + hasMultipleMatches = true; + } + + return { + threshold: matchingThresholds[0], + hasMultipleMatches, + }; +} diff --git a/frontend/src/container/NewWidget/RightContainer/Threshold/ShowCaseValue.styles.scss b/frontend/src/container/NewWidget/RightContainer/Threshold/ShowCaseValue.styles.scss index 360387c333..05d5233e52 100644 --- a/frontend/src/container/NewWidget/RightContainer/Threshold/ShowCaseValue.styles.scss +++ b/frontend/src/container/NewWidget/RightContainer/Threshold/ShowCaseValue.styles.scss @@ -1,6 +1,7 @@ .show-case-container { padding: 5px 15px; border-radius: 5px; + display: inline-block; } .show-case-dark { diff --git a/frontend/src/container/NewWidget/RightContainer/Threshold/Threshold.tsx b/frontend/src/container/NewWidget/RightContainer/Threshold/Threshold.tsx index 1a2347c9d6..b64c5b26e6 100644 --- a/frontend/src/container/NewWidget/RightContainer/Threshold/Threshold.tsx +++ b/frontend/src/container/NewWidget/RightContainer/Threshold/Threshold.tsx @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/cognitive-complexity */ import './Threshold.styles.scss'; import { CheckOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'; @@ -40,6 +41,8 @@ function Threshold({ moveThreshold, selectedGraph, thresholdLabel = '', + tableOptions, + thresholdTableOptions = '', }: ThresholdProps): JSX.Element { const [isEditMode, setIsEditMode] = useState(isEditEnabled); const [operator, setOperator] = useState( @@ -52,6 +55,9 @@ function Threshold({ thresholdFormat, ); const [label, setLabel] = useState(thresholdLabel); + const [tableSelectedOption, setTableSelectedOption] = useState( + thresholdTableOptions, + ); const isDarkMode = useIsDarkMode(); @@ -72,6 +78,7 @@ function Threshold({ thresholdUnit: unit, thresholdValue: value, thresholdLabel: label, + thresholdTableOptions: tableSelectedOption, }; } return threshold; @@ -104,6 +111,10 @@ function Threshold({ setFormat(value); }; + const handleTableOptionsChange = (value: string): void => { + setTableSelectedOption(value); + }; + const deleteHandler = (): void => { if (thresholdDeleteHandler) { thresholdDeleteHandler(index); @@ -203,7 +214,11 @@ function Threshold({ />
- + {selectedGraph === PANEL_TYPES.TIME_SERIES && ( <> Label @@ -219,19 +234,49 @@ function Threshold({ )} )} - {selectedGraph === PANEL_TYPES.VALUE && ( + {(selectedGraph === PANEL_TYPES.VALUE || + selectedGraph === PANEL_TYPES.TABLE) && ( <> - If value is + + If value {selectedGraph === PANEL_TYPES.TABLE ? 'in' : 'is'} + {isEditMode ? ( - + is + + )} +