From d0eefa0cf2e84a0ec0e58499afbf45f7178fe7d8 Mon Sep 17 00:00:00 2001 From: Amlan Kumar Nandy <45410599+amlannandy@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:17:23 +0530 Subject: [PATCH] feat: add hostname and os type quick filter to hosts list (#6926) --- .../InfraMonitoringHosts/HostsList.tsx | 217 +++++------------- .../InfraMonitoringHosts/HostsListTable.tsx | 183 +++++++++++++++ .../InfraMonitoring.styles.scss | 29 ++- .../container/InfraMonitoringHosts/utils.tsx | 71 +++++- 4 files changed, 341 insertions(+), 159 deletions(-) create mode 100644 frontend/src/container/InfraMonitoringHosts/HostsListTable.tsx diff --git a/frontend/src/container/InfraMonitoringHosts/HostsList.tsx b/frontend/src/container/InfraMonitoringHosts/HostsList.tsx index eab4ec0033..01dcabed0e 100644 --- a/frontend/src/container/InfraMonitoringHosts/HostsList.tsx +++ b/frontend/src/container/InfraMonitoringHosts/HostsList.tsx @@ -1,34 +1,25 @@ import './InfraMonitoring.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 { VerticalAlignTopOutlined } from '@ant-design/icons'; +import { Tooltip, Typography } from 'antd'; import logEvent from 'api/common/logEvent'; import { HostListPayload } from 'api/infraMonitoring/getHostLists'; import HostMetricDetail from 'components/HostMetricsDetail'; +import QuickFilters from 'components/QuickFilters/QuickFilters'; +import { QuickFiltersSource } from 'components/QuickFilters/types'; import { usePageSize } from 'container/InfraMonitoringK8s/utils'; import { useGetHostList } from 'hooks/infraMonitoring/useGetHostList'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; 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 { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; -import HostsEmptyOrIncorrectMetrics from './HostsEmptyOrIncorrectMetrics'; import HostsListControls from './HostsListControls'; -import { - formatDataForTable, - getHostListsQuery, - getHostsListColumns, - HostRowData, -} from './utils'; +import HostsListTable from './HostsListTable'; +import { getHostListsQuery, HostsQuickFiltersConfig } from './utils'; // eslint-disable-next-line sonarjs/cognitive-complexity function HostsList(): JSX.Element { @@ -41,6 +32,7 @@ function HostsList(): JSX.Element { items: [], op: 'and', }); + const [showFilters, setShowFilters] = useState(true); const [orderBy, setOrderBy] = useState<{ columnName: string; @@ -72,55 +64,24 @@ function HostsList(): JSX.Element { }, ); - const sentAnyHostMetricsData = useMemo( - () => data?.payload?.data?.sentAnyHostMetricsData || false, - [data], - ); - - const isSendingIncorrectK8SAgentMetrics = useMemo( - () => data?.payload?.data?.isSendingK8SAgentMetrics || false, - [data], - ); - const hostMetricsData = useMemo(() => data?.payload?.data?.records || [], [ data, ]); - const totalCount = data?.payload?.data?.total || 0; - const formattedHostMetricsData = useMemo( - () => formatDataForTable(hostMetricsData), - [hostMetricsData], - ); + const { currentQuery } = useQueryBuilder(); - const columns = useMemo(() => getHostsListColumns(), []); - - 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 => { const isNewFilterAdded = value.items.length !== filters.items.length; + setFilters(value); + handleChangeQueryData('filters', value); if (isNewFilterAdded) { - setFilters(value); setCurrentPage(1); logEvent('Infra Monitoring: Hosts list filters applied', { @@ -128,6 +89,7 @@ function HostsList(): JSX.Element { }); } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [filters], ); @@ -142,118 +104,59 @@ function HostsList(): JSX.Element { ); }, [selectedHostName, hostMetricsData]); - const handleRowClick = (record: HostRowData): void => { - setSelectedHostName(record.hostName); - - logEvent('Infra Monitoring: Hosts list item clicked', { - host: record.hostName, - }); - }; - const handleCloseHostDetail = (): void => { setSelectedHostName(null); }; - const showHostsTable = - !isError && - sentAnyHostMetricsData && - !isSendingIncorrectK8SAgentMetrics && - !(formattedHostMetricsData.length === 0 && filters.items.length > 0); + const handleFilterVisibilityChange = (): void => { + setShowFilters(!showFilters); + }; - const showNoFilteredHostsMessage = - !isFetching && - !isLoading && - formattedHostMetricsData.length === 0 && - filters.items.length > 0; - - const showHostsEmptyState = - !isFetching && - !isLoading && - (!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics) && - !filters.items.length; + const handleQuickFiltersChange = (query: Query): void => { + handleChangeQueryData('filters', query.builder.queryData[0].filters); + handleFiltersChange(query.builder.queryData[0].filters); + }; return (
- - {isError && {data?.error || 'Something went wrong'}} - - {showHostsEmptyState && ( - - )} - - {showNoFilteredHostsMessage && ( -
-
- thinking-emoji + {showFilters && ( +
+
+ Filters + + + +
+ - - - This query had no results. Edit your query and try again! -
-
- )} - - {(isFetching || isLoading) && ( -
- - - + +
- )} - - {showHostsTable && ( - { - setCurrentPage(page); - setPageSize(pageSize); - }, - }} - scroll={{ x: true }} - loading={{ - spinning: isFetching || isLoading, - indicator: } />, - }} - tableLayout="fixed" - rowKey={(record): string => record.hostName} - onChange={handleTableChange} - onRow={(record): { onClick: () => void; className: string } => ({ - onClick: (): void => handleRowClick(record), - className: 'clickable-row', - })} - /> - )} - + getHostsListColumns(), []); + + const sentAnyHostMetricsData = useMemo( + () => data?.payload?.data?.sentAnyHostMetricsData || false, + [data], + ); + + const isSendingIncorrectK8SAgentMetrics = useMemo( + () => data?.payload?.data?.isSendingK8SAgentMetrics || false, + [data], + ); + + const formattedHostMetricsData = useMemo( + () => formatDataForTable(hostMetricsData), + [hostMetricsData], + ); + + const totalCount = data?.payload?.data?.total || 0; + + 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); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleRowClick = (record: HostRowData): void => { + setSelectedHostName(record.hostName); + logEvent('Infra Monitoring: Hosts list item clicked', { + host: record.hostName, + }); + }; + + const showNoFilteredHostsMessage = + !isFetching && + !isLoading && + formattedHostMetricsData.length === 0 && + filters.items.length > 0; + + const showHostsEmptyState = + !isFetching && + !isLoading && + (!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics) && + !filters.items.length; + + if (isError) { + return {data?.error || 'Something went wrong'}; + } + + if (showHostsEmptyState) { + return ( + + ); + } + + if (showNoFilteredHostsMessage) { + return ( +
+
+ thinking-emoji + + + This query had no results. Edit your query and try again! + +
+
+ ); + } + + if (isLoading || isFetching) { + return ( +
+ + + +
+ ); + } + + return ( +
{ + setCurrentPage(page); + setPageSize(pageSize); + }, + }} + scroll={{ x: true }} + loading={{ + spinning: isFetching || isLoading, + indicator: } />, + }} + tableLayout="fixed" + rowKey={(record): string => record.hostName} + onChange={handleTableChange} + onRow={(record): { onClick: () => void; className: string } => ({ + onClick: (): void => handleRowClick(record), + className: 'clickable-row', + })} + /> + ); +} diff --git a/frontend/src/container/InfraMonitoringHosts/InfraMonitoring.styles.scss b/frontend/src/container/InfraMonitoringHosts/InfraMonitoring.styles.scss index 8066481a4f..52c51fd143 100644 --- a/frontend/src/container/InfraMonitoringHosts/InfraMonitoring.styles.scss +++ b/frontend/src/container/InfraMonitoringHosts/InfraMonitoring.styles.scss @@ -51,6 +51,30 @@ cursor: pointer; } + .hosts-list-content { + display: flex; + flex-direction: row; + + .hosts-quick-filters-container { + width: 280px; + min-width: 280px; + border-right: 1px solid var(--bg-slate-400); + + .hosts-quick-filters-container-header { + padding: 8px; + border-bottom: 1px solid var(--bg-slate-400); + + display: flex; + align-items: center; + justify-content: space-between; + } + } + } + + .hosts-list-table-container { + flex: 1; + } + .hosts-list-table { .ant-table { .ant-table-thead > tr > th { @@ -164,7 +188,7 @@ margin: 0; // this is to offset intercom icon till we improve the design - padding-right: 72px; + right: 20px; .ant-pagination-item { border-radius: 4px; @@ -214,6 +238,7 @@ display: flex; flex-direction: column; gap: 2px; + flex: 1; .hosts-list-loading-state-item { height: 48px; @@ -222,6 +247,7 @@ } .no-filtered-hosts-message-container { + flex: 1; height: 30vh; display: flex; flex-direction: column; @@ -246,6 +272,7 @@ .hosts-empty-state-container { padding: 16px; height: 40vh; + flex: 1; display: flex; flex-direction: column; align-items: center; diff --git a/frontend/src/container/InfraMonitoringHosts/utils.tsx b/frontend/src/container/InfraMonitoringHosts/utils.tsx index 8e3dc59e7b..743a9135b0 100644 --- a/frontend/src/container/InfraMonitoringHosts/utils.tsx +++ b/frontend/src/container/InfraMonitoringHosts/utils.tsx @@ -3,9 +3,22 @@ import './InfraMonitoring.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Progress, TabsProps, Tag } from 'antd'; import { ColumnType } from 'antd/es/table'; -import { HostData, HostListPayload } from 'api/infraMonitoring/getHostLists'; +import { + HostData, + HostListPayload, + HostListResponse, +} from 'api/infraMonitoring/getHostLists'; +import { + FiltersType, + IQuickFiltersConfig, +} from 'components/QuickFilters/types'; import TabLabel from 'components/TabLabel'; import { PANEL_TYPES } from 'constants/queryBuilder'; +import { Dispatch, SetStateAction } from 'react'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; import HostsList from './HostsList'; @@ -18,6 +31,29 @@ export interface HostRowData { active: React.ReactNode; } +export interface HostsListTableProps { + isLoading: boolean; + isError: boolean; + isFetching: boolean; + tableData: + | SuccessResponse + | ErrorResponse + | undefined; + hostMetricsData: HostData[]; + filters: TagFilter; + setSelectedHostName: Dispatch>; + currentPage: number; + setCurrentPage: Dispatch>; + pageSize: number; + setOrderBy: Dispatch< + SetStateAction<{ + columnName: string; + order: 'asc' | 'desc'; + } | null> + >; + setPageSize: (pageSize: number) => void; +} + export const getHostListsQuery = (): HostListPayload => ({ filters: { items: [], @@ -132,3 +168,36 @@ export const formatDataForTable = (data: HostData[]): HostRowData[] => wait: `${Number((host.wait * 100).toFixed(1))}%`, load15: host.load15, })); + +export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [ + { + type: FiltersType.CHECKBOX, + title: 'Host Name', + attributeKey: { + key: 'host_name', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'system_cpu_load_average_15m', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, + { + type: FiltersType.CHECKBOX, + title: 'OS Type', + attributeKey: { + key: 'os_type', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'system_cpu_load_average_15m', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, +];