Compare commits
7 Commits
v0.74.0-cl
...
chore/k8s-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d758b6981 | ||
|
|
d721c3f316 | ||
|
|
2df1344ee3 | ||
|
|
3fa27f55ac | ||
|
|
4134eb621c | ||
|
|
a3bc290500 | ||
|
|
ae73033826 |
@@ -181,7 +181,7 @@ services:
|
||||
- query-service
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:0.73.0
|
||||
image: signoz/query-service:0.74.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
- --use-logs-new-schema=true
|
||||
@@ -214,7 +214,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:0.73.0
|
||||
image: signoz/frontend:0.74.0
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
- query-service
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:0.73.0
|
||||
image: signoz/query-service:0.74.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
- --use-logs-new-schema=true
|
||||
@@ -150,7 +150,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:0.73.0
|
||||
image: signoz/frontend:0.74.0
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
|
||||
@@ -188,7 +188,7 @@ services:
|
||||
condition: service_healthy
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.73.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.74.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -222,7 +222,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.73.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.74.0}
|
||||
container_name: signoz-frontend
|
||||
depends_on:
|
||||
- alertmanager
|
||||
|
||||
@@ -121,7 +121,7 @@ services:
|
||||
condition: service_healthy
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.73.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.74.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.73.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.74.0}
|
||||
container_name: signoz-frontend
|
||||
depends_on:
|
||||
- alertmanager
|
||||
|
||||
@@ -121,7 +121,7 @@ services:
|
||||
condition: service_healthy
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.73.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.74.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -155,7 +155,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.73.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.74.0}
|
||||
container_name: signoz-frontend
|
||||
depends_on:
|
||||
- alertmanager
|
||||
|
||||
@@ -50,6 +50,8 @@ export interface HostListResponse {
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
clusterNames: string[];
|
||||
nodeNames: string[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
47
frontend/src/api/infraMonitoring/getK8sEntityStatus.ts
Normal file
47
frontend/src/api/infraMonitoring/getK8sEntityStatus.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -18,11 +18,13 @@ export const REACT_QUERY_KEY = {
|
||||
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
|
||||
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
|
||||
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
||||
GET_HOST_LIST: 'GET_HOST_LIST',
|
||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
||||
GET_TRACE_V2_WATERFALL: 'GET_TRACE_V2_WATERFALL',
|
||||
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_NODE_LIST: 'GET_NODE_LIST',
|
||||
GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST',
|
||||
@@ -32,6 +34,7 @@ export const REACT_QUERY_KEY = {
|
||||
GET_JOB_LIST: 'GET_JOB_LIST',
|
||||
GET_DAEMONSET_LIST: 'GET_DAEMONSET_LIST,',
|
||||
GET_VOLUME_LIST: 'GET_VOLUME_LIST',
|
||||
GET_K8S_ENTITY_STATUS: 'GET_K8S_ENTITY_STATUS',
|
||||
|
||||
// AWS Integration Query Keys
|
||||
AWS_ACCOUNTS: 'AWS_ACCOUNTS',
|
||||
|
||||
@@ -3,6 +3,7 @@ import './AccountActions.style.scss';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Select, Skeleton } from 'antd';
|
||||
import { SelectProps } from 'antd/lib';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Check, ChevronDown } from 'lucide-react';
|
||||
@@ -167,9 +168,31 @@ function AccountActions(): JSX.Element {
|
||||
}, [initialAccount]);
|
||||
|
||||
const [isIntegrationModalOpen, setIsIntegrationModalOpen] = useState(false);
|
||||
const startAccountConnectionAttempt = (): void => {
|
||||
setIsIntegrationModalOpen(true);
|
||||
logEvent('AWS Integration: Account connection attempt started', {});
|
||||
};
|
||||
|
||||
const [isAccountSettingsModalOpen, setIsAccountSettingsModalOpen] = useState(
|
||||
false,
|
||||
);
|
||||
const openAccountSettings = (): void => {
|
||||
setIsAccountSettingsModalOpen(true);
|
||||
logEvent('AWS Integration: Account settings viewed', {
|
||||
cloudAccountId: activeAccount?.cloud_account_id,
|
||||
});
|
||||
};
|
||||
|
||||
// log telemetry event when an account is viewed.
|
||||
useEffect(() => {
|
||||
if (activeAccount) {
|
||||
logEvent('AWS Integration: Account viewed', {
|
||||
cloudAccountId: activeAccount?.cloud_account_id,
|
||||
status: activeAccount?.status,
|
||||
enabledRegions: activeAccount?.config?.regions,
|
||||
});
|
||||
}
|
||||
}, [activeAccount]);
|
||||
|
||||
const selectOptions: SelectProps['options'] = useMemo(
|
||||
() =>
|
||||
@@ -196,8 +219,8 @@ function AccountActions(): JSX.Element {
|
||||
navigate({ search: urlQuery.toString() });
|
||||
}
|
||||
}}
|
||||
onIntegrationModalOpen={(): void => setIsIntegrationModalOpen(true)}
|
||||
onAccountSettingsModalOpen={(): void => setIsAccountSettingsModalOpen(true)}
|
||||
onIntegrationModalOpen={startAccountConnectionAttempt}
|
||||
onAccountSettingsModalOpen={openAccountSettings}
|
||||
/>
|
||||
|
||||
{isIntegrationModalOpen && (
|
||||
|
||||
@@ -12,6 +12,7 @@ import history from 'lib/history';
|
||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
import logEvent from '../../../../api/common/logEvent';
|
||||
import { CloudAccount } from '../../ServicesSection/types';
|
||||
import { RegionSelector } from './RegionSelector';
|
||||
import RemoveIntegrationAccount from './RemoveIntegrationAccount';
|
||||
@@ -50,6 +51,11 @@ function AccountSettingsModal({
|
||||
urlQuery.delete('cloudAccountId');
|
||||
handleClose();
|
||||
history.replace({ search: urlQuery.toString() });
|
||||
|
||||
logEvent('AWS Integration: Account removed', {
|
||||
id: account?.id,
|
||||
cloudAccountId: account?.cloud_account_id,
|
||||
});
|
||||
};
|
||||
|
||||
const renderRegionSelector = useCallback(() => {
|
||||
|
||||
@@ -106,7 +106,7 @@ function CloudAccountSetupModal({
|
||||
// Handle success state first
|
||||
if (modalState === ModalStateEnum.SUCCESS) {
|
||||
return {
|
||||
title: 'AWS Webservice Integration',
|
||||
title: 'AWS Integration',
|
||||
okText: (
|
||||
<div className="cloud-account-setup-success-view__footer-button">
|
||||
Continue
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRef } from 'react';
|
||||
import { AccountStatusResponse } from 'types/api/integrations/aws';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
import logEvent from '../../../../api/common/logEvent';
|
||||
import { ModalStateEnum, RegionFormProps } from '../types';
|
||||
import AlertMessage from './AlertMessage';
|
||||
import {
|
||||
@@ -41,7 +42,7 @@ export function RegionForm({
|
||||
}: RegionFormProps): JSX.Element {
|
||||
const startTimeRef = useRef(Date.now());
|
||||
const refetchInterval = 10 * 1000;
|
||||
const errorTimeout = 5 * 60 * 1000;
|
||||
const errorTimeout = 10 * 60 * 1000;
|
||||
|
||||
const { isLoading: isAccountStatusLoading } = useAccountStatus(accountId, {
|
||||
refetchInterval,
|
||||
@@ -49,9 +50,15 @@ export function RegionForm({
|
||||
onSuccess: (data: AccountStatusResponse) => {
|
||||
if (data.data.status.integration.last_heartbeat_ts_ms !== null) {
|
||||
setModalState(ModalStateEnum.SUCCESS);
|
||||
logEvent('AWS Integration: Account connected', {
|
||||
cloudAccountId: data?.data?.cloud_account_id,
|
||||
status: data?.data?.status,
|
||||
});
|
||||
} else if (Date.now() - startTimeRef.current >= errorTimeout) {
|
||||
// 5 minutes in milliseconds
|
||||
setModalState(ModalStateEnum.ERROR);
|
||||
logEvent('AWS Integration: Account connection attempt timed out', {
|
||||
id: accountId,
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useUpdateServiceConfig } from 'hooks/integration/aws/useUpdateServiceCo
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
|
||||
interface IConfigureServiceModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -82,6 +84,13 @@ function ConfigureServiceModal({
|
||||
serviceId,
|
||||
]);
|
||||
onClose();
|
||||
|
||||
logEvent('AWS Integration: Service settings saved', {
|
||||
cloudAccountId,
|
||||
serviceId,
|
||||
logsEnabled: values?.logs,
|
||||
metricsEnabled: values?.metrics,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Failed to update service config:', error);
|
||||
|
||||
@@ -7,8 +7,9 @@ import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/t
|
||||
import dayjs from 'dayjs';
|
||||
import { useServiceDetails } from 'hooks/integration/aws/useServiceDetails';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
import ConfigureServiceModal from './ConfigureServiceModal';
|
||||
|
||||
const getStatus = (
|
||||
@@ -57,6 +58,13 @@ function ServiceDetails(): JSX.Element | null {
|
||||
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
|
||||
false,
|
||||
);
|
||||
const openServiceConfigModal = (): void => {
|
||||
setIsConfigureServiceModalOpen(true);
|
||||
logEvent('AWS Integration: Service settings viewed', {
|
||||
cloudAccountId,
|
||||
serviceId,
|
||||
});
|
||||
};
|
||||
|
||||
const { data: serviceDetailsData, isLoading } = useServiceDetails(
|
||||
serviceId || '',
|
||||
@@ -80,6 +88,16 @@ function ServiceDetails(): JSX.Element | null {
|
||||
[config],
|
||||
);
|
||||
|
||||
// log telemetry event on visiting details of a service.
|
||||
useEffect(() => {
|
||||
if (serviceId) {
|
||||
logEvent('AWS Integration: Service viewed', {
|
||||
cloudAccountId,
|
||||
serviceId,
|
||||
});
|
||||
}
|
||||
}, [cloudAccountId, serviceId]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner size="large" height="50vh" />;
|
||||
}
|
||||
@@ -119,7 +137,7 @@ function ServiceDetails(): JSX.Element | null {
|
||||
(isAnySignalConfigured ? (
|
||||
<Button
|
||||
className="configure-button configure-button--default"
|
||||
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
||||
onClick={openServiceConfigModal}
|
||||
>
|
||||
Configure ({enabledSignals}/{totalSupportedSignals})
|
||||
</Button>
|
||||
@@ -127,7 +145,7 @@ function ServiceDetails(): JSX.Element | null {
|
||||
<Button
|
||||
type="primary"
|
||||
className="configure-button configure-button--primary"
|
||||
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
||||
onClick={openServiceConfigModal}
|
||||
>
|
||||
Enable Service
|
||||
</Button>
|
||||
|
||||
@@ -3,10 +3,72 @@ import { Typography } from 'antd';
|
||||
export default function HostsEmptyOrIncorrectMetrics({
|
||||
noData,
|
||||
incorrectData,
|
||||
clusterNames,
|
||||
nodeNames,
|
||||
}: {
|
||||
noData: boolean;
|
||||
incorrectData: boolean;
|
||||
clusterNames: string[];
|
||||
nodeNames: string[];
|
||||
}): JSX.Element {
|
||||
let emptyStateMessage = (
|
||||
<Typography.Text className="no-hosts-message-text">
|
||||
No host metrics were detected. To monitor your hosts, you'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 (
|
||||
<div className="hosts-empty-state-container">
|
||||
<div className="hosts-empty-state-container-content">
|
||||
@@ -18,29 +80,11 @@ export default function HostsEmptyOrIncorrectMetrics({
|
||||
No host metrics data received yet.
|
||||
</Typography.Title>
|
||||
|
||||
<Typography.Text className="no-hosts-message-text">
|
||||
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>
|
||||
{emptyStateMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{incorrectData && (
|
||||
{!clusterNames.length && !nodeNames.length && incorrectData && (
|
||||
<Typography.Text className="incorrect-metrics-message">
|
||||
To see host metrics, upgrade to the latest version of SigNoz k8s-infra
|
||||
chart. Please contact support if you need help.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Skeleton,
|
||||
Spin,
|
||||
Table,
|
||||
TablePaginationConfig,
|
||||
@@ -103,6 +102,8 @@ export default function HostsListTable({
|
||||
<HostsEmptyOrIncorrectMetrics
|
||||
noData={!sentAnyHostMetricsData}
|
||||
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 (
|
||||
<Table
|
||||
className="hosts-list-table"
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sClustersListPayload } from 'api/infraMonitoring/getK8sClustersList';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { useGetK8sClustersList } from 'hooks/infraMonitoring/useGetK8sClustersList';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -47,10 +49,12 @@ function K8sClustersList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -490,54 +494,58 @@ function K8sClustersList({
|
||||
entity={K8sCategory.NODES}
|
||||
/>
|
||||
{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
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<ClusterDetails
|
||||
cluster={selectedClusterData}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sDaemonSetsListPayload } from 'api/infraMonitoring/getK8sDaemonSetsList';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sDaemonSetsList } from 'hooks/infraMonitoring/useGetK8sDaemonSetsList';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,10 +50,12 @@ function K8sDaemonSetsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -498,55 +502,60 @@ function K8sDaemonSetsList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'daemonSets-list-table', {
|
||||
'expanded-daemonsets-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.DAEMONSETS}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'daemonSets-list-table', {
|
||||
'expanded-daemonsets-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<DaemonSetDetails
|
||||
daemonSet={selectedDaemonSetData}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,10 +50,12 @@ function K8sDeploymentsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -502,55 +506,60 @@ function K8sDeploymentsList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'deployments-list-table', {
|
||||
'expanded-deployments-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.DEPLOYMENTS}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'deployments-list-table', {
|
||||
'expanded-deployments-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<DeploymentDetails
|
||||
deployment={selectedDeploymentData}
|
||||
|
||||
@@ -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'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
|
||||
"node" 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;
|
||||
@@ -888,3 +888,16 @@
|
||||
.expanded-clickable-row {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Collapse, Tooltip, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { useGetK8sEntityStatus } from 'hooks/infraMonitoring/useGetK8sEntityStatus';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import {
|
||||
@@ -54,6 +55,9 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const { data: k8sEntityStatusData } = useGetK8sEntityStatus();
|
||||
const k8sEntityStatus = k8sEntityStatusData?.payload;
|
||||
|
||||
const handleFilterVisibilityChange = (): void => {
|
||||
setShowFilters(!showFilters);
|
||||
};
|
||||
@@ -335,6 +339,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -343,6 +348,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -351,6 +357,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -359,6 +366,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -367,6 +375,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -375,6 +384,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -383,6 +393,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -391,6 +402,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -399,6 +411,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
isFiltersVisible={showFilters}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||
entityStatus={k8sEntityStatus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'antd';
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { K8sJobsListPayload } from 'api/infraMonitoring/getK8sJobsList';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sJobsList } from 'hooks/infraMonitoring/useGetK8sJobsList';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,10 +50,12 @@ function K8sJobsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -474,56 +478,60 @@ function K8sJobsList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'jobs-list-table', {
|
||||
'expanded-jobs-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedJobsData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.JOBS}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'jobs-list-table', {
|
||||
'expanded-jobs-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedJobsData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
<JobDetails
|
||||
job={selectedJobData}
|
||||
isModalTimeSelection
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'antd';
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { K8sNamespacesListPayload } from 'api/infraMonitoring/getK8sNamespacesList';
|
||||
import { useGetK8sNamespacesList } from 'hooks/infraMonitoring/useGetK8sNamespacesList';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -47,10 +49,12 @@ function K8sNamespacesList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -499,53 +503,59 @@ function K8sNamespacesList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className="k8s-list-table namespaces-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedNamespacesData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.NAMESPACES}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className="k8s-list-table namespaces-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedNamespacesData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<NamespaceDetails
|
||||
namespace={selectedNamespaceData}
|
||||
isModalTimeSelection
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'antd';
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { K8sNodesListPayload } from 'api/infraMonitoring/getK8sNodesList';
|
||||
import { useGetK8sNodesList } from 'hooks/infraMonitoring/useGetK8sNodesList';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -47,10 +49,12 @@ function K8sNodesList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -477,53 +481,58 @@ function K8sNodesList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className="k8s-list-table nodes-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedNodesData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.NODES}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className="k8s-list-table nodes-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedNodesData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<NodeDetails
|
||||
node={selectedNodeData}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import get from 'api/browser/localstorage/get';
|
||||
import set from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { K8sPodsListPayload } from 'api/infraMonitoring/getK8sPodsList';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import {
|
||||
@@ -50,10 +52,12 @@ function K8sPodsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -538,55 +542,60 @@ function K8sPodsList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className={classNames('k8s-list-table', {
|
||||
'expanded-k8s-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize,
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.PODS}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', {
|
||||
'expanded-k8s-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize,
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
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>
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
{selectedPodData && (
|
||||
<PodDetails
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'antd';
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { K8sStatefulSetsListPayload } from 'api/infraMonitoring/getsK8sStatefulSetsList';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sStatefulSetsList } from 'hooks/infraMonitoring/useGetK8sStatefulSetsList';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,10 +50,12 @@ function K8sStatefulSetsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -499,55 +503,60 @@ function K8sStatefulSetsList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'statefulSets-list-table', {
|
||||
'expanded-statefulsets-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.STATEFULSETS}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'statefulSets-list-table', {
|
||||
'expanded-statefulsets-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<StatefulSetDetails
|
||||
statefulSet={selectedStatefulSetData}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'antd';
|
||||
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { K8sEntityStatusResponse } from 'api/infraMonitoring/getK8sEntityStatus';
|
||||
import { K8sVolumesListPayload } from 'api/infraMonitoring/getK8sVolumesList';
|
||||
import classNames from 'classnames';
|
||||
import { useGetK8sVolumesList } from 'hooks/infraMonitoring/useGetK8sVolumesList';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import EntityStatusEmptyStateWrapper from '../EntityStatusEmptyStateWrapper';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,10 +50,12 @@ function K8sVolumesList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
quickFiltersLastUpdated,
|
||||
entityStatus,
|
||||
}: {
|
||||
isFiltersVisible: boolean;
|
||||
handleFilterVisibilityChange: () => void;
|
||||
quickFiltersLastUpdated: number;
|
||||
entityStatus: K8sEntityStatusResponse | null | undefined;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
@@ -484,55 +488,60 @@ function K8sVolumesList({
|
||||
/>
|
||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'volumes-list-table', {
|
||||
'expanded-volumes-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedVolumesData}
|
||||
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"
|
||||
/>
|
||||
<EntityStatusEmptyStateWrapper
|
||||
category={K8sCategory.VOLUMES}
|
||||
data={entityStatus}
|
||||
>
|
||||
<Table
|
||||
className={classNames('k8s-list-table', 'volumes-list-table', {
|
||||
'expanded-volumes-list-table': isGroupedByAttribute,
|
||||
})}
|
||||
dataSource={isFetching || isLoading ? [] : formattedVolumesData}
|
||||
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"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-hosts-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
<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"
|
||||
onChange={handleTableChange}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => handleRowClick(record),
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
expandable={{
|
||||
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||
expandIcon: expandRowIconRenderer,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</EntityStatusEmptyStateWrapper>
|
||||
|
||||
<VolumeDetails
|
||||
volume={selectedVolumeData}
|
||||
|
||||
45
frontend/src/hooks/infraMonitoring/useGetK8sEntityStatus.ts
Normal file
45
frontend/src/hooks/infraMonitoring/useGetK8sEntityStatus.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
import { AccountConfigPayload } from 'types/api/integrations/aws';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
|
||||
interface UseAccountSettingsModalProps {
|
||||
onClose: () => void;
|
||||
account: CloudAccount;
|
||||
@@ -84,8 +86,14 @@ export function useAccountSettingsModal({
|
||||
{ accountId: account?.id, payload },
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
setActiveAccount(response.data);
|
||||
const newActiveAccount = response?.data;
|
||||
setActiveAccount(newActiveAccount);
|
||||
onClose();
|
||||
|
||||
logEvent('AWS Integration: Account settings Updated', {
|
||||
cloudAccountId: newActiveAccount?.cloud_account_id,
|
||||
enabledRegions: newActiveAccount?.config?.regions,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from 'types/api/integrations/aws';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
import { useConnectionParams } from './useConnectionParams';
|
||||
import { useGenerateConnectionUrl } from './useGenerateConnectionUrl';
|
||||
|
||||
@@ -117,6 +118,9 @@ export function useIntegrationModal({
|
||||
(payload: GenerateConnectionUrlPayload): void => {
|
||||
generateUrl(payload, {
|
||||
onSuccess: (data: ConnectionUrlResponse) => {
|
||||
logEvent('AWS Integration: Account connection attempt redirected to AWS', {
|
||||
id: data.account_id,
|
||||
});
|
||||
window.open(data.connection_url, '_blank');
|
||||
setModalState(ModalStateEnum.WAITING);
|
||||
setAccountId(data.account_id);
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface AccountStatusResponse {
|
||||
status: 'success';
|
||||
data: {
|
||||
id: string;
|
||||
cloud_account_id: string;
|
||||
status: {
|
||||
integration: {
|
||||
last_heartbeat_ts_ms: number | null;
|
||||
|
||||
@@ -5692,15 +5692,32 @@ func (r *ClickHouseReader) GetAllMetricFilterTypes(ctx context.Context, req *met
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsDataPointsAndLastReceived(ctx context.Context, metricName string) (uint64, uint64, *model.ApiError) {
|
||||
query := fmt.Sprintf("SELECT COUNT(*) AS data_points, MAX(unix_milli) AS last_received_time FROM %s.%s WHERE metric_name = ?", signozMetricDBName, signozSampleTableName)
|
||||
var lastRecievedTimestamp int64 // Changed from uint64 to int64
|
||||
func (r *ClickHouseReader) GetMetricsDataPoints(ctx context.Context, metricName string) (uint64, *model.ApiError) {
|
||||
query := fmt.Sprintf(`SELECT
|
||||
sum(count) as data_points
|
||||
FROM %s.%s
|
||||
WHERE metric_name = ?
|
||||
`, signozMetricDBName, constants.SIGNOZ_SAMPLES_V4_AGG_30M_TABLENAME)
|
||||
var dataPoints uint64
|
||||
err := r.db.QueryRow(ctx, query, metricName).Scan(&dataPoints, &lastRecievedTimestamp)
|
||||
err := r.db.QueryRow(ctx, query, metricName).Scan(&dataPoints)
|
||||
if err != nil {
|
||||
return 0, 0, &model.ApiError{Typ: "ClickHouseError", Err: err}
|
||||
return 0, &model.ApiError{Typ: "ClickHouseError", Err: err}
|
||||
}
|
||||
return dataPoints, uint64(lastRecievedTimestamp), nil // Convert to uint64 before returning
|
||||
return dataPoints, nil // Convert to uint64 before returning
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetMetricsLastReceived(ctx context.Context, metricName string) (int64, *model.ApiError) {
|
||||
query := fmt.Sprintf(`SELECT
|
||||
MAX(unix_milli) AS last_received_time
|
||||
FROM %s.%s
|
||||
WHERE metric_name = ?
|
||||
`, signozMetricDBName, signozSampleTableName)
|
||||
var lastReceived int64
|
||||
err := r.db.QueryRow(ctx, query, metricName).Scan(&lastReceived)
|
||||
if err != nil {
|
||||
return 0, &model.ApiError{Typ: "ClickHouseError", Err: err}
|
||||
}
|
||||
return lastReceived, nil // Convert to uint64 before returning
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetTotalTimeSeriesForMetricName(ctx context.Context, metricName string) (uint64, *model.ApiError) {
|
||||
@@ -5854,33 +5871,48 @@ func (r *ClickHouseReader) ListSummaryMetrics(ctx context.Context, req *metrics_
|
||||
orderByClauseFirstQuery = ""
|
||||
}
|
||||
|
||||
sampleQuery := fmt.Sprintf(
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`SELECT
|
||||
s.samples,
|
||||
s.metric_name,
|
||||
s.unix_milli AS lastReceived
|
||||
FROM (
|
||||
SELECT
|
||||
metric_name,
|
||||
%s AS samples,
|
||||
max(unix_milli) as unix_milli
|
||||
FROM %s.%s
|
||||
WHERE fingerprint IN (
|
||||
SELECT fingerprint
|
||||
FROM %s.%s
|
||||
WHERE unix_milli BETWEEN ? AND ?
|
||||
%s
|
||||
AND metric_name IN (%s)
|
||||
GROUP BY fingerprint
|
||||
)
|
||||
AND metric_name in (%s)
|
||||
GROUP BY metric_name
|
||||
) AS s
|
||||
%s
|
||||
LIMIT %d OFFSET %d;`,
|
||||
countExp, signozMetricDBName, sampleTable, signozMetricDBName, localTsTable,
|
||||
whereClause, metricsList, metricsList, orderByClauseFirstQuery,
|
||||
req.Limit, req.Offset)
|
||||
s.samples,
|
||||
s.metric_name,
|
||||
s.unix_milli AS lastReceived
|
||||
FROM (
|
||||
SELECT
|
||||
metric_name,
|
||||
%s AS samples,
|
||||
max(unix_milli) as unix_milli
|
||||
FROM %s.%s
|
||||
`, countExp, signozMetricDBName, sampleTable))
|
||||
|
||||
// Conditionally add the fingerprint subquery if `whereClause` is present
|
||||
if whereClause != "" {
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`WHERE fingerprint IN (
|
||||
SELECT fingerprint
|
||||
FROM %s.%s
|
||||
WHERE unix_milli BETWEEN ? AND ?
|
||||
%s
|
||||
AND metric_name IN (%s)
|
||||
GROUP BY fingerprint
|
||||
)
|
||||
AND metric_name IN (%s) `,
|
||||
signozMetricDBName, localTsTable, whereClause, metricsList, metricsList))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`WHERE metric_name IN (%s) `, metricsList))
|
||||
}
|
||||
|
||||
sb.WriteString(`GROUP BY metric_name ) AS s `)
|
||||
|
||||
if orderByClauseFirstQuery != "" {
|
||||
sb.WriteString(orderByClauseFirstQuery)
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" LIMIT %d OFFSET %d;", req.Limit, req.Offset))
|
||||
|
||||
sampleQuery := sb.String()
|
||||
|
||||
args = append(args, start, end)
|
||||
rows, err = r.db.Query(valueCtx, sampleQuery, args...)
|
||||
@@ -6063,8 +6095,10 @@ func (r *ClickHouseReader) GetMetricsSamplesPercentage(ctx context.Context, req
|
||||
// Format metric names for query
|
||||
metricsList := "'" + strings.Join(metricNames, "', '") + "'"
|
||||
|
||||
// Construct the sample percentage query
|
||||
sampleQuery := fmt.Sprintf(
|
||||
// Build query using string builder for better performance
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`WITH TotalSamples AS (
|
||||
SELECT %s AS total_samples
|
||||
FROM %s.%s
|
||||
@@ -6079,9 +6113,15 @@ func (r *ClickHouseReader) GetMetricsSamplesPercentage(ctx context.Context, req
|
||||
SELECT
|
||||
metric_name,
|
||||
%s AS samples
|
||||
FROM %s.%s
|
||||
WHERE fingerprint IN
|
||||
(
|
||||
FROM %s.%s`,
|
||||
countExp, signozMetricDBName, sampleTable, // Total samples
|
||||
countExp, signozMetricDBName, sampleTable, // Inner select samples
|
||||
))
|
||||
|
||||
// Conditionally add the fingerprint subquery if whereClause is present
|
||||
if whereClause != "" {
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
` WHERE fingerprint IN (
|
||||
SELECT fingerprint
|
||||
FROM %s.%s
|
||||
WHERE unix_milli BETWEEN ? AND ?
|
||||
@@ -6089,18 +6129,27 @@ func (r *ClickHouseReader) GetMetricsSamplesPercentage(ctx context.Context, req
|
||||
AND metric_name IN (%s)
|
||||
GROUP BY fingerprint
|
||||
)
|
||||
AND metric_name IN (%s)
|
||||
AND metric_name IN (%s)`,
|
||||
signozMetricDBName, localTsTable, whereClause, metricsList,
|
||||
metricsList,
|
||||
))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
` WHERE metric_name IN (%s)`,
|
||||
metricsList,
|
||||
))
|
||||
}
|
||||
|
||||
sb.WriteString(`
|
||||
GROUP BY metric_name
|
||||
) AS s
|
||||
JOIN TotalSamples t ON 1 = 1
|
||||
ORDER BY percentage DESC
|
||||
LIMIT %d;`,
|
||||
countExp, signozMetricDBName, sampleTable, // Total samples
|
||||
countExp, signozMetricDBName, sampleTable, // Inner select samples
|
||||
signozMetricDBName, localTsTable, whereClause, metricsList, // Subquery conditions
|
||||
metricsList, req.Limit, // Final conditions
|
||||
)
|
||||
LIMIT ?;`)
|
||||
|
||||
sampleQuery := sb.String()
|
||||
|
||||
// Add start and end time to args
|
||||
args = append(args, start, end)
|
||||
|
||||
// Execute the sample percentage query
|
||||
|
||||
@@ -219,7 +219,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
||||
jobsRepo := inframetrics.NewJobsRepo(opts.Reader, querierv2)
|
||||
pvcsRepo := inframetrics.NewPvcsRepo(opts.Reader, querierv2)
|
||||
//explorerCache := metricsexplorer.NewExplorerCache(metricsexplorer.WithCache(opts.Cache))
|
||||
summaryService := metricsexplorer.NewSummaryService(opts.Reader, querierv2)
|
||||
summaryService := metricsexplorer.NewSummaryService(opts.Reader, opts.RuleManager)
|
||||
|
||||
aH := &APIHandler{
|
||||
reader: opts.Reader,
|
||||
|
||||
@@ -3,6 +3,7 @@ package metricsexplorer
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -12,16 +13,17 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/model/metrics_explorer"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/rules"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type SummaryService struct {
|
||||
reader interfaces.Reader
|
||||
querierV2 interfaces.Querier
|
||||
reader interfaces.Reader
|
||||
rulesManager *rules.Manager
|
||||
}
|
||||
|
||||
func NewSummaryService(reader interfaces.Reader, querierV2 interfaces.Querier) *SummaryService {
|
||||
return &SummaryService{reader: reader, querierV2: querierV2}
|
||||
func NewSummaryService(reader interfaces.Reader, alertManager *rules.Manager) *SummaryService {
|
||||
return &SummaryService{reader: reader, rulesManager: alertManager}
|
||||
}
|
||||
|
||||
func (receiver *SummaryService) FilterKeys(ctx context.Context, params *metrics_explorer.FilterKeyRequest) (*metrics_explorer.FilterKeyResponse, *model.ApiError) {
|
||||
@@ -102,18 +104,24 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
|
||||
return nil
|
||||
})
|
||||
|
||||
// Call 2: GetMetricsDataPointsAndLastReceived
|
||||
g.Go(func() error {
|
||||
dataPoints, lastReceived, err := receiver.reader.GetMetricsDataPointsAndLastReceived(ctx, metricName)
|
||||
dataPoints, err := receiver.reader.GetMetricsDataPoints(ctx, metricName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metricDetailsDTO.Samples = dataPoints
|
||||
return nil
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
lastReceived, err := receiver.reader.GetMetricsLastReceived(ctx, metricName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metricDetailsDTO.LastReceived = lastReceived
|
||||
return nil
|
||||
})
|
||||
|
||||
// Call 3: GetTotalTimeSeriesForMetricName
|
||||
g.Go(func() error {
|
||||
totalSeries, err := receiver.reader.GetTotalTimeSeriesForMetricName(ctx, metricName)
|
||||
if err != nil {
|
||||
@@ -123,7 +131,6 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
|
||||
return nil
|
||||
})
|
||||
|
||||
// Call 4: GetActiveTimeSeriesForMetricName
|
||||
g.Go(func() error {
|
||||
activeSeries, err := receiver.reader.GetActiveTimeSeriesForMetricName(ctx, metricName, 120*time.Minute)
|
||||
if err != nil {
|
||||
@@ -133,7 +140,6 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
|
||||
return nil
|
||||
})
|
||||
|
||||
// Call 5: GetAttributesForMetricName
|
||||
g.Go(func() error {
|
||||
attributes, err := receiver.reader.GetAttributesForMetricName(ctx, metricName)
|
||||
if err != nil {
|
||||
@@ -145,7 +151,6 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
|
||||
return nil
|
||||
})
|
||||
|
||||
// Call 6: GetDashboardsWithMetricName
|
||||
g.Go(func() error {
|
||||
data, err := dashboards.GetDashboardsWithMetricName(ctx, metricName)
|
||||
if err != nil {
|
||||
@@ -169,13 +174,30 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
|
||||
return nil
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
var metrics []string
|
||||
var metricsAlerts []metrics_explorer.Alert
|
||||
metrics = append(metrics, metricName)
|
||||
data, err := receiver.rulesManager.GetAlertDetailsForMetricNames(ctx, metrics)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rulesLists, ok := data[metricName]; ok {
|
||||
for _, rule := range rulesLists {
|
||||
metricsAlerts = append(metricsAlerts, metrics_explorer.Alert{AlertName: rule.AlertName, AlertID: rule.Id})
|
||||
}
|
||||
}
|
||||
metricDetailsDTO.Alerts = metricsAlerts
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for all goroutines and handle any errors
|
||||
if err := g.Wait(); err != nil {
|
||||
// Type assert to check if it's already an ApiError
|
||||
if apiErr, ok := err.(*model.ApiError); ok {
|
||||
|
||||
var apiErr *model.ApiError
|
||||
if errors.As(err, &apiErr) {
|
||||
return metrics_explorer.MetricDetailsDTO{}, apiErr
|
||||
}
|
||||
// If it's not an ApiError, wrap it in one
|
||||
return metrics_explorer.MetricDetailsDTO{}, &model.ApiError{Typ: "InternalError", Err: err}
|
||||
}
|
||||
|
||||
@@ -194,14 +216,18 @@ func (receiver *SummaryService) GetMetricsTreemap(ctx context.Context, params *m
|
||||
if apiError != nil {
|
||||
return nil, apiError
|
||||
}
|
||||
response.TimeSeries = *cardinality
|
||||
if cardinality != nil {
|
||||
response.TimeSeries = *cardinality
|
||||
}
|
||||
return &response, nil
|
||||
case metrics_explorer.SamplesTreeMap:
|
||||
dataPoints, apiError := receiver.reader.GetMetricsSamplesPercentage(ctx, params)
|
||||
if apiError != nil {
|
||||
return nil, apiError
|
||||
}
|
||||
response.Samples = *dataPoints
|
||||
if dataPoints != nil {
|
||||
response.Samples = *dataPoints
|
||||
}
|
||||
return &response, nil
|
||||
default:
|
||||
return nil, nil
|
||||
|
||||
@@ -122,7 +122,8 @@ type Reader interface {
|
||||
GetAllMetricFilterTypes(ctx context.Context, req *metrics_explorer.FilterValueRequest) ([]string, *model.ApiError)
|
||||
GetAllMetricFilterAttributeKeys(ctx context.Context, req *metrics_explorer.FilterKeyRequest, skipDotNames bool) (*[]v3.AttributeKey, *model.ApiError)
|
||||
|
||||
GetMetricsDataPointsAndLastReceived(ctx context.Context, metricName string) (uint64, uint64, *model.ApiError)
|
||||
GetMetricsDataPoints(ctx context.Context, metricName string) (uint64, *model.ApiError)
|
||||
GetMetricsLastReceived(ctx context.Context, metricName string) (int64, *model.ApiError)
|
||||
GetTotalTimeSeriesForMetricName(ctx context.Context, metricName string) (uint64, *model.ApiError)
|
||||
GetActiveTimeSeriesForMetricName(ctx context.Context, metricName string, duration time.Duration) (uint64, *model.ApiError)
|
||||
GetAttributesForMetricName(ctx context.Context, metricName string) (*[]metrics_explorer.Attribute, *model.ApiError)
|
||||
|
||||
@@ -89,7 +89,7 @@ type MetricDetailsDTO struct {
|
||||
Samples uint64 `json:"samples"`
|
||||
TimeSeriesTotal uint64 `json:"timeSeriesTotal"`
|
||||
TimeSeriesActive uint64 `json:"timeSeriesActive"`
|
||||
LastReceived uint64 `json:"lastReceived"`
|
||||
LastReceived int64 `json:"lastReceived"`
|
||||
Attributes []Attribute `json:"attributes"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Alerts []Alert `json:"alerts"`
|
||||
|
||||
@@ -829,3 +829,77 @@ func (m *Manager) TestNotification(ctx context.Context, ruleStr string) (int, *m
|
||||
|
||||
return alertCount, apiErr
|
||||
}
|
||||
|
||||
func (m *Manager) GetAlertDetailsForMetricNames(ctx context.Context, metricNames []string) (map[string][]GettableRule, error) {
|
||||
result := make(map[string][]GettableRule)
|
||||
|
||||
rules, err := m.ruleDB.GetStoredRules(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("Error getting stored rules", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metricRulesMap := make(map[string][]GettableRule)
|
||||
|
||||
for _, storedRule := range rules {
|
||||
var rule GettableRule
|
||||
if err := json.Unmarshal([]byte(storedRule.Data), &rule); err != nil {
|
||||
zap.L().Error("Invalid rule data", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.AlertType != AlertTypeMetric || rule.RuleCondition == nil || rule.RuleCondition.CompositeQuery == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rule.Id = fmt.Sprintf("%d", storedRule.Id)
|
||||
rule.CreatedAt = storedRule.CreatedAt
|
||||
rule.CreatedBy = storedRule.CreatedBy
|
||||
rule.UpdatedAt = storedRule.UpdatedAt
|
||||
rule.UpdatedBy = storedRule.UpdatedBy
|
||||
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries {
|
||||
if query.AggregateAttribute.Key != "" {
|
||||
metricRulesMap[query.AggregateAttribute.Key] = append(metricRulesMap[query.AggregateAttribute.Key], rule)
|
||||
}
|
||||
}
|
||||
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.PromQueries {
|
||||
if query.Query != "" {
|
||||
for _, metricName := range metricNames {
|
||||
if strings.Contains(query.Query, metricName) {
|
||||
metricRulesMap[metricName] = append(metricRulesMap[metricName], rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.ClickHouseQueries {
|
||||
if query.Query != "" {
|
||||
for _, metricName := range metricNames {
|
||||
if strings.Contains(query.Query, metricName) {
|
||||
metricRulesMap[metricName] = append(metricRulesMap[metricName], rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, metricName := range metricNames {
|
||||
if rules, exists := metricRulesMap[metricName]; exists {
|
||||
seen := make(map[string]bool)
|
||||
uniqueRules := make([]GettableRule, 0)
|
||||
|
||||
for _, rule := range rules {
|
||||
if !seen[rule.Id] {
|
||||
seen[rule.Id] = true
|
||||
uniqueRules = append(uniqueRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
result[metricName] = uniqueRules
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user