/* eslint-disable no-restricted-syntax */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import '../InfraMonitoringK8s.styles.scss'; import './K8sNamespacesList.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 { K8sNamespacesListPayload } from 'api/infraMonitoring/getK8sNamespacesList'; import { InfraMonitoringEvents } from 'constants/events'; import { useGetK8sNamespacesList } from 'hooks/infraMonitoring/useGetK8sNamespacesList'; 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 { useSearchParams } from 'react-router-dom-v5-compat'; import { AppState } from 'store/reducers'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; import { getOrderByFromParams } from '../commonUtils'; import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategory, K8sEntityToAggregateAttributeMapping, } from '../constants'; import K8sHeader from '../K8sHeader'; import LoadingContainer from '../LoadingContainer'; import { usePageSize } from '../utils'; import NamespaceDetails from './NamespaceDetails'; import { defaultAddedColumns, formatDataForTable, getK8sNamespacesListColumns, getK8sNamespacesListQuery, K8sNamespacesRowData, } from './utils'; // eslint-disable-next-line sonarjs/cognitive-complexity function K8sNamespacesList({ isFiltersVisible, handleFilterVisibilityChange, quickFiltersLastUpdated, }: { isFiltersVisible: boolean; handleFilterVisibilityChange: () => void; quickFiltersLastUpdated: number; }): JSX.Element { const { maxTime, minTime } = useSelector( (state) => state.globalTime, ); const [currentPage, setCurrentPage] = useState(1); const [expandedRowKeys, setExpandedRowKeys] = useState([]); const [searchParams, setSearchParams] = useSearchParams(); const [orderBy, setOrderBy] = useState<{ columnName: string; order: 'asc' | 'desc'; } | null>(() => getOrderByFromParams(searchParams, true)); const [selectedNamespaceUID, setselectedNamespaceUID] = useState< string | null >(() => { const namespaceUID = searchParams.get( INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID, ); if (namespaceUID) { return namespaceUID; } return null; }); const { pageSize, setPageSize } = usePageSize(K8sCategory.NAMESPACES); const [groupBy, setGroupBy] = useState(() => { const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY); if (groupBy) { const decoded = decodeURIComponent(groupBy); const parsed = JSON.parse(decoded); return parsed as IBuilderQuery['groupBy']; } return []; }); const [ selectedRowData, setSelectedRowData, ] = useState(null); const [groupByOptions, setGroupByOptions] = useState< { value: string; label: string }[] >([]); const { currentQuery } = useQueryBuilder(); const queryFilters = useMemo( () => currentQuery?.builder?.queryData[0]?.filters || { items: [], op: 'and', }, [currentQuery?.builder?.queryData], ); // Reset pagination every time quick filters are changed useEffect(() => { setCurrentPage(1); }, [quickFiltersLastUpdated]); const createFiltersForSelectedRowData = ( selectedRowData: K8sNamespacesRowData, groupBy: IBuilderQuery['groupBy'], ): IBuilderQuery['filters'] => { const baseFilters: IBuilderQuery['filters'] = { items: [...queryFilters.items], op: 'and', }; if (!selectedRowData) return baseFilters; const { groupedByMeta } = selectedRowData; for (const key of groupBy) { baseFilters.items.push({ key: { key: key.key, type: null, }, op: '=', value: groupedByMeta[key.key], id: key.key, }); } return baseFilters; }; const fetchGroupedByRowDataQuery = useMemo(() => { if (!selectedRowData) return null; const baseQuery = getK8sNamespacesListQuery(); const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); return { ...baseQuery, limit: 10, offset: 0, filters, start: Math.floor(minTime / 1000000), end: Math.floor(maxTime / 1000000), orderBy, }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); const { data: groupedByRowData, isFetching: isFetchingGroupedByRowData, isLoading: isLoadingGroupedByRowData, isError: isErrorGroupedByRowData, refetch: fetchGroupedByRowData, } = useGetK8sNamespacesList( fetchGroupedByRowDataQuery as K8sNamespacesListPayload, { queryKey: ['namespaceList', fetchGroupedByRowDataQuery], enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, }, ); const { data: groupByFiltersData, isLoading: isLoadingGroupByFilters, } = useGetAggregateKeys( { dataSource: currentQuery.builder.queryData[0].dataSource, aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NAMESPACES], aggregateOperator: 'noop', searchText: '', tagType: '', }, { queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], }, true, K8sCategory.NODES, ); const query = useMemo(() => { const baseQuery = getK8sNamespacesListQuery(); 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; }, [pageSize, currentPage, queryFilters, minTime, maxTime, orderBy, groupBy]); const formattedGroupedByNamespacesData = useMemo( () => formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy), [groupedByRowData, groupBy], ); const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList( query as K8sNamespacesListPayload, { queryKey: ['namespaceList', query], enabled: !!query, }, ); const namespacesData = useMemo(() => data?.payload?.data?.records || [], [ data, ]); const totalCount = data?.payload?.data?.total || 0; const formattedNamespacesData = useMemo( () => formatDataForTable(namespacesData, groupBy), [namespacesData, groupBy], ); const nestedNamespacesData = useMemo(() => { if (!selectedRowData || !groupedByRowData?.payload?.data.records) return []; return groupedByRowData?.payload?.data?.records || []; }, [groupedByRowData, selectedRowData]); const columns = useMemo(() => getK8sNamespacesListColumns(groupBy), [groupBy]); const handleGroupByRowClick = (record: K8sNamespacesRowData): 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); logEvent(InfraMonitoringEvents.PageNumberChanged, { entity: InfraMonitoringEvents.K8sEntity, page: InfraMonitoringEvents.ListPage, category: InfraMonitoringEvents.Namespace, }); } if ('field' in sorter && sorter.order) { const currentOrderBy = { columnName: sorter.field as string, order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc', }; setOrderBy(currentOrderBy); setSearchParams({ ...Object.fromEntries(searchParams.entries()), [INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify( currentOrderBy, ), }); } else { setOrderBy(null); setSearchParams({ ...Object.fromEntries(searchParams.entries()), [INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null), }); } }, [searchParams, setSearchParams], ); const { handleChangeQueryData } = useQueryOperations({ index: 0, query: currentQuery.builder.queryData[0], entityVersion: '', }); const handleFiltersChange = useCallback( (value: IBuilderQuery['filters']): void => { handleChangeQueryData('filters', value); setCurrentPage(1); if (value.items.length > 0) { logEvent(InfraMonitoringEvents.FilterApplied, { entity: InfraMonitoringEvents.K8sEntity, page: InfraMonitoringEvents.ListPage, category: InfraMonitoringEvents.Namespace, }); } }, [handleChangeQueryData], ); useEffect(() => { logEvent(InfraMonitoringEvents.PageVisited, { entity: InfraMonitoringEvents.K8sEntity, page: InfraMonitoringEvents.ListPage, category: InfraMonitoringEvents.Namespace, total: data?.payload?.data?.total, }); }, [data?.payload?.data?.total]); const selectedNamespaceData = useMemo(() => { if (!selectedNamespaceUID) return null; if (groupBy.length > 0) { // If grouped by, return the namespace from the formatted grouped by namespaces data return ( nestedNamespacesData.find( (namespace) => namespace.namespaceName === selectedNamespaceUID, ) || null ); } // If not grouped by, return the node from the nodes data return ( namespacesData.find( (namespace) => namespace.namespaceName === selectedNamespaceUID, ) || null ); }, [ selectedNamespaceUID, groupBy.length, namespacesData, nestedNamespacesData, ]); const handleRowClick = (record: K8sNamespacesRowData): void => { if (groupBy.length === 0) { setSelectedRowData(null); setselectedNamespaceUID(record.namespaceUID); setSearchParams({ ...Object.fromEntries(searchParams.entries()), [INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID]: record.namespaceUID, }); } else { handleGroupByRowClick(record); } logEvent(InfraMonitoringEvents.ItemClicked, { entity: InfraMonitoringEvents.K8sEntity, page: InfraMonitoringEvents.ListPage, category: InfraMonitoringEvents.Namespace, }); }; const nestedColumns = useMemo(() => getK8sNamespacesListColumns([]), []); const isGroupedByAttribute = groupBy.length > 0; const handleExpandedRowViewAllClick = (): void => { if (!selectedRowData) return; const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); handleFiltersChange(filters); setCurrentPage(1); setSelectedRowData(null); setGroupBy([]); setOrderBy(null); setSearchParams({ ...Object.fromEntries(searchParams.entries()), [INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]), [INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null), }); }; const expandedRowRender = (): JSX.Element => (
{isErrorGroupedByRowData && ( {groupedByRowData?.error || 'Something went wrong'} )} {isFetchingGroupedByRowData || isLoadingGroupedByRowData ? ( ) : (
[]} dataSource={formattedGroupedByNamespacesData} pagination={false} scroll={{ x: true }} tableLayout="fixed" size="small" loading={{ spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, indicator: } />, }} showHeader={false} onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => { setselectedNamespaceUID(record.namespaceUID); }, className: 'expanded-clickable-row', })} /> {groupedByRowData?.payload?.data?.total && groupedByRowData?.payload?.data?.total > 10 ? (
) : null} )} ); const expandRowIconRenderer = ({ expanded, onExpand, record, }: { expanded: boolean; onExpand: ( record: K8sNamespacesRowData, e: React.MouseEvent, ) => void; record: K8sNamespacesRowData; }): JSX.Element | null => { if (!isGroupedByAttribute) { return null; } return expanded ? ( ) : ( ); }; const handleCloseNamespaceDetail = (): void => { setselectedNamespaceUID(null); setSearchParams({ ...Object.fromEntries( Array.from(searchParams.entries()).filter( ([key]) => ![ INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID, INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW, INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS, INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS, INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS, ].includes(key), ), ), }); }; 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); } } // Reset pagination on switching to groupBy setCurrentPage(1); setGroupBy(groupBy); setExpandedRowKeys([]); setSearchParams({ ...Object.fromEntries(searchParams.entries()), [INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy), }); logEvent(InfraMonitoringEvents.GroupByChanged, { entity: InfraMonitoringEvents.K8sEntity, page: InfraMonitoringEvents.ListPage, category: InfraMonitoringEvents.Namespace, }); }, [groupByFiltersData, searchParams, setSearchParams], ); useEffect(() => { if (groupByFiltersData?.payload) { setGroupByOptions( groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ value: filter.key, label: filter.key, })) || [], ); } }, [groupByFiltersData]); const onPaginationChange = (page: number, pageSize: number): void => { setCurrentPage(page); setPageSize(pageSize); logEvent(InfraMonitoringEvents.PageNumberChanged, { entity: InfraMonitoringEvents.K8sEntity, page: InfraMonitoringEvents.ListPage, category: InfraMonitoringEvents.Namespace, }); }; return (
{isError && {data?.error || 'Something went wrong'}}
} />, }} locale={{ emptyText: isFetching || isLoading ? null : (
thinking-emoji This query had no results. Edit your query and try again!
), }} 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, }} /> ); } export default K8sNamespacesList;