Compare commits

...

4 Commits

Author SHA1 Message Date
Amlan Kumar Nandy
6d758b6981 chore: add todo comments 2025-02-27 13:28:26 +05:30
Amlan Kumar Nandy
d721c3f316 chore: add new empty state conditions for hosts 2025-02-27 13:28:26 +05:30
Amlan Kumar Nandy
2df1344ee3 chore: add new empty state conditions for k8s entities 2025-02-27 13:28:26 +05:30
amlannandy
3fa27f55ac chore: add new api hook 2025-02-27 13:28:26 +05:30
18 changed files with 882 additions and 466 deletions

View File

@@ -50,6 +50,8 @@ export interface HostListResponse {
total: number; total: number;
sentAnyHostMetricsData: boolean; sentAnyHostMetricsData: boolean;
isSendingK8SAgentMetrics: boolean; isSendingK8SAgentMetrics: boolean;
clusterNames: string[];
nodeNames: string[];
}; };
} }

View File

@@ -0,0 +1,47 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
export interface K8sRequiredMetadataFields {
clusterName: string;
nodeName: string;
namespaceName: string;
podName: string;
hasClusterName: boolean;
hasNodeName: boolean;
hasNamespaceName: boolean;
hasDeploymentName: boolean;
hasStatefulsetName: boolean;
hasDaemonsetName: boolean;
hasCronjobName: boolean;
hasJobName: boolean;
}
export interface K8sEntityStatusResponse {
didSendPodMetrics: boolean;
didSendNodeMetrics: boolean;
didSendClusterMetrics: boolean;
isSendingOptionalPodMetrics: boolean;
isSendingRequiredMetadata: Array<K8sRequiredMetadataFields>;
}
export const getK8sEntityStatus = async (
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<K8sEntityStatusResponse> | ErrorResponse> => {
try {
const response = await axios.get('/infra_onboarding/k8s/status', {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -18,11 +18,13 @@ export const REACT_QUERY_KEY = {
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS', GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE', REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE', DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
GET_HOST_LIST: 'GET_HOST_LIST',
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE', UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3', GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
GET_TRACE_V2_WATERFALL: 'GET_TRACE_V2_WATERFALL', GET_TRACE_V2_WATERFALL: 'GET_TRACE_V2_WATERFALL',
GET_TRACE_V2_FLAMEGRAPH: 'GET_TRACE_V2_FLAMEGRAPH', GET_TRACE_V2_FLAMEGRAPH: 'GET_TRACE_V2_FLAMEGRAPH',
// Infra Monitoring Query Keys
GET_HOST_LIST: 'GET_HOST_LIST',
GET_POD_LIST: 'GET_POD_LIST', GET_POD_LIST: 'GET_POD_LIST',
GET_NODE_LIST: 'GET_NODE_LIST', GET_NODE_LIST: 'GET_NODE_LIST',
GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST', GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST',
@@ -32,6 +34,7 @@ export const REACT_QUERY_KEY = {
GET_JOB_LIST: 'GET_JOB_LIST', GET_JOB_LIST: 'GET_JOB_LIST',
GET_DAEMONSET_LIST: 'GET_DAEMONSET_LIST,', GET_DAEMONSET_LIST: 'GET_DAEMONSET_LIST,',
GET_VOLUME_LIST: 'GET_VOLUME_LIST', GET_VOLUME_LIST: 'GET_VOLUME_LIST',
GET_K8S_ENTITY_STATUS: 'GET_K8S_ENTITY_STATUS',
// AWS Integration Query Keys // AWS Integration Query Keys
AWS_ACCOUNTS: 'AWS_ACCOUNTS', AWS_ACCOUNTS: 'AWS_ACCOUNTS',

View File

@@ -3,10 +3,72 @@ import { Typography } from 'antd';
export default function HostsEmptyOrIncorrectMetrics({ export default function HostsEmptyOrIncorrectMetrics({
noData, noData,
incorrectData, incorrectData,
clusterNames,
nodeNames,
}: { }: {
noData: boolean; noData: boolean;
incorrectData: boolean; incorrectData: boolean;
clusterNames: string[];
nodeNames: string[];
}): JSX.Element { }): JSX.Element {
let emptyStateMessage = (
<Typography.Text className="no-hosts-message-text">
No host metrics were detected. To monitor your hosts, you&apos;ll need to
send{' '}
<a
href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md"
target="_blank"
rel="noreferrer"
>
OpenTelemetry system metrics
</a>
. Check out our host metrics setup guide{' '}
<a
href="https://signoz.io/docs/userguide/hostmetrics"
target="_blank"
rel="noreferrer"
>
here
</a>{' '}
to get started.
</Typography.Text>
);
if (nodeNames.length > 0) {
const nodeNamesString =
nodeNames.length > 1
? `${nodeNames.slice(0, -1).join(', ')} and ${
nodeNames[nodeNames.length - 1]
}`
: nodeNames[0];
emptyStateMessage = (
<Typography.Text className="no-hosts-message-text">
The k8s-infra chart version installed in nodes {nodeNamesString} has a known
issue where container metrics from agent pods are incorrectly categorized as
host metrics. To resolve this, please update to the latest version of the
SigNoz k8s-infra chart. Reach out to support if you need help with this.
</Typography.Text>
);
}
if (clusterNames.length > 0) {
const clusterNamesString =
clusterNames.length > 1
? `${clusterNames.slice(0, -1).join(', ')} and ${
clusterNames[clusterNames.length - 1]
}`
: clusterNames[0];
emptyStateMessage = (
<Typography.Text className="no-hosts-message-text">
The k8s-infra chart version installed in clusters {clusterNamesString} has a
known issue where container metrics from agent pods are incorrectly
categorized as host metrics. To resolve this, please update to the latest
version of the SigNoz k8s-infra chart. Reach out to support if you need help
with this.
</Typography.Text>
);
}
return ( return (
<div className="hosts-empty-state-container"> <div className="hosts-empty-state-container">
<div className="hosts-empty-state-container-content"> <div className="hosts-empty-state-container-content">
@@ -18,29 +80,11 @@ export default function HostsEmptyOrIncorrectMetrics({
No host metrics data received yet. No host metrics data received yet.
</Typography.Title> </Typography.Title>
<Typography.Text className="no-hosts-message-text"> {emptyStateMessage}
Infrastructure monitoring requires the{' '}
<a
href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md"
target="_blank"
rel="noreferrer"
>
OpenTelemetry system metrics
</a>
. Please refer to{' '}
<a
href="https://signoz.io/docs/userguide/hostmetrics"
target="_blank"
rel="noreferrer"
>
this
</a>{' '}
to learn how to send host metrics to SigNoz.
</Typography.Text>
</div> </div>
)} )}
{incorrectData && ( {!clusterNames.length && !nodeNames.length && incorrectData && (
<Typography.Text className="incorrect-metrics-message"> <Typography.Text className="incorrect-metrics-message">
To see host metrics, upgrade to the latest version of SigNoz k8s-infra To see host metrics, upgrade to the latest version of SigNoz k8s-infra
chart. Please contact support if you need help. chart. Please contact support if you need help.

View File

@@ -1,6 +1,5 @@
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { import {
Skeleton,
Spin, Spin,
Table, Table,
TablePaginationConfig, TablePaginationConfig,
@@ -103,6 +102,8 @@ export default function HostsListTable({
<HostsEmptyOrIncorrectMetrics <HostsEmptyOrIncorrectMetrics
noData={!sentAnyHostMetricsData} noData={!sentAnyHostMetricsData}
incorrectData={isSendingIncorrectK8SAgentMetrics} incorrectData={isSendingIncorrectK8SAgentMetrics}
clusterNames={data?.payload?.data?.clusterNames || []}
nodeNames={data?.payload?.data?.nodeNames || []}
/> />
); );
} }
@@ -125,31 +126,6 @@ export default function HostsListTable({
); );
} }
if (isLoading || isFetching) {
return (
<div className="hosts-list-loading-state">
<Skeleton.Input
className="hosts-list-loading-state-item"
size="large"
block
active
/>
<Skeleton.Input
className="hosts-list-loading-state-item"
size="large"
block
active
/>
<Skeleton.Input
className="hosts-list-loading-state-item"
size="large"
block
active
/>
</div>
);
}
return ( return (
<Table <Table
className="hosts-list-table" className="hosts-list-table"

View File

@@ -15,6 +15,7 @@ import {
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sClustersListPayload } from 'api/infraMonitoring/getK8sClustersList'; import { K8sClustersListPayload } from 'api/infraMonitoring/getK8sClustersList';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { useGetK8sClustersList } from 'hooks/infraMonitoring/useGetK8sClustersList'; import { useGetK8sClustersList } from 'hooks/infraMonitoring/useGetK8sClustersList';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -30,6 +31,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -47,10 +49,12 @@ function K8sClustersList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -490,54 +494,58 @@ function K8sClustersList({
entity={K8sCategory.NODES} entity={K8sCategory.NODES}
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<EntityStatusEmptyStateWrapper
category={K8sCategory.CLUSTERS}
data={entityStatus}
>
<Table
className="k8s-list-table clusters-list-table"
dataSource={isFetching || isLoading ? [] : formattedClustersData}
columns={columns}
pagination={{
current: currentPage,
pageSize,
total: totalCount,
showSizeChanger: true,
hideOnSinglePage: false,
onChange: onPaginationChange,
}}
scroll={{ x: true }}
loading={{
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
locale={{
emptyText:
isFetching || isLoading ? null : (
<div className="no-filtered-hosts-message-container">
<div className="no-filtered-hosts-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Table <Typography.Text className="no-filtered-hosts-message">
className="k8s-list-table clusters-list-table" This query had no results. Edit your query and try again!
dataSource={isFetching || isLoading ? [] : formattedClustersData} </Typography.Text>
columns={columns} </div>
pagination={{
current: currentPage,
pageSize,
total: totalCount,
showSizeChanger: true,
hideOnSinglePage: false,
onChange: onPaginationChange,
}}
scroll={{ x: true }}
loading={{
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
locale={{
emptyText:
isFetching || isLoading ? null : (
<div className="no-filtered-hosts-message-container">
<div className="no-filtered-hosts-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again!
</Typography.Text>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<ClusterDetails <ClusterDetails
cluster={selectedClusterData} cluster={selectedClusterData}

View File

@@ -15,6 +15,7 @@ import {
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sDaemonSetsListPayload } from 'api/infraMonitoring/getK8sDaemonSetsList'; import { K8sDaemonSetsListPayload } from 'api/infraMonitoring/getK8sDaemonSetsList';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import classNames from 'classnames'; import classNames from 'classnames';
import { useGetK8sDaemonSetsList } from 'hooks/infraMonitoring/useGetK8sDaemonSetsList'; import { useGetK8sDaemonSetsList } from 'hooks/infraMonitoring/useGetK8sDaemonSetsList';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
@@ -31,6 +32,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -48,10 +50,12 @@ function K8sDaemonSetsList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -498,55 +502,60 @@ function K8sDaemonSetsList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className={classNames('k8s-list-table', 'daemonSets-list-table', { category={K8sCategory.DAEMONSETS}
'expanded-daemonsets-list-table': isGroupedByAttribute, data={entityStatus}
})} >
dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData} <Table
columns={columns} className={classNames('k8s-list-table', 'daemonSets-list-table', {
pagination={{ 'expanded-daemonsets-list-table': isGroupedByAttribute,
current: currentPage, })}
pageSize, dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<DaemonSetDetails <DaemonSetDetails
daemonSet={selectedDaemonSetData} daemonSet={selectedDaemonSetData}

View File

@@ -15,6 +15,7 @@ import {
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList'; import { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import classNames from 'classnames'; import classNames from 'classnames';
import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList'; import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
@@ -31,6 +32,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -48,10 +50,12 @@ function K8sDeploymentsList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -502,55 +506,60 @@ function K8sDeploymentsList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className={classNames('k8s-list-table', 'deployments-list-table', { category={K8sCategory.DEPLOYMENTS}
'expanded-deployments-list-table': isGroupedByAttribute, data={entityStatus}
})} >
dataSource={isFetching || isLoading ? [] : formattedDeploymentsData} <Table
columns={columns} className={classNames('k8s-list-table', 'deployments-list-table', {
pagination={{ 'expanded-deployments-list-table': isGroupedByAttribute,
current: currentPage, })}
pageSize, dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<DeploymentDetails <DeploymentDetails
deployment={selectedDeploymentData} deployment={selectedDeploymentData}

View File

@@ -0,0 +1,193 @@
import { Typography } from 'antd';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sCategory } from './constants';
interface EntityStatusEmptyStateProps {
category: K8sCategory;
data: K8sEntityStatusResponse | null | undefined;
children: JSX.Element;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function EntityStatusEmptyStateWrapper({
category,
data,
children,
}: EntityStatusEmptyStateProps): JSX.Element {
if (!data) {
return children;
}
const {
didSendPodMetrics,
didSendNodeMetrics,
didSendClusterMetrics,
isSendingOptionalPodMetrics,
isSendingRequiredMetadata,
} = data;
const metaData = isSendingRequiredMetadata.length
? isSendingRequiredMetadata[0]
: undefined;
const noK8sMetrics = (
<Typography.Text>
No k8s metrics were detected. To monitor your k8s infra, you will need to
send{' '}
<Typography.Link href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/kubeletstatsreceiver">
kubelet
</Typography.Link>{' '}
and{' '}
<Typography.Link href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/k8sclusterreceiver">
k8scluster
</Typography.Link>{' '}
metrics. Check out our k8s infra installation guide{' '}
<Typography.Link href="https://signoz.io/docs/tutorial/kubernetes-infra-metrics/">
here
</Typography.Link>{' '}
to get started. Reach out to support if you need further assistance.
</Typography.Text>
);
const partialMetrics = (
<Typography.Text>
Partial metrics detected. The following metrics from the kubelet metrics are
not received. They help monitor the resource utilisation to their
requests/limits. Learn more{' '}
<Typography.Link
// TODO: Add link to docs
href="placeholder"
>
here
</Typography.Link>{' '}
about how to send them. Reach out to support if you need further assistance.
</Typography.Text>
);
const noMetadata = (
<Typography>
The following shows the sample pods without required metadata. Learn more{' '}
<Typography.Link
// TODO: Add link to docs
href="placeholder"
>
here
</Typography.Link>{' '}
on how to send enriched data for k8s metrics
<ul>
{!metaData?.hasClusterName && <li>No cluster name found</li>}
{!metaData?.hasNodeName && <li>No node name found</li>}
{!metaData?.hasNamespaceName && <li>No namespace found</li>}
{!metaData?.hasDeploymentName &&
!metaData?.hasStatefulsetName &&
!metaData?.hasDaemonsetName &&
!metaData?.hasCronjobName &&
!metaData?.hasJobName && (
<li>
Pod doesn&apos;t have any of the following set:
<ul>
<li>deployment</li>
<li>daemonset</li>
<li>statefulset</li>
<li>job</li>
<li>cronjob</li>
</ul>
</li>
)}
</ul>
</Typography>
);
const noNodeMetrics = (
<Typography.Text>
No node metrics were detected. This is likely due to not adding
&quot;node&quot; to the metric groups of kubelet receiver. Please update the
config and check back. Learn more{' '}
<Typography.Link
// TODO: Add link to docs
href="placeholder"
>
here
</Typography.Link>
. Reach out to support if you need further assistance.
</Typography.Text>
);
const noClusterMetrics = (
<Typography.Text>
We are receiving kubelet metrics but not k8scluster receiver metrics. Follow
the cluster metrics setup{' '}
<Typography.Link
// TODO: Add link to docs
href="placeholder"
>
here
</Typography.Link>
. Reach out to support if you need further assistance.
</Typography.Text>
);
let emptyStateContent;
if (category === K8sCategory.NODES) {
console.log({ didSendPodMetrics, didSendNodeMetrics });
if (didSendPodMetrics && !didSendNodeMetrics) {
emptyStateContent = noNodeMetrics;
} else if (!didSendClusterMetrics && !didSendNodeMetrics) {
emptyStateContent = noK8sMetrics;
}
}
if (category === K8sCategory.NAMESPACES && !didSendPodMetrics) {
emptyStateContent = noK8sMetrics;
}
if (category === K8sCategory.CLUSTERS) {
if (didSendPodMetrics && !didSendClusterMetrics) {
emptyStateContent = noClusterMetrics;
}
if (!didSendPodMetrics && !didSendClusterMetrics) {
emptyStateContent = noK8sMetrics;
}
}
if (
[
K8sCategory.PODS,
K8sCategory.NAMESPACES,
K8sCategory.DEPLOYMENTS,
K8sCategory.DAEMONSETS,
K8sCategory.JOBS,
K8sCategory.STATEFULSETS,
].includes(category)
) {
if (!didSendPodMetrics && !isSendingOptionalPodMetrics) {
emptyStateContent = noK8sMetrics;
} else if (didSendPodMetrics && !isSendingOptionalPodMetrics) {
emptyStateContent = partialMetrics;
} else if (isSendingRequiredMetadata.length > 0) {
emptyStateContent = noMetadata;
}
}
if (emptyStateContent) {
return (
<div className="entity-status-empty-state-container">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="entity-status-empty-state-message">
{emptyStateContent}
</Typography.Text>
</div>
);
}
return children;
}
export default EntityStatusEmptyStateWrapper;

View File

@@ -888,3 +888,16 @@
.expanded-clickable-row { .expanded-clickable-row {
cursor: pointer; cursor: pointer;
} }
.entity-status-empty-state-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 48px 120px;
.entity-status-empty-state-message {
text-align: center;
}
}

View File

@@ -7,6 +7,7 @@ import { Collapse, Tooltip, Typography } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import QuickFilters from 'components/QuickFilters/QuickFilters'; import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types'; import { QuickFiltersSource } from 'components/QuickFilters/types';
import { useGetK8sEntityStatus } from 'hooks/infraMonitoring/useGetK8sEntityStatus';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { import {
@@ -54,6 +55,9 @@ export default function InfraMonitoringK8s(): JSX.Element {
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
const { data: k8sEntityStatusData } = useGetK8sEntityStatus();
const k8sEntityStatus = k8sEntityStatusData?.payload;
const handleFilterVisibilityChange = (): void => { const handleFilterVisibilityChange = (): void => {
setShowFilters(!showFilters); setShowFilters(!showFilters);
}; };
@@ -335,6 +339,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -343,6 +348,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -351,6 +357,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -359,6 +366,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -367,6 +375,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -375,6 +384,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -383,6 +393,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -391,6 +402,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
@@ -399,6 +411,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
isFiltersVisible={showFilters} isFiltersVisible={showFilters}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
quickFiltersLastUpdated={quickFiltersLastUpdated} quickFiltersLastUpdated={quickFiltersLastUpdated}
entityStatus={k8sEntityStatus}
/> />
)} )}
</div> </div>

View File

@@ -14,6 +14,7 @@ import {
} from 'antd'; } from 'antd';
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sJobsListPayload } from 'api/infraMonitoring/getK8sJobsList'; import { K8sJobsListPayload } from 'api/infraMonitoring/getK8sJobsList';
import classNames from 'classnames'; import classNames from 'classnames';
import { useGetK8sJobsList } from 'hooks/infraMonitoring/useGetK8sJobsList'; import { useGetK8sJobsList } from 'hooks/infraMonitoring/useGetK8sJobsList';
@@ -31,6 +32,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -48,10 +50,12 @@ function K8sJobsList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -474,56 +478,60 @@ function K8sJobsList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className={classNames('k8s-list-table', 'jobs-list-table', { category={K8sCategory.JOBS}
'expanded-jobs-list-table': isGroupedByAttribute, data={entityStatus}
})} >
dataSource={isFetching || isLoading ? [] : formattedJobsData} <Table
columns={columns} className={classNames('k8s-list-table', 'jobs-list-table', {
pagination={{ 'expanded-jobs-list-table': isGroupedByAttribute,
current: currentPage, })}
pageSize, dataSource={isFetching || isLoading ? [] : formattedJobsData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<JobDetails <JobDetails
job={selectedJobData} job={selectedJobData}
isModalTimeSelection isModalTimeSelection

View File

@@ -14,6 +14,7 @@ import {
} from 'antd'; } from 'antd';
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sNamespacesListPayload } from 'api/infraMonitoring/getK8sNamespacesList'; import { K8sNamespacesListPayload } from 'api/infraMonitoring/getK8sNamespacesList';
import { useGetK8sNamespacesList } from 'hooks/infraMonitoring/useGetK8sNamespacesList'; import { useGetK8sNamespacesList } from 'hooks/infraMonitoring/useGetK8sNamespacesList';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
@@ -30,6 +31,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -47,10 +49,12 @@ function K8sNamespacesList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -499,53 +503,59 @@ function K8sNamespacesList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className="k8s-list-table namespaces-list-table" category={K8sCategory.NAMESPACES}
dataSource={isFetching || isLoading ? [] : formattedNamespacesData} data={entityStatus}
columns={columns} >
pagination={{ <Table
current: currentPage, className="k8s-list-table namespaces-list-table"
pageSize, dataSource={isFetching || isLoading ? [] : formattedNamespacesData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<NamespaceDetails <NamespaceDetails
namespace={selectedNamespaceData} namespace={selectedNamespaceData}
isModalTimeSelection isModalTimeSelection

View File

@@ -14,6 +14,7 @@ import {
} from 'antd'; } from 'antd';
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sNodesListPayload } from 'api/infraMonitoring/getK8sNodesList'; import { K8sNodesListPayload } from 'api/infraMonitoring/getK8sNodesList';
import { useGetK8sNodesList } from 'hooks/infraMonitoring/useGetK8sNodesList'; import { useGetK8sNodesList } from 'hooks/infraMonitoring/useGetK8sNodesList';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
@@ -30,6 +31,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -47,10 +49,12 @@ function K8sNodesList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -477,53 +481,58 @@ function K8sNodesList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className="k8s-list-table nodes-list-table" category={K8sCategory.NODES}
dataSource={isFetching || isLoading ? [] : formattedNodesData} data={entityStatus}
columns={columns} >
pagination={{ <Table
current: currentPage, className="k8s-list-table nodes-list-table"
pageSize, dataSource={isFetching || isLoading ? [] : formattedNodesData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<NodeDetails <NodeDetails
node={selectedNodeData} node={selectedNodeData}

View File

@@ -14,6 +14,7 @@ import { ColumnType, SorterResult } from 'antd/es/table/interface';
import get from 'api/browser/localstorage/get'; import get from 'api/browser/localstorage/get';
import set from 'api/browser/localstorage/set'; import set from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sPodsListPayload } from 'api/infraMonitoring/getK8sPodsList'; import { K8sPodsListPayload } from 'api/infraMonitoring/getK8sPodsList';
import classNames from 'classnames'; import classNames from 'classnames';
import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList'; import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList';
@@ -31,6 +32,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { import {
@@ -50,10 +52,12 @@ function K8sPodsList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -538,55 +542,60 @@ function K8sPodsList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className={classNames('k8s-list-table', { category={K8sCategory.PODS}
'expanded-k8s-list-table': isGroupedByAttribute, data={entityStatus}
})} >
dataSource={isFetching || isLoading ? [] : formattedPodsData} <Table
columns={columns} className={classNames('k8s-list-table', {
pagination={{ 'expanded-k8s-list-table': isGroupedByAttribute,
current: currentPage, })}
pageSize, dataSource={isFetching || isLoading ? [] : formattedPodsData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
loading={{ showSizeChanger: true,
spinning: isFetching || isLoading, hideOnSinglePage: false,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, onChange: onPaginationChange,
}} }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} scroll={{ x: true }}
scroll={{ x: true }} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
{selectedPodData && ( {selectedPodData && (
<PodDetails <PodDetails

View File

@@ -14,6 +14,7 @@ import {
} from 'antd'; } from 'antd';
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sStatefulSetsListPayload } from 'api/infraMonitoring/getsK8sStatefulSetsList'; import { K8sStatefulSetsListPayload } from 'api/infraMonitoring/getsK8sStatefulSetsList';
import classNames from 'classnames'; import classNames from 'classnames';
import { useGetK8sStatefulSetsList } from 'hooks/infraMonitoring/useGetK8sStatefulSetsList'; import { useGetK8sStatefulSetsList } from 'hooks/infraMonitoring/useGetK8sStatefulSetsList';
@@ -31,6 +32,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -48,10 +50,12 @@ function K8sStatefulSetsList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -499,55 +503,60 @@ function K8sStatefulSetsList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className={classNames('k8s-list-table', 'statefulSets-list-table', { category={K8sCategory.STATEFULSETS}
'expanded-statefulsets-list-table': isGroupedByAttribute, data={entityStatus}
})} >
dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData} <Table
columns={columns} className={classNames('k8s-list-table', 'statefulSets-list-table', {
pagination={{ 'expanded-statefulsets-list-table': isGroupedByAttribute,
current: currentPage, })}
pageSize, dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<StatefulSetDetails <StatefulSetDetails
statefulSet={selectedStatefulSetData} statefulSet={selectedStatefulSetData}

View File

@@ -14,6 +14,7 @@ import {
} from 'antd'; } from 'antd';
import { ColumnType, SorterResult } from 'antd/es/table/interface'; import { ColumnType, SorterResult } from 'antd/es/table/interface';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
import { K8sVolumesListPayload } from 'api/infraMonitoring/getK8sVolumesList'; import { K8sVolumesListPayload } from 'api/infraMonitoring/getK8sVolumesList';
import classNames from 'classnames'; import classNames from 'classnames';
import { useGetK8sVolumesList } from 'hooks/infraMonitoring/useGetK8sVolumesList'; import { useGetK8sVolumesList } from 'hooks/infraMonitoring/useGetK8sVolumesList';
@@ -31,6 +32,7 @@ import {
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping, K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
import { usePageSize } from '../utils'; import { usePageSize } from '../utils';
@@ -48,10 +50,12 @@ function K8sVolumesList({
isFiltersVisible, isFiltersVisible,
handleFilterVisibilityChange, handleFilterVisibilityChange,
quickFiltersLastUpdated, quickFiltersLastUpdated,
entityStatus,
}: { }: {
isFiltersVisible: boolean; isFiltersVisible: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
quickFiltersLastUpdated: number; quickFiltersLastUpdated: number;
entityStatus: K8sEntityStatusResponse | null | undefined;
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -484,55 +488,60 @@ function K8sVolumesList({
/> />
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table <EntityStatusEmptyStateWrapper
className={classNames('k8s-list-table', 'volumes-list-table', { category={K8sCategory.VOLUMES}
'expanded-volumes-list-table': isGroupedByAttribute, data={entityStatus}
})} >
dataSource={isFetching || isLoading ? [] : formattedVolumesData} <Table
columns={columns} className={classNames('k8s-list-table', 'volumes-list-table', {
pagination={{ 'expanded-volumes-list-table': isGroupedByAttribute,
current: currentPage, })}
pageSize, dataSource={isFetching || isLoading ? [] : formattedVolumesData}
total: totalCount, columns={columns}
showSizeChanger: true, pagination={{
hideOnSinglePage: false, current: currentPage,
onChange: onPaginationChange, pageSize,
}} total: totalCount,
scroll={{ x: true }} showSizeChanger: true,
loading={{ hideOnSinglePage: false,
spinning: isFetching || isLoading, onChange: onPaginationChange,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, }}
}} scroll={{ x: true }}
locale={{ loading={{
emptyText: spinning: isFetching || isLoading,
isFetching || isLoading ? null : ( indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
<div className="no-filtered-hosts-message-container"> }}
<div className="no-filtered-hosts-message-content"> locale={{
<img emptyText:
src="/Icons/emptyState.svg" isFetching || isLoading ? null : (
alt="thinking-emoji" <div className="no-filtered-hosts-message-container">
className="empty-state-svg" <div className="no-filtered-hosts-message-content">
/> <img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message"> <Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again! This query had no results. Edit your query and try again!
</Typography.Text> </Typography.Text>
</div>
</div> </div>
</div> ),
), }}
}} tableLayout="fixed"
tableLayout="fixed" onChange={handleTableChange}
onChange={handleTableChange} onRow={(record): { onClick: () => void; className: string } => ({
onRow={(record): { onClick: () => void; className: string } => ({ onClick: (): void => handleRowClick(record),
onClick: (): void => handleRowClick(record), className: 'clickable-row',
className: 'clickable-row', })}
})} expandable={{
expandable={{ expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined, expandIcon: expandRowIconRenderer,
expandIcon: expandRowIconRenderer, expandedRowKeys,
expandedRowKeys, }}
}} />
/> </EntityStatusEmptyStateWrapper>
<VolumeDetails <VolumeDetails
volume={selectedVolumeData} volume={selectedVolumeData}

View File

@@ -0,0 +1,45 @@
import {
getK8sEntityStatus,
K8sEntityStatusResponse,
} from 'api/infraMonitoring/getK8sEntityStatus';
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 UseGetK8sEntityStatus = (
options?: UseQueryOptions<
SuccessResponse<K8sEntityStatusResponse> | ErrorResponse,
Error
>,
headers?: Record<string, string>,
) => UseQueryResult<
SuccessResponse<K8sEntityStatusResponse> | ErrorResponse,
Error
>;
export const useGetK8sEntityStatus: UseGetK8sEntityStatus = (
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_K8S_ENTITY_STATUS];
}, [options?.queryKey]);
return useQuery<
SuccessResponse<K8sEntityStatusResponse> | ErrorResponse,
Error
>({
queryFn: ({ signal }) => getK8sEntityStatus(signal, headers),
...options,
queryKey,
});
};