From ad61feedaadbb87e9b6e0158216a73bc6f4acb77 Mon Sep 17 00:00:00 2001 From: amlannandy Date: Wed, 11 Dec 2024 16:33:49 +0530 Subject: [PATCH 01/15] feat: implement nodes list table in infra-monitoring --- .../api/infraMonitoring/getK8sNodesList.ts | 64 +++++ .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 244 ++++++++++++++++++ .../InfraMonitoringK8s/Nodes/utils.tsx | 126 +++++++++ .../container/InfraMonitoringK8s/utils.tsx | 7 + .../infraMonitoring/useGetK8sNodesList.ts | 45 ++++ 5 files changed, 486 insertions(+) create mode 100644 frontend/src/api/infraMonitoring/getK8sNodesList.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sNodesList.ts b/frontend/src/api/infraMonitoring/getK8sNodesList.ts new file mode 100644 index 0000000000..18fc3ce42a --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sNodesList.ts @@ -0,0 +1,64 @@ +import { ApiBaseInstance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sNodesListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sNodesData { + nodeUID: string; + nodeCPUUsage: number; + nodeCPUAllocatable: number; + nodeMemoryUsage: number; + nodeMemoryAllocatable: number; + meta: { + k8s_node_name: string; + k8s_node_uid: string; + }; +} + +export interface K8sNodesListResponse { + status: string; + data: { + type: string; + records: K8sNodesData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sNodesList = async ( + props: K8sNodesListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post('/nodes/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx new file mode 100644 index 0000000000..a0ff5608d4 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -0,0 +1,244 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Skeleton, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { 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 { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import K8sHeader from '../K8sHeader'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sNodesListColumns, + getK8sNodesListQuery, + K8sNodesRowData, +} from './utils'; + +// eslint-disable-next-line sonarjs/cognitive-complexity +function K8sNodesList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [filters, setFilters] = useState({ + items: [], + op: 'and', + }); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>(null); + + // const [selectedNodeUID, setselectedNodeUID] = useState(null); + + const pageSize = 10; + + const query = useMemo(() => { + const baseQuery = getK8sNodesListQuery(); + return { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [currentPage, filters, minTime, maxTime, orderBy]); + + const { data, isFetching, isLoading, isError } = useGetK8sNodesList( + query as K8sNodesListPayload, + { + queryKey: ['hostList', query], + enabled: !!query, + }, + ); + + const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedNodesData = useMemo(() => formatDataForTable(nodesData), [ + nodesData, + ]); + + const columns = useMemo(() => getK8sNodesListColumns(), []); + + const handleTableChange: TableProps['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record, + sorter: SorterResult | SorterResult[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + const isNewFilterAdded = value.items.length !== filters.items.length; + if (isNewFilterAdded) { + setFilters(value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + } + }, + [filters], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + // const selectedNodeData = useMemo(() => { + // if (!selectedNodeUID) return null; + // return nodesData.find((node) => node.nodeUID === selectedNodeUID) || null; + // }, [selectedNodeUID, nodesData]); + + const handleRowClick = (record: K8sNodesRowData): void => { + // setselectedNodeUID(record.nodeUID); + + logEvent('Infra Monitoring: K8s node list item clicked', { + nodeUID: record.nodeUID, + }); + }; + + // const handleCloseNodeDetail = (): void => { + // setselectedNodeUID(null); + // }; + + const showsNodesTable = + !isError && + !isLoading && + !isFetching && + !(formattedNodesData.length === 0 && filters.items.length > 0); + + const showNoFilteredNodesMessage = + !isFetching && + !isLoading && + formattedNodesData.length === 0 && + filters.items.length > 0; + + return ( +
+ {}} + onRemoveColumn={() => {}} + /> + {isError && {data?.error || 'Something went wrong'}} + + {showNoFilteredNodesMessage && ( +
+
+ thinking-emoji + + + This query had no results. Edit your query and try again! + +
+
+ )} + + {(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', + })} + /> + )} + {/* TODO - Handle Node Details flow */} + + ); +} + +export default K8sNodesList; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx new file mode 100644 index 0000000000..b11633d9cf --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx @@ -0,0 +1,126 @@ +import { ColumnType } from 'antd/es/table'; +import { + K8sNodesData, + K8sNodesListPayload, +} from 'api/infraMonitoring/getK8sNodesList'; + +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Node Status', + value: 'nodeStatus', + id: 'nodeStatus', + canRemove: false, + }, + { + label: 'CPU Utilization (cores)', + value: 'cpuUtilization', + id: 'cpuUtilization', + canRemove: false, + }, + { + label: 'CPU Allocatable (cores)', + value: 'cpuAllocatable', + id: 'cpuAllocatable', + canRemove: false, + }, + { + label: 'Memory Allocatable (bytes)', + value: 'memoryAllocatable', + id: 'memoryAllocatable', + canRemove: false, + }, + { + label: 'Pods count by phase', + value: 'podsCount', + id: 'podsCount', + canRemove: false, + }, +]; + +export interface K8sNodesRowData { + key: string; + nodeUID: string; + nodeStatus: string; + cpuUtilization: React.ReactNode; + cpuAllocatable: React.ReactNode; + memoryUtilization: React.ReactNode; + memoryAllocatable: React.ReactNode; + podsCount: number; +} + +export const getK8sNodesListQuery = (): K8sNodesListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +const columnsConfig = [ + { + title:
Node Status
, + dataIndex: 'nodeStatus', + key: 'nodeStatus', + ellipsis: true, + width: 150, + sorter: true, + align: 'left', + }, + { + title:
CPU Utilization (cores)
, + dataIndex: 'cpuUtilization', + key: 'cpuUtilization', + width: 100, + sorter: true, + align: 'left', + }, + { + title:
CPU Allocatable (cores)
, + dataIndex: 'cpuAllocatable', + key: 'cpuAllocatable', + width: 100, + sorter: true, + align: 'left', + }, + { + title:
Memory Utilization (bytes)
, + dataIndex: 'memoryUtilization', + key: 'memoryUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title:
Memory Allocatable (bytes)
, + dataIndex: 'memoryAllocatable', + key: 'memoryAllocatable', + width: 80, + sorter: true, + align: 'left', + }, + { + title:
Pods count by phase
, + dataIndex: 'containerRestarts', + key: 'containerRestarts', + width: 50, + sorter: true, + align: 'left', + }, +]; + +export const getK8sNodesListColumns = (): ColumnType[] => + columnsConfig as ColumnType[]; + +export const formatDataForTable = (data: K8sNodesData[]): K8sNodesRowData[] => + data.map((node, index) => ({ + key: `${node.nodeUID}-${index}`, + nodeUID: node.nodeUID || '', + cpuUtilization: node.nodeCPUUsage, + memoryUtilization: node.nodeMemoryUsage, + cpuAllocatable: node.nodeCPUAllocatable, + memoryAllocatable: node.nodeMemoryAllocatable, + nodeStatus: node.meta.k8s_node_name, + podsCount: node.nodeCPUAllocatable, + })); diff --git a/frontend/src/container/InfraMonitoringK8s/utils.tsx b/frontend/src/container/InfraMonitoringK8s/utils.tsx index f16b4fcb00..a3a328d613 100644 --- a/frontend/src/container/InfraMonitoringK8s/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/utils.tsx @@ -20,6 +20,13 @@ import { } from './commonUtils'; import { INVALID_MEMORY_CPU_VALUE_MESSAGE } from './constants'; +export interface IEntityColumn { + label: string; + value: string; + id: string; + canRemove: boolean; +} + export interface IPodColumn { label: string; value: string; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts new file mode 100644 index 0000000000..4e2ac964d2 --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts @@ -0,0 +1,45 @@ +import { + getK8sNodesList, + K8sNodesListPayload, + K8sNodesListResponse, +} from 'api/infraMonitoring/getK8sNodesList'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useMemo } from 'react'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +type UseGetK8sNodesList = ( + requestData: K8sNodesListPayload, + options?: UseQueryOptions< + SuccessResponse | ErrorResponse, + Error + >, + headers?: Record, +) => UseQueryResult< + SuccessResponse | ErrorResponse, + Error +>; + +export const useGetK8sNodesList: UseGetK8sNodesList = ( + requestData, + options, + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery | ErrorResponse, Error>({ + queryFn: ({ signal }) => getK8sNodesList(requestData, signal, headers), + ...options, + queryKey, + }); +}; From 15c5a70aa73d8ccb84653d58f332ba24350a937d Mon Sep 17 00:00:00 2001 From: amlannandy Date: Wed, 11 Dec 2024 22:19:05 +0530 Subject: [PATCH 02/15] chore: update header props --- .../container/InfraMonitoringK8s/K8sHeader.tsx | 18 +++++++++++++----- .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 4 ---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx index fa2cd0d83f..1776bc2f5c 100644 --- a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx +++ b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import './InfraMonitoringK8s.styles.scss'; import { Button, Select } from 'antd'; @@ -14,15 +15,15 @@ import { IPodColumn } from './utils'; interface K8sHeaderProps { selectedGroupBy: BaseAutocompleteData[]; - defaultAddedColumns: IPodColumn[]; groupByOptions: { value: string; label: string }[]; - addedColumns: IPodColumn[]; isLoadingGroupByFilters: boolean; - availableColumns: IPodColumn[]; handleFiltersChange: (value: IBuilderQuery['filters']) => void; handleGroupByChange: (value: IBuilderQuery['groupBy']) => void; - onAddColumn: (column: IPodColumn) => void; - onRemoveColumn: (column: IPodColumn) => void; + defaultAddedColumns: IPodColumn[]; + addedColumns?: IPodColumn[]; + availableColumns?: IPodColumn[]; + onAddColumn?: (column: IPodColumn) => void; + onRemoveColumn?: (column: IPodColumn) => void; handleFilterVisibilityChange: () => void; isFiltersVisible: boolean; } @@ -150,4 +151,11 @@ function K8sHeader({ ); } +K8sHeader.defaultProps = { + addedColumns: [], + availableColumns: [], + onAddColumn: () => {}, + onRemoveColumn: () => {}, +}; + export default K8sHeader; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx index a0ff5608d4..ae75ea16dd 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -163,11 +163,7 @@ function K8sNodesList({ isFiltersVisible={isFiltersVisible} handleFilterVisibilityChange={handleFilterVisibilityChange} defaultAddedColumns={defaultAddedColumns} - addedColumns={[]} - availableColumns={[]} handleFiltersChange={handleFiltersChange} - onAddColumn={() => {}} - onRemoveColumn={() => {}} /> {isError && {data?.error || 'Something went wrong'}} From 02073fe21fb129489c00379f946490d40f79e271 Mon Sep 17 00:00:00 2001 From: amlannandy Date: Sun, 15 Dec 2024 21:03:50 +0530 Subject: [PATCH 03/15] chore: update columns --- .../InfraMonitoringK8s/Nodes/utils.tsx | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx index b11633d9cf..0b48ffb271 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx @@ -8,7 +8,7 @@ import { IEntityColumn } from '../utils'; export const defaultAddedColumns: IEntityColumn[] = [ { - label: 'Node Status', + label: 'Node Name', value: 'nodeStatus', id: 'nodeStatus', canRemove: false, @@ -26,15 +26,15 @@ export const defaultAddedColumns: IEntityColumn[] = [ canRemove: false, }, { - label: 'Memory Allocatable (bytes)', - value: 'memoryAllocatable', - id: 'memoryAllocatable', + label: 'Memory Utilization (bytes)', + value: 'memoryUtilization', + id: 'memoryUtilization', canRemove: false, }, { - label: 'Pods count by phase', - value: 'podsCount', - id: 'podsCount', + label: 'Memory Allocatable (bytes)', + value: 'memoryAllocatable', + id: 'memoryAllocatable', canRemove: false, }, ]; @@ -42,12 +42,11 @@ export const defaultAddedColumns: IEntityColumn[] = [ export interface K8sNodesRowData { key: string; nodeUID: string; - nodeStatus: string; + nodeName: string; cpuUtilization: React.ReactNode; cpuAllocatable: React.ReactNode; memoryUtilization: React.ReactNode; memoryAllocatable: React.ReactNode; - podsCount: number; } export const getK8sNodesListQuery = (): K8sNodesListPayload => ({ @@ -60,9 +59,9 @@ export const getK8sNodesListQuery = (): K8sNodesListPayload => ({ const columnsConfig = [ { - title:
Node Status
, - dataIndex: 'nodeStatus', - key: 'nodeStatus', + title:
Node Name
, + dataIndex: 'nodeName', + key: 'nodeName', ellipsis: true, width: 150, sorter: true, @@ -72,7 +71,7 @@ const columnsConfig = [ title:
CPU Utilization (cores)
, dataIndex: 'cpuUtilization', key: 'cpuUtilization', - width: 100, + width: 80, sorter: true, align: 'left', }, @@ -80,7 +79,7 @@ const columnsConfig = [ title:
CPU Allocatable (cores)
, dataIndex: 'cpuAllocatable', key: 'cpuAllocatable', - width: 100, + width: 80, sorter: true, align: 'left', }, @@ -100,14 +99,6 @@ const columnsConfig = [ sorter: true, align: 'left', }, - { - title:
Pods count by phase
, - dataIndex: 'containerRestarts', - key: 'containerRestarts', - width: 50, - sorter: true, - align: 'left', - }, ]; export const getK8sNodesListColumns = (): ColumnType[] => @@ -117,10 +108,9 @@ export const formatDataForTable = (data: K8sNodesData[]): K8sNodesRowData[] => data.map((node, index) => ({ key: `${node.nodeUID}-${index}`, nodeUID: node.nodeUID || '', + nodeName: node.meta.k8s_node_name, cpuUtilization: node.nodeCPUUsage, memoryUtilization: node.nodeMemoryUsage, cpuAllocatable: node.nodeCPUAllocatable, memoryAllocatable: node.nodeMemoryAllocatable, - nodeStatus: node.meta.k8s_node_name, - podsCount: node.nodeCPUAllocatable, })); From ef29840040d50851962159cabd45069b2036365e Mon Sep 17 00:00:00 2001 From: amlannandy Date: Mon, 16 Dec 2024 11:31:56 +0530 Subject: [PATCH 04/15] chore: add cluster name column --- .../src/api/infraMonitoring/getK8sNodesList.ts | 1 + .../InfraMonitoringK8s/Nodes/utils.tsx | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/frontend/src/api/infraMonitoring/getK8sNodesList.ts b/frontend/src/api/infraMonitoring/getK8sNodesList.ts index 18fc3ce42a..e3c411b1d0 100644 --- a/frontend/src/api/infraMonitoring/getK8sNodesList.ts +++ b/frontend/src/api/infraMonitoring/getK8sNodesList.ts @@ -25,6 +25,7 @@ export interface K8sNodesData { meta: { k8s_node_name: string; k8s_node_uid: string; + k8s_cluster_name: string; }; } diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx index 0b48ffb271..b226bab2c6 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx @@ -13,6 +13,12 @@ export const defaultAddedColumns: IEntityColumn[] = [ id: 'nodeStatus', canRemove: false, }, + { + label: 'Cluster Name', + value: 'clusterStatus', + id: 'clusterStatus', + canRemove: false, + }, { label: 'CPU Utilization (cores)', value: 'cpuUtilization', @@ -43,6 +49,7 @@ export interface K8sNodesRowData { key: string; nodeUID: string; nodeName: string; + clusterName: string; cpuUtilization: React.ReactNode; cpuAllocatable: React.ReactNode; memoryUtilization: React.ReactNode; @@ -67,6 +74,15 @@ const columnsConfig = [ sorter: true, align: 'left', }, + { + title:
Cluster Name
, + dataIndex: 'clusterName', + key: 'clusterName', + ellipsis: true, + width: 150, + sorter: true, + align: 'left', + }, { title:
CPU Utilization (cores)
, dataIndex: 'cpuUtilization', @@ -109,6 +125,7 @@ export const formatDataForTable = (data: K8sNodesData[]): K8sNodesRowData[] => key: `${node.nodeUID}-${index}`, nodeUID: node.nodeUID || '', nodeName: node.meta.k8s_node_name, + clusterName: node.meta.k8s_cluster_name, cpuUtilization: node.nodeCPUUsage, memoryUtilization: node.nodeMemoryUsage, cpuAllocatable: node.nodeCPUAllocatable, From 0c8ba0360c5dbd9c6108289a5a0af7cd0dd987bc Mon Sep 17 00:00:00 2001 From: amlannandy Date: Wed, 18 Dec 2024 18:27:21 +0530 Subject: [PATCH 05/15] chore: update props --- .../K8sFiltersSidePanel.tsx | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/K8sFiltersSidePanel/K8sFiltersSidePanel.tsx b/frontend/src/container/InfraMonitoringK8s/K8sFiltersSidePanel/K8sFiltersSidePanel.tsx index 3b8beabb69..8062f338b0 100644 --- a/frontend/src/container/InfraMonitoringK8s/K8sFiltersSidePanel/K8sFiltersSidePanel.tsx +++ b/frontend/src/container/InfraMonitoringK8s/K8sFiltersSidePanel/K8sFiltersSidePanel.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import './K8sFiltersSidePanel.styles.scss'; @@ -8,20 +9,20 @@ import { useEffect, useRef, useState } from 'react'; import { IPodColumn } from '../utils'; -export default function K8sFiltersSidePanel({ +function K8sFiltersSidePanel({ defaultAddedColumns, onClose, - addedColumns, - availableColumns, - onAddColumn, - onRemoveColumn, + addedColumns = [], + availableColumns = [], + onAddColumn = () => {}, + onRemoveColumn = () => {}, }: { defaultAddedColumns: IPodColumn[]; onClose: () => void; - addedColumns: IPodColumn[]; - availableColumns: IPodColumn[]; - onAddColumn: (column: IPodColumn) => void; - onRemoveColumn: (column: IPodColumn) => void; + addedColumns?: IPodColumn[]; + availableColumns?: IPodColumn[]; + onAddColumn?: (column: IPodColumn) => void; + onRemoveColumn?: (column: IPodColumn) => void; }): JSX.Element { const [searchValue, setSearchValue] = useState(''); const sidePanelRef = useRef(null); @@ -117,3 +118,12 @@ export default function K8sFiltersSidePanel({ ); } + +K8sFiltersSidePanel.defaultProps = { + addedColumns: [], + availableColumns: [], + onAddColumn: () => {}, + onRemoveColumn: () => {}, +}; + +export default K8sFiltersSidePanel; From 57f33dc05e520cdadd36812f1a72ca69a9ab788e Mon Sep 17 00:00:00 2001 From: amlannandy Date: Wed, 18 Dec 2024 23:41:49 +0530 Subject: [PATCH 06/15] feat: implement group-by and quick select in node list table --- .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 8 ++ .../InfraMonitoringK8s/K8sHeader.tsx | 2 +- .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 122 ++++++++++++++---- .../InfraMonitoringK8s/Nodes/utils.tsx | 67 +++++++++- .../container/InfraMonitoringK8s/constants.ts | 21 ++- 5 files changed, 192 insertions(+), 28 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index f255d238a0..80eb521197 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -36,6 +36,7 @@ import { StatefulsetsQuickFiltersConfig, VolumesQuickFiltersConfig, } from './constants'; +import K8sNodesList from './Nodes/K8sNodesList'; import K8sPodLists from './Pods/K8sPodLists'; import Volumes from './Volumes/Volumes'; @@ -323,6 +324,13 @@ export default function InfraMonitoringK8s(): JSX.Element { /> )} + {selectedCategory === K8sCategories.NODES && ( + + )} + {selectedCategory === K8sCategories.VOLUMES && } diff --git a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx index 1776bc2f5c..ce19ced8be 100644 --- a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx +++ b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx @@ -126,7 +126,7 @@ function K8sHeader({
[]} + 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 20f75378dd..5485f32916 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, })); From 27fa29008e08fe8bf61f67b088fcb7afa57e078a Mon Sep 17 00:00:00 2001 From: amlannandy Date: Thu, 19 Dec 2024 12:32:06 +0530 Subject: [PATCH 08/15] chore: styling changes --- .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 24 +++++++++---------- .../InfraMonitoringK8s/Nodes/utils.tsx | 20 +++++++++------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx index 9bd4672a90..c6d35848de 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -307,18 +307,18 @@ function K8sNodesList({ /> {groupedByRowData?.payload?.data?.total && - groupedByRowData?.payload?.data?.total > 10 && ( -
- -
- )} + groupedByRowData?.payload?.data?.total > 10 ? ( +
+ +
+ ) : null}
)}
diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx index 5485f32916..810f70fd02 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx @@ -1,4 +1,4 @@ -import { Tag } from 'antd'; +import { Tag, Tooltip } from 'antd'; import { ColumnType } from 'antd/es/table'; import { K8sNodesData, @@ -88,7 +88,7 @@ const columnsConfig = [ dataIndex: 'nodeName', key: 'nodeName', ellipsis: true, - width: 150, + width: 120, sorter: true, align: 'left', }, @@ -97,7 +97,7 @@ const columnsConfig = [ dataIndex: 'clusterName', key: 'clusterName', ellipsis: true, - width: 150, + width: 120, sorter: true, align: 'left', }, @@ -179,14 +179,18 @@ export const formatDataForTable = ( nodeUID: node.nodeUID || '', nodeName: (
-
{node.meta.k8s_node_name || ''}
+ +
{node.meta.k8s_node_name || ''}
+
), clusterName: node.meta.k8s_cluster_name, - cpuUtilization: node.nodeCPUUsage, - memoryUtilization: node.nodeMemoryUsage, - cpuAllocatable: node.nodeCPUAllocatable, - memoryAllocatable: node.nodeMemoryAllocatable, + cpuUtilization: node.nodeCPUUsage === -1 ? '-' : node.nodeCPUUsage, + memoryUtilization: node.nodeMemoryUsage === -1 ? '-' : node.nodeMemoryUsage, + cpuAllocatable: + node.nodeCPUAllocatable === -1 ? '-' : node.nodeCPUAllocatable, + memoryAllocatable: + node.nodeMemoryAllocatable === -1 ? '-' : node.nodeMemoryAllocatable, nodeGroup: getGroupByEle(node, groupBy), meta: node.meta, ...node.meta, From f3d012caf34f212fe05ebb5e0be5b950a0d5d13a Mon Sep 17 00:00:00 2001 From: amlannandy Date: Thu, 19 Dec 2024 16:38:47 +0530 Subject: [PATCH 09/15] chore: fix expand issues --- .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx index c6d35848de..e448347d17 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -50,6 +50,8 @@ function K8sNodesList({ const [currentPage, setCurrentPage] = useState(1); + const [expandedRowKeys, setExpandedRowKeys] = useState([]); + const [orderBy, setOrderBy] = useState<{ columnName: string; order: 'asc' | 'desc'; @@ -194,6 +196,12 @@ function K8sNodesList({ const handleGroupByRowClick = (record: K8sNodesRowData): void => { setSelectedRowData(record); + + if (expandedRowKeys.includes(record.key)) { + setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); + } else { + setExpandedRowKeys([record.key]); + } }; useEffect(() => { @@ -396,6 +404,7 @@ function K8sNodesList({ } setGroupBy(groupBy); + setExpandedRowKeys([]); }, [groupByFiltersData], ); @@ -469,6 +478,7 @@ function K8sNodesList({ expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer, + expandedRowKeys, }} /> )} From 83bdab74544769889a23803fcf651340b427496d Mon Sep 17 00:00:00 2001 From: amlannandy Date: Wed, 11 Dec 2024 16:33:49 +0530 Subject: [PATCH 10/15] feat: implement nodes list table in infra-monitoring --- frontend/src/container/InfraMonitoringK8s/utils.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/container/InfraMonitoringK8s/utils.tsx b/frontend/src/container/InfraMonitoringK8s/utils.tsx index a3a328d613..3116ecd259 100644 --- a/frontend/src/container/InfraMonitoringK8s/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/utils.tsx @@ -27,6 +27,13 @@ export interface IEntityColumn { canRemove: boolean; } +export interface IEntityColumn { + label: string; + value: string; + id: string; + canRemove: boolean; +} + export interface IPodColumn { label: string; value: string; From fca50d6f1abd4096271ca62787dc3620f382193b Mon Sep 17 00:00:00 2001 From: amlannandy Date: Thu, 12 Dec 2024 00:12:44 +0530 Subject: [PATCH 11/15] feat: implement deployments list table in infra monitoring --- .../infraMonitoring/getK8sDeploymentsList.ts | 70 +++++ .../Deployments/K8sDeploymentsList.tsx | 248 ++++++++++++++++++ .../InfraMonitoringK8s/Deployments/utils.tsx | 248 ++++++++++++++++++ .../useGetK8sDeploymentsList.ts | 48 ++++ 4 files changed, 614 insertions(+) create mode 100644 frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts b/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts new file mode 100644 index 0000000000..9ce4643687 --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts @@ -0,0 +1,70 @@ +import { ApiBaseInstance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sDeploymentsListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sDeploymentsData { + deploymentName: string; + cpuUsage: number; + memoryUsage: number; + desiredPods: number; + availablePods: number; + cpuRequest: number; + memoryRequest: number; + cpuLimit: number; + memoryLimit: number; + restarts: number; + meta: { + k8s_cluster_name: string; + k8s_deployment_name: string; + k8s_namespace_name: string; + }; +} + +export interface K8sDeploymentsListResponse { + status: string; + data: { + type: string; + records: K8sDeploymentsData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sDeploymentsList = async ( + props: K8sDeploymentsListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post('/deployments/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx new file mode 100644 index 0000000000..15d6fda34c --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx @@ -0,0 +1,248 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Skeleton, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; +import { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList'; +import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import K8sHeader from '../K8sHeader'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sDeploymentsListColumns, + getK8sDeploymentsListQuery, + K8sDeploymentsRowData, +} from './utils'; + +function K8sDeploymentsList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [filters, setFilters] = useState({ + items: [], + op: 'and', + }); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>(null); + + // const [selectedDeploymentUID, setselectedDeploymentUID] = useState(null); + + const pageSize = 10; + + const query = useMemo(() => { + const baseQuery = getK8sDeploymentsListQuery(); + return { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [currentPage, filters, minTime, maxTime, orderBy]); + + const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList( + query as K8sDeploymentsListPayload, + { + queryKey: ['hostList', query], + enabled: !!query, + }, + ); + + const DeploymentsData = useMemo(() => data?.payload?.data?.records || [], [ + data, + ]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedDeploymentsData = useMemo( + () => formatDataForTable(DeploymentsData), + [DeploymentsData], + ); + + const columns = useMemo(() => getK8sDeploymentsListColumns(), []); + + const handleTableChange: TableProps['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record, + sorter: + | SorterResult + | SorterResult[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + const isNewFilterAdded = value.items.length !== filters.items.length; + if (isNewFilterAdded) { + setFilters(value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + } + }, + [filters], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + // const selectedDeploymentData = useMemo(() => { + // if (!selectedDeploymentUID) return null; + // return DeploymentsData.find((deployment) => deployment.DeploymentUID === selectedDeploymentUID) || null; + // }, [selectedDeploymentUID, DeploymentsData]); + + const handleRowClick = (record: K8sDeploymentsRowData): void => { + // setselectedDeploymentUID(record.DeploymentUID); + + logEvent('Infra Monitoring: K8s deployment list item clicked', { + deploymentName: record.deploymentName, + }); + }; + + // const handleCloseDeploymentDetail = (): void => { + // setselectedDeploymentUID(null); + // }; + + const showsDeploymentsTable = + !isError && + !isLoading && + !isFetching && + !(formattedDeploymentsData.length === 0 && filters.items.length > 0); + + const showNoFilteredDeploymentsMessage = + !isFetching && + !isLoading && + formattedDeploymentsData.length === 0 && + filters.items.length > 0; + + return ( +
+ {}} + onRemoveColumn={() => {}} + /> + {isError && {data?.error || 'Something went wrong'}} + + {showNoFilteredDeploymentsMessage && ( +
+
+ thinking-emoji + + + This query had no results. Edit your query and try again! + +
+
+ )} + + {(isFetching || isLoading) && ( +
+ + + +
+ )} + + {showsDeploymentsTable && ( +
} />, + }} + tableLayout="fixed" + rowKey={(record): string => record.deploymentName} + onChange={handleTableChange} + onRow={(record): { onClick: () => void; className: string } => ({ + onClick: (): void => handleRowClick(record), + className: 'clickable-row', + })} + /> + )} + {/* TODO - Handle Deployment Details flow */} + + ); +} + +export default K8sDeploymentsList; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx new file mode 100644 index 0000000000..347762d772 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -0,0 +1,248 @@ +import { Color } from '@signozhq/design-tokens'; +import { Progress } from 'antd'; +import { ColumnType } from 'antd/es/table'; +import { + K8sDeploymentsData, + K8sDeploymentsListPayload, +} from 'api/infraMonitoring/getK8sDeploymentsList'; + +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Namespace Status', + value: 'NamespaceStatus', + id: 'NamespaceStatus', + canRemove: false, + }, + { + label: 'CPU Utilization (cores)', + value: 'cpuUsage', + id: 'cpuUsage', + canRemove: false, + }, + { + label: 'CPU Allocatable (cores)', + value: 'cpuAllocatable', + id: 'cpuAllocatable', + canRemove: false, + }, + { + label: 'Memory Allocatable (bytes)', + value: 'memoryAllocatable', + id: 'memoryAllocatable', + canRemove: false, + }, + { + label: 'Pods count by phase', + value: 'podsCount', + id: 'podsCount', + canRemove: false, + }, +]; + +export interface K8sDeploymentsRowData { + key: string; + deploymentName: string; + availableReplicas: number; + desiredReplicas: number; + cpuRequestUtilization: React.ReactNode; + cpuLimitUtilization: React.ReactNode; + cpuUtilization: number; + memoryRequestUtilization: React.ReactNode; + memoryLimitUtilization: React.ReactNode; + memoryUtilization: number; + containerRestarts: number; +} + +export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +// - Available Replicas +// - Desired Replicas +// - CPU Request Utilization (% of limit) +// - CPU Limit Utilization (% of request) +// - CPU Utilization (cores) +// - Memory Request Utilization (% of limit) +// - Memory Limit Utilization (% of request) +// - Memory Utilization (bytes) +// - Container Restarts + +const columnsConfig = [ + { + title:
Deployment
, + dataIndex: 'deploymentName', + key: 'deploymentName', + ellipsis: true, + width: 150, + sorter: true, + align: 'left', + }, + { + title:
Available Replicas
, + dataIndex: 'availableReplicas', + key: 'availableReplicas', + width: 100, + sorter: true, + align: 'left', + }, + { + title:
Desired Replicas
, + dataIndex: 'desiredReplicas', + key: 'desiredReplicas', + width: 80, + sorter: true, + align: 'left', + }, + { + title: ( +
+ CPU Request Utilization (% of limit) +
+ ), + dataIndex: 'cpuRequestUtilization', + key: 'cpuRequestUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title: ( +
+ CPU Limit Utilization (% of request) +
+ ), + dataIndex: 'cpuLimitUtilization', + key: 'cpuLimitUtilization', + width: 50, + sorter: true, + align: 'left', + }, + { + title:
CPU Utilization (cores)
, + dataIndex: 'cpuUtilization', + key: 'cpuUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title: ( +
+ Memory Request Utilization (% of limit) +
+ ), + dataIndex: 'memoryRequestUtilization', + key: 'memoryRequestUtilization', + width: 50, + sorter: true, + align: 'left', + }, + { + title: ( +
+ Memory Limit Utilization (% of request) +
+ ), + dataIndex: 'memoryLimitUtilization', + key: 'memoryLimitUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title:
Container Restarts
, + dataIndex: 'containerRestarts', + key: 'containerRestarts', + width: 50, + sorter: true, + align: 'left', + }, +]; + +export const getK8sDeploymentsListColumns = (): ColumnType[] => + columnsConfig as ColumnType[]; + +const getStrokeColorForProgressBar = (value: number): string => { + if (value >= 90) return Color.BG_SAKURA_500; + if (value >= 60) return Color.BG_AMBER_500; + return Color.BG_FOREST_500; +}; + +export const formatDataForTable = ( + data: K8sDeploymentsData[], +): K8sDeploymentsRowData[] => + data.map((deployment, index) => ({ + key: `${deployment.meta.k8s_deployment_name}-${index}`, + deploymentName: deployment.meta.k8s_deployment_name, + availableReplicas: deployment.availablePods, + desiredReplicas: deployment.desiredPods, + containerRestarts: deployment.restarts, + cpuUtilization: deployment.cpuUsage, + cpuRequestUtilization: ( +
+ { + const cpuPercent = Number((deployment.cpuRequest * 100).toFixed(1)); + return getStrokeColorForProgressBar(cpuPercent); + })()} + className="progress-bar" + /> +
+ ), + cpuLimitUtilization: ( +
+ { + const cpuPercent = Number((deployment.cpuLimit * 100).toFixed(1)); + return getStrokeColorForProgressBar(cpuPercent); + })()} + className="progress-bar" + /> +
+ ), + memoryUtilization: deployment.memoryUsage, + memoryRequestUtilization: ( +
+ { + const memoryPercent = Number((deployment.memoryRequest * 100).toFixed(1)); + return getStrokeColorForProgressBar(memoryPercent); + })()} + className="progress-bar" + /> +
+ ), + memoryLimitUtilization: ( +
+ { + const memoryPercent = Number((deployment.memoryLimit * 100).toFixed(1)); + return getStrokeColorForProgressBar(memoryPercent); + })()} + className="progress-bar" + /> +
+ ), + })); diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts new file mode 100644 index 0000000000..8e4926b78a --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts @@ -0,0 +1,48 @@ +import { + getK8sDeploymentsList, + K8sDeploymentsListPayload, + K8sDeploymentsListResponse, +} from 'api/infraMonitoring/getK8sDeploymentsList'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useMemo } from 'react'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +type UseGetK8sDeploymentsList = ( + requestData: K8sDeploymentsListPayload, + options?: UseQueryOptions< + SuccessResponse | ErrorResponse, + Error + >, + headers?: Record, +) => UseQueryResult< + SuccessResponse | ErrorResponse, + Error +>; + +export const useGetK8sDeploymentsList: UseGetK8sDeploymentsList = ( + requestData, + options, + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery< + SuccessResponse | ErrorResponse, + Error + >({ + queryFn: ({ signal }) => getK8sDeploymentsList(requestData, signal, headers), + ...options, + queryKey, + }); +}; From e1366c00eedd2f72fb8d6b1c11e54ae55b3c2b8e Mon Sep 17 00:00:00 2001 From: amlannandy Date: Thu, 12 Dec 2024 10:28:32 +0530 Subject: [PATCH 12/15] chore: remove comments --- .../container/InfraMonitoringK8s/Deployments/utils.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx index 347762d772..4c50531838 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -63,16 +63,6 @@ export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ orderBy: { columnName: 'cpu', order: 'desc' }, }); -// - Available Replicas -// - Desired Replicas -// - CPU Request Utilization (% of limit) -// - CPU Limit Utilization (% of request) -// - CPU Utilization (cores) -// - Memory Request Utilization (% of limit) -// - Memory Limit Utilization (% of request) -// - Memory Utilization (bytes) -// - Container Restarts - const columnsConfig = [ { title:
Deployment
, From bdcd995aafad21479e65855ffb7861857605a541 Mon Sep 17 00:00:00 2001 From: amlannandy Date: Sun, 22 Dec 2024 19:11:16 +0530 Subject: [PATCH 13/15] feat: remove nodes data --- .../api/infraMonitoring/getK8sNodesList.ts | 65 --- .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 490 ------------------ .../InfraMonitoringK8s/Nodes/utils.tsx | 198 ------- .../infraMonitoring/useGetK8sNodesList.ts | 45 -- 4 files changed, 798 deletions(-) delete mode 100644 frontend/src/api/infraMonitoring/getK8sNodesList.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx delete mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sNodesList.ts b/frontend/src/api/infraMonitoring/getK8sNodesList.ts deleted file mode 100644 index e3c411b1d0..0000000000 --- a/frontend/src/api/infraMonitoring/getK8sNodesList.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ApiBaseInstance } from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; -import { ErrorResponse, SuccessResponse } from 'types/api'; -import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; - -export interface K8sNodesListPayload { - filters: TagFilter; - groupBy?: BaseAutocompleteData[]; - offset?: number; - limit?: number; - orderBy?: { - columnName: string; - order: 'asc' | 'desc'; - }; -} - -export interface K8sNodesData { - nodeUID: string; - nodeCPUUsage: number; - nodeCPUAllocatable: number; - nodeMemoryUsage: number; - nodeMemoryAllocatable: number; - meta: { - k8s_node_name: string; - k8s_node_uid: string; - k8s_cluster_name: string; - }; -} - -export interface K8sNodesListResponse { - status: string; - data: { - type: string; - records: K8sNodesData[]; - groups: null; - total: number; - sentAnyHostMetricsData: boolean; - isSendingK8SAgentMetrics: boolean; - }; -} - -export const getK8sNodesList = async ( - props: K8sNodesListPayload, - signal?: AbortSignal, - headers?: Record, -): Promise | ErrorResponse> => { - try { - const response = await ApiBaseInstance.post('/nodes/list', props, { - signal, - headers, - }); - - return { - statusCode: 200, - error: null, - message: 'Success', - payload: response.data, - params: props, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx deleted file mode 100644 index e448347d17..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx +++ /dev/null @@ -1,490 +0,0 @@ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import '../InfraMonitoringK8s.styles.scss'; - -import { LoadingOutlined } from '@ant-design/icons'; -import { - Button, - Spin, - Table, - TablePaginationConfig, - TableProps, - Typography, -} from 'antd'; -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'; -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, - getK8sNodesListColumns, - getK8sNodesListQuery, - K8sNodesRowData, -} from './utils'; - -// eslint-disable-next-line sonarjs/cognitive-complexity -function K8sNodesList({ - isFiltersVisible, - handleFilterVisibilityChange, -}: { - isFiltersVisible: boolean; - handleFilterVisibilityChange: () => void; -}): JSX.Element { - const { maxTime, minTime } = useSelector( - (state) => state.globalTime, - ); - - const [currentPage, setCurrentPage] = useState(1); - - const [expandedRowKeys, setExpandedRowKeys] = useState([]); - - const [orderBy, setOrderBy] = useState<{ - columnName: string; - order: 'asc' | 'desc'; - } | null>(null); - - // const [selectedNodeUID, setselectedNodeUID] = useState(null); - - const pageSize = 10; - - 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 { - data: groupByFiltersData, - isLoading: isLoadingGroupByFilters, - } = useGetAggregateKeys( - { - dataSource: currentQuery.builder.queryData[0].dataSource, - aggregateAttribute: '', - aggregateOperator: 'noop', - searchText: '', - tagType: '', - }, - { - queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], - }, - true, - ); - - const queryFilters = useMemo( - () => - currentQuery?.builder?.queryData[0]?.filters || { - items: [], - op: 'and', - }, - [currentQuery?.builder?.queryData], - ); - - const query = useMemo(() => { - const baseQuery = getK8sNodesListQuery(); - const queryPayload = { - ...baseQuery, - limit: pageSize, - offset: (currentPage - 1) * pageSize, - filters: queryFilters, - start: Math.floor(minTime / 1000000), - end: Math.floor(maxTime / 1000000), - orderBy, - }; - if (groupBy.length > 0) { - queryPayload.groupBy = groupBy; - } - 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: ['nodeList', query], - enabled: !!query, - }, - ); - - const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]); - const totalCount = data?.payload?.data?.total || 0; - - const formattedNodesData = useMemo( - () => formatDataForTable(nodesData, groupBy), - [nodesData, groupBy], - ); - - const columns = useMemo(() => getK8sNodesListColumns(groupBy), [groupBy]); - - const handleGroupByRowClick = (record: K8sNodesRowData): void => { - setSelectedRowData(record); - - if (expandedRowKeys.includes(record.key)) { - setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); - } else { - setExpandedRowKeys([record.key]); - } - }; - - useEffect(() => { - if (selectedRowData) { - fetchGroupedByRowData(); - } - }, [selectedRowData, fetchGroupedByRowData]); - - const handleTableChange: TableProps['onChange'] = useCallback( - ( - pagination: TablePaginationConfig, - _filters: Record, - sorter: SorterResult | SorterResult[], - ): void => { - if (pagination.current) { - setCurrentPage(pagination.current); - } - - if ('field' in sorter && sorter.order) { - setOrderBy({ - columnName: sorter.field as string, - order: sorter.order === 'ascend' ? 'asc' : 'desc', - }); - } else { - setOrderBy(null); - } - }, - [], - ); - - const { handleChangeQueryData } = useQueryOperations({ - index: 0, - query: currentQuery.builder.queryData[0], - entityVersion: '', - }); - - const handleFiltersChange = useCallback( - (value: IBuilderQuery['filters']): void => { - handleChangeQueryData('filters', value); - setCurrentPage(1); - - logEvent('Infra Monitoring: K8s list filters applied', { - filters: value, - }); - }, - [handleChangeQueryData], - ); - - useEffect(() => { - logEvent('Infra Monitoring: K8s list page visited', {}); - }, []); - - // const selectedNodeData = useMemo(() => { - // if (!selectedNodeUID) return null; - // return nodesData.find((node) => node.nodeUID === selectedNodeUID) || null; - // }, [selectedNodeUID, nodesData]); - - const handleRowClick = (record: K8sNodesRowData): void => { - 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 ? ( -
- -
- ) : null} - - )} - - ); - - 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); - // }; - - const showsNodesTable = - !isError && - !isLoading && - !isFetching && - !(formattedNodesData.length === 0 && queryFilters.items.length > 0); - - const showNoFilteredNodesMessage = - !isFetching && - !isLoading && - formattedNodesData.length === 0 && - queryFilters.items.length > 0; - - const handleGroupByChange = useCallback( - (value: IBuilderQuery['groupBy']) => { - const groupBy = []; - - for (let index = 0; index < value.length; index++) { - const element = (value[index] as unknown) as string; - - const key = groupByFiltersData?.payload?.attributeKeys?.find( - (key) => key.key === element, - ); - - if (key) { - groupBy.push(key); - } - } - - setGroupBy(groupBy); - setExpandedRowKeys([]); - }, - [groupByFiltersData], - ); - - useEffect(() => { - if (groupByFiltersData?.payload) { - setGroupByOptions( - groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ - value: filter.key, - label: filter.key, - })) || [], - ); - } - }, [groupByFiltersData]); - - return ( -
- - {isError && {data?.error || 'Something went wrong'}} - - {showNoFilteredNodesMessage && ( -
-
- thinking-emoji - - - This query had no results. Edit your query and try again! - -
-
- )} - - {(isFetching || isLoading) && } - - {showsNodesTable && ( -
} />, - }} - tableLayout="fixed" - onChange={handleTableChange} - onRow={(record): { onClick: () => void; className: string } => ({ - onClick: (): void => handleRowClick(record), - className: 'clickable-row', - })} - expandable={{ - expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, - expandIcon: expandRowIconRenderer, - expandedRowKeys, - }} - /> - )} - {/* TODO - Handle Node Details flow */} - - ); -} - -export default K8sNodesList; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx deleted file mode 100644 index 810f70fd02..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { Tag, Tooltip } from 'antd'; -import { ColumnType } from 'antd/es/table'; -import { - K8sNodesData, - K8sNodesListPayload, -} from 'api/infraMonitoring/getK8sNodesList'; -import { Group } from 'lucide-react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; - -import { IEntityColumn } from '../utils'; - -export const defaultAddedColumns: IEntityColumn[] = [ - { - label: 'Node Name', - value: 'nodeStatus', - id: 'nodeStatus', - canRemove: false, - }, - { - label: 'Cluster Name', - value: 'clusterStatus', - id: 'clusterStatus', - canRemove: false, - }, - { - label: 'CPU Utilization (cores)', - value: 'cpuUtilization', - id: 'cpuUtilization', - canRemove: false, - }, - { - label: 'CPU Allocatable (cores)', - value: 'cpuAllocatable', - id: 'cpuAllocatable', - canRemove: false, - }, - { - label: 'Memory Utilization (bytes)', - value: 'memoryUtilization', - id: 'memoryUtilization', - canRemove: false, - }, - { - label: 'Memory Allocatable (bytes)', - value: 'memoryAllocatable', - id: 'memoryAllocatable', - canRemove: false, - }, -]; - -export interface K8sNodesRowData { - key: string; - nodeUID: string; - nodeName: React.ReactNode; - clusterName: string; - cpuUtilization: React.ReactNode; - cpuAllocatable: React.ReactNode; - memoryUtilization: React.ReactNode; - memoryAllocatable: React.ReactNode; - groupedByMeta?: any; -} - -const nodeGroupColumnConfig = { - title: ( -
- NODE GROUP -
- ), - dataIndex: 'nodeGroup', - key: 'nodeGroup', - ellipsis: true, - width: 150, - align: 'left', - sorter: false, -}; - -export const getK8sNodesListQuery = (): K8sNodesListPayload => ({ - filters: { - items: [], - op: 'and', - }, - orderBy: { columnName: 'cpu', order: 'desc' }, -}); - -const columnsConfig = [ - { - title:
Node Name
, - dataIndex: 'nodeName', - key: 'nodeName', - ellipsis: true, - width: 120, - sorter: true, - align: 'left', - }, - { - title:
Cluster Name
, - dataIndex: 'clusterName', - key: 'clusterName', - ellipsis: true, - width: 120, - sorter: true, - align: 'left', - }, - { - title:
CPU Utilization (cores)
, - dataIndex: 'cpuUtilization', - key: 'cpuUtilization', - width: 80, - sorter: true, - align: 'left', - }, - { - title:
CPU Allocatable (cores)
, - dataIndex: 'cpuAllocatable', - key: 'cpuAllocatable', - width: 80, - sorter: true, - align: 'left', - }, - { - title:
Memory Utilization (bytes)
, - dataIndex: 'memoryUtilization', - key: 'memoryUtilization', - width: 80, - sorter: true, - align: 'left', - }, - { - title:
Memory Allocatable (bytes)
, - dataIndex: 'memoryAllocatable', - key: 'memoryAllocatable', - width: 80, - sorter: true, - align: 'left', - }, -]; - -export const getK8sNodesListColumns = ( - groupBy: IBuilderQuery['groupBy'], -): ColumnType[] => { - if (groupBy.length > 0) { - const filteredColumns = [...columnsConfig].filter( - (column) => column.key !== 'nodeName', - ); - filteredColumns.unshift(nodeGroupColumnConfig); - return filteredColumns as ColumnType[]; - } - - return columnsConfig as ColumnType[]; -}; - -const getGroupByEle = ( - node: K8sNodesData, - groupBy: IBuilderQuery['groupBy'], -): React.ReactNode => { - const groupByValues: string[] = []; - - groupBy.forEach((group) => { - groupByValues.push(node.meta[group.key as keyof typeof node.meta]); - }); - - return ( -
- {groupByValues.map((value) => ( - - {value === '' ? '' : value} - - ))} -
- ); -}; - -export const formatDataForTable = ( - data: K8sNodesData[], - groupBy: IBuilderQuery['groupBy'], -): K8sNodesRowData[] => - data.map((node, index) => ({ - key: `${node.nodeUID}-${index}`, - nodeUID: node.nodeUID || '', - nodeName: ( -
- -
{node.meta.k8s_node_name || ''}
-
-
- ), - clusterName: node.meta.k8s_cluster_name, - cpuUtilization: node.nodeCPUUsage === -1 ? '-' : node.nodeCPUUsage, - memoryUtilization: node.nodeMemoryUsage === -1 ? '-' : node.nodeMemoryUsage, - cpuAllocatable: - node.nodeCPUAllocatable === -1 ? '-' : node.nodeCPUAllocatable, - memoryAllocatable: - node.nodeMemoryAllocatable === -1 ? '-' : node.nodeMemoryAllocatable, - nodeGroup: getGroupByEle(node, groupBy), - meta: node.meta, - ...node.meta, - groupedByMeta: node.meta, - })); diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts deleted file mode 100644 index 4e2ac964d2..0000000000 --- a/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - getK8sNodesList, - K8sNodesListPayload, - K8sNodesListResponse, -} from 'api/infraMonitoring/getK8sNodesList'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { useMemo } from 'react'; -import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; -import { ErrorResponse, SuccessResponse } from 'types/api'; - -type UseGetK8sNodesList = ( - requestData: K8sNodesListPayload, - options?: UseQueryOptions< - SuccessResponse | ErrorResponse, - Error - >, - headers?: Record, -) => UseQueryResult< - SuccessResponse | ErrorResponse, - Error ->; - -export const useGetK8sNodesList: UseGetK8sNodesList = ( - requestData, - options, - headers, -) => { - const queryKey = useMemo(() => { - if (options?.queryKey && Array.isArray(options.queryKey)) { - return [...options.queryKey]; - } - - if (options?.queryKey && typeof options.queryKey === 'string') { - return options.queryKey; - } - - return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; - }, [options?.queryKey, requestData]); - - return useQuery | ErrorResponse, Error>({ - queryFn: ({ signal }) => getK8sNodesList(requestData, signal, headers), - ...options, - queryKey, - }); -}; From 0ec6a594c31a579c74da14c5ea59fd69e4166295 Mon Sep 17 00:00:00 2001 From: amlannandy Date: Sun, 22 Dec 2024 20:37:17 +0530 Subject: [PATCH 14/15] feat: implement quick filters and group by --- .../Deployments/K8sDeploymentsList.tsx | 364 +++++++++++++++--- .../InfraMonitoringK8s/Deployments/utils.tsx | 147 +++++-- .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 6 +- .../container/InfraMonitoringK8s/constants.ts | 35 +- .../container/InfraMonitoringK8s/utils.tsx | 7 - 5 files changed, 468 insertions(+), 91 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx index 15d6fda34c..d995adada5 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx @@ -1,19 +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 { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList'; import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList'; +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'; @@ -21,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, @@ -29,6 +36,7 @@ import { K8sDeploymentsRowData, } from './utils'; +// eslint-disable-next-line sonarjs/cognitive-complexity function K8sDeploymentsList({ isFiltersVisible, handleFilterVisibilityChange, @@ -42,10 +50,7 @@ function K8sDeploymentsList({ const [currentPage, setCurrentPage] = useState(1); - const [filters, setFilters] = useState({ - items: [], - op: 'and', - }); + const [expandedRowKeys, setExpandedRowKeys] = useState([]); const [orderBy, setOrderBy] = useState<{ columnName: string; @@ -56,38 +61,162 @@ function K8sDeploymentsList({ const pageSize = 10; - const query = useMemo(() => { + const [groupBy, setGroupBy] = useState([]); + + const [ + selectedRowData, + setSelectedRowData, + ] = useState(null); + + const [groupByOptions, setGroupByOptions] = useState< + { value: string; label: string }[] + >([]); + + const createFiltersForSelectedRowData = ( + selectedRowData: K8sDeploymentsRowData, + ): 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 = getK8sDeploymentsListQuery(); + + 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, + } = useGetK8sDeploymentsList( + fetchGroupedByRowDataQuery as K8sDeploymentsListPayload, + { + queryKey: ['deploymentList', fetchGroupedByRowDataQuery], + enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, + }, + ); + + const { currentQuery } = useQueryBuilder(); + + const { + data: groupByFiltersData, + isLoading: isLoadingGroupByFilters, + } = useGetAggregateKeys( + { + dataSource: currentQuery.builder.queryData[0].dataSource, + aggregateAttribute: '', + aggregateOperator: 'noop', + searchText: '', + tagType: '', + }, + { + queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], + }, + true, + ); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + const query = useMemo(() => { + const baseQuery = getK8sDeploymentsListQuery(); + const queryPayload = { ...baseQuery, limit: pageSize, offset: (currentPage - 1) * pageSize, - filters, + filters: queryFilters, start: Math.floor(minTime / 1000000), end: Math.floor(maxTime / 1000000), orderBy, }; - }, [currentPage, filters, minTime, maxTime, orderBy]); + if (groupBy.length > 0) { + queryPayload.groupBy = groupBy; + } + return queryPayload; + }, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]); + + const formattedGroupedByDeploymentsData = useMemo( + () => + formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy), + [groupedByRowData, groupBy], + ); const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList( query as K8sDeploymentsListPayload, { - queryKey: ['hostList', query], + queryKey: ['deploymentList', query], enabled: !!query, }, ); - const DeploymentsData = useMemo(() => data?.payload?.data?.records || [], [ + const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [ data, ]); const totalCount = data?.payload?.data?.total || 0; const formattedDeploymentsData = useMemo( - () => formatDataForTable(DeploymentsData), - [DeploymentsData], + () => formatDataForTable(deploymentsData, groupBy), + [deploymentsData, groupBy], ); - const columns = useMemo(() => getK8sDeploymentsListColumns(), []); + const columns = useMemo(() => getK8sDeploymentsListColumns(groupBy), [ + groupBy, + ]); + + const handleGroupByRowClick = (record: K8sDeploymentsRowData): void => { + setSelectedRowData(record); + + if (expandedRowKeys.includes(record.key)) { + setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); + } else { + setExpandedRowKeys([record.key]); + } + }; + + useEffect(() => { + if (selectedRowData) { + fetchGroupedByRowData(); + } + }, [selectedRowData, fetchGroupedByRowData]); const handleTableChange: TableProps['onChange'] = useCallback( ( @@ -113,19 +242,22 @@ function K8sDeploymentsList({ [], ); + const { handleChangeQueryData } = useQueryOperations({ + index: 0, + query: currentQuery.builder.queryData[0], + entityVersion: '', + }); + const handleFiltersChange = useCallback( (value: IBuilderQuery['filters']): void => { - const isNewFilterAdded = value.items.length !== filters.items.length; - if (isNewFilterAdded) { - setFilters(value); - setCurrentPage(1); + handleChangeQueryData('filters', value); + setCurrentPage(1); - logEvent('Infra Monitoring: K8s list filters applied', { - filters: value, - }); - } + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); }, - [filters], + [handleChangeQueryData], ); useEffect(() => { @@ -134,17 +266,121 @@ function K8sDeploymentsList({ // const selectedDeploymentData = useMemo(() => { // if (!selectedDeploymentUID) return null; - // return DeploymentsData.find((deployment) => deployment.DeploymentUID === selectedDeploymentUID) || null; - // }, [selectedDeploymentUID, DeploymentsData]); + // return deploymentsData.find((deployment) => deployment.deploymentUID === selectedDeploymentUID) || null; + // }, [selectedDeploymentUID, deploymentsData]); const handleRowClick = (record: K8sDeploymentsRowData): void => { - // setselectedDeploymentUID(record.DeploymentUID); + if (groupBy.length === 0) { + setSelectedRowData(null); + // setselectedDeploymentUID(record.deploymentUID); + } else { + handleGroupByRowClick(record); + } logEvent('Infra Monitoring: K8s deployment list item clicked', { - deploymentName: record.deploymentName, + deploymentUID: record.deploymentName, }); }; + const nestedColumns = useMemo(() => { + const nestedColumns = getK8sDeploymentsListColumns([]); + 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={formattedGroupedByDeploymentsData} + pagination={false} + scroll={{ x: true }} + tableLayout="fixed" + size="small" + loading={{ + spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, + indicator: } />, + }} + /> + + {groupedByRowData?.payload?.data?.total && + groupedByRowData?.payload?.data?.total > 10 ? ( +
+ +
+ ) : null} + + )} + + ); + + const expandRowIconRenderer = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: K8sDeploymentsRowData, + e: React.MouseEvent, + ) => void; + record: K8sDeploymentsRowData; + }): JSX.Element | null => { + if (!isGroupedByAttribute) { + return null; + } + + return expanded ? ( + + ) : ( + + ); + }; + // const handleCloseDeploymentDetail = (): void => { // setselectedDeploymentUID(null); // }; @@ -153,13 +389,46 @@ function K8sDeploymentsList({ !isError && !isLoading && !isFetching && - !(formattedDeploymentsData.length === 0 && filters.items.length > 0); + !(formattedDeploymentsData.length === 0 && queryFilters.items.length > 0); const showNoFilteredDeploymentsMessage = !isFetching && !isLoading && formattedDeploymentsData.length === 0 && - filters.items.length > 0; + queryFilters.items.length > 0; + + const handleGroupByChange = useCallback( + (value: IBuilderQuery['groupBy']) => { + const groupBy = []; + + for (let index = 0; index < value.length; index++) { + const element = (value[index] as unknown) as string; + + const key = groupByFiltersData?.payload?.attributeKeys?.find( + (key) => key.key === element, + ); + + if (key) { + groupBy.push(key); + } + } + + setGroupBy(groupBy); + setExpandedRowKeys([]); + }, + [groupByFiltersData], + ); + + useEffect(() => { + if (groupByFiltersData?.payload) { + setGroupByOptions( + groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ + value: filter.key, + label: filter.key, + })) || [], + ); + } + }, [groupByFiltersData]); return (
@@ -167,11 +436,11 @@ function K8sDeploymentsList({ isFiltersVisible={isFiltersVisible} handleFilterVisibilityChange={handleFilterVisibilityChange} defaultAddedColumns={defaultAddedColumns} - addedColumns={[]} - availableColumns={[]} handleFiltersChange={handleFiltersChange} - onAddColumn={() => {}} - onRemoveColumn={() => {}} + groupByOptions={groupByOptions} + isLoadingGroupByFilters={isLoadingGroupByFilters} + handleGroupByChange={handleGroupByChange} + selectedGroupBy={groupBy} /> {isError && {data?.error || 'Something went wrong'}} @@ -191,28 +460,7 @@ function K8sDeploymentsList({
)} - {(isFetching || isLoading) && ( -
- - - -
- )} + {(isFetching || isLoading) && } {showsDeploymentsTable && (
} />, }} tableLayout="fixed" - rowKey={(record): string => record.deploymentName} onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record), className: 'clickable-row', })} + expandable={{ + expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, + expandIcon: expandRowIconRenderer, + expandedRowKeys, + }} /> )} {/* TODO - Handle Deployment Details flow */} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx index 4c50531838..a2f60e21b1 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -1,42 +1,74 @@ import { Color } from '@signozhq/design-tokens'; -import { Progress } from 'antd'; +import { Progress, Tag } from 'antd'; import { ColumnType } from 'antd/es/table'; import { K8sDeploymentsData, K8sDeploymentsListPayload, } from 'api/infraMonitoring/getK8sDeploymentsList'; +import { Group } from 'lucide-react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IEntityColumn } from '../utils'; export const defaultAddedColumns: IEntityColumn[] = [ { - label: 'Namespace Status', - value: 'NamespaceStatus', - id: 'NamespaceStatus', + label: 'Deployment Name', + value: 'deploymentName', + id: 'deploymentName', + canRemove: false, + }, + { + label: 'Namespace Name', + value: 'namespaceName', + id: 'namespaceName', + canRemove: false, + }, + { + label: 'Available', + value: 'available', + id: 'available', + canRemove: false, + }, + { + label: 'Desired', + value: 'desired', + id: 'desired', + canRemove: false, + }, + { + label: 'CPU Request Utilization (% of limit)', + value: 'cpuRequestUtilization', + id: 'cpuRequestUtilization', + canRemove: false, + }, + { + label: 'CPU Limit Utilization (% of request)', + value: 'cpuLimitUtilization', + id: 'cpuLimitUtilization', canRemove: false, }, { label: 'CPU Utilization (cores)', - value: 'cpuUsage', - id: 'cpuUsage', + value: 'cpuUtilization', + id: 'cpuUtilization', canRemove: false, }, { - label: 'CPU Allocatable (cores)', - value: 'cpuAllocatable', - id: 'cpuAllocatable', + label: 'Memory Request Utilization (% of limit)', + value: 'memoryRequestUtilization', + id: 'memoryRequestUtilization', canRemove: false, }, { - label: 'Memory Allocatable (bytes)', - value: 'memoryAllocatable', - id: 'memoryAllocatable', + label: 'Memory Limit Utilization (% of request)', + value: 'memoryLimitUtilization', + id: 'memoryLimitUtilization', canRemove: false, }, { - label: 'Pods count by phase', - value: 'podsCount', - id: 'podsCount', + label: 'Memory Utilization (bytes)', + value: 'memoryUtilization', + id: 'memoryUtilization', canRemove: false, }, ]; @@ -53,8 +85,25 @@ export interface K8sDeploymentsRowData { memoryLimitUtilization: React.ReactNode; memoryUtilization: number; containerRestarts: number; + clusterName: string; + namespaceName: string; + groupedByMeta?: any; } +const deploymentGroupColumnConfig = { + title: ( +
+ DEPLOYMENT GROUP +
+ ), + dataIndex: 'deploymentGroup', + key: 'deploymentGroup', + ellipsis: true, + width: 150, + align: 'left', + sorter: false, +}; + export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ filters: { items: [], @@ -65,7 +114,7 @@ export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ const columnsConfig = [ { - title:
Deployment
, + title:
Deployment Name
, dataIndex: 'deploymentName', key: 'deploymentName', ellipsis: true, @@ -74,7 +123,16 @@ const columnsConfig = [ align: 'left', }, { - title:
Available Replicas
, + title:
Namespace Name
, + dataIndex: 'namespaceName', + key: 'namespaceName', + ellipsis: true, + width: 150, + sorter: true, + align: 'left', + }, + { + title:
Available
, dataIndex: 'availableReplicas', key: 'availableReplicas', width: 100, @@ -82,7 +140,7 @@ const columnsConfig = [ align: 'left', }, { - title:
Desired Replicas
, + title:
Desired
, dataIndex: 'desiredReplicas', key: 'desiredReplicas', width: 80, @@ -146,17 +204,51 @@ const columnsConfig = [ align: 'left', }, { - title:
Container Restarts
, - dataIndex: 'containerRestarts', - key: 'containerRestarts', - width: 50, + title:
Memory Utilization (bytes)
, + dataIndex: 'memoryUtilization', + key: 'memoryUtilization', + width: 80, sorter: true, align: 'left', }, ]; -export const getK8sDeploymentsListColumns = (): ColumnType[] => - columnsConfig as ColumnType[]; +export const getK8sDeploymentsListColumns = ( + groupBy: IBuilderQuery['groupBy'], +): ColumnType[] => { + if (groupBy.length > 0) { + const filteredColumns = [...columnsConfig].filter( + (column) => column.key !== 'deploymentName', + ); + filteredColumns.unshift(deploymentGroupColumnConfig); + return filteredColumns as ColumnType[]; + } + + return columnsConfig as ColumnType[]; +}; + +const getGroupByEle = ( + deployment: K8sDeploymentsData, + groupBy: IBuilderQuery['groupBy'], +): React.ReactNode => { + const groupByValues: string[] = []; + + groupBy.forEach((group) => { + groupByValues.push( + deployment.meta[group.key as keyof typeof deployment.meta], + ); + }); + + return ( +
+ {groupByValues.map((value) => ( + + {value === '' ? '' : value} + + ))} +
+ ); +}; const getStrokeColorForProgressBar = (value: number): string => { if (value >= 90) return Color.BG_SAKURA_500; @@ -166,6 +258,7 @@ const getStrokeColorForProgressBar = (value: number): string => { export const formatDataForTable = ( data: K8sDeploymentsData[], + groupBy: IBuilderQuery['groupBy'], ): K8sDeploymentsRowData[] => data.map((deployment, index) => ({ key: `${deployment.meta.k8s_deployment_name}-${index}`, @@ -235,4 +328,10 @@ export const formatDataForTable = ( /> ), + clusterName: deployment.meta.k8s_cluster_name, + namespaceName: deployment.meta.k8s_namespace_name, + deploymentGroup: getGroupByEle(deployment, groupBy), + meta: deployment.meta, + ...deployment.meta, + groupedByMeta: deployment.meta, })); diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index 80eb521197..d2771d8990 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -36,7 +36,7 @@ import { StatefulsetsQuickFiltersConfig, VolumesQuickFiltersConfig, } from './constants'; -import K8sNodesList from './Nodes/K8sNodesList'; +import K8sDeploymentsList from './Deployments/K8sDeploymentsList'; import K8sPodLists from './Pods/K8sPodLists'; import Volumes from './Volumes/Volumes'; @@ -324,8 +324,8 @@ export default function InfraMonitoringK8s(): JSX.Element { /> )} - {selectedCategory === K8sCategories.NODES && ( - diff --git a/frontend/src/container/InfraMonitoringK8s/constants.ts b/frontend/src/container/InfraMonitoringK8s/constants.ts index 7d92e19f00..029f071421 100644 --- a/frontend/src/container/InfraMonitoringK8s/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/constants.ts @@ -251,7 +251,7 @@ export const VolumesQuickFiltersConfig: IQuickFiltersConfig[] = [ export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [ { type: FiltersType.CHECKBOX, - title: 'Deployments', + title: 'Deployment Name', attributeKey: { key: 'k8s_deployment_name', dataType: DataTypes.String, @@ -259,6 +259,39 @@ export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [ isColumn: false, isJSON: false, }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, + { + type: FiltersType.CHECKBOX, + title: 'Namespace Name', + attributeKey: { + key: 'k8s_namespace_name', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, + { + type: FiltersType.CHECKBOX, + title: 'Cluster Name', + attributeKey: { + key: 'k8s_cluster_name', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, defaultOpen: true, }, ]; diff --git a/frontend/src/container/InfraMonitoringK8s/utils.tsx b/frontend/src/container/InfraMonitoringK8s/utils.tsx index 3116ecd259..a3a328d613 100644 --- a/frontend/src/container/InfraMonitoringK8s/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/utils.tsx @@ -27,13 +27,6 @@ export interface IEntityColumn { canRemove: boolean; } -export interface IEntityColumn { - label: string; - value: string; - id: string; - canRemove: boolean; -} - export interface IPodColumn { label: string; value: string; From 6b1f3daaae5dcc85b9156851a0ccf2fd49803b71 Mon Sep 17 00:00:00 2001 From: amlannandy Date: Tue, 24 Dec 2024 20:19:07 +0530 Subject: [PATCH 15/15] chore: fix issues --- .../Deployments/K8sDeploymentsList.tsx | 3 + .../InfraMonitoringK8s/Deployments/utils.tsx | 279 ++++++++++-------- 2 files changed, 166 insertions(+), 116 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx index d995adada5..8e2487107c 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx @@ -25,6 +25,7 @@ import { AppState } from 'store/reducers'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { K8sCategory } from '../constants'; import K8sHeader from '../K8sHeader'; import LoadingContainer from '../LoadingContainer'; import { dummyColumnConfig } from '../utils'; @@ -146,6 +147,7 @@ function K8sDeploymentsList({ queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], }, true, + K8sCategory.DEPLOYMENTS, ); const queryFilters = useMemo( @@ -322,6 +324,7 @@ function K8sDeploymentsList({ spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, indicator: } />, }} + showHeader={false} /> {groupedByRowData?.payload?.data?.total && diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx index a2f60e21b1..b389f8d578 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -1,5 +1,6 @@ -import { Color } from '@signozhq/design-tokens'; -import { Progress, Tag } from 'antd'; +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + +import { Progress, Tag, Tooltip } from 'antd'; import { ColumnType } from 'antd/es/table'; import { K8sDeploymentsData, @@ -8,8 +9,18 @@ import { import { Group } from 'lucide-react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { + formatBytes, + getProgressBarText, + getStrokeColorForLimitUtilization, + getStrokeColorForRequestUtilization, + ValidateColumnValueWrapper, +} from '../commonUtils'; import { IEntityColumn } from '../utils'; +const INVALID_MEMORY_CPU_VALUE_MESSAGE = + 'Some deployments do not have memory requests/limits.'; + export const defaultAddedColumns: IEntityColumn[] = [ { label: 'Deployment Name', @@ -37,54 +48,54 @@ export const defaultAddedColumns: IEntityColumn[] = [ }, { label: 'CPU Request Utilization (% of limit)', - value: 'cpuRequestUtilization', - id: 'cpuRequestUtilization', + value: 'cpu_request', + id: 'cpu_request', canRemove: false, }, { label: 'CPU Limit Utilization (% of request)', - value: 'cpuLimitUtilization', - id: 'cpuLimitUtilization', + value: 'cpu_limit', + id: 'cpu_limit', canRemove: false, }, { label: 'CPU Utilization (cores)', - value: 'cpuUtilization', - id: 'cpuUtilization', + value: 'cpu', + id: 'cpu', canRemove: false, }, { label: 'Memory Request Utilization (% of limit)', - value: 'memoryRequestUtilization', - id: 'memoryRequestUtilization', + value: 'memory_request', + id: 'memory_request', canRemove: false, }, { label: 'Memory Limit Utilization (% of request)', - value: 'memoryLimitUtilization', - id: 'memoryLimitUtilization', + value: 'memory_limit', + id: 'memory_limit', canRemove: false, }, { label: 'Memory Utilization (bytes)', - value: 'memoryUtilization', - id: 'memoryUtilization', + value: 'memory', + id: 'memory', canRemove: false, }, ]; export interface K8sDeploymentsRowData { key: string; - deploymentName: string; - availableReplicas: number; - desiredReplicas: number; - cpuRequestUtilization: React.ReactNode; - cpuLimitUtilization: React.ReactNode; - cpuUtilization: number; - memoryRequestUtilization: React.ReactNode; - memoryLimitUtilization: React.ReactNode; - memoryUtilization: number; - containerRestarts: number; + deploymentName: React.ReactNode; + availableReplicas: React.ReactNode; + desiredReplicas: React.ReactNode; + cpu_request: React.ReactNode; + cpu_limit: React.ReactNode; + cpu: React.ReactNode; + memory_request: React.ReactNode; + memory_limit: React.ReactNode; + memory: React.ReactNode; + containerRestarts: React.ReactNode; clusterName: string; namespaceName: string; groupedByMeta?: any; @@ -149,64 +160,72 @@ const columnsConfig = [ }, { title: ( -
- CPU Request Utilization (% of limit) -
+ +
+ CPU Request Utilization (% of limit) +
+
), - dataIndex: 'cpuRequestUtilization', - key: 'cpuRequestUtilization', + dataIndex: 'cpu_request', + key: 'cpu_request', width: 80, sorter: true, align: 'left', }, { title: ( -
- CPU Limit Utilization (% of request) -
+ +
+ CPU Limit Utilization (% of request) +
+
), - dataIndex: 'cpuLimitUtilization', - key: 'cpuLimitUtilization', + dataIndex: 'cpu_limit', + key: 'cpu_limit', width: 50, sorter: true, align: 'left', }, { title:
CPU Utilization (cores)
, - dataIndex: 'cpuUtilization', - key: 'cpuUtilization', + dataIndex: 'cpu', + key: 'cpu', width: 80, sorter: true, align: 'left', }, { title: ( -
- Memory Request Utilization (% of limit) -
+ +
+ Memory Request Utilization (% of limit) +
+
), - dataIndex: 'memoryRequestUtilization', - key: 'memoryRequestUtilization', + dataIndex: 'memory_request', + key: 'memory_request', width: 50, sorter: true, align: 'left', }, { title: ( -
- Memory Limit Utilization (% of request) -
+ +
+ Memory Limit Utilization (% of request) +
+
), - dataIndex: 'memoryLimitUtilization', - key: 'memoryLimitUtilization', + dataIndex: 'memory_limit', + key: 'memory_limit', width: 80, sorter: true, align: 'left', }, { title:
Memory Utilization (bytes)
, - dataIndex: 'memoryUtilization', - key: 'memoryUtilization', + dataIndex: 'memory', + key: 'memory', width: 80, sorter: true, align: 'left', @@ -250,83 +269,111 @@ const getGroupByEle = ( ); }; -const getStrokeColorForProgressBar = (value: number): string => { - if (value >= 90) return Color.BG_SAKURA_500; - if (value >= 60) return Color.BG_AMBER_500; - return Color.BG_FOREST_500; -}; - export const formatDataForTable = ( data: K8sDeploymentsData[], groupBy: IBuilderQuery['groupBy'], ): K8sDeploymentsRowData[] => data.map((deployment, index) => ({ key: `${deployment.meta.k8s_deployment_name}-${index}`, - deploymentName: deployment.meta.k8s_deployment_name, - availableReplicas: deployment.availablePods, - desiredReplicas: deployment.desiredPods, - containerRestarts: deployment.restarts, - cpuUtilization: deployment.cpuUsage, - cpuRequestUtilization: ( -
- { - const cpuPercent = Number((deployment.cpuRequest * 100).toFixed(1)); - return getStrokeColorForProgressBar(cpuPercent); - })()} - className="progress-bar" - /> -
+ deploymentName: ( + + {deployment.meta.k8s_deployment_name} + + ), + availableReplicas: ( + + {deployment.availablePods} + + ), + desiredReplicas: ( + + {deployment.desiredPods} + + ), + containerRestarts: ( + + {deployment.restarts} + + ), + cpu: ( + + {deployment.cpuUsage} + + ), + cpu_request: ( + +
+ + getProgressBarText(Number((deployment.cpuRequest * 100).toFixed(1))) + } + className="progress-bar" + /> +
+
+ ), + cpu_limit: ( + +
+ + getProgressBarText(Number((deployment.cpuLimit * 100).toFixed(1))) + } + className="progress-bar" + /> +
+
), - cpuLimitUtilization: ( -
- { - const cpuPercent = Number((deployment.cpuLimit * 100).toFixed(1)); - return getStrokeColorForProgressBar(cpuPercent); - })()} - className="progress-bar" - /> -
+ memory: ( + + {formatBytes(deployment.memoryUsage)} + ), - memoryUtilization: deployment.memoryUsage, - memoryRequestUtilization: ( -
- { - const memoryPercent = Number((deployment.memoryRequest * 100).toFixed(1)); - return getStrokeColorForProgressBar(memoryPercent); - })()} - className="progress-bar" - /> -
+ memory_request: ( + +
+ + getProgressBarText(Number((deployment.memoryRequest * 100).toFixed(1))) + } + className="progress-bar" + /> +
+
), - memoryLimitUtilization: ( -
- { - const memoryPercent = Number((deployment.memoryLimit * 100).toFixed(1)); - return getStrokeColorForProgressBar(memoryPercent); - })()} - className="progress-bar" - /> -
+ memory_limit: ( + +
+ + getProgressBarText(Number((deployment.memoryLimit * 100).toFixed(1))) + } + className="progress-bar" + /> +
+
), clusterName: deployment.meta.k8s_cluster_name, namespaceName: deployment.meta.k8s_namespace_name,