diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx index fe4cdc144a6..9bd4672a90d 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -1,22 +1,24 @@ +/* eslint-disable no-restricted-syntax */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import '../InfraMonitoringK8s.styles.scss'; import { LoadingOutlined } from '@ant-design/icons'; import { - Skeleton, + Button, Spin, Table, TablePaginationConfig, TableProps, Typography, } from 'antd'; -import { SorterResult } from 'antd/es/table/interface'; +import { ColumnType, SorterResult } from 'antd/es/table/interface'; import logEvent from 'api/common/logEvent'; import { K8sNodesListPayload } from 'api/infraMonitoring/getK8sNodesList'; import { useGetK8sNodesList } from 'hooks/infraMonitoring/useGetK8sNodesList'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; +import { ChevronDown, ChevronRight } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; @@ -24,6 +26,8 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; import K8sHeader from '../K8sHeader'; +import LoadingContainer from '../LoadingContainer'; +import { dummyColumnConfig } from '../utils'; import { defaultAddedColumns, formatDataForTable, @@ -57,10 +61,68 @@ function K8sNodesList({ const [groupBy, setGroupBy] = useState([]); + const [selectedRowData, setSelectedRowData] = useState( + null, + ); + const [groupByOptions, setGroupByOptions] = useState< { value: string; label: string }[] >([]); + const createFiltersForSelectedRowData = ( + selectedRowData: K8sNodesRowData, + ): IBuilderQuery['filters'] => { + const baseFilters: IBuilderQuery['filters'] = { + items: [], + op: 'and', + }; + + if (!selectedRowData) return baseFilters; + + const { groupedByMeta } = selectedRowData; + + for (const key of Object.keys(groupedByMeta)) { + baseFilters.items.push({ + key: { + key, + }, + op: '=', + value: groupedByMeta[key], + }); + } + + return baseFilters; + }; + + const fetchGroupedByRowDataQuery = useMemo(() => { + if (!selectedRowData) return null; + + const baseQuery = getK8sNodesListQuery(); + + const filters = createFiltersForSelectedRowData(selectedRowData); + + return { + ...baseQuery, + limit: 10, + offset: 0, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [minTime, maxTime, orderBy, selectedRowData]); + + const { + data: groupedByRowData, + isFetching: isFetchingGroupedByRowData, + isLoading: isLoadingGroupedByRowData, + isError: isErrorGroupedByRowData, + refetch: fetchGroupedByRowData, + } = useGetK8sNodesList(fetchGroupedByRowDataQuery as K8sNodesListPayload, { + queryKey: ['nodeList', fetchGroupedByRowDataQuery], + enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, + }); + const { currentQuery } = useQueryBuilder(); const { @@ -106,10 +168,16 @@ function K8sNodesList({ return queryPayload; }, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]); + const formattedGroupedByNodesData = useMemo( + () => + formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy), + [groupedByRowData, groupBy], + ); + const { data, isFetching, isLoading, isError } = useGetK8sNodesList( query as K8sNodesListPayload, { - queryKey: ['hostList', query], + queryKey: ['nodeList', query], enabled: !!query, }, ); @@ -124,6 +192,16 @@ function K8sNodesList({ const columns = useMemo(() => getK8sNodesListColumns(groupBy), [groupBy]); + const handleGroupByRowClick = (record: K8sNodesRowData): void => { + setSelectedRowData(record); + }; + + useEffect(() => { + if (selectedRowData) { + fetchGroupedByRowData(); + } + }, [selectedRowData, fetchGroupedByRowData]); + const handleTableChange: TableProps['onChange'] = useCallback( ( pagination: TablePaginationConfig, @@ -174,13 +252,117 @@ function K8sNodesList({ // }, [selectedNodeUID, nodesData]); const handleRowClick = (record: K8sNodesRowData): void => { - // setselectedNodeUID(record.nodeUID); + if (groupBy.length === 0) { + setSelectedRowData(null); + // setselectedNodeUID(record.nodeUID); + } else { + handleGroupByRowClick(record); + } logEvent('Infra Monitoring: K8s node list item clicked', { nodeUID: record.nodeUID, }); }; + const nestedColumns = useMemo(() => { + const nestedColumns = getK8sNodesListColumns([]); + return [dummyColumnConfig, ...nestedColumns]; + }, []); + + const isGroupedByAttribute = groupBy.length > 0; + + const handleExpandedRowViewAllClick = (): void => { + if (!selectedRowData) return; + + const filters = createFiltersForSelectedRowData(selectedRowData); + + handleFiltersChange(filters); + + setCurrentPage(1); + setSelectedRowData(null); + setGroupBy([]); + setOrderBy(null); + }; + + const expandedRowRender = (): JSX.Element => ( +
+ {isErrorGroupedByRowData && ( + {groupedByRowData?.error || 'Something went wrong'} + )} + {isFetchingGroupedByRowData || isLoadingGroupedByRowData ? ( + + ) : ( +
+ []} + dataSource={formattedGroupedByNodesData} + pagination={false} + scroll={{ x: true }} + tableLayout="fixed" + size="small" + loading={{ + spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, + indicator: } />, + }} + /> + + {groupedByRowData?.payload?.data?.total && + groupedByRowData?.payload?.data?.total > 10 && ( +
+ +
+ )} + + )} + + ); + + const expandRowIconRenderer = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: K8sNodesRowData, + e: React.MouseEvent, + ) => void; + record: K8sNodesRowData; + }): JSX.Element | null => { + if (!isGroupedByAttribute) { + return null; + } + + return expanded ? ( + + ) : ( + + ); + }; + // const handleCloseNodeDetail = (): void => { // setselectedNodeUID(null); // }; @@ -259,28 +441,7 @@ function K8sNodesList({ )} - {(isFetching || isLoading) && ( -
- - - -
- )} + {(isFetching || isLoading) && } {showsNodesTable && (
} />, }} tableLayout="fixed" - rowKey={(record): string => record.nodeUID} onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record), className: 'clickable-row', })} + expandable={{ + expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, + expandIcon: expandRowIconRenderer, + }} /> )} {/* TODO - Handle Node Details flow */} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx index 20f75378dd5..5485f329161 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx @@ -1,10 +1,10 @@ -import { Button, Tag } from 'antd'; +import { Tag } from 'antd'; import { ColumnType } from 'antd/es/table'; import { K8sNodesData, K8sNodesListPayload, } from 'api/infraMonitoring/getK8sNodesList'; -import { ChevronRight, Group } from 'lucide-react'; +import { Group } from 'lucide-react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IEntityColumn } from '../utils'; @@ -51,15 +51,16 @@ export const defaultAddedColumns: IEntityColumn[] = [ export interface K8sNodesRowData { key: string; nodeUID: string; - nodeName: string; + nodeName: React.ReactNode; clusterName: string; cpuUtilization: React.ReactNode; cpuAllocatable: React.ReactNode; memoryUtilization: React.ReactNode; memoryAllocatable: React.ReactNode; + groupedByMeta?: any; } -const podGroupColumnConfig = { +const nodeGroupColumnConfig = { title: (
NODE GROUP @@ -141,7 +142,7 @@ export const getK8sNodesListColumns = ( const filteredColumns = [...columnsConfig].filter( (column) => column.key !== 'nodeName', ); - filteredColumns.unshift(podGroupColumnConfig); + filteredColumns.unshift(nodeGroupColumnConfig); return filteredColumns as ColumnType[]; } @@ -160,14 +161,6 @@ const getGroupByEle = ( return (
-
-
- {groupByValues.map((value) => ( {value === '' ? '' : value} @@ -184,11 +177,18 @@ export const formatDataForTable = ( data.map((node, index) => ({ key: `${node.nodeUID}-${index}`, nodeUID: node.nodeUID || '', - nodeName: node.meta.k8s_node_name, + nodeName: ( +
+
{node.meta.k8s_node_name || ''}
+
+ ), clusterName: node.meta.k8s_cluster_name, cpuUtilization: node.nodeCPUUsage, memoryUtilization: node.nodeMemoryUsage, cpuAllocatable: node.nodeCPUAllocatable, memoryAllocatable: node.nodeMemoryAllocatable, nodeGroup: getGroupByEle(node, groupBy), + meta: node.meta, + ...node.meta, + groupedByMeta: node.meta, }));