From d6e4e3c5ed30d74739af2f6ef26f769ef6957df6 Mon Sep 17 00:00:00 2001 From: sawhil Date: Fri, 25 Apr 2025 20:37:41 +0530 Subject: [PATCH] feat: added custom cell rendering in gridcard, added new table view in all endpoints --- .../Domains/DomainDetails/AllEndPoints.tsx | 190 +---------- .../src/container/ApiMonitoring/utils.tsx | 323 ++++++++++++++++++ .../GridCard/WidgetGraphComponent.tsx | 2 + .../GridCardLayout/GridCard/index.tsx | 2 + .../GridCardLayout/GridCard/types.ts | 2 + .../container/GridTableComponent/index.tsx | 26 +- .../src/container/GridTableComponent/types.ts | 3 + .../container/PanelWrapper/PanelWrapper.tsx | 2 + .../PanelWrapper/TablePanelWrapper.tsx | 3 + .../PanelWrapper/panelWrapper.types.ts | 1 + .../MQDetails/MetricPage/MetricPageUtil.ts | 8 +- frontend/src/types/api/dashboard/getAll.ts | 2 + 12 files changed, 385 insertions(+), 179 deletions(-) diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx index 8f28a6984a..cf8136d518 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/AllEndPoints.tsx @@ -1,34 +1,12 @@ -import { LoadingOutlined } from '@ant-design/icons'; -import { - Select, - Spin, - Table, - TablePaginationConfig, - TableProps, - Typography, -} from 'antd'; -import { SorterResult } from 'antd/lib/table/interface'; -import logEvent from 'api/common/logEvent'; -import { ENTITY_VERSION_V4 } from 'constants/app'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { - EndPointsTableRowData, - formatEndPointsDataForTable, - getEndPointsColumnsConfig, - getEndPointsQueryPayload, -} from 'container/ApiMonitoring/utils'; +import { Select } from 'antd'; +import { getAllEndpointsWidgetData } from 'container/ApiMonitoring/utils'; +import GridCard from 'container/GridCardLayout/GridCard'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQueries } from 'react-query'; -import { SuccessResponse } from 'types/api'; -import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import ErrorState from './components/ErrorState'; -import ExpandedRow from './components/ExpandedRow'; -import { VIEW_TYPES, VIEWS } from './constants'; +import { VIEWS } from './constants'; function AllEndPoints({ domainName, @@ -63,13 +41,6 @@ function AllEndPoints({ { value: string; label: string }[] >([]); - const [orderBy, setOrderBy] = useState<{ - columnName: string; - order: 'asc' | 'desc'; - } | null>(null); - - const [expandedRowKeys, setExpandedRowKeys] = useState([]); - const handleGroupByChange = useCallback( (value: IBuilderQuery['groupBy']) => { const groupBy = []; @@ -101,109 +72,11 @@ function AllEndPoints({ } }, [groupByFiltersData]); - const { startTime: minTime, endTime: maxTime } = timeRange; - - const queryPayloads = useMemo( - () => getEndPointsQueryPayload(groupBy, domainName, minTime, maxTime), - [groupBy, domainName, minTime, maxTime], + const allEndpointsWidgetData = useMemo( + () => getAllEndpointsWidgetData(groupBy, domainName), + [groupBy, domainName], ); - // Since only one query here - const endPointsDataQueries = useQueries( - queryPayloads.map((payload) => ({ - queryKey: [ - REACT_QUERY_KEY.GET_ENDPOINTS_LIST_BY_DOMAIN, - payload, - ENTITY_VERSION_V4, - groupBy, - ], - queryFn: (): Promise> => - GetMetricQueryRange(payload, ENTITY_VERSION_V4), - enabled: !!payload, - staleTime: 60 * 1000, // 1 minute stale time : optimize this part - })), - ); - - const endPointsDataQuery = endPointsDataQueries[0]; - const { - data: allEndPointsData, - isLoading, - isRefetching, - isError, - refetch, - } = endPointsDataQuery; - - const endPointsColumnsConfig = useMemo( - () => getEndPointsColumnsConfig(groupBy.length > 0, expandedRowKeys), - [groupBy.length, expandedRowKeys], - ); - - const expandedRowRender = (record: EndPointsTableRowData): JSX.Element => ( - - ); - - const handleGroupByRowClick = (record: EndPointsTableRowData): void => { - if (expandedRowKeys.includes(record.key)) { - setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); - } else { - setExpandedRowKeys((expandedRowKeys) => [...expandedRowKeys, record.key]); - } - }; - - const handleRowClick = (record: EndPointsTableRowData): void => { - if (groupBy.length === 0) { - setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab - setSelectedView(VIEW_TYPES.ENDPOINT_STATS); - logEvent('API Monitoring: Endpoint name row clicked', {}); - } else { - handleGroupByRowClick(record); // this will prepare the nested query payload - } - }; - - const handleTableChange: TableProps['onChange'] = useCallback( - ( - _pagination: TablePaginationConfig, - _filters: Record, - sorter: - | SorterResult - | SorterResult[], - ): void => { - if ('field' in sorter && sorter.order) { - setOrderBy({ - columnName: sorter.field as string, - order: sorter.order === 'ascend' ? 'asc' : 'desc', - }); - } else { - setOrderBy(null); - } - }, - [], - ); - - const formattedEndPointsData = useMemo( - () => - formatEndPointsDataForTable( - allEndPointsData?.payload?.data?.result[0]?.table?.rows, - groupBy, - orderBy, - ), - [groupBy, allEndPointsData, orderBy], - ); - - if (isError) { - return ( -
- -
- ); - } - return (
@@ -221,47 +94,16 @@ function AllEndPoints({ />{' '}
-
Endpoint overview
- } />, + {}} + customOnDragSelect={(): void => {}} + customTimeRange={timeRange} + customOnRowClick={(props): void => { + setSelectedEndPointName(props['http.url'] as string); + setSelectedView(VIEWS.ENDPOINT_STATS); }} - dataSource={isLoading || isRefetching ? [] : formattedEndPointsData} - locale={{ - emptyText: - isLoading || isRefetching ? null : ( -
-
- thinking-emoji - - - This query had no results. Edit your query and try again! - -
-
- ), - }} - scroll={{ x: true }} - tableLayout="fixed" - onRow={(record): { onClick: () => void; className: string } => ({ - onClick: (): void => handleRowClick(record), - className: 'clickable-row', - })} - expandable={{ - expandedRowRender: groupBy.length > 0 ? expandedRowRender : undefined, - expandedRowKeys, - expandIconColumnIndex: -1, - }} - rowClassName={(_, index): string => - index % 2 === 0 ? 'table-row-dark' : 'table-row-light' - } - onChange={handleTableChange} /> diff --git a/frontend/src/container/ApiMonitoring/utils.tsx b/frontend/src/container/ApiMonitoring/utils.tsx index 96b6b1965b..149682b304 100644 --- a/frontend/src/container/ApiMonitoring/utils.tsx +++ b/frontend/src/container/ApiMonitoring/utils.tsx @@ -3276,6 +3276,329 @@ export const END_POINT_DETAILS_QUERY_KEYS_ARRAY = [ REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA, ]; +export const getAllEndpointsWidgetData = ( + groupBy: BaseAutocompleteData[], + domainName: string, + // eslint-disable-next-line sonarjs/cognitive-complexity +): Widgets => { + const isGroupedByAttribute = groupBy.length > 0; + + const widget = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Endpoint Overview', + description: 'Endpoint Overview', + panelTypes: PANEL_TYPES.TABLE, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: 'span_id--string----true', + isColumn: true, + isJSON: false, + key: 'span_id', + type: '', + }, + aggregateOperator: 'count', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'ec316e57', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '212678b9', + key: { + key: 'kind_string', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + id: 'kind_string--string----true', + }, + op: '=', + value: 'Client', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: isGroupedByAttribute + ? [...defaultGroupBy, ...groupBy] + : defaultGroupBy, + having: [], + legend: 'Num of Calls', + limit: 1000, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'count', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'p99', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '46d57857', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '212678b9', + key: { + key: 'kind_string', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + id: 'kind_string--string----true', + }, + op: '=', + value: 'Client', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: isGroupedByAttribute + ? [...defaultGroupBy, ...groupBy] + : defaultGroupBy, + having: [], + legend: 'Latency (ms)', + limit: 1000, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'p99', + }, + { + aggregateAttribute: { + dataType: DataTypes.String, + id: 'timestamp------false', + isColumn: false, + key: 'timestamp', + type: '', + }, + aggregateOperator: 'max', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '4a237616', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '212678b9', + key: { + key: 'kind_string', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + id: 'kind_string--string----true', + }, + op: '=', + value: 'Client', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: isGroupedByAttribute + ? [...defaultGroupBy, ...groupBy] + : defaultGroupBy, + having: [], + legend: 'Last Used', + limit: 1000, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.String, + id: 'span_id--string----true', + isColumn: true, + isJSON: false, + key: 'span_id', + type: '', + }, + aggregateOperator: 'count', + dataSource: DataSource.TRACES, + disabled: true, + expression: 'D', + filters: { + items: [ + { + id: 'f162de1e', + key: { + dataType: DataTypes.String, + id: 'net.peer.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'net.peer.name', + type: 'tag', + }, + op: '=', + value: domainName, + }, + { + id: '3df0ac1d', + key: { + dataType: DataTypes.bool, + id: 'has_error--bool----true', + isColumn: true, + isJSON: false, + key: 'has_error', + type: '', + }, + op: '=', + value: 'true', + }, + { + id: '212678b9', + key: { + key: 'kind_string', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + id: 'kind_string--string----true', + }, + op: '=', + value: 'Client', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: isGroupedByAttribute + ? [...defaultGroupBy, ...groupBy] + : defaultGroupBy, + having: [], + legend: '', + limit: 1000, + orderBy: [], + queryName: 'D', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'count', + }, + ], + queryFormulas: [ + { + queryName: 'F1', + expression: '(D/A)*100', + disabled: false, + legend: 'error percentage', + }, + ], + yAxisUnit: 'ops/s', + }), + ); + + widget.renderColumnCell = { + A: (numOfCalls: any): ReactNode => ( + + {numOfCalls === 'n/a' || numOfCalls === undefined ? '-' : numOfCalls} + + ), + B: (latency: any): ReactNode => ( + + {latency === 'n/a' || latency === undefined + ? '-' + : `${Math.round(Number(latency) / 1000000)} ms`} + + ), + C: (lastUsed: any): ReactNode => ( + + {lastUsed === 'n/a' || lastUsed === undefined + ? '-' + : getLastUsedRelativeTime( + new Date( + new Date(Math.floor(Number(lastUsed) / 1000000)).toISOString(), + ).getTime(), + )} + + ), + F1: (errorRate: any): ReactNode => ( + { + const errorRatePercent = Number( + ((errorRate === 'n/a' || errorRate === '-' + ? 0 + : errorRate) as number).toFixed(1), + ); + if (errorRatePercent >= 90) return Color.BG_SAKURA_500; + if (errorRatePercent >= 60) return Color.BG_AMBER_500; + return Color.BG_FOREST_500; + })()} + className="progress-bar error-rate" + /> + ), + }; + + return widget; +}; + export const getRateOverTimeWidgetData = ( domainName: string, endPointName: string, diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index ada8f24b70..cef61361e3 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -56,6 +56,7 @@ function WidgetGraphComponent({ onOpenTraceBtnClick, customSeries, customErrorMessage, + customOnRowClick, }: WidgetGraphComponentProps): JSX.Element { const { safeNavigate } = useSafeNavigate(); const [deleteModal, setDeleteModal] = useState(false); @@ -380,6 +381,7 @@ function WidgetGraphComponent({ openTracesButton={openTracesButton} onOpenTraceBtnClick={onOpenTraceBtnClick} customSeries={customSeries} + customOnRowClick={customOnRowClick} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 667f5a9594..d5fa5ab43c 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -48,6 +48,7 @@ function GridCardGraph({ end, analyticsEvent, customTimeRange, + customOnRowClick, }: GridCardGraphProps): JSX.Element { const dispatch = useDispatch(); const [errorMessage, setErrorMessage] = useState(); @@ -287,6 +288,7 @@ function GridCardGraph({ onOpenTraceBtnClick={onOpenTraceBtnClick} customSeries={customSeries} customErrorMessage={isInternalServerError ? customErrorMessage : undefined} + customOnRowClick={customOnRowClick} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index e1ba7cb918..7c166dc032 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -39,6 +39,7 @@ export interface WidgetGraphComponentProps { onOpenTraceBtnClick?: (record: RowData) => void; customSeries?: (data: QueryData[]) => uPlot.Series[]; customErrorMessage?: string; + customOnRowClick?: (record: RowData) => void; } export interface GridCardGraphProps { @@ -65,6 +66,7 @@ export interface GridCardGraphProps { startTime: number; endTime: number; }; + customOnRowClick?: (record: RowData) => void; } export interface GetGraphVisibilityStateOnLegendClickProps { diff --git a/frontend/src/container/GridTableComponent/index.tsx b/frontend/src/container/GridTableComponent/index.tsx index 87ab191eed..ca608f4002 100644 --- a/frontend/src/container/GridTableComponent/index.tsx +++ b/frontend/src/container/GridTableComponent/index.tsx @@ -43,6 +43,7 @@ function GridTableComponent({ sticky, openTracesButton, onOpenTraceBtnClick, + customOnRowClick, widgetId, ...props }: GridTableComponentProps): JSX.Element { @@ -214,6 +215,18 @@ function GridTableComponent({ [newColumnData], ); + const newColumnsWithRenderColumnCell = useMemo( + () => + newColumnData.map((column) => ({ + ...column, + ...('dataIndex' in column && + props.renderColumnCell?.[column.dataIndex as string] + ? { render: props.renderColumnCell[column.dataIndex as string] } + : {}), + })), + [newColumnData, props.renderColumnCell], + ); + useEffect(() => { eventEmitter.emit(Events.TABLE_COLUMNS_DATA, { columns: newColumnData, @@ -227,15 +240,22 @@ function GridTableComponent({ query={query} queryTableData={data} loading={false} - columns={openTracesButton ? columnDataWithOpenTracesButton : newColumnData} + columns={ + openTracesButton + ? columnDataWithOpenTracesButton + : newColumnsWithRenderColumnCell + } dataSource={dataSource} sticky={sticky} widgetId={widgetId} onRow={ - openTracesButton + openTracesButton || customOnRowClick ? (record): React.HTMLAttributes => ({ onClick: (): void => { - onOpenTraceBtnClick?.(record); + if (openTracesButton) { + onOpenTraceBtnClick?.(record); + } + customOnRowClick?.(record); }, }) : undefined diff --git a/frontend/src/container/GridTableComponent/types.ts b/frontend/src/container/GridTableComponent/types.ts index 778e439a22..a8b7c4db21 100644 --- a/frontend/src/container/GridTableComponent/types.ts +++ b/frontend/src/container/GridTableComponent/types.ts @@ -4,6 +4,7 @@ import { ThresholdOperators, ThresholdProps, } from 'container/NewWidget/RightContainer/Threshold/types'; +import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { ColumnUnit } from 'types/api/dashboard/getAll'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; @@ -17,7 +18,9 @@ export type GridTableComponentProps = { searchTerm?: string; openTracesButton?: boolean; onOpenTraceBtnClick?: (record: RowData) => void; + customOnRowClick?: (record: RowData) => void; widgetId?: string; + renderColumnCell?: QueryTableProps['renderColumnCell']; } & Pick & Omit, 'columns' | 'dataSource'>; diff --git a/frontend/src/container/PanelWrapper/PanelWrapper.tsx b/frontend/src/container/PanelWrapper/PanelWrapper.tsx index 99a1f23e8d..36d3e1d288 100644 --- a/frontend/src/container/PanelWrapper/PanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/PanelWrapper.tsx @@ -20,6 +20,7 @@ function PanelWrapper({ openTracesButton, onOpenTraceBtnClick, customSeries, + customOnRowClick, }: PanelWrapperProps): JSX.Element { const Component = PanelTypeVsPanelWrapper[ selectedGraph || widget.panelTypes @@ -46,6 +47,7 @@ function PanelWrapper({ searchTerm={searchTerm} openTracesButton={openTracesButton} onOpenTraceBtnClick={onOpenTraceBtnClick} + customOnRowClick={customOnRowClick} customSeries={customSeries} /> ); diff --git a/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx b/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx index 88da74fc6b..9b504b8a2b 100644 --- a/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx @@ -11,6 +11,7 @@ function TablePanelWrapper({ searchTerm, openTracesButton, onOpenTraceBtnClick, + customOnRowClick, }: PanelWrapperProps): JSX.Element { const panelData = (queryResponse.data?.payload?.data?.result?.[0] as any)?.table || []; @@ -26,7 +27,9 @@ function TablePanelWrapper({ searchTerm={searchTerm} openTracesButton={openTracesButton} onOpenTraceBtnClick={onOpenTraceBtnClick} + customOnRowClick={customOnRowClick} widgetId={widget.id} + renderColumnCell={widget.renderColumnCell} // eslint-disable-next-line react/jsx-props-no-spreading {...GRID_TABLE_CONFIG} /> diff --git a/frontend/src/container/PanelWrapper/panelWrapper.types.ts b/frontend/src/container/PanelWrapper/panelWrapper.types.ts index e1983a5b69..66216bf284 100644 --- a/frontend/src/container/PanelWrapper/panelWrapper.types.ts +++ b/frontend/src/container/PanelWrapper/panelWrapper.types.ts @@ -28,6 +28,7 @@ export type PanelWrapperProps = { customTooltipElement?: HTMLDivElement; openTracesButton?: boolean; onOpenTraceBtnClick?: (record: RowData) => void; + customOnRowClick?: (record: RowData) => void; customSeries?: (data: QueryData[]) => uPlot.Series[]; }; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts b/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts index f2f84c0f88..66999c77ae 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts +++ b/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts @@ -3,7 +3,10 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types'; import { Widgets } from 'types/api/dashboard/getAll'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderFormula, + IBuilderQuery, +} from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; import { v4 as uuid } from 'uuid'; @@ -12,6 +15,7 @@ interface GetWidgetQueryProps { title: string; description: string; queryData: IBuilderQuery[]; + queryFormulas?: IBuilderFormula[]; panelTypes?: PANEL_TYPES; yAxisUnit?: string; columnUnits?: Record; @@ -67,7 +71,7 @@ export function getWidgetQuery( promql: [], builder: { queryData: props.queryData, - queryFormulas: [], + queryFormulas: (props.queryFormulas as IBuilderFormula[]) || [], }, clickhouse_sql: [], id: uuid(), diff --git a/frontend/src/types/api/dashboard/getAll.ts b/frontend/src/types/api/dashboard/getAll.ts index d531338eae..8ec581bb60 100644 --- a/frontend/src/types/api/dashboard/getAll.ts +++ b/frontend/src/types/api/dashboard/getAll.ts @@ -1,6 +1,7 @@ import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder'; import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; +import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; import { ReactNode } from 'react'; import { Layout } from 'react-grid-layout'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; @@ -113,6 +114,7 @@ export interface IBaseWidget { } export interface Widgets extends IBaseWidget { query: Query; + renderColumnCell?: QueryTableProps['renderColumnCell']; } export interface PromQLWidgets extends IBaseWidget {