From 364be556a1bc2a59ae112fba4331f6aad6ff5d5e Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Thu, 9 Jan 2025 12:36:16 +0530 Subject: [PATCH 01/20] feat: implement deployments in k8s infra monitoring --- .../infraMonitoring/getK8sDeploymentsList.ts | 70 +++ .../DeploymentDetails.interfaces.ts | 7 + .../DeploymentDetails.styles.scss | 247 ++++++++ .../DeploymentDetails/DeploymentDetails.tsx | 577 ++++++++++++++++++ .../Events/DeploymentEvents.styles.scss | 289 +++++++++ .../Events/DeploymentEvents.tsx | 363 +++++++++++ .../Events/NoEventsContainer.tsx | 16 + .../DeploymentDetails/Events/constants.ts | 65 ++ .../DeploymentDetails/Events/index.ts | 3 + .../Logs/DeploymentLogs.styles.scss | 133 ++++ .../DeploymentDetails/Logs/DeploymentLogs.tsx | 216 +++++++ .../Logs/DeploymentLogsDetailedView.tsx | 99 +++ .../Logs/NoLogsContainer.tsx | 16 + .../DeploymentDetails/Logs/constants.ts | 65 ++ .../DeploymentDetails/Logs/index.ts | 3 + .../Metrics/DeploymentMetrics.styles.scss | 45 ++ .../Metrics/DeploymentMetrics.tsx | 145 +++++ .../DeploymentDetails/Metrics/constants.ts | 544 +++++++++++++++++ .../DeploymentDetails/Metrics/index.ts | 3 + .../Traces/DeploymentTraces.styles.scss | 193 ++++++ .../Traces/DeploymentTraces.tsx | 199 ++++++ .../DeploymentDetails/Traces/constants.ts | 200 ++++++ .../DeploymentDetails/Traces/index.ts | 3 + .../DeploymentDetails/constants.ts | 6 + .../Deployments/DeploymentDetails/index.ts | 3 + .../K8sDeploymentsList.styles.scss | 17 + .../Deployments/K8sDeploymentsList.tsx | 518 ++++++++++++++++ .../InfraMonitoringK8s/Deployments/utils.tsx | 311 ++++++++++ .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 51 +- .../container/InfraMonitoringK8s/constants.ts | 35 +- .../useGetK8sDeploymentsList.ts | 48 ++ 31 files changed, 4468 insertions(+), 22 deletions(-) create mode 100644 frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts b/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts new file mode 100644 index 0000000000..9ce4643687 --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts @@ -0,0 +1,70 @@ +import { ApiBaseInstance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sDeploymentsListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sDeploymentsData { + deploymentName: string; + cpuUsage: number; + memoryUsage: number; + desiredPods: number; + availablePods: number; + cpuRequest: number; + memoryRequest: number; + cpuLimit: number; + memoryLimit: number; + restarts: number; + meta: { + k8s_cluster_name: string; + k8s_deployment_name: string; + k8s_namespace_name: string; + }; +} + +export interface K8sDeploymentsListResponse { + status: string; + data: { + type: string; + records: K8sDeploymentsData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sDeploymentsList = async ( + props: K8sDeploymentsListPayload, + signal?: AbortSignal, + headers?: Record<string, string>, +): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post('/deployments/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.interfaces.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.interfaces.ts new file mode 100644 index 0000000000..eb547aa343 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.interfaces.ts @@ -0,0 +1,7 @@ +import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList'; + +export type DeploymentDetailsProps = { + deployment: K8sDeploymentsData | null; + isModalTimeSelection: boolean; + onClose: () => void; +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss new file mode 100644 index 0000000000..22d2934367 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss @@ -0,0 +1,247 @@ +.deployment-detail-drawer { + border-left: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); + + .ant-drawer-header { + padding: 8px 16px; + border-bottom: none; + + align-items: stretch; + + border-bottom: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + } + + .ant-drawer-close { + margin-inline-end: 0px; + } + + .ant-drawer-body { + display: flex; + flex-direction: column; + padding: 16px; + } + + .title { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .radio-button { + display: flex; + align-items: center; + justify-content: center; + padding-top: var(--padding-1); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .deployment-detail-drawer__deployment { + .deployment-details-grid { + .labels-row, + .values-row { + display: grid; + grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; + gap: 30px; + align-items: center; + } + + .labels-row { + margin-bottom: 8px; + } + + .deployment-details-metadata-label { + color: var(--text-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + } + + .deployment-details-metadata-value { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .status-tag { + margin: 0; + + &.active { + color: var(--success-500); + background: var(--success-100); + border-color: var(--success-500); + } + + &.inactive { + color: var(--error-500); + background: var(--error-100); + border-color: var(--error-500); + } + } + + .progress-container { + width: 158px; + .ant-progress { + margin: 0; + + .ant-progress-text { + font-weight: 600; + } + } + } + + .ant-card { + &.ant-card-bordered { + border: 1px solid var(--bg-slate-500) !important; + } + } + } + } + + .tabs-and-search { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; + + .action-btn { + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + } + } + + .views-tabs-container { + margin-top: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + + .views-tabs { + color: var(--text-vanilla-400); + + .view-title { + display: flex; + gap: var(--margin-2); + align-items: center; + justify-content: center; + font-size: var(--font-size-xs); + font-style: normal; + font-weight: var(--font-weight-normal); + } + + .tab { + border: 1px solid var(--bg-slate-400); + width: 114px; + } + + .tab::before { + background: var(--bg-slate-400); + } + + .selected_view { + background: var(--bg-slate-300); + color: var(--text-vanilla-100); + border: 1px solid var(--bg-slate-400); + } + + .selected_view::before { + background: var(--bg-slate-400); + } + } + + .compass-button { + width: 30px; + height: 30px; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + } + .ant-drawer-close { + padding: 0px; + } +} + +.lightMode { + .ant-drawer-header { + border-bottom: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + } + + .deployment-detail-drawer { + .title { + color: var(--text-ink-300); + } + + .deployment-detail-drawer__deployment { + .ant-typography { + color: var(--text-ink-300); + background: transparent; + } + } + + .radio-button { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .views-tabs { + .tab { + background: var(--bg-vanilla-100); + } + + .selected_view { + background: var(--bg-vanilla-300); + border: 1px solid var(--bg-slate-300); + color: var(--text-ink-400); + } + + .selected_view::before { + background: var(--bg-vanilla-300); + border-left: 1px solid var(--bg-slate-300); + } + } + + .compass-button { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .tabs-and-search { + .action-btn { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx new file mode 100644 index 0000000000..a9f7481c4c --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -0,0 +1,577 @@ +/* eslint-disable sonarjs/no-identical-functions */ +import './DeploymentDetails.styles.scss'; + +import { Color, Spacing } from '@signozhq/design-tokens'; +import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; +import { RadioChangeEvent } from 'antd/lib'; +import logEvent from 'api/common/logEvent'; +import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; +import { QueryParams } from 'constants/query'; +import { + initialQueryBuilderFormValuesMap, + initialQueryState, +} from 'constants/queryBuilder'; +import ROUTES from 'constants/routes'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; +import GetMinMax from 'lib/getMinMax'; +import { + BarChart2, + ChevronsLeftRight, + Compass, + DraftingCompass, + ScrollText, + X, +} from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { + LogsAggregatorOperator, + TracesAggregatorOperator, +} from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { v4 as uuidv4 } from 'uuid'; + +import { QUERY_KEYS } from './constants'; +import { DeploymentDetailsProps } from './DeploymentDetails.interfaces'; +import DeploymentEvents from './Events'; +import DeploymentLogs from './Logs'; +import DeploymentMetrics from './Metrics'; +import DeploymentTraces from './Traces'; + +function DeploymentDetails({ + deployment, + onClose, + isModalTimeSelection, +}: DeploymentDetailsProps): JSX.Element { + const { maxTime, minTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [ + minTime, + ]); + const endMs = useMemo(() => Math.floor(Number(maxTime) / 1000000000), [ + maxTime, + ]); + + const urlQuery = useUrlQuery(); + + const [modalTimeRange, setModalTimeRange] = useState(() => ({ + startTime: startMs, + endTime: endMs, + })); + + const [selectedInterval, setSelectedInterval] = useState<Time>( + selectedTime as Time, + ); + + const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS); + const isDarkMode = useIsDarkMode(); + + const initialFilters = useMemo( + () => ({ + op: 'AND', + items: [ + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_NODE_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s_deployment_name--string--resource--false', + }, + op: '=', + value: deployment?.meta.k8s_deployment_name || '', + }, + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_CLUSTER_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s_deployment_name--string--resource--false', + }, + op: '=', + value: deployment?.meta.k8s_cluster_name || '', + }, + ], + }), + [deployment?.meta.k8s_deployment_name, deployment?.meta.k8s_cluster_name], + ); + + const initialEventsFilters = useMemo( + () => ({ + op: 'AND', + items: [ + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_OBJECT_KIND, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s.object.kind--string--resource--false', + }, + op: '=', + value: 'Deployment', + }, + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_OBJECT_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s.object.name--string--resource--false', + }, + op: '=', + value: deployment?.meta.k8s_deployment_name || '', + }, + ], + }), + [deployment?.meta.k8s_deployment_name], + ); + + const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( + initialFilters, + ); + + const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>( + initialFilters, + ); + + const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( + initialEventsFilters, + ); + + useEffect(() => { + logEvent('Infra Monitoring: Deployments list details page visited', { + deployment: deployment?.deploymentName, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setLogFilters(initialFilters); + setTracesFilters(initialFilters); + setEventsFilters(initialEventsFilters); + }, [initialFilters, initialEventsFilters]); + + useEffect(() => { + setSelectedInterval(selectedTime as Time); + + if (selectedTime !== 'custom') { + const { maxTime, minTime } = GetMinMax(selectedTime); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + }, [selectedTime, minTime, maxTime]); + + const handleTabChange = (e: RadioChangeEvent): void => { + setSelectedView(e.target.value); + }; + + const handleTimeChange = useCallback( + (interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => { + setSelectedInterval(interval as Time); + + if (interval === 'custom' && dateTimeRange) { + setModalTimeRange({ + startTime: Math.floor(dateTimeRange[0] / 1000), + endTime: Math.floor(dateTimeRange[1] / 1000), + }); + } else { + const { maxTime, minTime } = GetMinMax(interval); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + + logEvent('Infra Monitoring: Deployments list details time updated', { + deployment: deployment?.deploymentName, + interval, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeLogFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setLogFilters((prevFilters) => { + const primaryFilters = prevFilters.items.filter((item) => + [QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + item.key?.key ?? '', + ), + ); + const paginationFilter = value.items.find((item) => item.key?.key === 'id'); + const newFilters = value.items.filter( + (item) => + item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_NODE_NAME, + ); + + logEvent( + 'Infra Monitoring: Deployments list details logs filters applied', + { + deployment: deployment?.deploymentName, + }, + ); + + return { + op: 'AND', + items: [ + ...primaryFilters, + ...newFilters, + ...(paginationFilter ? [paginationFilter] : []), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeTracesFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setTracesFilters((prevFilters) => { + const primaryFilters = prevFilters.items.filter((item) => + [QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + item.key?.key ?? '', + ), + ); + + logEvent( + 'Infra Monitoring: Deployments list details traces filters applied', + { + deployment: deployment?.deploymentName, + }, + ); + + return { + op: 'AND', + items: [ + ...primaryFilters, + ...value.items.filter( + (item) => item.key?.key !== QUERY_KEYS.K8S_NODE_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeEventsFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setEventsFilters((prevFilters) => { + const deploymentKindFilter = prevFilters.items.find( + (item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND, + ); + const deploymentNameFilter = prevFilters.items.find( + (item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME, + ); + + logEvent( + 'Infra Monitoring: Deployments list details events filters applied', + { + deployment: deployment?.deploymentName, + }, + ); + + return { + op: 'AND', + items: [ + deploymentKindFilter, + deploymentNameFilter, + ...value.items.filter( + (item) => + item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && + item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleExplorePagesRedirect = (): void => { + if (selectedInterval !== 'custom') { + urlQuery.set(QueryParams.relativeTime, selectedInterval); + } else { + urlQuery.delete(QueryParams.relativeTime); + urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString()); + urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString()); + } + + logEvent('Infra Monitoring: Deployments list details explore clicked', { + deployment: deployment?.deploymentName, + view: selectedView, + }); + + if (selectedView === VIEW_TYPES.LOGS) { + const filtersWithoutPagination = { + ...logFilters, + items: logFilters.items.filter((item) => item.key?.key !== 'id'), + }; + + const compositeQuery = { + ...initialQueryState, + queryType: 'builder', + builder: { + ...initialQueryState.builder, + queryData: [ + { + ...initialQueryBuilderFormValuesMap.logs, + aggregateOperator: LogsAggregatorOperator.NOOP, + filters: filtersWithoutPagination, + }, + ], + }, + }; + + urlQuery.set('compositeQuery', JSON.stringify(compositeQuery)); + + window.open( + `${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`, + '_blank', + ); + } else if (selectedView === VIEW_TYPES.TRACES) { + const compositeQuery = { + ...initialQueryState, + queryType: 'builder', + builder: { + ...initialQueryState.builder, + queryData: [ + { + ...initialQueryBuilderFormValuesMap.traces, + aggregateOperator: TracesAggregatorOperator.NOOP, + filters: tracesFilters, + }, + ], + }, + }; + + urlQuery.set('compositeQuery', JSON.stringify(compositeQuery)); + + window.open( + `${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`, + '_blank', + ); + } + }; + + const handleClose = (): void => { + setSelectedInterval(selectedTime as Time); + + if (selectedTime !== 'custom') { + const { maxTime, minTime } = GetMinMax(selectedTime); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + setSelectedView(VIEW_TYPES.METRICS); + onClose(); + }; + + return ( + <Drawer + width="70%" + title={ + <> + <Divider type="vertical" /> + <Typography.Text className="title"> + {deployment?.meta.k8s_deployment_name} + </Typography.Text> + </> + } + placement="right" + onClose={handleClose} + open={!!deployment} + style={{ + overscrollBehavior: 'contain', + background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, + }} + className="deployment-detail-drawer" + destroyOnClose + closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} + > + {deployment && ( + <> + <div className="deployment-detail-drawer__deployment"> + <div className="deployment-details-grid"> + <div className="labels-row"> + <Typography.Text + type="secondary" + className="deployment-details-metadata-label" + > + Deployment Name + </Typography.Text> + <Typography.Text + type="secondary" + className="deployment-details-metadata-label" + > + Cluster Name + </Typography.Text> + <Typography.Text + type="secondary" + className="deployment-details-metadata-label" + > + Namespace Name + </Typography.Text> + </div> + <div className="values-row"> + <Typography.Text className="deployment-details-metadata-value"> + <Tooltip title={deployment.meta.k8s_deployment_name}> + {deployment.meta.k8s_deployment_name} + </Tooltip> + </Typography.Text> + <Typography.Text className="deployment-details-metadata-value"> + <Tooltip title={deployment.meta.k8s_cluster_name}> + {deployment.meta.k8s_cluster_name} + </Tooltip> + </Typography.Text> + <Typography.Text className="deployment-details-metadata-value"> + <Tooltip title={deployment.meta.k8s_namespace_name}> + {deployment.meta.k8s_namespace_name} + </Tooltip> + </Typography.Text> + </div> + </div> + </div> + + <div className="views-tabs-container"> + <Radio.Group + className="views-tabs" + onChange={handleTabChange} + value={selectedView} + > + <Radio.Button + className={ + // eslint-disable-next-line sonarjs/no-duplicate-string + selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.METRICS} + > + <div className="view-title"> + <BarChart2 size={14} /> + Metrics + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.LOGS} + > + <div className="view-title"> + <ScrollText size={14} /> + Logs + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.TRACES} + > + <div className="view-title"> + <DraftingCompass size={14} /> + Traces + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.EVENTS} + > + <div className="view-title"> + <ChevronsLeftRight size={14} /> + Events + </div> + </Radio.Button> + </Radio.Group> + + {(selectedView === VIEW_TYPES.LOGS || + selectedView === VIEW_TYPES.TRACES) && ( + <Button + icon={<Compass size={18} />} + className="compass-button" + onClick={handleExplorePagesRedirect} + /> + )} + </div> + {selectedView === VIEW_TYPES.METRICS && ( + <DeploymentMetrics + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + selectedInterval={selectedInterval} + deployment={deployment} + /> + )} + {selectedView === VIEW_TYPES.LOGS && ( + <DeploymentLogs + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + handleChangeLogFilters={handleChangeLogFilters} + logFilters={logFilters} + selectedInterval={selectedInterval} + /> + )} + {selectedView === VIEW_TYPES.TRACES && ( + <DeploymentTraces + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + handleChangeTracesFilters={handleChangeTracesFilters} + tracesFilters={tracesFilters} + selectedInterval={selectedInterval} + /> + )} + {selectedView === VIEW_TYPES.EVENTS && ( + <DeploymentEvents + timeRange={modalTimeRange} + handleChangeEventFilters={handleChangeEventsFilters} + filters={eventsFilters} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + selectedInterval={selectedInterval} + /> + )} + </> + )} + </Drawer> + ); +} + +export default DeploymentDetails; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss new file mode 100644 index 0000000000..d7b54bc629 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss @@ -0,0 +1,289 @@ +.deployment-events-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .deployment-events-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .deployment-events { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } + + .ant-table { + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgb(18, 19, 23); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.deploymentname-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgb(18, 19, 23); + border-bottom: none; + } + + .ant-table-cell:has(.deploymentname-column-value) { + background: var(--bg-ink-400); + } + + .deploymentname-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-pagination { + position: fixed; + bottom: 0; + width: calc(100% - 64px); + background: rgb(18, 19, 23); + padding: 16px; + margin: 0; + + // this is to offset intercom icon till we improve the design + padding-right: 72px; + + .ant-pagination-item { + border-radius: 4px; + + &-active { + background: var(--bg-robin-500); + border-color: var(--bg-robin-500); + + a { + color: var(--bg-ink-500) !important; + } + } + } + } +} + +.deployment-events-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.deployment-events-list-card { + width: 100%; + margin-top: 12px; + + .ant-table-wrapper { + height: 100%; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} + +.periscope-btn-icon { + cursor: pointer; +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx new file mode 100644 index 0000000000..58fff15e92 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx @@ -0,0 +1,363 @@ +/* eslint-disable no-nested-ternary */ +import './DeploymentEvents.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Button, Table, TableColumnsType } from 'antd'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; +import LogsError from 'container/LogsError/LogsError'; +import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { isArray } from 'lodash-es'; +import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 } from 'uuid'; + +import { getDeploymentsEventsQueryPayload } from './constants'; +import NoEventsContainer from './NoEventsContainer'; + +interface EventDataType { + key: string; + timestamp: string; + body: string; + id: string; + attributes_bool?: Record<string, boolean>; + attributes_number?: Record<string, number>; + attributes_string?: Record<string, string>; + resources_string?: Record<string, string>; + scope_name?: string; + scope_string?: Record<string, string>; + scope_version?: string; + severity_number?: number; + severity_text?: string; + span_id?: string; + trace_flags?: number; + trace_id?: string; + severity?: string; +} + +interface IDeploymentEventsProps { + timeRange: { + startTime: number; + endTime: number; + }; + handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void; + filters: IBuilderQuery['filters']; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + selectedInterval: Time; +} + +const EventsPageSize = 10; + +export default function Events({ + timeRange, + handleChangeEventFilters, + filters, + isModalTimeSelection, + handleTimeChange, + selectedInterval, +}: IDeploymentEventsProps): JSX.Element { + const { currentQuery } = useQueryBuilder(); + + const [formattedDeploymentEvents, setFormattedDeploymentEvents] = useState< + EventDataType[] + >([]); + + const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); + + const [page, setPage] = useState(1); + + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.LOGS, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + const queryPayload = useMemo(() => { + const basePayload = getDeploymentsEventsQueryPayload( + timeRange.startTime, + timeRange.endTime, + filters, + ); + + basePayload.query.builder.queryData[0].pageSize = 10; + basePayload.query.builder.queryData[0].orderBy = [ + { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, + ]; + + return basePayload; + }, [timeRange.startTime, timeRange.endTime, filters]); + + const { data: eventsData, isLoading, isFetching, isError } = useQuery({ + queryKey: [ + 'deploymentEvents', + timeRange.startTime, + timeRange.endTime, + filters, + ], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + }); + + const columns: TableColumnsType<EventDataType> = [ + { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, + { + title: 'Timestamp', + dataIndex: 'timestamp', + width: 200, + ellipsis: true, + key: 'timestamp', + }, + { title: 'Body', dataIndex: 'body', key: 'body' }, + ]; + + useEffect(() => { + if (eventsData?.payload?.data?.newResult?.data?.result) { + const responsePayload = + eventsData?.payload.data.newResult.data.result[0].list || []; + + const formattedData = responsePayload?.map( + (event): EventDataType => ({ + timestamp: event.timestamp, + severity: event.data.severity_text, + body: event.data.body, + id: event.data.id, + key: event.data.id, + resources_string: event.data.resources_string, + }), + ); + + setFormattedDeploymentEvents(formattedData); + + if ( + !responsePayload || + (responsePayload && + isArray(responsePayload) && + responsePayload.length < EventsPageSize) + ) { + setHasReachedEndOfEvents(true); + } else { + setHasReachedEndOfEvents(false); + } + } + }, [eventsData]); + + const handleExpandRow = (record: EventDataType): JSX.Element => ( + <EventContents data={record.resources_string} /> + ); + + const handlePrev = (): void => { + if (!formattedDeploymentEvents.length) return; + + setPage(page - 1); + + const firstEvent = formattedDeploymentEvents[0]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '>', + value: firstEvent.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeEventFilters(newFilters); + }; + + const handleNext = (): void => { + if (!formattedDeploymentEvents.length) return; + + setPage(page + 1); + const lastEvent = + formattedDeploymentEvents[formattedDeploymentEvents.length - 1]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '<', + value: lastEvent.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeEventFilters(newFilters); + }; + + const handleExpandRowIcon = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: EventDataType, + e: React.MouseEvent<HTMLElement, MouseEvent>, + ) => void; + record: EventDataType; + }): JSX.Element => + expanded ? ( + <ChevronDown + className="periscope-btn-icon" + size={14} + onClick={(e): void => + onExpand( + record, + (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, + ) + } + /> + ) : ( + <ChevronRight + className="periscope-btn-icon" + size={14} + // eslint-disable-next-line sonarjs/no-identical-functions + onClick={(e): void => + onExpand( + record, + (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, + ) + } + /> + ); + + return ( + <div className="deployment-events-container"> + <div className="deployment-events-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeEventFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + + {isLoading && <LoadingContainer />} + + {!isLoading && !isError && formattedDeploymentEvents.length === 0 && ( + <NoEventsContainer /> + )} + + {isError && !isLoading && <LogsError />} + + {!isLoading && !isError && formattedDeploymentEvents.length > 0 && ( + <div className="deployment-events-list-container"> + <div className="deployment-events-list-card"> + <Table<EventDataType> + loading={isLoading && page > 1} + columns={columns} + expandable={{ + expandedRowRender: handleExpandRow, + rowExpandable: (record): boolean => record.body !== 'Not Expandable', + expandIcon: handleExpandRowIcon, + }} + dataSource={formattedDeploymentEvents} + pagination={false} + rowKey={(record): string => record.id} + /> + </div> + </div> + )} + + {!isError && formattedDeploymentEvents.length > 0 && ( + <div className="deployment-events-footer"> + <Button + className="deployment-events-footer-button periscope-btn ghost" + type="link" + onClick={handlePrev} + disabled={page === 1 || isFetching || isLoading} + > + {!isFetching && <ChevronLeft size={14} />} + Prev + </Button> + + <Button + className="deployment-events-footer-button periscope-btn ghost" + type="link" + onClick={handleNext} + disabled={hasReachedEndOfEvents || isFetching || isLoading} + > + Next + {!isFetching && <ChevronRight size={14} />} + </Button> + + {(isFetching || isLoading) && ( + <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> + )} + </div> + )} + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx new file mode 100644 index 0000000000..779968c842 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx @@ -0,0 +1,16 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; +import { Ghost } from 'lucide-react'; + +const { Text } = Typography; + +export default function NoEventsContainer(): React.ReactElement { + return ( + <div className="no-logs-found"> + <Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this + deployment in the selected time range. + </Text> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts new file mode 100644 index 0000000000..0012d25ad5 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts @@ -0,0 +1,65 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; + +export const getDeploymentsEventsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts new file mode 100644 index 0000000000..6780621713 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts @@ -0,0 +1,3 @@ +import DeploymentEvents from './DeploymentEvents'; + +export default DeploymentEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss new file mode 100644 index 0000000000..fd91f49f65 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss @@ -0,0 +1,133 @@ +.deployment-logs-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .deployment-logs-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .deployment-logs { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } +} + +.deployment-logs-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.deployment-logs-list-card { + width: 100%; + margin-top: 12px; + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx new file mode 100644 index 0000000000..0573444ed8 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx @@ -0,0 +1,216 @@ +/* eslint-disable no-nested-ternary */ +import './DeploymentLogs.styles.scss'; + +import { Card } from 'antd'; +import RawLogView from 'components/Logs/RawLogView'; +import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import LogsError from 'container/LogsError/LogsError'; +import { LogsLoading } from 'container/LogsLoading/LogsLoading'; +import { FontSize } from 'container/OptionsMenu/types'; +import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { isEqual } from 'lodash-es'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { Virtuoso } from 'react-virtuoso'; +import { ILog } from 'types/api/logs/log'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { v4 } from 'uuid'; + +import { QUERY_KEYS } from '../constants'; +import { getDeploymentLogsQueryPayload } from './constants'; +import NoLogsContainer from './NoLogsContainer'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; + filters: IBuilderQuery['filters']; +} + +function PodLogs({ + timeRange, + handleChangeLogFilters, + filters, +}: Props): JSX.Element { + const [logs, setLogs] = useState<ILog[]>([]); + const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); + const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); + const [resetLogsList, setResetLogsList] = useState<boolean>(false); + + useEffect(() => { + const newRestFilters = filters.items.filter( + (item) => + item.key?.key !== 'id' && + ![QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + item.key?.key ?? '', + ), + ); + + const areFiltersSame = isEqual(restFilters, newRestFilters); + + if (!areFiltersSame) { + setResetLogsList(true); + } + + setRestFilters(newRestFilters); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filters]); + + const queryPayload = useMemo(() => { + const basePayload = getDeploymentLogsQueryPayload( + timeRange.startTime, + timeRange.endTime, + filters, + ); + + basePayload.query.builder.queryData[0].pageSize = 100; + basePayload.query.builder.queryData[0].orderBy = [ + { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, + ]; + + return basePayload; + }, [timeRange.startTime, timeRange.endTime, filters]); + + const [isPaginating, setIsPaginating] = useState(false); + + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: ['deploymentLogs', timeRange.startTime, timeRange.endTime, filters], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + keepPreviousData: isPaginating, + }); + + useEffect(() => { + if (data?.payload?.data?.newResult?.data?.result) { + const currentData = data.payload.data.newResult.data.result; + + if (resetLogsList) { + const currentLogs: ILog[] = + currentData[0].list?.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })) || []; + + setLogs(currentLogs); + + setResetLogsList(false); + } + + if (currentData.length > 0 && currentData[0].list) { + const currentLogs: ILog[] = + currentData[0].list.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })) || []; + + setLogs((prev) => [...prev, ...currentLogs]); + } else { + setHasReachedEndOfLogs(true); + } + } + }, [data, restFilters, isPaginating, resetLogsList]); + + const getItemContent = useCallback( + (_: number, logToRender: ILog): JSX.Element => ( + <RawLogView + isReadOnly + isTextOverflowEllipsisDisabled + key={logToRender.id} + data={logToRender} + linesPerRow={5} + fontSize={FontSize.MEDIUM} + /> + ), + [], + ); + + const loadMoreLogs = useCallback(() => { + if (!logs.length) return; + + setIsPaginating(true); + const lastLog = logs[logs.length - 1]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '<', + value: lastLog.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeLogFilters(newFilters); + }, [logs, filters, handleChangeLogFilters]); + + useEffect(() => { + setIsPaginating(false); + }, [data]); + + const renderFooter = useCallback( + (): JSX.Element | null => ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {isFetching ? ( + <div className="logs-loading-skeleton"> Loading more logs ... </div> + ) : hasReachedEndOfLogs ? ( + <div className="logs-loading-skeleton"> *** End *** </div> + ) : null} + </> + ), + [isFetching, hasReachedEndOfLogs], + ); + + const renderContent = useMemo( + () => ( + <Card bordered={false} className="deployment-logs-list-card"> + <OverlayScrollbar isVirtuoso> + <Virtuoso + className="deployment-logs-virtuoso" + key="deployment-logs-virtuoso" + data={logs} + endReached={loadMoreLogs} + totalCount={logs.length} + itemContent={getItemContent} + overscan={200} + components={{ + Footer: renderFooter, + }} + /> + </OverlayScrollbar> + </Card> + ), + [logs, loadMoreLogs, getItemContent, renderFooter], + ); + + return ( + <div className="deployment-logs"> + {isLoading && <LogsLoading />} + {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {isError && !isLoading && <LogsError />} + {!isLoading && !isError && logs.length > 0 && ( + <div className="deployment-logs-list-container">{renderContent}</div> + )} + </div> + ); +} + +export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx new file mode 100644 index 0000000000..1c1e59bebc --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx @@ -0,0 +1,99 @@ +import './DeploymentLogs.styles.scss'; + +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useMemo } from 'react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import DeploymentLogs from './DeploymentLogs'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; + logFilters: IBuilderQuery['filters']; + selectedInterval: Time; +} + +function DeploymentLogsDetailedView({ + timeRange, + isModalTimeSelection, + handleTimeChange, + handleChangeLogFilters, + logFilters, + selectedInterval, +}: Props): JSX.Element { + const { currentQuery } = useQueryBuilder(); + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.LOGS, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + return ( + <div className="deployment-logs-container"> + <div className="deployment-logs-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeLogFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + <DeploymentLogs + timeRange={timeRange} + handleChangeLogFilters={handleChangeLogFilters} + filters={logFilters} + /> + </div> + ); +} + +export default DeploymentLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx new file mode 100644 index 0000000000..99a791b620 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx @@ -0,0 +1,16 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; +import { Ghost } from 'lucide-react'; + +const { Text } = Typography; + +export default function NoLogsContainer(): React.ReactElement { + return ( + <div className="no-logs-found"> + <Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this + deployment in the selected time range. + </Text> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts new file mode 100644 index 0000000000..fb620033cf --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts @@ -0,0 +1,65 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; + +export const getDeploymentLogsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts new file mode 100644 index 0000000000..47b16ec9b6 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts @@ -0,0 +1,3 @@ +import DeploymentLogs from './DeploymentLogsDetailedView'; + +export default DeploymentLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss new file mode 100644 index 0000000000..c94162f9d9 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss @@ -0,0 +1,45 @@ +.empty-container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.deployment-metrics-container { + margin-top: 1rem; +} + +.metrics-header { + display: flex; + justify-content: flex-end; + margin-top: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); +} + +.deployment-metrics-card { + margin: 8px 0 1rem 0; + height: 300px; + padding: 10px; + + border: 1px solid var(--bg-slate-500); + + .ant-card-body { + padding: 0; + } + + .chart-container { + width: 100%; + height: 100%; + } + + .no-data-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx new file mode 100644 index 0000000000..fcbb986cae --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx @@ -0,0 +1,145 @@ +import './DeploymentMetrics.styles.scss'; + +import { Card, Col, Row, Skeleton, Typography } from 'antd'; +import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList'; +import cx from 'classnames'; +import Uplot from 'components/Uplot'; +import { ENTITY_VERSION_V4 } from 'constants/app'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useResizeObserver } from 'hooks/useDimensions'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; +import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; +import { useMemo, useRef } from 'react'; +import { useQueries, UseQueryResult } from 'react-query'; +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; + +import { deploymentWidgetInfo, getDeploymentQueryPayload } from './constants'; + +interface DeploymentMetricsProps { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + selectedInterval: Time; + deployment: K8sDeploymentsData; +} + +function DeploymentMetrics({ + selectedInterval, + deployment, + timeRange, + handleTimeChange, + isModalTimeSelection, +}: DeploymentMetricsProps): JSX.Element { + const queryPayloads = useMemo( + () => + getDeploymentQueryPayload( + deployment, + timeRange.startTime, + timeRange.endTime, + ), + [deployment, timeRange.startTime, timeRange.endTime], + ); + + const queries = useQueries( + queryPayloads.map((payload) => ({ + queryKey: ['deployment-metrics', payload, ENTITY_VERSION_V4, 'NODE'], + queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + })), + ); + + const isDarkMode = useIsDarkMode(); + const graphRef = useRef<HTMLDivElement>(null); + const dimensions = useResizeObserver(graphRef); + + const chartData = useMemo( + () => queries.map(({ data }) => getUPlotChartData(data?.payload)), + [queries], + ); + + const options = useMemo( + () => + queries.map(({ data }, idx) => + getUPlotChartOptions({ + apiResponse: data?.payload, + isDarkMode, + dimensions, + yAxisUnit: deploymentWidgetInfo[idx].yAxisUnit, + softMax: null, + softMin: null, + minTimeScale: timeRange.startTime, + maxTimeScale: timeRange.endTime, + }), + ), + [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], + ); + + const renderCardContent = ( + query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, + idx: number, + ): JSX.Element => { + if (query.isLoading) { + return <Skeleton />; + } + + if (query.error) { + const errorMessage = + (query.error as Error)?.message || 'Something went wrong'; + return <div>{errorMessage}</div>; + } + return ( + <div + className={cx('chart-container', { + 'no-data-container': + !query.isLoading && !query?.data?.payload?.data?.result?.length, + })} + > + <Uplot options={options[idx]} data={chartData[idx]} /> + </div> + ); + }; + + return ( + <> + <div className="metrics-header"> + <div className="metrics-datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + isModalTimeSelection={isModalTimeSelection} + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + <Row gutter={24} className="deployment-metrics-container"> + {queries.map((query, idx) => ( + <Col span={12} key={deploymentWidgetInfo[idx].title}> + <Typography.Text>{deploymentWidgetInfo[idx].title}</Typography.Text> + <Card bordered className="deployment-metrics-card" ref={graphRef}> + {renderCardContent(query, idx)} + </Card> + </Col> + ))} + </Row> + </> + ); +} + +export default DeploymentMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts new file mode 100644 index 0000000000..75f1934240 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts @@ -0,0 +1,544 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 } from 'uuid'; + +export const deploymentWidgetInfo = [ + { + title: 'CPU usage, request, limits', + yAxisUnit: '', + }, + { + title: 'Memory usage, request, limits)', + yAxisUnit: 'bytes', + }, + { + title: 'Network IO', + yAxisUnit: 'binBps', + }, + { + title: 'Network error count', + yAxisUnit: '', + }, +]; + +export const getDeploymentQueryPayload = ( + deployment: K8sDeploymentsData, + start: number, + end: number, +): GetQueryResultsProps[] => [ + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'aec60cba', + key: { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + op: '=', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_container_cpu_request--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_container_cpu_request', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'd047ec14', + key: { + dataType: DataTypes.String, + id: 'k8s_pod_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_pod_name', + type: 'tag', + }, + op: 'contains', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'requests', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_container_cpu_limit--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_container_cpu_limit', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '750b7856', + key: { + dataType: DataTypes.String, + id: 'k8s_pod_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_pod_name', + type: 'tag', + }, + op: 'contains', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'limits', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '768c2f47', + key: { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + op: '=', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_container_memory_request--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_container_memory_request', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '1a96fa81', + key: { + dataType: DataTypes.String, + id: 'k8s_pod_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_pod_name', + type: 'tag', + }, + op: 'contains', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'requests', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_container_memory_limit--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_container_memory_limit', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: 'e69a2b7e', + key: { + dataType: DataTypes.String, + id: 'k8s_pod_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_pod_name', + type: 'tag', + }, + op: 'contains', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'limits', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_network_io--float64--Sum--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_network_io', + type: 'Sum', + }, + aggregateOperator: 'rate', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '8b550f6d', + key: { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + op: '=', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'direction--string--tag--false', + isColumn: false, + isJSON: false, + key: 'direction', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'interface--string--tag--false', + isColumn: false, + isJSON: false, + key: 'interface', + type: 'tag', + }, + ], + having: [], + legend: '{{direction}} :: {{interface}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_network_errors--float64--Sum--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_network_errors', + type: 'Sum', + }, + aggregateOperator: 'increase', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'e16c1e4a', + key: { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + op: '=', + value: deployment.meta.k8s_deployment_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'direction--string--tag--false', + isColumn: false, + isJSON: false, + key: 'direction', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'interface--string--tag--false', + isColumn: false, + isJSON: false, + key: 'interface', + type: 'tag', + }, + ], + having: [], + legend: '{{direction}} :: {{interface}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'increase', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, +]; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts new file mode 100644 index 0000000000..729438baaa --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts @@ -0,0 +1,3 @@ +import DeploymentMetrics from './DeploymentMetrics'; + +export default DeploymentMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss new file mode 100644 index 0000000000..059e07b4f2 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss @@ -0,0 +1,193 @@ +.deployment-metric-traces { + margin-top: 1rem; + + .deployment-metric-traces-header { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + } + + .deployment-metric-traces-table { + .ant-table-content { + overflow: hidden !important; + } + + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgba(171, 189, 255, 0.01); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.hostname-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgba(171, 189, 255, 0.01); + } + + .ant-table-cell:has(.hostname-column-value) { + background: var(--bg-ink-400); + } + + .hostname-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-table-container::after { + content: none; + } + } +} + +.lightMode { + .host-metric-traces-header { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } + } + + .host-metric-traces-table { + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-vanilla-300); + + .ant-table-thead > tr > th { + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .ant-table-thead > tr > th:has(.hostname-column-header) { + background: var(--bg-vanilla-100); + } + + .ant-table-cell { + background: var(--bg-vanilla-100); + color: var(--bg-ink-500); + } + + .ant-table-cell:has(.hostname-column-value) { + background: var(--bg-vanilla-100); + } + + .hostname-column-value { + color: var(--bg-ink-300); + } + + .ant-table-tbody > tr:hover > td { + background: rgba(0, 0, 0, 0.04); + } + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx new file mode 100644 index 0000000000..0c9d137380 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx @@ -0,0 +1,199 @@ +import './DeploymentTraces.styles.scss'; + +import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; +import { ResizeTable } from 'components/ResizeTable'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { QueryParams } from 'constants/query'; +import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; +import NoLogs from 'container/NoLogs/NoLogs'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import { ErrorText } from 'container/TimeSeriesView/styles'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import TraceExplorerControls from 'container/TracesExplorer/Controls'; +import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; +import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { Pagination } from 'hooks/queryPagination'; +import useUrlQueryData from 'hooks/useUrlQueryData'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import { getDeploymentTracesQueryPayload, selectedColumns } from './constants'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; + tracesFilters: IBuilderQuery['filters']; + selectedInterval: Time; +} + +function DeploymentTraces({ + timeRange, + isModalTimeSelection, + handleTimeChange, + handleChangeTracesFilters, + tracesFilters, + selectedInterval, +}: Props): JSX.Element { + const [traces, setTraces] = useState<any[]>([]); + const [offset] = useState<number>(0); + + const { currentQuery } = useQueryBuilder(); + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.TRACES, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( + QueryParams.pagination, + ); + + const queryPayload = useMemo( + () => + getDeploymentTracesQueryPayload( + timeRange.startTime, + timeRange.endTime, + paginationQueryData?.offset || offset, + tracesFilters, + ), + [ + timeRange.startTime, + timeRange.endTime, + offset, + tracesFilters, + paginationQueryData, + ], + ); + + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: [ + 'hostMetricTraces', + timeRange.startTime, + timeRange.endTime, + offset, + tracesFilters, + DEFAULT_ENTITY_VERSION, + paginationQueryData, + ], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + }); + + const traceListColumns = getListColumns(selectedColumns); + + useEffect(() => { + if (data?.payload?.data?.newResult?.data?.result) { + const currentData = data.payload.data.newResult.data.result; + if (currentData.length > 0 && currentData[0].list) { + if (offset === 0) { + setTraces(currentData[0].list ?? []); + } else { + setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); + } + } + } + }, [data, offset]); + + const isDataEmpty = + !isLoading && !isFetching && !isError && traces.length === 0; + const hasAdditionalFilters = tracesFilters.items.length > 1; + + const totalCount = + data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; + + return ( + <div className="deployment-metric-traces"> + <div className="deployment-metric-traces-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeTracesFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + + {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} + + {isLoading && traces.length === 0 && <TracesLoading />} + + {isDataEmpty && !hasAdditionalFilters && ( + <NoLogs dataSource={DataSource.TRACES} /> + )} + + {isDataEmpty && hasAdditionalFilters && ( + <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> + )} + + {!isError && traces.length > 0 && ( + <div className="deployment-traces-table"> + <TraceExplorerControls + isLoading={isFetching} + totalCount={totalCount} + perPageOptions={PER_PAGE_OPTIONS} + showSizeChanger={false} + /> + <ResizeTable + tableLayout="fixed" + pagination={false} + scroll={{ x: true }} + loading={isFetching} + dataSource={traces} + columns={traceListColumns} + /> + </div> + )} + </div> + ); +} + +export default DeploymentTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts new file mode 100644 index 0000000000..d592b4db74 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts @@ -0,0 +1,200 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { + BaseAutocompleteData, + DataTypes, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { nanoToMilli } from 'utils/timeUtils'; + +export const columns = [ + { + dataIndex: 'timestamp', + key: 'timestamp', + title: 'Timestamp', + width: 200, + render: (timestamp: string): string => new Date(timestamp).toLocaleString(), + }, + { + title: 'Service Name', + dataIndex: ['data', 'serviceName'], + key: 'serviceName-string-tag', + width: 150, + }, + { + title: 'Name', + dataIndex: ['data', 'name'], + key: 'name-string-tag', + width: 145, + }, + { + title: 'Duration', + dataIndex: ['data', 'durationNano'], + key: 'durationNano-float64-tag', + width: 145, + render: (duration: number): string => `${nanoToMilli(duration)}ms`, + }, + { + title: 'HTTP Method', + dataIndex: ['data', 'httpMethod'], + key: 'httpMethod-string-tag', + width: 145, + }, + { + title: 'Status Code', + dataIndex: ['data', 'responseStatusCode'], + key: 'responseStatusCode-string-tag', + width: 145, + }, +]; + +export const selectedColumns: BaseAutocompleteData[] = [ + { + key: 'timestamp', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'serviceName', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'name', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'durationNano', + dataType: DataTypes.Float64, + type: 'tag', + isColumn: true, + }, + { + key: 'httpMethod', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'responseStatusCode', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, +]; + +export const getDeploymentTracesQueryPayload = ( + start: number, + end: number, + offset = 0, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + query: { + promql: [], + clickhouse_sql: [], + builder: { + queryData: [ + { + dataSource: DataSource.TRACES, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.EMPTY, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + }, + ], + queryFormulas: [], + }, + id: '572f1d91-6ac0-46c0-b726-c21488b34434', + queryType: EQueryType.QUERY_BUILDER, + }, + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + start, + end, + params: { + dataSource: DataSource.TRACES, + }, + tableParams: { + pagination: { + limit: 10, + offset, + }, + selectColumns: [ + { + key: 'serviceName', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'serviceName--string--tag--true', + isIndexed: false, + }, + { + key: 'name', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'name--string--tag--true', + isIndexed: false, + }, + { + key: 'durationNano', + dataType: 'float64', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'durationNano--float64--tag--true', + isIndexed: false, + }, + { + key: 'httpMethod', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'httpMethod--string--tag--true', + isIndexed: false, + }, + { + key: 'responseStatusCode', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'responseStatusCode--string--tag--true', + isIndexed: false, + }, + ], + }, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts new file mode 100644 index 0000000000..2f1f125a99 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts @@ -0,0 +1,3 @@ +import DeploymentTraces from './DeploymentTraces'; + +export default DeploymentTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts new file mode 100644 index 0000000000..67983c3f97 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts @@ -0,0 +1,6 @@ +export const QUERY_KEYS = { + K8S_OBJECT_KIND: 'k8s.object.kind', + K8S_OBJECT_NAME: 'k8s.object.name', + K8S_NODE_NAME: 'k8s.deployment.name', + K8S_CLUSTER_NAME: 'k8s.cluster.name', +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/index.ts new file mode 100644 index 0000000000..8fb076b645 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/index.ts @@ -0,0 +1,3 @@ +import DeploymentDetails from './DeploymentDetails'; + +export default DeploymentDetails; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss new file mode 100644 index 0000000000..24a522df86 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss @@ -0,0 +1,17 @@ +.infra-monitoring-container { + .deployments-list-table { + .expanded-table-container { + padding-left: 20px; + } + + .ant-table-cell { + min-width: 180px !important; + max-width: 180px !important; + } + + .ant-table-row-expand-icon-cell { + min-width: 30px !important; + max-width: 30px !important; + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx new file mode 100644 index 0000000000..713c5495db --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx @@ -0,0 +1,518 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; +import './K8sDeploymentsList.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Button, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { ColumnType, SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; +import { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList'; +import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { + K8sCategory, + K8sEntityToAggregateAttributeMapping, +} from '../constants'; +import K8sHeader from '../K8sHeader'; +import LoadingContainer from '../LoadingContainer'; +import DeploymentDetails from './DeploymentDetails'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sDeploymentsListColumns, + getK8sDeploymentsListQuery, + K8sDeploymentsRowData, +} from './utils'; + +// eslint-disable-next-line sonarjs/cognitive-complexity +function K8sDeploymentsList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>(null); + + const [selectedDeploymentUID, setselectedDeploymentUID] = useState< + string | null + >(null); + + const pageSize = 10; + + const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]); + + const [ + selectedRowData, + setSelectedRowData, + ] = useState<K8sDeploymentsRowData | null>(null); + + const [groupByOptions, setGroupByOptions] = useState< + { value: string; label: string }[] + >([]); + + const createFiltersForSelectedRowData = ( + selectedRowData: K8sDeploymentsRowData, + groupBy: IBuilderQuery['groupBy'], + ): IBuilderQuery['filters'] => { + const baseFilters: IBuilderQuery['filters'] = { + items: [], + op: 'and', + }; + + if (!selectedRowData) return baseFilters; + + const { groupedByMeta } = selectedRowData; + + for (const key of groupBy) { + baseFilters.items.push({ + key: { + key: key.key, + type: null, + }, + op: '=', + value: groupedByMeta[key.key], + id: key.key, + }); + } + + return baseFilters; + }; + + const fetchGroupedByRowDataQuery = useMemo(() => { + if (!selectedRowData) return null; + + const baseQuery = getK8sDeploymentsListQuery(); + + const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); + + return { + ...baseQuery, + limit: 10, + offset: 0, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); + + const { + data: groupedByRowData, + isFetching: isFetchingGroupedByRowData, + isLoading: isLoadingGroupedByRowData, + isError: isErrorGroupedByRowData, + refetch: fetchGroupedByRowData, + } = useGetK8sDeploymentsList( + fetchGroupedByRowDataQuery as K8sDeploymentsListPayload, + { + queryKey: ['deploymentList', fetchGroupedByRowDataQuery], + enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, + }, + ); + + const { currentQuery } = useQueryBuilder(); + + const { + data: groupByFiltersData, + isLoading: isLoadingGroupByFilters, + } = useGetAggregateKeys( + { + dataSource: currentQuery.builder.queryData[0].dataSource, + aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES], + aggregateOperator: 'noop', + searchText: '', + tagType: '', + }, + { + queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], + }, + true, + K8sCategory.NODES, + ); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + const query = useMemo(() => { + const baseQuery = getK8sDeploymentsListQuery(); + const queryPayload = { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters: queryFilters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + if (groupBy.length > 0) { + queryPayload.groupBy = groupBy; + } + return queryPayload; + }, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]); + + const formattedGroupedByDeploymentsData = useMemo( + () => + formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy), + [groupedByRowData, groupBy], + ); + + const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList( + query as K8sDeploymentsListPayload, + { + queryKey: ['deploymentList', query], + enabled: !!query, + }, + ); + + const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [ + data, + ]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedDeploymentsData = useMemo( + () => formatDataForTable(deploymentsData, groupBy), + [deploymentsData, groupBy], + ); + + const columns = useMemo(() => getK8sDeploymentsListColumns(groupBy), [ + groupBy, + ]); + + const handleGroupByRowClick = (record: K8sDeploymentsRowData): void => { + setSelectedRowData(record); + + if (expandedRowKeys.includes(record.key)) { + setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); + } else { + setExpandedRowKeys([record.key]); + } + }; + + useEffect(() => { + if (selectedRowData) { + fetchGroupedByRowData(); + } + }, [selectedRowData, fetchGroupedByRowData]); + + const handleTableChange: TableProps<K8sDeploymentsRowData>['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record<string, (string | number | boolean)[] | null>, + sorter: + | SorterResult<K8sDeploymentsRowData> + | SorterResult<K8sDeploymentsRowData>[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const { handleChangeQueryData } = useQueryOperations({ + index: 0, + query: currentQuery.builder.queryData[0], + entityVersion: '', + }); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + handleChangeQueryData('filters', value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + }, + [handleChangeQueryData], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + const selectedDeploymentData = useMemo(() => { + if (!selectedDeploymentUID) return null; + return ( + deploymentsData.find( + (deployment) => deployment.deploymentName === selectedDeploymentUID, + ) || null + ); + }, [selectedDeploymentUID, deploymentsData]); + + const handleRowClick = (record: K8sDeploymentsRowData): void => { + if (groupBy.length === 0) { + setSelectedRowData(null); + setselectedDeploymentUID(record.key); + } else { + handleGroupByRowClick(record); + } + + logEvent('Infra Monitoring: K8s deployment list item clicked', { + deploymentUID: record.key, + }); + }; + + const nestedColumns = useMemo(() => getK8sDeploymentsListColumns([]), []); + + const isGroupedByAttribute = groupBy.length > 0; + + const handleExpandedRowViewAllClick = (): void => { + if (!selectedRowData) return; + + const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); + + handleFiltersChange(filters); + + setCurrentPage(1); + setSelectedRowData(null); + setGroupBy([]); + setOrderBy(null); + }; + + const expandedRowRender = (): JSX.Element => ( + <div className="expanded-table-container"> + {isErrorGroupedByRowData && ( + <Typography>{groupedByRowData?.error || 'Something went wrong'}</Typography> + )} + {isFetchingGroupedByRowData || isLoadingGroupedByRowData ? ( + <LoadingContainer /> + ) : ( + <div className="expanded-table"> + <Table + columns={nestedColumns as ColumnType<K8sDeploymentsRowData>[]} + dataSource={formattedGroupedByDeploymentsData} + pagination={false} + scroll={{ x: true }} + tableLayout="fixed" + size="small" + loading={{ + spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + showHeader={false} + /> + + {groupedByRowData?.payload?.data?.total && + groupedByRowData?.payload?.data?.total > 10 ? ( + <div className="expanded-table-footer"> + <Button + type="default" + size="small" + className="periscope-btn secondary" + onClick={handleExpandedRowViewAllClick} + > + View All + </Button> + </div> + ) : null} + </div> + )} + </div> + ); + + const expandRowIconRenderer = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: K8sDeploymentsRowData, + e: React.MouseEvent<HTMLButtonElement>, + ) => void; + record: K8sDeploymentsRowData; + }): JSX.Element | null => { + if (!isGroupedByAttribute) { + return null; + } + + return expanded ? ( + <Button + className="periscope-btn ghost" + onClick={(e: React.MouseEvent<HTMLButtonElement>): void => + onExpand(record, e) + } + role="button" + > + <ChevronDown size={14} /> + </Button> + ) : ( + <Button + className="periscope-btn ghost" + onClick={(e: React.MouseEvent<HTMLButtonElement>): void => + onExpand(record, e) + } + role="button" + > + <ChevronRight size={14} /> + </Button> + ); + }; + + const handleCloseDeploymentDetail = (): void => { + setselectedDeploymentUID(null); + }; + + const showsDeploymentsTable = + !isError && + !isLoading && + !isFetching && + !(formattedDeploymentsData.length === 0 && queryFilters.items.length > 0); + + const showNoFilteredDeploymentsMessage = + !isFetching && + !isLoading && + formattedDeploymentsData.length === 0 && + queryFilters.items.length > 0; + + const handleGroupByChange = useCallback( + (value: IBuilderQuery['groupBy']) => { + const groupBy = []; + + for (let index = 0; index < value.length; index++) { + const element = (value[index] as unknown) as string; + + const key = groupByFiltersData?.payload?.attributeKeys?.find( + (key) => key.key === element, + ); + + if (key) { + groupBy.push(key); + } + } + + setGroupBy(groupBy); + setExpandedRowKeys([]); + }, + [groupByFiltersData], + ); + + useEffect(() => { + if (groupByFiltersData?.payload) { + setGroupByOptions( + groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ + value: filter.key, + label: filter.key, + })) || [], + ); + } + }, [groupByFiltersData]); + + return ( + <div className="k8s-list"> + <K8sHeader + isFiltersVisible={isFiltersVisible} + handleFilterVisibilityChange={handleFilterVisibilityChange} + defaultAddedColumns={defaultAddedColumns} + handleFiltersChange={handleFiltersChange} + groupByOptions={groupByOptions} + isLoadingGroupByFilters={isLoadingGroupByFilters} + handleGroupByChange={handleGroupByChange} + selectedGroupBy={groupBy} + entity={K8sCategory.NODES} + /> + {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} + + {showNoFilteredDeploymentsMessage && ( + <div className="no-filtered-hosts-message-container"> + <div className="no-filtered-hosts-message-content"> + <img + src="/Icons/emptyState.svg" + alt="thinking-emoji" + className="empty-state-svg" + /> + + <Typography.Text className="no-filtered-hosts-message"> + This query had no results. Edit your query and try again! + </Typography.Text> + </div> + </div> + )} + + {(isFetching || isLoading) && <LoadingContainer />} + + {showsDeploymentsTable && ( + <Table + className="k8s-list-table deployments-list-table" + dataSource={isFetching || isLoading ? [] : formattedDeploymentsData} + columns={columns} + pagination={{ + current: currentPage, + pageSize, + total: totalCount, + showSizeChanger: false, + hideOnSinglePage: true, + }} + scroll={{ x: true }} + loading={{ + spinning: isFetching || isLoading, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + 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, + }} + /> + )} + <DeploymentDetails + deployment={selectedDeploymentData} + isModalTimeSelection + onClose={handleCloseDeploymentDetail} + /> + </div> + ); +} + +export default K8sDeploymentsList; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx new file mode 100644 index 0000000000..5469dbb445 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -0,0 +1,311 @@ +import { Tag, Tooltip } from 'antd'; +import { ColumnType } from 'antd/es/table'; +import { + K8sDeploymentsData, + K8sDeploymentsListPayload, +} from 'api/infraMonitoring/getK8sDeploymentsList'; +import { Group } from 'lucide-react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; + +import { + EntityProgressBar, + formatBytes, + ValidateColumnValueWrapper, +} from '../commonUtils'; +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Deployment Name', + value: 'deploymentName', + id: 'deploymentName', + canRemove: false, + }, + { + label: 'Namespace Name', + value: 'namespaceName', + id: 'namespaceName', + canRemove: false, + }, + { + label: 'Available', + value: 'available', + id: 'available', + canRemove: false, + }, + { + label: 'Desired', + value: 'desired', + id: 'desired', + canRemove: false, + }, + { + label: 'CPU Request Utilization (% of limit)', + value: 'cpu_request', + id: 'cpu_request', + canRemove: false, + }, + { + label: 'CPU Limit Utilization (% of request)', + value: 'cpu_limit', + id: 'cpu_limit', + canRemove: false, + }, + { + label: 'CPU Utilization (cores)', + value: 'cpu', + id: 'cpu', + canRemove: false, + }, + { + label: 'Memory Request Utilization (% of limit)', + value: 'memory_request', + id: 'memory_request', + canRemove: false, + }, + { + label: 'Memory Limit Utilization (% of request)', + value: 'memory_limit', + id: 'memory_limit', + canRemove: false, + }, + { + label: 'Memory Utilization (bytes)', + value: 'memory', + id: 'memory', + canRemove: false, + }, +]; + +export interface K8sDeploymentsRowData { + key: string; + deploymentName: React.ReactNode; + availableReplicas: React.ReactNode; + desiredReplicas: React.ReactNode; + cpu_request: React.ReactNode; + cpu_limit: React.ReactNode; + cpu: React.ReactNode; + memory_request: React.ReactNode; + memory_limit: React.ReactNode; + memory: React.ReactNode; + restarts: React.ReactNode; + clusterName: string; + namespaceName: string; + groupedByMeta?: any; +} + +const deploymentGroupColumnConfig = { + title: ( + <div className="column-header pod-group-header"> + <Group size={14} /> DEPLOYMENT GROUP + </div> + ), + dataIndex: 'deploymentGroup', + key: 'deploymentGroup', + ellipsis: true, + width: 150, + align: 'left', + sorter: false, +}; + +export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +const columnsConfig = [ + { + title: <div className="column-header-left">Deployment Name</div>, + dataIndex: 'deploymentName', + key: 'deploymentName', + ellipsis: true, + width: 150, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">Namespace Name</div>, + dataIndex: 'namespaceName', + key: 'namespaceName', + ellipsis: true, + width: 150, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">Available</div>, + dataIndex: 'availableReplicas', + key: 'availableReplicas', + width: 100, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">Desired</div>, + dataIndex: 'desiredReplicas', + key: 'desiredReplicas', + width: 80, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">CPU Req Usage (%)</div>, + dataIndex: 'cpu_request', + key: 'cpu_request', + width: 80, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">CPU Limit Usage (%)</div>, + dataIndex: 'cpu_limit', + key: 'cpu_limit', + width: 50, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">CPU Usage (cores)</div>, + dataIndex: 'cpu', + key: 'cpu', + width: 80, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">Mem Req Usage (%)</div>, + dataIndex: 'memory_request', + key: 'memory_request', + width: 50, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">Mem Limit Usage (%)</div>, + dataIndex: 'memory_limit', + key: 'memory_limit', + width: 80, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">Mem Usage</div>, + dataIndex: 'memory', + key: 'memory', + width: 80, + sorter: true, + align: 'left', + }, +]; + +export const getK8sDeploymentsListColumns = ( + groupBy: IBuilderQuery['groupBy'], +): ColumnType<K8sDeploymentsRowData>[] => { + if (groupBy.length > 0) { + const filteredColumns = [...columnsConfig].filter( + (column) => column.key !== 'deploymentName', + ); + filteredColumns.unshift(deploymentGroupColumnConfig); + return filteredColumns as ColumnType<K8sDeploymentsRowData>[]; + } + + return columnsConfig as ColumnType<K8sDeploymentsRowData>[]; +}; + +const getGroupByEle = ( + deployment: K8sDeploymentsData, + groupBy: IBuilderQuery['groupBy'], +): React.ReactNode => { + const groupByValues: string[] = []; + + groupBy.forEach((group) => { + groupByValues.push( + deployment.meta[group.key as keyof typeof deployment.meta], + ); + }); + + return ( + <div className="pod-group"> + {groupByValues.map((value) => ( + <Tag key={value} color="#1D212D" className="pod-group-tag-item"> + {value === '' ? '<no-value>' : value} + </Tag> + ))} + </div> + ); +}; + +export const formatDataForTable = ( + data: K8sDeploymentsData[], + groupBy: IBuilderQuery['groupBy'], +): K8sDeploymentsRowData[] => + data.map((deployment) => ({ + key: deployment.meta.k8s_deployment_name, + deploymentName: ( + <Tooltip title={deployment.meta.k8s_deployment_name}> + {deployment.meta.k8s_deployment_name} + </Tooltip> + ), + availableReplicas: ( + <ValidateColumnValueWrapper value={deployment.availablePods}> + {deployment.availablePods} + </ValidateColumnValueWrapper> + ), + desiredReplicas: ( + <ValidateColumnValueWrapper value={deployment.desiredPods}> + {deployment.desiredPods} + </ValidateColumnValueWrapper> + ), + restarts: ( + <ValidateColumnValueWrapper value={deployment.restarts}> + {deployment.restarts} + </ValidateColumnValueWrapper> + ), + cpu: ( + <ValidateColumnValueWrapper value={deployment.cpuUsage}> + {deployment.cpuUsage} + </ValidateColumnValueWrapper> + ), + cpu_request: ( + <ValidateColumnValueWrapper value={deployment.cpuRequest}> + <div className="progress-container"> + <EntityProgressBar value={deployment.cpuRequest} /> + </div> + </ValidateColumnValueWrapper> + ), + cpu_limit: ( + <ValidateColumnValueWrapper value={deployment.cpuLimit}> + <div className="progress-container"> + <EntityProgressBar value={deployment.cpuLimit} /> + </div> + </ValidateColumnValueWrapper> + ), + memory: ( + <ValidateColumnValueWrapper value={deployment.memoryUsage}> + {formatBytes(deployment.memoryUsage)} + </ValidateColumnValueWrapper> + ), + memory_request: ( + <ValidateColumnValueWrapper value={deployment.memoryRequest}> + <div className="progress-container"> + <EntityProgressBar value={deployment.memoryRequest} /> + </div> + </ValidateColumnValueWrapper> + ), + memory_limit: ( + <ValidateColumnValueWrapper value={deployment.memoryLimit}> + <div className="progress-container"> + <EntityProgressBar value={deployment.memoryLimit} /> + </div> + </ValidateColumnValueWrapper> + ), + clusterName: deployment.meta.k8s_cluster_name, + namespaceName: deployment.meta.k8s_namespace_name, + deploymentGroup: getGroupByEle(deployment, groupBy), + meta: deployment.meta, + ...deployment.meta, + groupedByMeta: deployment.meta, + })); diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index 40b166fb3f..93a550fe71 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -7,16 +7,18 @@ import { Collapse, Tooltip, Typography } from 'antd'; import QuickFilters from 'components/QuickFilters/QuickFilters'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; -import { Container, Workflow } from 'lucide-react'; +import { Computer, Container, Workflow } from 'lucide-react'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import { useState } from 'react'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { + DeploymentsQuickFiltersConfig, K8sCategories, NodesQuickFiltersConfig, PodsQuickFiltersConfig, } from './constants'; +import K8sDeploymentsList from './Deployments/K8sDeploymentsList'; import K8sNodesList from './Nodes/K8sNodesList'; import K8sPodLists from './Pods/K8sPodLists'; @@ -173,26 +175,26 @@ export default function InfraMonitoringK8s(): JSX.Element { // /> // ), // }, - // { - // label: ( - // <div className="k8s-quick-filters-category-label"> - // <div className="k8s-quick-filters-category-label-container"> - // <Computer size={14} className="k8s-quick-filters-category-label-icon" /> - // <Typography.Text>Deployments</Typography.Text> - // </div> - // </div> - // ), - // key: K8sCategories.DEPLOYMENTS, - // showArrow: false, - // children: ( - // <QuickFilters - // source="infra-monitoring" - // config={DeploymentsQuickFiltersConfig} - // handleFilterVisibilityChange={handleFilterVisibilityChange} - // onFilterChange={handleFilterChange} - // /> - // ), - // }, + { + label: ( + <div className="k8s-quick-filters-category-label"> + <div className="k8s-quick-filters-category-label-container"> + <Computer size={14} className="k8s-quick-filters-category-label-icon" /> + <Typography.Text>Deployments</Typography.Text> + </div> + </div> + ), + key: K8sCategories.DEPLOYMENTS, + showArrow: false, + children: ( + <QuickFilters + source="infra-monitoring" + config={DeploymentsQuickFiltersConfig} + handleFilterVisibilityChange={handleFilterVisibilityChange} + onFilterChange={handleFilterChange} + /> + ), + }, // { // label: ( // <div className="k8s-quick-filters-category-label"> @@ -314,6 +316,13 @@ export default function InfraMonitoringK8s(): JSX.Element { quickFiltersLastUpdated={quickFiltersLastUpdated} /> )} + + {selectedCategory === K8sCategories.DEPLOYMENTS && ( + <K8sDeploymentsList + isFiltersVisible={showFilters} + handleFilterVisibilityChange={handleFilterVisibilityChange} + /> + )} </div> </div> </div> diff --git a/frontend/src/container/InfraMonitoringK8s/constants.ts b/frontend/src/container/InfraMonitoringK8s/constants.ts index e97ea61f90..da6a9dab03 100644 --- a/frontend/src/container/InfraMonitoringK8s/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/constants.ts @@ -268,7 +268,7 @@ export const VolumesQuickFiltersConfig: IQuickFiltersConfig[] = [ export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [ { type: FiltersType.CHECKBOX, - title: 'Deployment', + title: 'Deployment Name', attributeKey: { key: 'k8s_deployment_name', dataType: DataTypes.String, @@ -276,6 +276,39 @@ export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [ isColumn: false, isJSON: false, }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, + { + type: FiltersType.CHECKBOX, + title: 'Namespace Name', + attributeKey: { + key: 'k8s_namespace_name', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, + { + type: FiltersType.CHECKBOX, + title: 'Cluster Name', + attributeKey: { + key: 'k8s_cluster_name', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, defaultOpen: true, }, ]; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts new file mode 100644 index 0000000000..8e4926b78a --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts @@ -0,0 +1,48 @@ +import { + getK8sDeploymentsList, + K8sDeploymentsListPayload, + K8sDeploymentsListResponse, +} from 'api/infraMonitoring/getK8sDeploymentsList'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useMemo } from 'react'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +type UseGetK8sDeploymentsList = ( + requestData: K8sDeploymentsListPayload, + options?: UseQueryOptions< + SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse, + Error + >, + headers?: Record<string, string>, +) => UseQueryResult< + SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse, + Error +>; + +export const useGetK8sDeploymentsList: UseGetK8sDeploymentsList = ( + requestData, + options, + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery< + SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse, + Error + >({ + queryFn: ({ signal }) => getK8sDeploymentsList(requestData, signal, headers), + ...options, + queryKey, + }); +}; From 4a2ffbbdbd39eba106cb5b738b460f11aa883aec Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Thu, 9 Jan 2025 13:28:34 +0530 Subject: [PATCH 02/20] chore: minor fixes --- frontend/src/constants/reactQueryKeys.ts | 2 ++ frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts | 2 +- frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index 5520872201..886c4f48bd 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -22,4 +22,6 @@ export const REACT_QUERY_KEY = { UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE', GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3', GET_POD_LIST: 'GET_POD_LIST', + GET_NODE_LIST: 'GET_NODE_LIST', + GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST', }; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts index 8e4926b78a..0b075c330d 100644 --- a/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts +++ b/frontend/src/hooks/infraMonitoring/useGetK8sDeploymentsList.ts @@ -34,7 +34,7 @@ export const useGetK8sDeploymentsList: UseGetK8sDeploymentsList = ( return options.queryKey; } - return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + return [REACT_QUERY_KEY.GET_DEPLOYMENT_LIST, requestData]; }, [options?.queryKey, requestData]); return useQuery< diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts index 4e2ac964d2..f88ffa113c 100644 --- a/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts +++ b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts @@ -34,7 +34,7 @@ export const useGetK8sNodesList: UseGetK8sNodesList = ( return options.queryKey; } - return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + return [REACT_QUERY_KEY.GET_NODE_LIST, requestData]; }, [options?.queryKey, requestData]); return useQuery<SuccessResponse<K8sNodesListResponse> | ErrorResponse, Error>({ From 4f0ad05e779c8494cb18fca3b298f163196a584d Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Thu, 9 Jan 2025 13:29:58 +0530 Subject: [PATCH 03/20] chore: minor fixes --- .../src/container/InfraMonitoringK8s/Deployments/utils.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx index 5469dbb445..23b533b05b 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -1,3 +1,4 @@ +import { Color } from '@signozhq/design-tokens'; import { Tag, Tooltip } from 'antd'; import { ColumnType } from 'antd/es/table'; import { @@ -230,7 +231,7 @@ const getGroupByEle = ( return ( <div className="pod-group"> {groupByValues.map((value) => ( - <Tag key={value} color="#1D212D" className="pod-group-tag-item"> + <Tag key={value} color={Color.BG_SLATE_400} className="pod-group-tag-item"> {value === '' ? '<no-value>' : value} </Tag> ))} From 84718baf2ed4f3cf74d10373a72be0194cd6ebd3 Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Tue, 14 Jan 2025 13:55:15 +0530 Subject: [PATCH 04/20] chore: fix deployment filters --- .../DeploymentDetails/DeploymentDetails.tsx | 17 +++++++++-------- .../Events/DeploymentEvents.tsx | 5 ++++- .../DeploymentDetails/Logs/DeploymentLogs.tsx | 2 +- .../Deployments/DeploymentDetails/constants.ts | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index a9f7481c4c..e59d2f5f17 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -87,7 +87,7 @@ function DeploymentDetails({ { id: uuidv4(), key: { - key: QUERY_KEYS.K8S_NODE_NAME, + key: QUERY_KEYS.K8S_DEPLOYMENT_NAME, dataType: DataTypes.String, type: 'resource', isColumn: false, @@ -100,7 +100,7 @@ function DeploymentDetails({ { id: uuidv4(), key: { - key: QUERY_KEYS.K8S_CLUSTER_NAME, + key: QUERY_KEYS.K8S_NAMESPACE_NAME, dataType: DataTypes.String, type: 'resource', isColumn: false, @@ -108,11 +108,11 @@ function DeploymentDetails({ id: 'k8s_deployment_name--string--resource--false', }, op: '=', - value: deployment?.meta.k8s_cluster_name || '', + value: deployment?.meta.k8s_namespace_name || '', }, ], }), - [deployment?.meta.k8s_deployment_name, deployment?.meta.k8s_cluster_name], + [deployment?.meta.k8s_deployment_name, deployment?.meta.k8s_namespace_name], ); const initialEventsFilters = useMemo( @@ -223,14 +223,15 @@ function DeploymentDetails({ (value: IBuilderQuery['filters']) => { setLogFilters((prevFilters) => { const primaryFilters = prevFilters.items.filter((item) => - [QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + [QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( item.key?.key ?? '', ), ); const paginationFilter = value.items.find((item) => item.key?.key === 'id'); const newFilters = value.items.filter( (item) => - item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_NODE_NAME, + item.key?.key !== 'id' && + item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME, ); logEvent( @@ -258,7 +259,7 @@ function DeploymentDetails({ (value: IBuilderQuery['filters']) => { setTracesFilters((prevFilters) => { const primaryFilters = prevFilters.items.filter((item) => - [QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + [QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( item.key?.key ?? '', ), ); @@ -275,7 +276,7 @@ function DeploymentDetails({ items: [ ...primaryFilters, ...value.items.filter( - (item) => item.key?.key !== QUERY_KEYS.K8S_NODE_NAME, + (item) => item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME, ), ].filter((item): item is TagFilterItem => item !== undefined), }; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx index 58fff15e92..e9b95e83ea 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx @@ -160,6 +160,7 @@ export default function Events({ id: event.data.id, key: event.data.id, resources_string: event.data.resources_string, + attributes_string: event.data.attributes_string, }), ); @@ -179,7 +180,9 @@ export default function Events({ }, [eventsData]); const handleExpandRow = (record: EventDataType): JSX.Element => ( - <EventContents data={record.resources_string} /> + <EventContents + data={{ ...record.attributes_string, ...record.resources_string }} + /> ); const handlePrev = (): void => { diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx index 0573444ed8..9936ab7483 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx @@ -49,7 +49,7 @@ function PodLogs({ const newRestFilters = filters.items.filter( (item) => item.key?.key !== 'id' && - ![QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + ![QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( item.key?.key ?? '', ), ); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts index 67983c3f97..dc94ffe217 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts @@ -1,6 +1,6 @@ export const QUERY_KEYS = { K8S_OBJECT_KIND: 'k8s.object.kind', K8S_OBJECT_NAME: 'k8s.object.name', - K8S_NODE_NAME: 'k8s.deployment.name', - K8S_CLUSTER_NAME: 'k8s.cluster.name', + K8S_DEPLOYMENT_NAME: 'k8s.deployment.name', + K8S_NAMESPACE_NAME: 'k8s.namespace.name', }; From 1bcc3e601ef0fe159af4beec36f9984f7b49d5b3 Mon Sep 17 00:00:00 2001 From: Amlan Kumar Nandy <45410599+amlannandy@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:24:07 +0530 Subject: [PATCH 05/20] feat: namespace implementation in k8s infra monitoring (#6617) --- .../infraMonitoring/getK8sNamespacesList.ts | 62 + frontend/src/constants/reactQueryKeys.ts | 2 + .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 58 +- .../Namespaces/K8sNamespacesList.styles.scss | 17 + .../Namespaces/K8sNamespacesList.tsx | 516 ++++++ .../Events/NamespaceEvents.styles.scss | 289 +++ .../Events/NamespaceEvents.tsx | 366 ++++ .../Events/NoEventsContainer.tsx | 16 + .../NamespaceDetails/Events/constants.ts | 65 + .../NamespaceDetails/Events/index.ts | 3 + .../Logs/NamespaceLogs.styles.scss | 133 ++ .../NamespaceDetails/Logs/NamespaceLogs.tsx | 214 +++ .../Logs/NamespaceLogsDetailedView.tsx | 99 + .../NamespaceDetails/Logs/NoLogsContainer.tsx | 16 + .../NamespaceDetails/Logs/constants.ts | 65 + .../Namespaces/NamespaceDetails/Logs/index.ts | 3 + .../Metrics/NamespaceMetrics.styles.scss | 45 + .../Metrics/NamespaceMetrics.tsx | 167 ++ .../NamespaceDetails/Metrics/constants.ts | 1632 +++++++++++++++++ .../NamespaceDetails/Metrics/index.ts | 3 + .../NamespaceDetails.interfaces.ts | 7 + .../NamespaceDetails.styles.scss | 247 +++ .../NamespaceDetails/NamespaceDetails.tsx | 550 ++++++ .../Traces/NamespaceTraces.styles.scss | 193 ++ .../Traces/NamespaceTraces.tsx | 199 ++ .../NamespaceDetails/Traces/constants.ts | 200 ++ .../NamespaceDetails/Traces/index.ts | 3 + .../Namespaces/NamespaceDetails/constants.ts | 6 + .../Namespaces/NamespaceDetails/index.ts | 3 + .../InfraMonitoringK8s/Namespaces/utils.tsx | 168 ++ .../InfraMonitoringK8s/commonUtils.tsx | 59 +- .../container/InfraMonitoringK8s/constants.ts | 28 +- .../useGetK8sNamespacesList.ts | 48 + 33 files changed, 5454 insertions(+), 28 deletions(-) create mode 100644 frontend/src/api/infraMonitoring/getK8sNamespacesList.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sNamespacesList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts b/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts new file mode 100644 index 0000000000..69835fe5b0 --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts @@ -0,0 +1,62 @@ +import { ApiBaseInstance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sNamespacesListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sNamespacesData { + namespaceName: string; + cpuUsage: number; + memoryUsage: number; + meta: { + k8s_cluster_name: string; + k8s_namespace_name: string; + }; +} + +export interface K8sNamespacesListResponse { + status: string; + data: { + type: string; + records: K8sNamespacesData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sNamespacesList = async ( + props: K8sNamespacesListPayload, + signal?: AbortSignal, + headers?: Record<string, string>, +): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post('/namespaces/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index 886c4f48bd..30da4e1362 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -24,4 +24,6 @@ export const REACT_QUERY_KEY = { GET_POD_LIST: 'GET_POD_LIST', GET_NODE_LIST: 'GET_NODE_LIST', GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST', + GET_CLUSTER_LIST: 'GET_CLUSTER_LIST', + GET_NAMESPACE_LIST: 'GET_NAMESPACE_LIST', }; diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index 93a550fe71..24c54cbd0f 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -7,7 +7,7 @@ import { Collapse, Tooltip, Typography } from 'antd'; import QuickFilters from 'components/QuickFilters/QuickFilters'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; -import { Computer, Container, Workflow } from 'lucide-react'; +import { Computer, Container, FilePenLine, Workflow } from 'lucide-react'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import { useState } from 'react'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; @@ -15,10 +15,12 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { DeploymentsQuickFiltersConfig, K8sCategories, + NamespaceQuickFiltersConfig, NodesQuickFiltersConfig, PodsQuickFiltersConfig, } from './constants'; import K8sDeploymentsList from './Deployments/K8sDeploymentsList'; +import K8sNamespacesList from './Namespaces/K8sNamespacesList'; import K8sNodesList from './Nodes/K8sNodesList'; import K8sPodLists from './Pods/K8sPodLists'; @@ -89,29 +91,29 @@ export default function InfraMonitoringK8s(): JSX.Element { ), }, // NOTE - Enabled these as we release new entities - // { - // label: ( - // <div className="k8s-quick-filters-category-label"> - // <div className="k8s-quick-filters-category-label-container"> - // <FilePenLine - // size={14} - // className="k8s-quick-filters-category-label-icon" - // /> - // <Typography.Text>Namespace</Typography.Text> - // </div> - // </div> - // ), - // key: K8sCategories.NAMESPACES, - // showArrow: false, - // children: ( - // <QuickFilters - // source="infra-monitoring" - // config={NamespaceQuickFiltersConfig} - // handleFilterVisibilityChange={handleFilterVisibilityChange} - // onFilterChange={handleFilterChange} - // /> - // ), - // }, + { + label: ( + <div className="k8s-quick-filters-category-label"> + <div className="k8s-quick-filters-category-label-container"> + <FilePenLine + size={14} + className="k8s-quick-filters-category-label-icon" + /> + <Typography.Text>Namespace</Typography.Text> + </div> + </div> + ), + key: K8sCategories.NAMESPACES, + showArrow: false, + children: ( + <QuickFilters + source="infra-monitoring" + config={NamespaceQuickFiltersConfig} + handleFilterVisibilityChange={handleFilterVisibilityChange} + onFilterChange={handleFilterChange} + /> + ), + }, // { // label: ( // <div className="k8s-quick-filters-category-label"> @@ -323,6 +325,14 @@ export default function InfraMonitoringK8s(): JSX.Element { handleFilterVisibilityChange={handleFilterVisibilityChange} /> )} + + {selectedCategory === K8sCategories.NAMESPACES && ( + <K8sNamespacesList + isFiltersVisible={showFilters} + handleFilterVisibilityChange={handleFilterVisibilityChange} + /> + )} + </div> </div> </div> diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss new file mode 100644 index 0000000000..231314989b --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss @@ -0,0 +1,17 @@ +.infra-monitoring-container { + .namespaces-list-table { + .expanded-table-container { + padding-left: 40px; + } + + .ant-table-cell { + min-width: 223px !important; + max-width: 223px !important; + } + + .ant-table-row-expand-icon-cell { + min-width: 30px !important; + max-width: 30px !important; + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx new file mode 100644 index 0000000000..9f5b43c1d5 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx @@ -0,0 +1,516 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; +import './K8sNamespacesList.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Button, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { ColumnType, SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; +import { K8sNamespacesListPayload } from 'api/infraMonitoring/getK8sNamespacesList'; +import { useGetK8sNamespacesList } from 'hooks/infraMonitoring/useGetK8sNamespacesList'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { + K8sCategory, + K8sEntityToAggregateAttributeMapping, +} from '../constants'; +import K8sHeader from '../K8sHeader'; +import LoadingContainer from '../LoadingContainer'; +import NamespaceDetails from './NamespaceDetails'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sNamespacesListColumns, + getK8sNamespacesListQuery, + K8sNamespacesRowData, +} from './utils'; + +// eslint-disable-next-line sonarjs/cognitive-complexity +function K8sNamespacesList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>(null); + + const [selectedNamespaceUID, setselectedNamespaceUID] = useState< + string | null + >(null); + + const pageSize = 10; + + const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]); + + const [ + selectedRowData, + setSelectedRowData, + ] = useState<K8sNamespacesRowData | null>(null); + + const [groupByOptions, setGroupByOptions] = useState< + { value: string; label: string }[] + >([]); + + const createFiltersForSelectedRowData = ( + selectedRowData: K8sNamespacesRowData, + groupBy: IBuilderQuery['groupBy'], + ): IBuilderQuery['filters'] => { + const baseFilters: IBuilderQuery['filters'] = { + items: [], + op: 'and', + }; + + if (!selectedRowData) return baseFilters; + + const { groupedByMeta } = selectedRowData; + + for (const key of groupBy) { + baseFilters.items.push({ + key: { + key: key.key, + type: null, + }, + op: '=', + value: groupedByMeta[key.key], + id: key.key, + }); + } + + return baseFilters; + }; + + const fetchGroupedByRowDataQuery = useMemo(() => { + if (!selectedRowData) return null; + + const baseQuery = getK8sNamespacesListQuery(); + + const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); + + return { + ...baseQuery, + limit: 10, + offset: 0, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); + + const { + data: groupedByRowData, + isFetching: isFetchingGroupedByRowData, + isLoading: isLoadingGroupedByRowData, + isError: isErrorGroupedByRowData, + refetch: fetchGroupedByRowData, + } = useGetK8sNamespacesList( + fetchGroupedByRowDataQuery as K8sNamespacesListPayload, + { + queryKey: ['namespaceList', fetchGroupedByRowDataQuery], + enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, + }, + ); + + const { currentQuery } = useQueryBuilder(); + + const { + data: groupByFiltersData, + isLoading: isLoadingGroupByFilters, + } = useGetAggregateKeys( + { + dataSource: currentQuery.builder.queryData[0].dataSource, + aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES], + aggregateOperator: 'noop', + searchText: '', + tagType: '', + }, + { + queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], + }, + true, + K8sCategory.NODES, + ); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + const query = useMemo(() => { + const baseQuery = getK8sNamespacesListQuery(); + const queryPayload = { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters: queryFilters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + if (groupBy.length > 0) { + queryPayload.groupBy = groupBy; + } + return queryPayload; + }, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]); + + const formattedGroupedByNamespacesData = useMemo( + () => + formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy), + [groupedByRowData, groupBy], + ); + + const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList( + query as K8sNamespacesListPayload, + { + queryKey: ['namespaceList', query], + enabled: !!query, + }, + ); + + const namespacesData = useMemo(() => data?.payload?.data?.records || [], [ + data, + ]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedNamespacesData = useMemo( + () => formatDataForTable(namespacesData, groupBy), + [namespacesData, groupBy], + ); + + const columns = useMemo(() => getK8sNamespacesListColumns(groupBy), [groupBy]); + + const handleGroupByRowClick = (record: K8sNamespacesRowData): void => { + setSelectedRowData(record); + + if (expandedRowKeys.includes(record.key)) { + setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); + } else { + setExpandedRowKeys([record.key]); + } + }; + + useEffect(() => { + if (selectedRowData) { + fetchGroupedByRowData(); + } + }, [selectedRowData, fetchGroupedByRowData]); + + const handleTableChange: TableProps<K8sNamespacesRowData>['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record<string, (string | number | boolean)[] | null>, + sorter: + | SorterResult<K8sNamespacesRowData> + | SorterResult<K8sNamespacesRowData>[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const { handleChangeQueryData } = useQueryOperations({ + index: 0, + query: currentQuery.builder.queryData[0], + entityVersion: '', + }); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + handleChangeQueryData('filters', value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + }, + [handleChangeQueryData], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + const selectedNamespaceData = useMemo(() => { + if (!selectedNamespaceUID) return null; + return ( + namespacesData.find( + (namespace) => namespace.namespaceName === selectedNamespaceUID, + ) || null + ); + }, [selectedNamespaceUID, namespacesData]); + + const handleRowClick = (record: K8sNamespacesRowData): void => { + if (groupBy.length === 0) { + setSelectedRowData(null); + setselectedNamespaceUID(record.namespaceUID); + } else { + handleGroupByRowClick(record); + } + + logEvent('Infra Monitoring: K8s namespace list item clicked', { + namespaceUID: record.namespaceUID, + }); + }; + + const nestedColumns = useMemo(() => getK8sNamespacesListColumns([]), []); + + const isGroupedByAttribute = groupBy.length > 0; + + const handleExpandedRowViewAllClick = (): void => { + if (!selectedRowData) return; + + const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); + + handleFiltersChange(filters); + + setCurrentPage(1); + setSelectedRowData(null); + setGroupBy([]); + setOrderBy(null); + }; + + const expandedRowRender = (): JSX.Element => ( + <div className="expanded-table-container"> + {isErrorGroupedByRowData && ( + <Typography>{groupedByRowData?.error || 'Something went wrong'}</Typography> + )} + {isFetchingGroupedByRowData || isLoadingGroupedByRowData ? ( + <LoadingContainer /> + ) : ( + <div className="expanded-table"> + <Table + columns={nestedColumns as ColumnType<K8sNamespacesRowData>[]} + dataSource={formattedGroupedByNamespacesData} + pagination={false} + scroll={{ x: true }} + tableLayout="fixed" + size="small" + loading={{ + spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + showHeader={false} + /> + + {groupedByRowData?.payload?.data?.total && + groupedByRowData?.payload?.data?.total > 10 ? ( + <div className="expanded-table-footer"> + <Button + type="default" + size="small" + className="periscope-btn secondary" + onClick={handleExpandedRowViewAllClick} + > + View All + </Button> + </div> + ) : null} + </div> + )} + </div> + ); + + const expandRowIconRenderer = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: K8sNamespacesRowData, + e: React.MouseEvent<HTMLButtonElement>, + ) => void; + record: K8sNamespacesRowData; + }): JSX.Element | null => { + if (!isGroupedByAttribute) { + return null; + } + + return expanded ? ( + <Button + className="periscope-btn ghost" + onClick={(e: React.MouseEvent<HTMLButtonElement>): void => + onExpand(record, e) + } + role="button" + > + <ChevronDown size={14} /> + </Button> + ) : ( + <Button + className="periscope-btn ghost" + onClick={(e: React.MouseEvent<HTMLButtonElement>): void => + onExpand(record, e) + } + role="button" + > + <ChevronRight size={14} /> + </Button> + ); + }; + + const handleCloseNamespaceDetail = (): void => { + setselectedNamespaceUID(null); + }; + + const showsNamespacesTable = + !isError && + !isLoading && + !isFetching && + !(formattedNamespacesData.length === 0 && queryFilters.items.length > 0); + + const showNoFilteredNamespacesMessage = + !isFetching && + !isLoading && + formattedNamespacesData.length === 0 && + queryFilters.items.length > 0; + + const handleGroupByChange = useCallback( + (value: IBuilderQuery['groupBy']) => { + const groupBy = []; + + for (let index = 0; index < value.length; index++) { + const element = (value[index] as unknown) as string; + + const key = groupByFiltersData?.payload?.attributeKeys?.find( + (key) => key.key === element, + ); + + if (key) { + groupBy.push(key); + } + } + + setGroupBy(groupBy); + setExpandedRowKeys([]); + }, + [groupByFiltersData], + ); + + useEffect(() => { + if (groupByFiltersData?.payload) { + setGroupByOptions( + groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ + value: filter.key, + label: filter.key, + })) || [], + ); + } + }, [groupByFiltersData]); + + return ( + <div className="k8s-list"> + <K8sHeader + isFiltersVisible={isFiltersVisible} + handleFilterVisibilityChange={handleFilterVisibilityChange} + defaultAddedColumns={defaultAddedColumns} + handleFiltersChange={handleFiltersChange} + groupByOptions={groupByOptions} + isLoadingGroupByFilters={isLoadingGroupByFilters} + handleGroupByChange={handleGroupByChange} + selectedGroupBy={groupBy} + entity={K8sCategory.NODES} + /> + {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} + + {showNoFilteredNamespacesMessage && ( + <div className="no-filtered-hosts-message-container"> + <div className="no-filtered-hosts-message-content"> + <img + src="/Icons/emptyState.svg" + alt="thinking-emoji" + className="empty-state-svg" + /> + + <Typography.Text className="no-filtered-hosts-message"> + This query had no results. Edit your query and try again! + </Typography.Text> + </div> + </div> + )} + + {(isFetching || isLoading) && <LoadingContainer />} + + {showsNamespacesTable && ( + <Table + className="k8s-list-table namespaces-list-table" + dataSource={isFetching || isLoading ? [] : formattedNamespacesData} + columns={columns} + pagination={{ + current: currentPage, + pageSize, + total: totalCount, + showSizeChanger: false, + hideOnSinglePage: true, + }} + scroll={{ x: true }} + loading={{ + spinning: isFetching || isLoading, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + 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, + }} + /> + )} + <NamespaceDetails + namespace={selectedNamespaceData} + isModalTimeSelection + onClose={handleCloseNamespaceDetail} + /> + </div> + ); +} + +export default K8sNamespacesList; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss new file mode 100644 index 0000000000..6aa2b22fb9 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss @@ -0,0 +1,289 @@ +.namespace-events-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .namespace-events-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .namespace-events { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } + + .ant-table { + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgb(18, 19, 23); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.namespacename-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgb(18, 19, 23); + border-bottom: none; + } + + .ant-table-cell:has(.namespacename-column-value) { + background: var(--bg-ink-400); + } + + .namespacename-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-pagination { + position: fixed; + bottom: 0; + width: calc(100% - 64px); + background: rgb(18, 19, 23); + padding: 16px; + margin: 0; + + // this is to offset intercom icon till we improve the design + padding-right: 72px; + + .ant-pagination-item { + border-radius: 4px; + + &-active { + background: var(--bg-robin-500); + border-color: var(--bg-robin-500); + + a { + color: var(--bg-ink-500) !important; + } + } + } + } +} + +.namespace-events-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.namespace-events-list-card { + width: 100%; + margin-top: 12px; + + .ant-table-wrapper { + height: 100%; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} + +.periscope-btn-icon { + cursor: pointer; +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx new file mode 100644 index 0000000000..00e2770dc4 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx @@ -0,0 +1,366 @@ +/* eslint-disable no-nested-ternary */ +import './NamespaceEvents.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Button, Table, TableColumnsType } from 'antd'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; +import LogsError from 'container/LogsError/LogsError'; +import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { isArray } from 'lodash-es'; +import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 } from 'uuid'; + +import { getNamespacesEventsQueryPayload } from './constants'; +import NoEventsContainer from './NoEventsContainer'; + +interface EventDataType { + key: string; + timestamp: string; + body: string; + id: string; + attributes_bool?: Record<string, boolean>; + attributes_number?: Record<string, number>; + attributes_string?: Record<string, string>; + resources_string?: Record<string, string>; + scope_name?: string; + scope_string?: Record<string, string>; + scope_version?: string; + severity_number?: number; + severity_text?: string; + span_id?: string; + trace_flags?: number; + trace_id?: string; + severity?: string; +} + +interface INamespaceEventsProps { + timeRange: { + startTime: number; + endTime: number; + }; + handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void; + filters: IBuilderQuery['filters']; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + selectedInterval: Time; +} + +const EventsPageSize = 10; + +export default function Events({ + timeRange, + handleChangeEventFilters, + filters, + isModalTimeSelection, + handleTimeChange, + selectedInterval, +}: INamespaceEventsProps): JSX.Element { + const { currentQuery } = useQueryBuilder(); + + const [formattedNamespaceEvents, setFormattedNamespaceEvents] = useState< + EventDataType[] + >([]); + + const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); + + const [page, setPage] = useState(1); + + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.LOGS, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + const queryPayload = useMemo(() => { + const basePayload = getNamespacesEventsQueryPayload( + timeRange.startTime, + timeRange.endTime, + filters, + ); + + basePayload.query.builder.queryData[0].pageSize = 10; + basePayload.query.builder.queryData[0].orderBy = [ + { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, + ]; + + return basePayload; + }, [timeRange.startTime, timeRange.endTime, filters]); + + const { data: eventsData, isLoading, isFetching, isError } = useQuery({ + queryKey: [ + 'namespaceEvents', + timeRange.startTime, + timeRange.endTime, + filters, + ], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + }); + + const columns: TableColumnsType<EventDataType> = [ + { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, + { + title: 'Timestamp', + dataIndex: 'timestamp', + width: 200, + ellipsis: true, + key: 'timestamp', + }, + { title: 'Body', dataIndex: 'body', key: 'body' }, + ]; + + useEffect(() => { + if (eventsData?.payload?.data?.newResult?.data?.result) { + const responsePayload = + eventsData?.payload.data.newResult.data.result[0].list || []; + + const formattedData = responsePayload?.map( + (event): EventDataType => ({ + timestamp: event.timestamp, + severity: event.data.severity_text, + body: event.data.body, + id: event.data.id, + key: event.data.id, + resources_string: event.data.resources_string, + attributes_string: event.data.attributes_string, + }), + ); + + setFormattedNamespaceEvents(formattedData); + + if ( + !responsePayload || + (responsePayload && + isArray(responsePayload) && + responsePayload.length < EventsPageSize) + ) { + setHasReachedEndOfEvents(true); + } else { + setHasReachedEndOfEvents(false); + } + } + }, [eventsData]); + + const handleExpandRow = (record: EventDataType): JSX.Element => ( + <EventContents + data={{ ...record.attributes_string, ...record.resources_string }} + /> + ); + + const handlePrev = (): void => { + if (!formattedNamespaceEvents.length) return; + + setPage(page - 1); + + const firstEvent = formattedNamespaceEvents[0]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '>', + value: firstEvent.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeEventFilters(newFilters); + }; + + const handleNext = (): void => { + if (!formattedNamespaceEvents.length) return; + + setPage(page + 1); + const lastEvent = + formattedNamespaceEvents[formattedNamespaceEvents.length - 1]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '<', + value: lastEvent.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeEventFilters(newFilters); + }; + + const handleExpandRowIcon = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: EventDataType, + e: React.MouseEvent<HTMLElement, MouseEvent>, + ) => void; + record: EventDataType; + }): JSX.Element => + expanded ? ( + <ChevronDown + className="periscope-btn-icon" + size={14} + onClick={(e): void => + onExpand( + record, + (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, + ) + } + /> + ) : ( + <ChevronRight + className="periscope-btn-icon" + size={14} + // eslint-disable-next-line sonarjs/no-identical-functions + onClick={(e): void => + onExpand( + record, + (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, + ) + } + /> + ); + + return ( + <div className="namespace-events-container"> + <div className="namespace-events-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeEventFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + + {isLoading && <LoadingContainer />} + + {!isLoading && !isError && formattedNamespaceEvents.length === 0 && ( + <NoEventsContainer /> + )} + + {isError && !isLoading && <LogsError />} + + {!isLoading && !isError && formattedNamespaceEvents.length > 0 && ( + <div className="namespace-events-list-container"> + <div className="namespace-events-list-card"> + <Table<EventDataType> + loading={isLoading && page > 1} + columns={columns} + expandable={{ + expandedRowRender: handleExpandRow, + rowExpandable: (record): boolean => record.body !== 'Not Expandable', + expandIcon: handleExpandRowIcon, + }} + dataSource={formattedNamespaceEvents} + pagination={false} + rowKey={(record): string => record.id} + /> + </div> + </div> + )} + + {!isError && formattedNamespaceEvents.length > 0 && ( + <div className="namespace-events-footer"> + <Button + className="namespace-events-footer-button periscope-btn ghost" + type="link" + onClick={handlePrev} + disabled={page === 1 || isFetching || isLoading} + > + {!isFetching && <ChevronLeft size={14} />} + Prev + </Button> + + <Button + className="namespace-events-footer-button periscope-btn ghost" + type="link" + onClick={handleNext} + disabled={hasReachedEndOfEvents || isFetching || isLoading} + > + Next + {!isFetching && <ChevronRight size={14} />} + </Button> + + {(isFetching || isLoading) && ( + <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> + )} + </div> + )} + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx new file mode 100644 index 0000000000..c64c048277 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx @@ -0,0 +1,16 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; +import { Ghost } from 'lucide-react'; + +const { Text } = Typography; + +export default function NoEventsContainer(): React.ReactElement { + return ( + <div className="no-logs-found"> + <Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this + namespace in the selected time range. + </Text> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts new file mode 100644 index 0000000000..4016dbeaa1 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts @@ -0,0 +1,65 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; + +export const getNamespacesEventsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts new file mode 100644 index 0000000000..2ce2229591 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts @@ -0,0 +1,3 @@ +import NamespaceEvents from './NamespaceEvents'; + +export default NamespaceEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss new file mode 100644 index 0000000000..4a7171344d --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss @@ -0,0 +1,133 @@ +.namespace-logs-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .namespace-logs-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .namespace-logs { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } +} + +.namespace-logs-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.namespace-logs-list-card { + width: 100%; + margin-top: 12px; + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx new file mode 100644 index 0000000000..36175a962d --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx @@ -0,0 +1,214 @@ +/* eslint-disable no-nested-ternary */ +import './NamespaceLogs.styles.scss'; + +import { Card } from 'antd'; +import RawLogView from 'components/Logs/RawLogView'; +import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import LogsError from 'container/LogsError/LogsError'; +import { LogsLoading } from 'container/LogsLoading/LogsLoading'; +import { FontSize } from 'container/OptionsMenu/types'; +import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { isEqual } from 'lodash-es'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { Virtuoso } from 'react-virtuoso'; +import { ILog } from 'types/api/logs/log'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { v4 } from 'uuid'; + +import { QUERY_KEYS } from '../constants'; +import { getNamespaceLogsQueryPayload } from './constants'; +import NoLogsContainer from './NoLogsContainer'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; + filters: IBuilderQuery['filters']; +} + +function PodLogs({ + timeRange, + handleChangeLogFilters, + filters, +}: Props): JSX.Element { + const [logs, setLogs] = useState<ILog[]>([]); + const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); + const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); + const [resetLogsList, setResetLogsList] = useState<boolean>(false); + + useEffect(() => { + const newRestFilters = filters.items.filter( + (item) => + item.key?.key !== 'id' && + ![QUERY_KEYS.K8S_NAMESPACE_NAME].includes(item.key?.key ?? ''), + ); + + const areFiltersSame = isEqual(restFilters, newRestFilters); + + if (!areFiltersSame) { + setResetLogsList(true); + } + + setRestFilters(newRestFilters); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filters]); + + const queryPayload = useMemo(() => { + const basePayload = getNamespaceLogsQueryPayload( + timeRange.startTime, + timeRange.endTime, + filters, + ); + + basePayload.query.builder.queryData[0].pageSize = 100; + basePayload.query.builder.queryData[0].orderBy = [ + { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, + ]; + + return basePayload; + }, [timeRange.startTime, timeRange.endTime, filters]); + + const [isPaginating, setIsPaginating] = useState(false); + + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: ['namespaceLogs', timeRange.startTime, timeRange.endTime, filters], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + keepPreviousData: isPaginating, + }); + + useEffect(() => { + if (data?.payload?.data?.newResult?.data?.result) { + const currentData = data.payload.data.newResult.data.result; + + if (resetLogsList) { + const currentLogs: ILog[] = + currentData[0].list?.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })) || []; + + setLogs(currentLogs); + + setResetLogsList(false); + } + + if (currentData.length > 0 && currentData[0].list) { + const currentLogs: ILog[] = + currentData[0].list.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })) || []; + + setLogs((prev) => [...prev, ...currentLogs]); + } else { + setHasReachedEndOfLogs(true); + } + } + }, [data, restFilters, isPaginating, resetLogsList]); + + const getItemContent = useCallback( + (_: number, logToRender: ILog): JSX.Element => ( + <RawLogView + isReadOnly + isTextOverflowEllipsisDisabled + key={logToRender.id} + data={logToRender} + linesPerRow={5} + fontSize={FontSize.MEDIUM} + /> + ), + [], + ); + + const loadMoreLogs = useCallback(() => { + if (!logs.length) return; + + setIsPaginating(true); + const lastLog = logs[logs.length - 1]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '<', + value: lastLog.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeLogFilters(newFilters); + }, [logs, filters, handleChangeLogFilters]); + + useEffect(() => { + setIsPaginating(false); + }, [data]); + + const renderFooter = useCallback( + (): JSX.Element | null => ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {isFetching ? ( + <div className="logs-loading-skeleton"> Loading more logs ... </div> + ) : hasReachedEndOfLogs ? ( + <div className="logs-loading-skeleton"> *** End *** </div> + ) : null} + </> + ), + [isFetching, hasReachedEndOfLogs], + ); + + const renderContent = useMemo( + () => ( + <Card bordered={false} className="namespace-logs-list-card"> + <OverlayScrollbar isVirtuoso> + <Virtuoso + className="namespace-logs-virtuoso" + key="namespace-logs-virtuoso" + data={logs} + endReached={loadMoreLogs} + totalCount={logs.length} + itemContent={getItemContent} + overscan={200} + components={{ + Footer: renderFooter, + }} + /> + </OverlayScrollbar> + </Card> + ), + [logs, loadMoreLogs, getItemContent, renderFooter], + ); + + return ( + <div className="namespace-logs"> + {isLoading && <LogsLoading />} + {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {isError && !isLoading && <LogsError />} + {!isLoading && !isError && logs.length > 0 && ( + <div className="namespace-logs-list-container">{renderContent}</div> + )} + </div> + ); +} + +export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx new file mode 100644 index 0000000000..f70c8986c7 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx @@ -0,0 +1,99 @@ +import './NamespaceLogs.styles.scss'; + +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useMemo } from 'react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import NamespaceLogs from './NamespaceLogs'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; + logFilters: IBuilderQuery['filters']; + selectedInterval: Time; +} + +function NamespaceLogsDetailedView({ + timeRange, + isModalTimeSelection, + handleTimeChange, + handleChangeLogFilters, + logFilters, + selectedInterval, +}: Props): JSX.Element { + const { currentQuery } = useQueryBuilder(); + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.LOGS, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + return ( + <div className="namespace-logs-container"> + <div className="namespace-logs-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeLogFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + <NamespaceLogs + timeRange={timeRange} + handleChangeLogFilters={handleChangeLogFilters} + filters={logFilters} + /> + </div> + ); +} + +export default NamespaceLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx new file mode 100644 index 0000000000..ba16e99b05 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx @@ -0,0 +1,16 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; +import { Ghost } from 'lucide-react'; + +const { Text } = Typography; + +export default function NoLogsContainer(): React.ReactElement { + return ( + <div className="no-logs-found"> + <Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this + namespace in the selected time range. + </Text> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts new file mode 100644 index 0000000000..48d222c6dc --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts @@ -0,0 +1,65 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; + +export const getNamespaceLogsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts new file mode 100644 index 0000000000..eb3fc025c5 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts @@ -0,0 +1,3 @@ +import NamespaceLogs from './NamespaceLogsDetailedView'; + +export default NamespaceLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss new file mode 100644 index 0000000000..7425085ae6 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss @@ -0,0 +1,45 @@ +.empty-container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.namespace-metrics-container { + margin-top: 1rem; +} + +.metrics-header { + display: flex; + justify-content: flex-end; + margin-top: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); +} + +.namespace-metrics-card { + margin: 8px 0 1rem 0; + height: 300px; + padding: 10px; + + border: 1px solid var(--bg-slate-500); + + .ant-card-body { + padding: 0; + } + + .chart-container { + width: 100%; + height: 100%; + } + + .no-data-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx new file mode 100644 index 0000000000..0e3e91d703 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx @@ -0,0 +1,167 @@ +import './NamespaceMetrics.styles.scss'; + +import { Card, Col, Row, Skeleton, Typography } from 'antd'; +import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList'; +import cx from 'classnames'; +import Uplot from 'components/Uplot'; +import { ENTITY_VERSION_V4 } from 'constants/app'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { + getMetricsTableData, + MetricsTable, +} from 'container/InfraMonitoringK8s/commonUtils'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useResizeObserver } from 'hooks/useDimensions'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; +import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; +import { useMemo, useRef } from 'react'; +import { useQueries, UseQueryResult } from 'react-query'; +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { Options } from 'uplot'; + +import { getNamespaceQueryPayload, namespaceWidgetInfo } from './constants'; + +interface NamespaceMetricsProps { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + selectedInterval: Time; + namespace: K8sNamespacesData; +} + +function NamespaceMetrics({ + selectedInterval, + namespace, + timeRange, + handleTimeChange, + isModalTimeSelection, +}: NamespaceMetricsProps): JSX.Element { + const queryPayloads = useMemo( + () => + getNamespaceQueryPayload(namespace, timeRange.startTime, timeRange.endTime), + [namespace, timeRange.startTime, timeRange.endTime], + ); + + const queries = useQueries( + queryPayloads.map((payload) => ({ + queryKey: ['namespace-metrics', payload, ENTITY_VERSION_V4, 'NAMESPACE'], + queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + })), + ); + + const isDarkMode = useIsDarkMode(); + const graphRef = useRef<HTMLDivElement>(null); + const dimensions = useResizeObserver(graphRef); + + const chartData = useMemo( + () => + queries.map(({ data }) => { + const panelType = (data?.params as any)?.compositeQuery?.panelType; + return panelType === PANEL_TYPES.TABLE + ? getMetricsTableData(data) + : getUPlotChartData(data?.payload); + }), + [queries], + ); + + const options = useMemo( + () => + queries.map(({ data }, idx) => { + const panelType = (data?.params as any)?.compositeQuery?.panelType; + if (panelType === PANEL_TYPES.TABLE) { + return null; + } + return getUPlotChartOptions({ + apiResponse: data?.payload, + isDarkMode, + dimensions, + yAxisUnit: namespaceWidgetInfo[idx].yAxisUnit, + softMax: null, + softMin: null, + minTimeScale: timeRange.startTime, + maxTimeScale: timeRange.endTime, + }); + }), + [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], + ); + + const renderCardContent = ( + query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, + idx: number, + ): JSX.Element => { + if (query.isLoading) { + return <Skeleton />; + } + + if (query.error) { + const errorMessage = + (query.error as Error)?.message || 'Something went wrong'; + return <div>{errorMessage}</div>; + } + + const { panelType } = (query.data?.params as any).compositeQuery; + + return ( + <div + className={cx('chart-container', { + 'no-data-container': + !query.isLoading && !query?.data?.payload?.data?.result?.length, + })} + > + {panelType === PANEL_TYPES.TABLE ? ( + <MetricsTable + rows={chartData[idx][0].rows} + columns={chartData[idx][0].columns} + /> + ) : ( + <Uplot options={options[idx] as Options} data={chartData[idx]} /> + )} + </div> + ); + }; + + return ( + <> + <div className="metrics-header"> + <div className="metrics-datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + isModalTimeSelection={isModalTimeSelection} + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + <Row gutter={24} className="namespace-metrics-container"> + {queries.map((query, idx) => ( + <Col span={12} key={namespaceWidgetInfo[idx].title}> + <Typography.Text>{namespaceWidgetInfo[idx].title}</Typography.Text> + <Card bordered className="namespace-metrics-card" ref={graphRef}> + {renderCardContent(query, idx)} + </Card> + </Col> + ))} + </Row> + </> + ); +} + +export default NamespaceMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts new file mode 100644 index 0000000000..23c451c866 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts @@ -0,0 +1,1632 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 } from 'uuid'; + +export const namespaceWidgetInfo = [ + { + title: 'CPU Usage (cores)', + yAxisUnit: '', + }, + { + title: 'Memory Usage (bytes)', + yAxisUnit: 'bytes', + }, + { + title: 'Pods CPU (top 10)', + yAxisUnit: '', + }, + { + title: 'Pods Memory (top 10)', + yAxisUnit: 'bytes', + }, + { + title: 'Network rate', + yAxisUnit: 'binBps', + }, + { + title: 'Network errors', + yAxisUnit: '', + }, + { + title: 'StatefulSets', + }, + { + title: 'ReplicaSets', + yAxisUnit: '', + }, + { + title: 'DaemonSets', + yAxisUnit: '', + }, + { + title: 'Deployments', + yAxisUnit: '', + }, +]; + +export const getNamespaceQueryPayload = ( + namespace: K8sNamespacesData, + start: number, + end: number, +): GetQueryResultsProps[] => [ + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '47b3adae', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (avg)', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_container_cpu_request--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_container_cpu_request', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '93d2be5e', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'requests', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'min', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '795eb679', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (min)', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'min', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: '6792adbe', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (max)', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '10011298', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (avg)', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_container_memory_request--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_container_memory_request', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'ea53b656', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'request', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_working_set--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_working_set', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '674ace83', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'working set', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_rss--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_rss', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: '187dbdb3', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'rss', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'min', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'E', + filters: { + items: [ + { + id: 'a3dbf468', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (min)', + limit: null, + orderBy: [], + queryName: 'E', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'min', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'F', + filters: { + items: [ + { + id: '4b2406c2', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (max)', + limit: null, + orderBy: [], + queryName: 'F', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'c3a73f0a', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_pod_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_pod_name', + type: 'tag', + }, + ], + having: [], + legend: '{{k8s_pod_name}}', + limit: 20, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '5cad3379', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_pod_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_pod_name', + type: 'tag', + }, + ], + having: [], + legend: '{{k8s_pod_name}}', + limit: 10, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_network_io--float64--Sum--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_network_io', + type: 'Sum', + }, + aggregateOperator: 'rate', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '00f5c5e1', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'interface--string--tag--false', + isColumn: false, + isJSON: false, + key: 'interface', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'direction--string--tag--false', + isColumn: false, + isJSON: false, + key: 'direction', + type: 'tag', + }, + ], + having: [], + legend: '{{direction}} :: {{interface}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: 'k8s_pod_network_errors--float64--Sum--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_network_errors', + type: 'Sum', + }, + aggregateOperator: 'increase', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '3aa8e064', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'interface--string--tag--false', + isColumn: false, + isJSON: false, + key: 'interface', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'direction--string--tag--false', + isColumn: false, + isJSON: false, + key: 'direction', + type: 'tag', + }, + ], + having: [], + legend: '{{direction}} :: {{interface}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'increase', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_current_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_current_pods', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '5f2a55c5', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + ], + having: [], + legend: 'current', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_desired_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_desired_pods', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '13bd7a1d', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_updated_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_updated_pods', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '9d287c73', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + ], + having: [], + legend: 'updated', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_replicaset_desired--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_replicaset_desired', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '0c1e655c', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_replicaset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_replicaset_name', + type: 'tag', + }, + ], + having: [ + { + columnName: 'MAX(k8s_replicaset_desired)', + op: '>', + value: 0, + }, + ], + legend: 'desired', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_replicaset_available--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_replicaset_available', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'b2296bdb', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_replicaset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_replicaset_name', + type: 'tag', + }, + ], + having: [ + { + columnName: 'MAX(k8s_replicaset_available)', + op: '>', + value: 0, + }, + ], + legend: 'available', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_desired_scheduled_namespaces--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_desired_scheduled_namespaces', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '2964eb92', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_current_scheduled_namespaces--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_current_scheduled_namespaces', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'cd324eff', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'current', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_ready_namespaces--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_ready_namespaces', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '0416fa6f', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'ready', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_misscheduled_namespaces--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_misscheduled_namespaces', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: 'c0a126d3', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'misscheduled', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_deployment_desired--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_deployment_desired', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '9bc659c1', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_deployment_available--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_deployment_available', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'e1696631', + key: { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + op: '=', + value: namespace.namespaceName, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + ], + having: [], + legend: 'available', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [ + { + disabled: false, + expression: 'A/B', + legend: 'util %', + queryName: 'F1', + }, + ], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, +]; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts new file mode 100644 index 0000000000..7dcd578b80 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts @@ -0,0 +1,3 @@ +import NamespaceMetrics from './NamespaceMetrics'; + +export default NamespaceMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.interfaces.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.interfaces.ts new file mode 100644 index 0000000000..d24c992bfc --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.interfaces.ts @@ -0,0 +1,7 @@ +import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList'; + +export type NamespaceDetailsProps = { + namespace: K8sNamespacesData | null; + isModalTimeSelection: boolean; + onClose: () => void; +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.styles.scss new file mode 100644 index 0000000000..caad387e4b --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.styles.scss @@ -0,0 +1,247 @@ +.namespace-detail-drawer { + border-left: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); + + .ant-drawer-header { + padding: 8px 16px; + border-bottom: none; + + align-items: stretch; + + border-bottom: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + } + + .ant-drawer-close { + margin-inline-end: 0px; + } + + .ant-drawer-body { + display: flex; + flex-direction: column; + padding: 16px; + } + + .title { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .radio-button { + display: flex; + align-items: center; + justify-content: center; + padding-top: var(--padding-1); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .namespace-detail-drawer__namespace { + .namespace-details-grid { + .labels-row, + .values-row { + display: grid; + grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; + gap: 30px; + align-items: center; + } + + .labels-row { + margin-bottom: 8px; + } + + .namespace-details-metadata-label { + color: var(--text-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + } + + .namespace-details-metadata-value { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .status-tag { + margin: 0; + + &.active { + color: var(--success-500); + background: var(--success-100); + border-color: var(--success-500); + } + + &.inactive { + color: var(--error-500); + background: var(--error-100); + border-color: var(--error-500); + } + } + + .progress-container { + width: 158px; + .ant-progress { + margin: 0; + + .ant-progress-text { + font-weight: 600; + } + } + } + + .ant-card { + &.ant-card-bordered { + border: 1px solid var(--bg-slate-500) !important; + } + } + } + } + + .tabs-and-search { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; + + .action-btn { + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + } + } + + .views-tabs-container { + margin-top: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + + .views-tabs { + color: var(--text-vanilla-400); + + .view-title { + display: flex; + gap: var(--margin-2); + align-items: center; + justify-content: center; + font-size: var(--font-size-xs); + font-style: normal; + font-weight: var(--font-weight-normal); + } + + .tab { + border: 1px solid var(--bg-slate-400); + width: 114px; + } + + .tab::before { + background: var(--bg-slate-400); + } + + .selected_view { + background: var(--bg-slate-300); + color: var(--text-vanilla-100); + border: 1px solid var(--bg-slate-400); + } + + .selected_view::before { + background: var(--bg-slate-400); + } + } + + .compass-button { + width: 30px; + height: 30px; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + } + .ant-drawer-close { + padding: 0px; + } +} + +.lightMode { + .ant-drawer-header { + border-bottom: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + } + + .namespace-detail-drawer { + .title { + color: var(--text-ink-300); + } + + .namespace-detail-drawer__namespace { + .ant-typography { + color: var(--text-ink-300); + background: transparent; + } + } + + .radio-button { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .views-tabs { + .tab { + background: var(--bg-vanilla-100); + } + + .selected_view { + background: var(--bg-vanilla-300); + border: 1px solid var(--bg-slate-300); + color: var(--text-ink-400); + } + + .selected_view::before { + background: var(--bg-vanilla-300); + border-left: 1px solid var(--bg-slate-300); + } + } + + .compass-button { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .tabs-and-search { + .action-btn { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx new file mode 100644 index 0000000000..791e55efed --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -0,0 +1,550 @@ +/* eslint-disable sonarjs/no-identical-functions */ +import './NamespaceDetails.styles.scss'; + +import { Color, Spacing } from '@signozhq/design-tokens'; +import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; +import { RadioChangeEvent } from 'antd/lib'; +import logEvent from 'api/common/logEvent'; +import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; +import { QueryParams } from 'constants/query'; +import { + initialQueryBuilderFormValuesMap, + initialQueryState, +} from 'constants/queryBuilder'; +import ROUTES from 'constants/routes'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; +import GetMinMax from 'lib/getMinMax'; +import { + BarChart2, + ChevronsLeftRight, + Compass, + DraftingCompass, + ScrollText, + X, +} from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { + LogsAggregatorOperator, + TracesAggregatorOperator, +} from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { v4 as uuidv4 } from 'uuid'; + +import { QUERY_KEYS } from './constants'; +import NamespaceEvents from './Events'; +import NamespaceLogs from './Logs'; +import NamespaceMetrics from './Metrics'; +import { NamespaceDetailsProps } from './NamespaceDetails.interfaces'; +import NamespaceTraces from './Traces'; + +function NamespaceDetails({ + namespace, + onClose, + isModalTimeSelection, +}: NamespaceDetailsProps): JSX.Element { + const { maxTime, minTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [ + minTime, + ]); + const endMs = useMemo(() => Math.floor(Number(maxTime) / 1000000000), [ + maxTime, + ]); + + const urlQuery = useUrlQuery(); + + const [modalTimeRange, setModalTimeRange] = useState(() => ({ + startTime: startMs, + endTime: endMs, + })); + + const [selectedInterval, setSelectedInterval] = useState<Time>( + selectedTime as Time, + ); + + const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS); + const isDarkMode = useIsDarkMode(); + + const initialFilters = useMemo( + () => ({ + op: 'AND', + items: [ + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_NAMESPACE_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s_namespace_name--string--resource--false', + }, + op: '=', + value: namespace?.namespaceName || '', + }, + ], + }), + [namespace?.namespaceName], + ); + + const initialEventsFilters = useMemo( + () => ({ + op: 'AND', + items: [ + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_OBJECT_KIND, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s.object.kind--string--resource--false', + }, + op: '=', + value: 'Namespace', + }, + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_OBJECT_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s.object.name--string--resource--false', + }, + op: '=', + value: namespace?.namespaceName || '', + }, + ], + }), + [namespace?.namespaceName], + ); + + const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( + initialFilters, + ); + + const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>( + initialFilters, + ); + + const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( + initialEventsFilters, + ); + + useEffect(() => { + logEvent('Infra Monitoring: Namespaces list details page visited', { + namespace: namespace?.namespaceName, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setLogFilters(initialFilters); + setTracesFilters(initialFilters); + setEventsFilters(initialEventsFilters); + }, [initialFilters, initialEventsFilters]); + + useEffect(() => { + setSelectedInterval(selectedTime as Time); + + if (selectedTime !== 'custom') { + const { maxTime, minTime } = GetMinMax(selectedTime); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + }, [selectedTime, minTime, maxTime]); + + const handleTabChange = (e: RadioChangeEvent): void => { + setSelectedView(e.target.value); + }; + + const handleTimeChange = useCallback( + (interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => { + setSelectedInterval(interval as Time); + + if (interval === 'custom' && dateTimeRange) { + setModalTimeRange({ + startTime: Math.floor(dateTimeRange[0] / 1000), + endTime: Math.floor(dateTimeRange[1] / 1000), + }); + } else { + const { maxTime, minTime } = GetMinMax(interval); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + + logEvent('Infra Monitoring: Namespaces list details time updated', { + namespace: namespace?.namespaceName, + interval, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeLogFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setLogFilters((prevFilters) => { + const primaryFilters = prevFilters.items.filter((item) => + [QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + item.key?.key ?? '', + ), + ); + const paginationFilter = value.items.find((item) => item.key?.key === 'id'); + const newFilters = value.items.filter( + (item) => + item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_NAMESPACE_NAME, + ); + + logEvent('Infra Monitoring: Namespaces list details logs filters applied', { + namespace: namespace?.namespaceName, + }); + + return { + op: 'AND', + items: [ + ...primaryFilters, + ...newFilters, + ...(paginationFilter ? [paginationFilter] : []), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeTracesFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setTracesFilters((prevFilters) => { + const primaryFilters = prevFilters.items.filter((item) => + [QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( + item.key?.key ?? '', + ), + ); + + logEvent( + 'Infra Monitoring: Namespaces list details traces filters applied', + { + namespace: namespace?.namespaceName, + }, + ); + + return { + op: 'AND', + items: [ + ...primaryFilters, + ...value.items.filter( + (item) => item.key?.key !== QUERY_KEYS.K8S_NAMESPACE_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeEventsFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setEventsFilters((prevFilters) => { + const namespaceKindFilter = prevFilters.items.find( + (item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND, + ); + const namespaceNameFilter = prevFilters.items.find( + (item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME, + ); + + logEvent( + 'Infra Monitoring: Namespaces list details events filters applied', + { + namespace: namespace?.namespaceName, + }, + ); + + return { + op: 'AND', + items: [ + namespaceKindFilter, + namespaceNameFilter, + ...value.items.filter( + (item) => + item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && + item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleExplorePagesRedirect = (): void => { + if (selectedInterval !== 'custom') { + urlQuery.set(QueryParams.relativeTime, selectedInterval); + } else { + urlQuery.delete(QueryParams.relativeTime); + urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString()); + urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString()); + } + + logEvent('Infra Monitoring: Namespaces list details explore clicked', { + namespace: namespace?.namespaceName, + view: selectedView, + }); + + if (selectedView === VIEW_TYPES.LOGS) { + const filtersWithoutPagination = { + ...logFilters, + items: logFilters.items.filter((item) => item.key?.key !== 'id'), + }; + + const compositeQuery = { + ...initialQueryState, + queryType: 'builder', + builder: { + ...initialQueryState.builder, + queryData: [ + { + ...initialQueryBuilderFormValuesMap.logs, + aggregateOperator: LogsAggregatorOperator.NOOP, + filters: filtersWithoutPagination, + }, + ], + }, + }; + + urlQuery.set('compositeQuery', JSON.stringify(compositeQuery)); + + window.open( + `${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`, + '_blank', + ); + } else if (selectedView === VIEW_TYPES.TRACES) { + const compositeQuery = { + ...initialQueryState, + queryType: 'builder', + builder: { + ...initialQueryState.builder, + queryData: [ + { + ...initialQueryBuilderFormValuesMap.traces, + aggregateOperator: TracesAggregatorOperator.NOOP, + filters: tracesFilters, + }, + ], + }, + }; + + urlQuery.set('compositeQuery', JSON.stringify(compositeQuery)); + + window.open( + `${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`, + '_blank', + ); + } + }; + + const handleClose = (): void => { + setSelectedInterval(selectedTime as Time); + + if (selectedTime !== 'custom') { + const { maxTime, minTime } = GetMinMax(selectedTime); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + setSelectedView(VIEW_TYPES.METRICS); + onClose(); + }; + + return ( + <Drawer + width="70%" + title={ + <> + <Divider type="vertical" /> + <Typography.Text className="title"> + {namespace?.namespaceName} + </Typography.Text> + </> + } + placement="right" + onClose={handleClose} + open={!!namespace} + style={{ + overscrollBehavior: 'contain', + background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, + }} + className="namespace-detail-drawer" + destroyOnClose + closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} + > + {namespace && ( + <> + <div className="namespace-detail-drawer__namespace"> + <div className="namespace-details-grid"> + <div className="labels-row"> + <Typography.Text + type="secondary" + className="namespace-details-metadata-label" + > + Namespace Name + </Typography.Text> + <Typography.Text + type="secondary" + className="namespace-details-metadata-label" + > + Cluster Name + </Typography.Text> + </div> + <div className="values-row"> + <Typography.Text className="namespace-details-metadata-value"> + <Tooltip title={namespace.namespaceName}> + {namespace.namespaceName} + </Tooltip> + </Typography.Text> + <Typography.Text className="namespace-details-metadata-value"> + <Tooltip title="Cluster name"> + {namespace.meta.k8s_cluster_name} + </Tooltip> + </Typography.Text> + </div> + </div> + </div> + + <div className="views-tabs-container"> + <Radio.Group + className="views-tabs" + onChange={handleTabChange} + value={selectedView} + > + <Radio.Button + className={ + // eslint-disable-next-line sonarjs/no-duplicate-string + selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.METRICS} + > + <div className="view-title"> + <BarChart2 size={14} /> + Metrics + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.LOGS} + > + <div className="view-title"> + <ScrollText size={14} /> + Logs + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.TRACES} + > + <div className="view-title"> + <DraftingCompass size={14} /> + Traces + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.EVENTS} + > + <div className="view-title"> + <ChevronsLeftRight size={14} /> + Events + </div> + </Radio.Button> + </Radio.Group> + + {(selectedView === VIEW_TYPES.LOGS || + selectedView === VIEW_TYPES.TRACES) && ( + <Button + icon={<Compass size={18} />} + className="compass-button" + onClick={handleExplorePagesRedirect} + /> + )} + </div> + {selectedView === VIEW_TYPES.METRICS && ( + <NamespaceMetrics + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + selectedInterval={selectedInterval} + namespace={namespace} + /> + )} + {selectedView === VIEW_TYPES.LOGS && ( + <NamespaceLogs + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + handleChangeLogFilters={handleChangeLogFilters} + logFilters={logFilters} + selectedInterval={selectedInterval} + /> + )} + {selectedView === VIEW_TYPES.TRACES && ( + <NamespaceTraces + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + handleChangeTracesFilters={handleChangeTracesFilters} + tracesFilters={tracesFilters} + selectedInterval={selectedInterval} + /> + )} + {selectedView === VIEW_TYPES.EVENTS && ( + <NamespaceEvents + timeRange={modalTimeRange} + handleChangeEventFilters={handleChangeEventsFilters} + filters={eventsFilters} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + selectedInterval={selectedInterval} + /> + )} + </> + )} + </Drawer> + ); +} + +export default NamespaceDetails; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss new file mode 100644 index 0000000000..e3d1e6bf1d --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss @@ -0,0 +1,193 @@ +.namespace-metric-traces { + margin-top: 1rem; + + .namespace-metric-traces-header { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + } + + .namespace-metric-traces-table { + .ant-table-content { + overflow: hidden !important; + } + + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgba(171, 189, 255, 0.01); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.hostname-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgba(171, 189, 255, 0.01); + } + + .ant-table-cell:has(.hostname-column-value) { + background: var(--bg-ink-400); + } + + .hostname-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-table-container::after { + content: none; + } + } +} + +.lightMode { + .host-metric-traces-header { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } + } + + .host-metric-traces-table { + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-vanilla-300); + + .ant-table-thead > tr > th { + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .ant-table-thead > tr > th:has(.hostname-column-header) { + background: var(--bg-vanilla-100); + } + + .ant-table-cell { + background: var(--bg-vanilla-100); + color: var(--bg-ink-500); + } + + .ant-table-cell:has(.hostname-column-value) { + background: var(--bg-vanilla-100); + } + + .hostname-column-value { + color: var(--bg-ink-300); + } + + .ant-table-tbody > tr:hover > td { + background: rgba(0, 0, 0, 0.04); + } + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx new file mode 100644 index 0000000000..b535c1d5c6 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx @@ -0,0 +1,199 @@ +import './NamespaceTraces.styles.scss'; + +import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; +import { ResizeTable } from 'components/ResizeTable'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { QueryParams } from 'constants/query'; +import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; +import NoLogs from 'container/NoLogs/NoLogs'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import { ErrorText } from 'container/TimeSeriesView/styles'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import TraceExplorerControls from 'container/TracesExplorer/Controls'; +import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; +import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { Pagination } from 'hooks/queryPagination'; +import useUrlQueryData from 'hooks/useUrlQueryData'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import { getNamespaceTracesQueryPayload, selectedColumns } from './constants'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; + tracesFilters: IBuilderQuery['filters']; + selectedInterval: Time; +} + +function NamespaceTraces({ + timeRange, + isModalTimeSelection, + handleTimeChange, + handleChangeTracesFilters, + tracesFilters, + selectedInterval, +}: Props): JSX.Element { + const [traces, setTraces] = useState<any[]>([]); + const [offset] = useState<number>(0); + + const { currentQuery } = useQueryBuilder(); + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.TRACES, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( + QueryParams.pagination, + ); + + const queryPayload = useMemo( + () => + getNamespaceTracesQueryPayload( + timeRange.startTime, + timeRange.endTime, + paginationQueryData?.offset || offset, + tracesFilters, + ), + [ + timeRange.startTime, + timeRange.endTime, + offset, + tracesFilters, + paginationQueryData, + ], + ); + + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: [ + 'hostMetricTraces', + timeRange.startTime, + timeRange.endTime, + offset, + tracesFilters, + DEFAULT_ENTITY_VERSION, + paginationQueryData, + ], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + }); + + const traceListColumns = getListColumns(selectedColumns); + + useEffect(() => { + if (data?.payload?.data?.newResult?.data?.result) { + const currentData = data.payload.data.newResult.data.result; + if (currentData.length > 0 && currentData[0].list) { + if (offset === 0) { + setTraces(currentData[0].list ?? []); + } else { + setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); + } + } + } + }, [data, offset]); + + const isDataEmpty = + !isLoading && !isFetching && !isError && traces.length === 0; + const hasAdditionalFilters = tracesFilters.items.length > 1; + + const totalCount = + data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; + + return ( + <div className="namespace-metric-traces"> + <div className="namespace-metric-traces-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeTracesFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + + {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} + + {isLoading && traces.length === 0 && <TracesLoading />} + + {isDataEmpty && !hasAdditionalFilters && ( + <NoLogs dataSource={DataSource.TRACES} /> + )} + + {isDataEmpty && hasAdditionalFilters && ( + <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> + )} + + {!isError && traces.length > 0 && ( + <div className="namespace-traces-table"> + <TraceExplorerControls + isLoading={isFetching} + totalCount={totalCount} + perPageOptions={PER_PAGE_OPTIONS} + showSizeChanger={false} + /> + <ResizeTable + tableLayout="fixed" + pagination={false} + scroll={{ x: true }} + loading={isFetching} + dataSource={traces} + columns={traceListColumns} + /> + </div> + )} + </div> + ); +} + +export default NamespaceTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts new file mode 100644 index 0000000000..b29db78f45 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts @@ -0,0 +1,200 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { + BaseAutocompleteData, + DataTypes, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { nanoToMilli } from 'utils/timeUtils'; + +export const columns = [ + { + dataIndex: 'timestamp', + key: 'timestamp', + title: 'Timestamp', + width: 200, + render: (timestamp: string): string => new Date(timestamp).toLocaleString(), + }, + { + title: 'Service Name', + dataIndex: ['data', 'serviceName'], + key: 'serviceName-string-tag', + width: 150, + }, + { + title: 'Name', + dataIndex: ['data', 'name'], + key: 'name-string-tag', + width: 145, + }, + { + title: 'Duration', + dataIndex: ['data', 'durationNano'], + key: 'durationNano-float64-tag', + width: 145, + render: (duration: number): string => `${nanoToMilli(duration)}ms`, + }, + { + title: 'HTTP Method', + dataIndex: ['data', 'httpMethod'], + key: 'httpMethod-string-tag', + width: 145, + }, + { + title: 'Status Code', + dataIndex: ['data', 'responseStatusCode'], + key: 'responseStatusCode-string-tag', + width: 145, + }, +]; + +export const selectedColumns: BaseAutocompleteData[] = [ + { + key: 'timestamp', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'serviceName', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'name', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'durationNano', + dataType: DataTypes.Float64, + type: 'tag', + isColumn: true, + }, + { + key: 'httpMethod', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'responseStatusCode', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, +]; + +export const getNamespaceTracesQueryPayload = ( + start: number, + end: number, + offset = 0, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + query: { + promql: [], + clickhouse_sql: [], + builder: { + queryData: [ + { + dataSource: DataSource.TRACES, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.EMPTY, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + }, + ], + queryFormulas: [], + }, + id: '572f1d91-6ac0-46c0-b726-c21488b34434', + queryType: EQueryType.QUERY_BUILDER, + }, + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + start, + end, + params: { + dataSource: DataSource.TRACES, + }, + tableParams: { + pagination: { + limit: 10, + offset, + }, + selectColumns: [ + { + key: 'serviceName', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'serviceName--string--tag--true', + isIndexed: false, + }, + { + key: 'name', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'name--string--tag--true', + isIndexed: false, + }, + { + key: 'durationNano', + dataType: 'float64', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'durationNano--float64--tag--true', + isIndexed: false, + }, + { + key: 'httpMethod', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'httpMethod--string--tag--true', + isIndexed: false, + }, + { + key: 'responseStatusCode', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'responseStatusCode--string--tag--true', + isIndexed: false, + }, + ], + }, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts new file mode 100644 index 0000000000..6aa2bfd70b --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts @@ -0,0 +1,3 @@ +import NamespaceTraces from './NamespaceTraces'; + +export default NamespaceTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts new file mode 100644 index 0000000000..70bdcbf74d --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts @@ -0,0 +1,6 @@ +export const QUERY_KEYS = { + K8S_OBJECT_KIND: 'k8s.object.kind', + K8S_OBJECT_NAME: 'k8s.object.name', + K8S_NAMESPACE_NAME: 'k8s.namespace.name', + K8S_CLUSTER_NAME: 'k8s.cluster.name', +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/index.ts new file mode 100644 index 0000000000..0dc1f3330a --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/index.ts @@ -0,0 +1,3 @@ +import NamespaceDetails from './NamespaceDetails'; + +export default NamespaceDetails; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx new file mode 100644 index 0000000000..b0ba92d6d9 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx @@ -0,0 +1,168 @@ +import { Color } from '@signozhq/design-tokens'; +import { Tag } from 'antd'; +import { ColumnType } from 'antd/es/table'; +import { + K8sNamespacesData, + K8sNamespacesListPayload, +} from 'api/infraMonitoring/getK8sNamespacesList'; +import { Group } from 'lucide-react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; + +import { formatBytes, ValidateColumnValueWrapper } from '../commonUtils'; +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Namespace Name', + value: 'namespaceName', + id: 'namespaceName', + canRemove: false, + }, + { + label: 'Cluster Name', + value: 'clusterName', + id: 'clusterName', + canRemove: false, + }, + { + label: 'CPU Utilization (cores)', + value: 'cpu', + id: 'cpu', + canRemove: false, + }, + { + label: 'Memory Utilization (bytes)', + value: 'memory', + id: 'memory', + canRemove: false, + }, +]; + +export interface K8sNamespacesRowData { + key: string; + namespaceUID: string; + namespaceName: string; + clusterName: string; + cpu: React.ReactNode; + memory: React.ReactNode; + groupedByMeta?: any; +} + +const namespaceGroupColumnConfig = { + title: ( + <div className="column-header pod-group-header"> + <Group size={14} /> NAMESPACE GROUP + </div> + ), + dataIndex: 'namespaceGroup', + key: 'namespaceGroup', + ellipsis: true, + width: 150, + align: 'left', + sorter: false, +}; + +export const getK8sNamespacesListQuery = (): K8sNamespacesListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +const columnsConfig = [ + { + title: <div className="column-header-left">Namespace Name</div>, + dataIndex: 'namespaceName', + key: 'namespaceName', + ellipsis: true, + width: 120, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">Cluster Name</div>, + dataIndex: 'clusterName', + key: 'clusterName', + ellipsis: true, + width: 120, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">CPU Usage (cores)</div>, + dataIndex: 'cpu', + key: 'cpu', + width: 100, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">Mem Usage</div>, + dataIndex: 'memory', + key: 'memory', + width: 80, + sorter: true, + align: 'left', + }, +]; + +export const getK8sNamespacesListColumns = ( + groupBy: IBuilderQuery['groupBy'], +): ColumnType<K8sNamespacesRowData>[] => { + if (groupBy.length > 0) { + const filteredColumns = [...columnsConfig].filter( + (column) => column.key !== 'namespaceName' && column.key !== 'clusterName', + ); + filteredColumns.unshift(namespaceGroupColumnConfig); + return filteredColumns as ColumnType<K8sNamespacesRowData>[]; + } + + return columnsConfig as ColumnType<K8sNamespacesRowData>[]; +}; + +const getGroupByEle = ( + namespace: K8sNamespacesData, + groupBy: IBuilderQuery['groupBy'], +): React.ReactNode => { + const groupByValues: string[] = []; + + groupBy.forEach((group) => { + groupByValues.push(namespace.meta[group.key as keyof typeof namespace.meta]); + }); + + return ( + <div className="pod-group"> + {groupByValues.map((value) => ( + <Tag key={value} color={Color.BG_SLATE_400} className="pod-group-tag-item"> + {value === '' ? '<no-value>' : value} + </Tag> + ))} + </div> + ); +}; + +export const formatDataForTable = ( + data: K8sNamespacesData[], + groupBy: IBuilderQuery['groupBy'], +): K8sNamespacesRowData[] => + data.map((namespace) => ({ + key: namespace.namespaceName, + namespaceUID: namespace.namespaceName, + namespaceName: namespace.namespaceName, + clusterName: namespace.meta.k8s_cluster_name, + cpu: ( + <ValidateColumnValueWrapper value={namespace.cpuUsage}> + {namespace.cpuUsage} + </ValidateColumnValueWrapper> + ), + memory: ( + <ValidateColumnValueWrapper value={namespace.memoryUsage}> + {formatBytes(namespace.memoryUsage)} + </ValidateColumnValueWrapper> + ), + namespaceGroup: getGroupByEle(namespace, groupBy), + meta: namespace.meta, + ...namespace.meta, + groupedByMeta: namespace.meta, + })); diff --git a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx index e1fb2bbf4b..e1b588b4f7 100644 --- a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx @@ -3,7 +3,7 @@ import { Color } from '@signozhq/design-tokens'; import { Tooltip, Typography } from 'antd'; -import { ColumnsType } from 'antd/es/table'; +import Table, { ColumnsType } from 'antd/es/table'; import { Progress } from 'antd/lib'; import { ResizeTable } from 'components/ResizeTable'; import FieldRenderer from 'container/LogDetailedView/FieldRenderer'; @@ -173,3 +173,60 @@ export function EventContents({ /> ); } + +export const getMetricsTableData = (data: any): any[] => { + if (data?.params && data?.payload?.data?.result?.length) { + const rowsData = (data?.payload.data.result[0] as any).table.rows; + const columnsData = (data?.payload.data.result[0] as any).table.columns; + const builderQueries = data.params?.compositeQuery?.builderQueries; + const columns = columnsData.map((columnData: any) => { + console.log({ columnData }); + if (columnData.isValueColumn) { + return { + key: columnData.name, + label: builderQueries[columnData.name].legend, + isValueColumn: true, + }; + } + return { + key: columnData.name, + label: columnData.name, + isValueColumn: false, + }; + }); + + const rows = rowsData.map((rowData: any) => rowData.data); + return [{ rows, columns }]; + } + return [{ rows: [], columns: [] }]; +}; + +export function MetricsTable({ + rows, + columns, +}: { + rows: any[]; + columns: any[]; +}): JSX.Element { + const columnsData = columns.map((col: any) => ({ + title: <Tooltip title={col.label}>{col.label}</Tooltip>, + dataIndex: col.key, + key: col.key, + sorter: false, + ellipsis: true, + render: (value: string) => <Tooltip title={value}>{value}</Tooltip>, + })); + + return ( + <div className="metrics-table"> + <Table + dataSource={rows} + columns={columnsData} + tableLayout="fixed" + pagination={{ pageSize: 10, showSizeChanger: false }} + scroll={{ y: 200 }} + sticky + /> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/constants.ts b/frontend/src/container/InfraMonitoringK8s/constants.ts index da6a9dab03..c04ae05c76 100644 --- a/frontend/src/container/InfraMonitoringK8s/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/constants.ts @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/no-duplicate-string */ import { FiltersType, IQuickFiltersConfig, @@ -208,7 +209,7 @@ export const NodesQuickFiltersConfig: IQuickFiltersConfig[] = [ export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [ { type: FiltersType.CHECKBOX, - title: 'Namespace', + title: 'Namespace Name', attributeKey: { key: 'k8s_namespace_name', dataType: DataTypes.String, @@ -216,6 +217,24 @@ export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [ isColumn: false, isJSON: false, }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, + defaultOpen: true, + }, + { + type: FiltersType.CHECKBOX, + title: 'Cluster Name', + attributeKey: { + key: 'k8s_cluster_name', + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, defaultOpen: true, }, ]; @@ -223,14 +242,17 @@ export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [ export const ClustersQuickFiltersConfig: IQuickFiltersConfig[] = [ { type: FiltersType.CHECKBOX, - title: 'Cluster', + title: 'Cluster Name', attributeKey: { - key: 'k8s.cluster.name', + key: 'k8s_cluster_name', dataType: DataTypes.String, type: 'resource', isColumn: false, isJSON: false, }, + aggregateOperator: 'noop', + aggregateAttribute: 'k8s_pod_cpu_utilization', + dataSource: DataSource.METRICS, defaultOpen: true, }, ]; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sNamespacesList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sNamespacesList.ts new file mode 100644 index 0000000000..24763b4657 --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sNamespacesList.ts @@ -0,0 +1,48 @@ +import { + getK8sNamespacesList, + K8sNamespacesListPayload, + K8sNamespacesListResponse, +} from 'api/infraMonitoring/getK8sNamespacesList'; +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 UseGetK8sNamespacesList = ( + requestData: K8sNamespacesListPayload, + options?: UseQueryOptions< + SuccessResponse<K8sNamespacesListResponse> | ErrorResponse, + Error + >, + headers?: Record<string, string>, +) => UseQueryResult< + SuccessResponse<K8sNamespacesListResponse> | ErrorResponse, + Error +>; + +export const useGetK8sNamespacesList: UseGetK8sNamespacesList = ( + requestData, + options, + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_NAMESPACE_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery< + SuccessResponse<K8sNamespacesListResponse> | ErrorResponse, + Error + >({ + queryFn: ({ signal }) => getK8sNamespacesList(requestData, signal, headers), + ...options, + queryKey, + }); +}; From 077e2cb9b4e54ffd9f0b64c48be5d1a085dd2ec3 Mon Sep 17 00:00:00 2001 From: Amlan Kumar Nandy <45410599+amlannandy@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:46:32 +0530 Subject: [PATCH 06/20] feat: clusters implementation in k8s infra monitoring (#6628) --- .../api/infraMonitoring/getK8sClustersList.ts | 64 + .../ClusterDetails.interfaces.ts | 7 + .../ClusterDetails/ClusterDetails.styles.scss | 247 +++ .../ClusterDetails/ClusterDetails.tsx | 538 ++++++ .../Events/ClusterEvents.styles.scss | 289 ++++ .../ClusterDetails/Events/ClusterEvents.tsx | 360 ++++ .../Events/NoEventsContainer.tsx | 16 + .../ClusterDetails/Events/constants.ts | 65 + .../Clusters/ClusterDetails/Events/index.ts | 3 + .../Logs/ClusterLogs.styles.scss | 133 ++ .../ClusterDetails/Logs/ClusterLogs.tsx | 214 +++ .../Logs/ClusterLogsDetailedView.tsx | 99 ++ .../ClusterDetails/Logs/NoLogsContainer.tsx | 16 + .../Clusters/ClusterDetails/Logs/constants.ts | 65 + .../Clusters/ClusterDetails/Logs/index.ts | 3 + .../Metrics/ClusterMetrics.styles.scss | 45 + .../ClusterDetails/Metrics/ClusterMetrics.tsx | 166 ++ .../ClusterDetails/Metrics/constants.ts | 1530 +++++++++++++++++ .../Clusters/ClusterDetails/Metrics/index.ts | 3 + .../Traces/ClusterTraces.styles.scss | 193 +++ .../ClusterDetails/Traces/ClusterTraces.tsx | 199 +++ .../ClusterDetails/Traces/constants.ts | 200 +++ .../Clusters/ClusterDetails/Traces/index.ts | 3 + .../Clusters/ClusterDetails/constants.ts | 5 + .../Clusters/ClusterDetails/index.ts | 3 + .../Clusters/K8sClustersList.styles.scss | 17 + .../Clusters/K8sClustersList.tsx | 501 ++++++ .../InfraMonitoringK8s/Clusters/utils.tsx | 195 +++ .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 56 +- .../InfraMonitoringK8s/commonUtils.tsx | 3 + .../infraMonitoring/useGetK8sClustersList.ts | 54 + 31 files changed, 5271 insertions(+), 21 deletions(-) create mode 100644 frontend/src/api/infraMonitoring/getK8sClustersList.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sClustersList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sClustersList.ts b/frontend/src/api/infraMonitoring/getK8sClustersList.ts new file mode 100644 index 0000000000..f799676568 --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sClustersList.ts @@ -0,0 +1,64 @@ +import { ApiBaseInstance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sClustersListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sClustersData { + clusterUID: string; + cpuUsage: number; + cpuAllocatable: number; + memoryUsage: number; + memoryAllocatable: number; + meta: { + k8s_cluster_name: string; + k8s_cluster_uid: string; + }; +} + +export interface K8sClustersListResponse { + status: string; + data: { + type: string; + records: K8sClustersData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sClustersList = async ( + props: K8sClustersListPayload, + signal?: AbortSignal, + headers?: Record<string, string>, +): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post('/clusters/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.interfaces.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.interfaces.ts new file mode 100644 index 0000000000..8126d50fba --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.interfaces.ts @@ -0,0 +1,7 @@ +import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList'; + +export type ClusterDetailsProps = { + cluster: K8sClustersData | null; + isModalTimeSelection: boolean; + onClose: () => void; +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss new file mode 100644 index 0000000000..52309b5da5 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss @@ -0,0 +1,247 @@ +.cluster-detail-drawer { + border-left: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); + + .ant-drawer-header { + padding: 8px 16px; + border-bottom: none; + + align-items: stretch; + + border-bottom: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + } + + .ant-drawer-close { + margin-inline-end: 0px; + } + + .ant-drawer-body { + display: flex; + flex-direction: column; + padding: 16px; + } + + .title { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .radio-button { + display: flex; + align-items: center; + justify-content: center; + padding-top: var(--padding-1); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .cluster-detail-drawer__cluster { + .cluster-details-grid { + .labels-row, + .values-row { + display: grid; + grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; + gap: 30px; + align-items: center; + } + + .labels-row { + margin-bottom: 8px; + } + + .cluster-details-metadata-label { + color: var(--text-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + } + + .cluster-details-metadata-value { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .status-tag { + margin: 0; + + &.active { + color: var(--success-500); + background: var(--success-100); + border-color: var(--success-500); + } + + &.inactive { + color: var(--error-500); + background: var(--error-100); + border-color: var(--error-500); + } + } + + .progress-container { + width: 158px; + .ant-progress { + margin: 0; + + .ant-progress-text { + font-weight: 600; + } + } + } + + .ant-card { + &.ant-card-bordered { + border: 1px solid var(--bg-slate-500) !important; + } + } + } + } + + .tabs-and-search { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; + + .action-btn { + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + } + } + + .views-tabs-container { + margin-top: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + + .views-tabs { + color: var(--text-vanilla-400); + + .view-title { + display: flex; + gap: var(--margin-2); + align-items: center; + justify-content: center; + font-size: var(--font-size-xs); + font-style: normal; + font-weight: var(--font-weight-normal); + } + + .tab { + border: 1px solid var(--bg-slate-400); + width: 114px; + } + + .tab::before { + background: var(--bg-slate-400); + } + + .selected_view { + background: var(--bg-slate-300); + color: var(--text-vanilla-100); + border: 1px solid var(--bg-slate-400); + } + + .selected_view::before { + background: var(--bg-slate-400); + } + } + + .compass-button { + width: 30px; + height: 30px; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + } + .ant-drawer-close { + padding: 0px; + } +} + +.lightMode { + .ant-drawer-header { + border-bottom: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + } + + .cluster-detail-drawer { + .title { + color: var(--text-ink-300); + } + + .cluster-detail-drawer__cluster { + .ant-typography { + color: var(--text-ink-300); + background: transparent; + } + } + + .radio-button { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .views-tabs { + .tab { + background: var(--bg-vanilla-100); + } + + .selected_view { + background: var(--bg-vanilla-300); + border: 1px solid var(--bg-slate-300); + color: var(--text-ink-400); + } + + .selected_view::before { + background: var(--bg-vanilla-300); + border-left: 1px solid var(--bg-slate-300); + } + } + + .compass-button { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .tabs-and-search { + .action-btn { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx new file mode 100644 index 0000000000..c044948d06 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -0,0 +1,538 @@ +/* eslint-disable sonarjs/no-identical-functions */ +import './ClusterDetails.styles.scss'; + +import { Color, Spacing } from '@signozhq/design-tokens'; +import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; +import { RadioChangeEvent } from 'antd/lib'; +import logEvent from 'api/common/logEvent'; +import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; +import { QueryParams } from 'constants/query'; +import { + initialQueryBuilderFormValuesMap, + initialQueryState, +} from 'constants/queryBuilder'; +import ROUTES from 'constants/routes'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; +import GetMinMax from 'lib/getMinMax'; +import { + BarChart2, + ChevronsLeftRight, + Compass, + DraftingCompass, + ScrollText, + X, +} from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { + LogsAggregatorOperator, + TracesAggregatorOperator, +} from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { v4 as uuidv4 } from 'uuid'; + +import { ClusterDetailsProps } from './ClusterDetails.interfaces'; +import { QUERY_KEYS } from './constants'; +import ClusterEvents from './Events'; +import ClusterLogs from './Logs'; +import ClusterMetrics from './Metrics'; +import ClusterTraces from './Traces'; + +function ClusterDetails({ + cluster, + onClose, + isModalTimeSelection, +}: ClusterDetailsProps): JSX.Element { + const { maxTime, minTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [ + minTime, + ]); + const endMs = useMemo(() => Math.floor(Number(maxTime) / 1000000000), [ + maxTime, + ]); + + const urlQuery = useUrlQuery(); + + const [modalTimeRange, setModalTimeRange] = useState(() => ({ + startTime: startMs, + endTime: endMs, + })); + + const [selectedInterval, setSelectedInterval] = useState<Time>( + selectedTime as Time, + ); + + const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS); + const isDarkMode = useIsDarkMode(); + + const initialFilters = useMemo( + () => ({ + op: 'AND', + items: [ + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_CLUSTER_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s_cluster_name--string--resource--false', + }, + op: '=', + value: cluster?.meta.k8s_cluster_name || '', + }, + ], + }), + [cluster?.meta.k8s_cluster_name], + ); + + const initialEventsFilters = useMemo( + () => ({ + op: 'AND', + items: [ + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_OBJECT_KIND, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s.object.kind--string--resource--false', + }, + op: '=', + value: 'Cluster', + }, + { + id: uuidv4(), + key: { + key: QUERY_KEYS.K8S_OBJECT_NAME, + dataType: DataTypes.String, + type: 'resource', + isColumn: false, + isJSON: false, + id: 'k8s.object.name--string--resource--false', + }, + op: '=', + value: cluster?.meta.k8s_cluster_name || '', + }, + ], + }), + [cluster?.meta.k8s_cluster_name], + ); + + const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( + initialFilters, + ); + + const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>( + initialFilters, + ); + + const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( + initialEventsFilters, + ); + + useEffect(() => { + logEvent('Infra Monitoring: Clusters list details page visited', { + cluster: cluster?.clusterUID, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setLogFilters(initialFilters); + setTracesFilters(initialFilters); + setEventsFilters(initialEventsFilters); + }, [initialFilters, initialEventsFilters]); + + useEffect(() => { + setSelectedInterval(selectedTime as Time); + + if (selectedTime !== 'custom') { + const { maxTime, minTime } = GetMinMax(selectedTime); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + }, [selectedTime, minTime, maxTime]); + + const handleTabChange = (e: RadioChangeEvent): void => { + setSelectedView(e.target.value); + }; + + const handleTimeChange = useCallback( + (interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => { + setSelectedInterval(interval as Time); + + if (interval === 'custom' && dateTimeRange) { + setModalTimeRange({ + startTime: Math.floor(dateTimeRange[0] / 1000), + endTime: Math.floor(dateTimeRange[1] / 1000), + }); + } else { + const { maxTime, minTime } = GetMinMax(interval); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + + logEvent('Infra Monitoring: Clusters list details time updated', { + cluster: cluster?.clusterUID, + interval, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeLogFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setLogFilters((prevFilters) => { + const primaryFilters = prevFilters.items.filter((item) => + [QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''), + ); + const paginationFilter = value.items.find((item) => item.key?.key === 'id'); + const newFilters = value.items.filter( + (item) => + item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME, + ); + + logEvent('Infra Monitoring: Clusters list details logs filters applied', { + cluster: cluster?.clusterUID, + }); + + return { + op: 'AND', + items: [ + ...primaryFilters, + ...newFilters, + ...(paginationFilter ? [paginationFilter] : []), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeTracesFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setTracesFilters((prevFilters) => { + const primaryFilters = prevFilters.items.filter((item) => + [QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''), + ); + + logEvent('Infra Monitoring: Clusters list details traces filters applied', { + cluster: cluster?.clusterUID, + }); + + return { + op: 'AND', + items: [ + ...primaryFilters, + ...value.items.filter( + (item) => item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleChangeEventsFilters = useCallback( + (value: IBuilderQuery['filters']) => { + setEventsFilters((prevFilters) => { + const clusterKindFilter = prevFilters.items.find( + (item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND, + ); + const clusterNameFilter = prevFilters.items.find( + (item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME, + ); + + logEvent('Infra Monitoring: Clusters list details events filters applied', { + cluster: cluster?.clusterUID, + }); + + return { + op: 'AND', + items: [ + clusterKindFilter, + clusterNameFilter, + ...value.items.filter( + (item) => + item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && + item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + }; + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const handleExplorePagesRedirect = (): void => { + if (selectedInterval !== 'custom') { + urlQuery.set(QueryParams.relativeTime, selectedInterval); + } else { + urlQuery.delete(QueryParams.relativeTime); + urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString()); + urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString()); + } + + logEvent('Infra Monitoring: Clusters list details explore clicked', { + cluster: cluster?.clusterUID, + view: selectedView, + }); + + if (selectedView === VIEW_TYPES.LOGS) { + const filtersWithoutPagination = { + ...logFilters, + items: logFilters.items.filter((item) => item.key?.key !== 'id'), + }; + + const compositeQuery = { + ...initialQueryState, + queryType: 'builder', + builder: { + ...initialQueryState.builder, + queryData: [ + { + ...initialQueryBuilderFormValuesMap.logs, + aggregateOperator: LogsAggregatorOperator.NOOP, + filters: filtersWithoutPagination, + }, + ], + }, + }; + + urlQuery.set('compositeQuery', JSON.stringify(compositeQuery)); + + window.open( + `${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`, + '_blank', + ); + } else if (selectedView === VIEW_TYPES.TRACES) { + const compositeQuery = { + ...initialQueryState, + queryType: 'builder', + builder: { + ...initialQueryState.builder, + queryData: [ + { + ...initialQueryBuilderFormValuesMap.traces, + aggregateOperator: TracesAggregatorOperator.NOOP, + filters: tracesFilters, + }, + ], + }, + }; + + urlQuery.set('compositeQuery', JSON.stringify(compositeQuery)); + + window.open( + `${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`, + '_blank', + ); + } + }; + + const handleClose = (): void => { + setSelectedInterval(selectedTime as Time); + + if (selectedTime !== 'custom') { + const { maxTime, minTime } = GetMinMax(selectedTime); + + setModalTimeRange({ + startTime: Math.floor(minTime / 1000000000), + endTime: Math.floor(maxTime / 1000000000), + }); + } + setSelectedView(VIEW_TYPES.METRICS); + onClose(); + }; + + return ( + <Drawer + width="70%" + title={ + <> + <Divider type="vertical" /> + <Typography.Text className="title"> + {cluster?.meta.k8s_cluster_name} + </Typography.Text> + </> + } + placement="right" + onClose={handleClose} + open={!!cluster} + style={{ + overscrollBehavior: 'contain', + background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, + }} + className="cluster-detail-drawer" + destroyOnClose + closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} + > + {cluster && ( + <> + <div className="cluster-detail-drawer__cluster"> + <div className="cluster-details-grid"> + <div className="labels-row"> + <Typography.Text + type="secondary" + className="cluster-details-metadata-label" + > + Cluster Name + </Typography.Text> + <Typography.Text + type="secondary" + className="cluster-details-metadata-label" + > + Cluster Name + </Typography.Text> + </div> + <div className="values-row"> + <Typography.Text className="cluster-details-metadata-value"> + <Tooltip title={cluster.meta.k8s_cluster_name}> + {cluster.meta.k8s_cluster_name} + </Tooltip> + </Typography.Text> + <Typography.Text className="cluster-details-metadata-value"> + <Tooltip title="Cluster name">{cluster.meta.k8s_cluster_name}</Tooltip> + </Typography.Text> + </div> + </div> + </div> + + <div className="views-tabs-container"> + <Radio.Group + className="views-tabs" + onChange={handleTabChange} + value={selectedView} + > + <Radio.Button + className={ + // eslint-disable-next-line sonarjs/no-duplicate-string + selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.METRICS} + > + <div className="view-title"> + <BarChart2 size={14} /> + Metrics + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.LOGS} + > + <div className="view-title"> + <ScrollText size={14} /> + Logs + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.TRACES} + > + <div className="view-title"> + <DraftingCompass size={14} /> + Traces + </div> + </Radio.Button> + <Radio.Button + className={ + selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab' + } + value={VIEW_TYPES.EVENTS} + > + <div className="view-title"> + <ChevronsLeftRight size={14} /> + Events + </div> + </Radio.Button> + </Radio.Group> + + {(selectedView === VIEW_TYPES.LOGS || + selectedView === VIEW_TYPES.TRACES) && ( + <Button + icon={<Compass size={18} />} + className="compass-button" + onClick={handleExplorePagesRedirect} + /> + )} + </div> + {selectedView === VIEW_TYPES.METRICS && ( + <ClusterMetrics + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + selectedInterval={selectedInterval} + cluster={cluster} + /> + )} + {selectedView === VIEW_TYPES.LOGS && ( + <ClusterLogs + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + handleChangeLogFilters={handleChangeLogFilters} + logFilters={logFilters} + selectedInterval={selectedInterval} + /> + )} + {selectedView === VIEW_TYPES.TRACES && ( + <ClusterTraces + timeRange={modalTimeRange} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + handleChangeTracesFilters={handleChangeTracesFilters} + tracesFilters={tracesFilters} + selectedInterval={selectedInterval} + /> + )} + {selectedView === VIEW_TYPES.EVENTS && ( + <ClusterEvents + timeRange={modalTimeRange} + handleChangeEventFilters={handleChangeEventsFilters} + filters={eventsFilters} + isModalTimeSelection={isModalTimeSelection} + handleTimeChange={handleTimeChange} + selectedInterval={selectedInterval} + /> + )} + </> + )} + </Drawer> + ); +} + +export default ClusterDetails; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss new file mode 100644 index 0000000000..3d76fe1772 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss @@ -0,0 +1,289 @@ +.cluster-events-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .cluster-events-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .cluster-events { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } + + .ant-table { + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgb(18, 19, 23); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.clustername-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgb(18, 19, 23); + border-bottom: none; + } + + .ant-table-cell:has(.clustername-column-value) { + background: var(--bg-ink-400); + } + + .clustername-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-pagination { + position: fixed; + bottom: 0; + width: calc(100% - 64px); + background: rgb(18, 19, 23); + padding: 16px; + margin: 0; + + // this is to offset intercom icon till we improve the design + padding-right: 72px; + + .ant-pagination-item { + border-radius: 4px; + + &-active { + background: var(--bg-robin-500); + border-color: var(--bg-robin-500); + + a { + color: var(--bg-ink-500) !important; + } + } + } + } +} + +.cluster-events-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.cluster-events-list-card { + width: 100%; + margin-top: 12px; + + .ant-table-wrapper { + height: 100%; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} + +.periscope-btn-icon { + cursor: pointer; +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx new file mode 100644 index 0000000000..c2b013749b --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx @@ -0,0 +1,360 @@ +/* eslint-disable no-nested-ternary */ +import './ClusterEvents.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Button, Table, TableColumnsType } from 'antd'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; +import LogsError from 'container/LogsError/LogsError'; +import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { isArray } from 'lodash-es'; +import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 } from 'uuid'; + +import { getClustersEventsQueryPayload } from './constants'; +import NoEventsContainer from './NoEventsContainer'; + +interface EventDataType { + key: string; + timestamp: string; + body: string; + id: string; + attributes_bool?: Record<string, boolean>; + attributes_number?: Record<string, number>; + attributes_string?: Record<string, string>; + resources_string?: Record<string, string>; + scope_name?: string; + scope_string?: Record<string, string>; + scope_version?: string; + severity_number?: number; + severity_text?: string; + span_id?: string; + trace_flags?: number; + trace_id?: string; + severity?: string; +} + +interface IClusterEventsProps { + timeRange: { + startTime: number; + endTime: number; + }; + handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void; + filters: IBuilderQuery['filters']; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + selectedInterval: Time; +} + +const EventsPageSize = 10; + +export default function Events({ + timeRange, + handleChangeEventFilters, + filters, + isModalTimeSelection, + handleTimeChange, + selectedInterval, +}: IClusterEventsProps): JSX.Element { + const { currentQuery } = useQueryBuilder(); + + const [formattedClusterEvents, setFormattedClusterEvents] = useState< + EventDataType[] + >([]); + + const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); + + const [page, setPage] = useState(1); + + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.LOGS, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + const queryPayload = useMemo(() => { + const basePayload = getClustersEventsQueryPayload( + timeRange.startTime, + timeRange.endTime, + filters, + ); + + basePayload.query.builder.queryData[0].pageSize = 10; + basePayload.query.builder.queryData[0].orderBy = [ + { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, + ]; + + return basePayload; + }, [timeRange.startTime, timeRange.endTime, filters]); + + const { data: eventsData, isLoading, isFetching, isError } = useQuery({ + queryKey: ['clusterEvents', timeRange.startTime, timeRange.endTime, filters], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + }); + + const columns: TableColumnsType<EventDataType> = [ + { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, + { + title: 'Timestamp', + dataIndex: 'timestamp', + width: 200, + ellipsis: true, + key: 'timestamp', + }, + { title: 'Body', dataIndex: 'body', key: 'body' }, + ]; + + useEffect(() => { + if (eventsData?.payload?.data?.newResult?.data?.result) { + const responsePayload = + eventsData?.payload.data.newResult.data.result[0].list || []; + + const formattedData = responsePayload?.map( + (event): EventDataType => ({ + timestamp: event.timestamp, + severity: event.data.severity_text, + body: event.data.body, + id: event.data.id, + key: event.data.id, + resources_string: event.data.resources_string, + attributes_string: event.data.attributes_string, + }), + ); + + setFormattedClusterEvents(formattedData); + + if ( + !responsePayload || + (responsePayload && + isArray(responsePayload) && + responsePayload.length < EventsPageSize) + ) { + setHasReachedEndOfEvents(true); + } else { + setHasReachedEndOfEvents(false); + } + } + }, [eventsData]); + + const handleExpandRow = (record: EventDataType): JSX.Element => ( + <EventContents + data={{ ...record.attributes_string, ...record.resources_string }} + /> + ); + + const handlePrev = (): void => { + if (!formattedClusterEvents.length) return; + + setPage(page - 1); + + const firstEvent = formattedClusterEvents[0]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '>', + value: firstEvent.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeEventFilters(newFilters); + }; + + const handleNext = (): void => { + if (!formattedClusterEvents.length) return; + + setPage(page + 1); + const lastEvent = formattedClusterEvents[formattedClusterEvents.length - 1]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '<', + value: lastEvent.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeEventFilters(newFilters); + }; + + const handleExpandRowIcon = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: EventDataType, + e: React.MouseEvent<HTMLElement, MouseEvent>, + ) => void; + record: EventDataType; + }): JSX.Element => + expanded ? ( + <ChevronDown + className="periscope-btn-icon" + size={14} + onClick={(e): void => + onExpand( + record, + (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, + ) + } + /> + ) : ( + <ChevronRight + className="periscope-btn-icon" + size={14} + // eslint-disable-next-line sonarjs/no-identical-functions + onClick={(e): void => + onExpand( + record, + (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, + ) + } + /> + ); + + return ( + <div className="cluster-events-container"> + <div className="cluster-events-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeEventFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + + {isLoading && <LoadingContainer />} + + {!isLoading && !isError && formattedClusterEvents.length === 0 && ( + <NoEventsContainer /> + )} + + {isError && !isLoading && <LogsError />} + + {!isLoading && !isError && formattedClusterEvents.length > 0 && ( + <div className="cluster-events-list-container"> + <div className="cluster-events-list-card"> + <Table<EventDataType> + loading={isLoading && page > 1} + columns={columns} + expandable={{ + expandedRowRender: handleExpandRow, + rowExpandable: (record): boolean => record.body !== 'Not Expandable', + expandIcon: handleExpandRowIcon, + }} + dataSource={formattedClusterEvents} + pagination={false} + rowKey={(record): string => record.id} + /> + </div> + </div> + )} + + {!isError && formattedClusterEvents.length > 0 && ( + <div className="cluster-events-footer"> + <Button + className="cluster-events-footer-button periscope-btn ghost" + type="link" + onClick={handlePrev} + disabled={page === 1 || isFetching || isLoading} + > + {!isFetching && <ChevronLeft size={14} />} + Prev + </Button> + + <Button + className="cluster-events-footer-button periscope-btn ghost" + type="link" + onClick={handleNext} + disabled={hasReachedEndOfEvents || isFetching || isLoading} + > + Next + {!isFetching && <ChevronRight size={14} />} + </Button> + + {(isFetching || isLoading) && ( + <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> + )} + </div> + )} + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx new file mode 100644 index 0000000000..c253774f68 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx @@ -0,0 +1,16 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; +import { Ghost } from 'lucide-react'; + +const { Text } = Typography; + +export default function NoEventsContainer(): React.ReactElement { + return ( + <div className="no-logs-found"> + <Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this + cluster in the selected time range. + </Text> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts new file mode 100644 index 0000000000..5b27953b35 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts @@ -0,0 +1,65 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; + +export const getClustersEventsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts new file mode 100644 index 0000000000..31af59a90e --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts @@ -0,0 +1,3 @@ +import ClusterEvents from './ClusterEvents'; + +export default ClusterEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss new file mode 100644 index 0000000000..c04a4a49c0 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss @@ -0,0 +1,133 @@ +.cluster-logs-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .cluster-logs-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .cluster-logs { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } +} + +.cluster-logs-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.cluster-logs-list-card { + width: 100%; + margin-top: 12px; + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx new file mode 100644 index 0000000000..0f9ad02709 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx @@ -0,0 +1,214 @@ +/* eslint-disable no-nested-ternary */ +import './ClusterLogs.styles.scss'; + +import { Card } from 'antd'; +import RawLogView from 'components/Logs/RawLogView'; +import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import LogsError from 'container/LogsError/LogsError'; +import { LogsLoading } from 'container/LogsLoading/LogsLoading'; +import { FontSize } from 'container/OptionsMenu/types'; +import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { isEqual } from 'lodash-es'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { Virtuoso } from 'react-virtuoso'; +import { ILog } from 'types/api/logs/log'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { v4 } from 'uuid'; + +import { QUERY_KEYS } from '../constants'; +import { getClusterLogsQueryPayload } from './constants'; +import NoLogsContainer from './NoLogsContainer'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; + filters: IBuilderQuery['filters']; +} + +function PodLogs({ + timeRange, + handleChangeLogFilters, + filters, +}: Props): JSX.Element { + const [logs, setLogs] = useState<ILog[]>([]); + const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); + const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); + const [resetLogsList, setResetLogsList] = useState<boolean>(false); + + useEffect(() => { + const newRestFilters = filters.items.filter( + (item) => + item.key?.key !== 'id' && + ![QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''), + ); + + const areFiltersSame = isEqual(restFilters, newRestFilters); + + if (!areFiltersSame) { + setResetLogsList(true); + } + + setRestFilters(newRestFilters); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filters]); + + const queryPayload = useMemo(() => { + const basePayload = getClusterLogsQueryPayload( + timeRange.startTime, + timeRange.endTime, + filters, + ); + + basePayload.query.builder.queryData[0].pageSize = 100; + basePayload.query.builder.queryData[0].orderBy = [ + { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, + ]; + + return basePayload; + }, [timeRange.startTime, timeRange.endTime, filters]); + + const [isPaginating, setIsPaginating] = useState(false); + + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: ['clusterLogs', timeRange.startTime, timeRange.endTime, filters], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + keepPreviousData: isPaginating, + }); + + useEffect(() => { + if (data?.payload?.data?.newResult?.data?.result) { + const currentData = data.payload.data.newResult.data.result; + + if (resetLogsList) { + const currentLogs: ILog[] = + currentData[0].list?.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })) || []; + + setLogs(currentLogs); + + setResetLogsList(false); + } + + if (currentData.length > 0 && currentData[0].list) { + const currentLogs: ILog[] = + currentData[0].list.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })) || []; + + setLogs((prev) => [...prev, ...currentLogs]); + } else { + setHasReachedEndOfLogs(true); + } + } + }, [data, restFilters, isPaginating, resetLogsList]); + + const getItemContent = useCallback( + (_: number, logToRender: ILog): JSX.Element => ( + <RawLogView + isReadOnly + isTextOverflowEllipsisDisabled + key={logToRender.id} + data={logToRender} + linesPerRow={5} + fontSize={FontSize.MEDIUM} + /> + ), + [], + ); + + const loadMoreLogs = useCallback(() => { + if (!logs.length) return; + + setIsPaginating(true); + const lastLog = logs[logs.length - 1]; + + const newItems = [ + ...filters.items.filter((item) => item.key?.key !== 'id'), + { + id: v4(), + key: { + key: 'id', + type: '', + dataType: DataTypes.String, + isColumn: true, + }, + op: '<', + value: lastLog.id, + }, + ]; + + const newFilters = { + op: 'AND', + items: newItems, + } as IBuilderQuery['filters']; + + handleChangeLogFilters(newFilters); + }, [logs, filters, handleChangeLogFilters]); + + useEffect(() => { + setIsPaginating(false); + }, [data]); + + const renderFooter = useCallback( + (): JSX.Element | null => ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {isFetching ? ( + <div className="logs-loading-skeleton"> Loading more logs ... </div> + ) : hasReachedEndOfLogs ? ( + <div className="logs-loading-skeleton"> *** End *** </div> + ) : null} + </> + ), + [isFetching, hasReachedEndOfLogs], + ); + + const renderContent = useMemo( + () => ( + <Card bordered={false} className="cluster-logs-list-card"> + <OverlayScrollbar isVirtuoso> + <Virtuoso + className="cluster-logs-virtuoso" + key="cluster-logs-virtuoso" + data={logs} + endReached={loadMoreLogs} + totalCount={logs.length} + itemContent={getItemContent} + overscan={200} + components={{ + Footer: renderFooter, + }} + /> + </OverlayScrollbar> + </Card> + ), + [logs, loadMoreLogs, getItemContent, renderFooter], + ); + + return ( + <div className="cluster-logs"> + {isLoading && <LogsLoading />} + {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {isError && !isLoading && <LogsError />} + {!isLoading && !isError && logs.length > 0 && ( + <div className="cluster-logs-list-container">{renderContent}</div> + )} + </div> + ); +} + +export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx new file mode 100644 index 0000000000..d5f08bc7d9 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx @@ -0,0 +1,99 @@ +import './ClusterLogs.styles.scss'; + +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useMemo } from 'react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import ClusterLogs from './ClusterLogs'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; + logFilters: IBuilderQuery['filters']; + selectedInterval: Time; +} + +function ClusterLogsDetailedView({ + timeRange, + isModalTimeSelection, + handleTimeChange, + handleChangeLogFilters, + logFilters, + selectedInterval, +}: Props): JSX.Element { + const { currentQuery } = useQueryBuilder(); + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.LOGS, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + return ( + <div className="cluster-logs-container"> + <div className="cluster-logs-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeLogFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + <ClusterLogs + timeRange={timeRange} + handleChangeLogFilters={handleChangeLogFilters} + filters={logFilters} + /> + </div> + ); +} + +export default ClusterLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx new file mode 100644 index 0000000000..ebcecff969 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx @@ -0,0 +1,16 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; +import { Ghost } from 'lucide-react'; + +const { Text } = Typography; + +export default function NoLogsContainer(): React.ReactElement { + return ( + <div className="no-logs-found"> + <Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this + cluster in the selected time range. + </Text> + </div> + ); +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts new file mode 100644 index 0000000000..ae2f7c6cfb --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts @@ -0,0 +1,65 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; + +export const getClusterLogsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts new file mode 100644 index 0000000000..5210b7ab20 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts @@ -0,0 +1,3 @@ +import ClusterLogs from './ClusterLogsDetailedView'; + +export default ClusterLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss new file mode 100644 index 0000000000..804fca7485 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss @@ -0,0 +1,45 @@ +.empty-container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.cluster-metrics-container { + margin-top: 1rem; +} + +.metrics-header { + display: flex; + justify-content: flex-end; + margin-top: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); +} + +.cluster-metrics-card { + margin: 8px 0 1rem 0; + height: 300px; + padding: 10px; + + border: 1px solid var(--bg-slate-500); + + .ant-card-body { + padding: 0; + } + + .chart-container { + width: 100%; + height: 100%; + } + + .no-data-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx new file mode 100644 index 0000000000..dd71739913 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx @@ -0,0 +1,166 @@ +import './ClusterMetrics.styles.scss'; + +import { Card, Col, Row, Skeleton, Typography } from 'antd'; +import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList'; +import cx from 'classnames'; +import Uplot from 'components/Uplot'; +import { ENTITY_VERSION_V4 } from 'constants/app'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { + getMetricsTableData, + MetricsTable, +} from 'container/InfraMonitoringK8s/commonUtils'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useResizeObserver } from 'hooks/useDimensions'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; +import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; +import { useMemo, useRef } from 'react'; +import { useQueries, UseQueryResult } from 'react-query'; +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { Options } from 'uplot'; + +import { clusterWidgetInfo, getClusterQueryPayload } from './constants'; + +interface ClusterMetricsProps { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + selectedInterval: Time; + cluster: K8sClustersData; +} + +function ClusterMetrics({ + selectedInterval, + cluster, + timeRange, + handleTimeChange, + isModalTimeSelection, +}: ClusterMetricsProps): JSX.Element { + const queryPayloads = useMemo( + () => getClusterQueryPayload(cluster, timeRange.startTime, timeRange.endTime), + [cluster, timeRange.startTime, timeRange.endTime], + ); + + const queries = useQueries( + queryPayloads.map((payload) => ({ + queryKey: ['cluster-metrics', payload, ENTITY_VERSION_V4, 'NODE'], + queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + })), + ); + + const isDarkMode = useIsDarkMode(); + const graphRef = useRef<HTMLDivElement>(null); + const dimensions = useResizeObserver(graphRef); + + const chartData = useMemo( + () => + queries.map(({ data }) => { + const panelType = (data?.params as any)?.compositeQuery?.panelType; + return panelType === PANEL_TYPES.TABLE + ? getMetricsTableData(data) + : getUPlotChartData(data?.payload); + }), + [queries], + ); + + const options = useMemo( + () => + queries.map(({ data }, idx) => { + const panelType = (data?.params as any)?.compositeQuery?.panelType; + if (panelType === PANEL_TYPES.TABLE) { + return null; + } + return getUPlotChartOptions({ + apiResponse: data?.payload, + isDarkMode, + dimensions, + yAxisUnit: clusterWidgetInfo[idx].yAxisUnit, + softMax: null, + softMin: null, + minTimeScale: timeRange.startTime, + maxTimeScale: timeRange.endTime, + }); + }), + [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], + ); + + const renderCardContent = ( + query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, + idx: number, + ): JSX.Element => { + if (query.isLoading) { + return <Skeleton />; + } + + if (query.error) { + const errorMessage = + (query.error as Error)?.message || 'Something went wrong'; + return <div>{errorMessage}</div>; + } + + const { panelType } = (query.data?.params as any).compositeQuery; + + return ( + <div + className={cx('chart-container', { + 'no-data-container': + !query.isLoading && !query?.data?.payload?.data?.result?.length, + })} + > + {panelType === PANEL_TYPES.TABLE ? ( + <MetricsTable + rows={chartData[idx][0].rows} + columns={chartData[idx][0].columns} + /> + ) : ( + <Uplot options={options[idx] as Options} data={chartData[idx]} /> + )} + </div> + ); + }; + + return ( + <> + <div className="metrics-header"> + <div className="metrics-datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + isModalTimeSelection={isModalTimeSelection} + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + <Row gutter={24} className="cluster-metrics-container"> + {queries.map((query, idx) => ( + <Col span={12} key={clusterWidgetInfo[idx].title}> + <Typography.Text>{clusterWidgetInfo[idx].title}</Typography.Text> + <Card bordered className="cluster-metrics-card" ref={graphRef}> + {renderCardContent(query, idx)} + </Card> + </Col> + ))} + </Row> + </> + ); +} + +export default ClusterMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts new file mode 100644 index 0000000000..595fb4bdd9 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts @@ -0,0 +1,1530 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 } from 'uuid'; + +export const clusterWidgetInfo = [ + { + title: 'CPU Usage, allocatable', + yAxisUnit: '', + }, + { + title: 'Memory Usage, allocatable', + yAxisUnit: 'bytes', + }, + { + title: 'Ready Nodes', + yAxisUnit: '', + }, + { + title: 'NotReady Nodes', + yAxisUnit: '', + }, + { + title: 'Deployments available and desired', + yAxisUnit: '', + }, + { + title: 'Statefulset pods', + yAxisUnit: '', + }, + { + title: 'Daemonset nodes', + yAxisUnit: '', + }, + { + title: 'Jobs', + yAxisUnit: '', + }, +]; + +export const getClusterQueryPayload = ( + cluster: K8sClustersData, + start: number, + end: number, +): GetQueryResultsProps[] => [ + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '0224c582', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (avg)', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'min', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'a0e11f0c', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (min)', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'min', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_cpu_utilization--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_cpu_utilization', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: 'c775629c', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (max)', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_node_allocatable_cpu--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_node_allocatable_cpu', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: '31f6c43a', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'allocatable', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '61a3c55e', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (avg)', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'min', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '834a887f', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (min)', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'min', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_pod_memory_usage--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_pod_memory_usage', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '9b002160', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'usage (max)', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_node_allocatable_memory--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_node_allocatable_memory', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: '02a9a67e', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: 'allocatable', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_node_condition_ready--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_node_condition_ready', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'd7779183', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_node_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_node_name', + type: 'tag', + }, + ], + having: [ + { + columnName: 'MAX(k8s_node_condition_ready)', + op: '=', + value: 1, + }, + ], + legend: '{{k8s_node_name}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TIME_SERIES, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_node_condition_ready--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_node_condition_ready', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'd7779183', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_node_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_node_name', + type: 'tag', + }, + ], + having: [ + { + columnName: 'MAX(k8s_node_condition_ready)', + op: '=', + value: 0, + }, + ], + legend: '{{k8s_node_name}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'max', + stepInterval: 60, + timeAggregation: 'latest', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: false, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_deployment_available--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_deployment_available', + type: 'Gauge', + }, + aggregateOperator: 'latest', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'a7da59c7', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'available', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'latest', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_deployment_desired--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_deployment_desired', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '55110885', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_deployment_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_deployment_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_current_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_current_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '3c57b4d1', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'current', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_desired_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_desired_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '0f49fe64', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_ready_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_ready_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '0bebf625', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'ready', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_statefulset_updated_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_statefulset_updated_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: '1ddacbbe', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_statefulset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_statefulset_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'updated', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_current_scheduled_nodes--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_current_scheduled_nodes', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'e0bea554', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'current_nodes', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'avg', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_desired_scheduled_nodes--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_desired_scheduled_nodes', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '741052f7', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired_nodes', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'avg', + stepInterval: 60, + timeAggregation: 'avg', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_daemonset_ready_nodes--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_daemonset_ready_nodes', + type: 'Gauge', + }, + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: 'f23759f2', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_daemonset_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_daemonset_name', + type: 'tag', + }, + ], + having: [], + legend: 'ready_nodes', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'last', + spaceAggregation: 'avg', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, + { + selectedTime: 'GLOBAL_TIME', + graphType: PANEL_TYPES.TABLE, + query: { + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_job_active_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_job_active_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'fd485d85', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_job_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_job_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'running', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_job_successful_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_job_successful_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: 'fc1beb81', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_job_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_job_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'successful', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_job_failed_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_job_failed_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: '97773348', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_job_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_job_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'failed', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'k8s_job_desired_successful_pods--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'k8s_job_desired_successful_pods', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: '5911618b', + key: { + dataType: DataTypes.String, + id: 'k8s_cluster_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_cluster_name', + type: 'tag', + }, + op: '=', + value: cluster.meta.k8s_cluster_name, + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'k8s_job_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_job_name', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'k8s_namespace_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'k8s_namespace_name', + type: 'tag', + }, + ], + having: [], + legend: 'desired successful', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'last', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'max', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + id: v4(), + promql: [ + { + disabled: false, + legend: '', + name: 'A', + query: '', + }, + ], + queryType: EQueryType.QUERY_BUILDER, + }, + variables: {}, + formatForWeb: true, + start, + end, + }, +]; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts new file mode 100644 index 0000000000..cf254e714f --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts @@ -0,0 +1,3 @@ +import ClusterMetrics from './ClusterMetrics'; + +export default ClusterMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss new file mode 100644 index 0000000000..8f6c5c01db --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss @@ -0,0 +1,193 @@ +.cluster-metric-traces { + margin-top: 1rem; + + .cluster-metric-traces-header { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + } + + .cluster-metric-traces-table { + .ant-table-content { + overflow: hidden !important; + } + + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgba(171, 189, 255, 0.01); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.hostname-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgba(171, 189, 255, 0.01); + } + + .ant-table-cell:has(.hostname-column-value) { + background: var(--bg-ink-400); + } + + .hostname-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-table-container::after { + content: none; + } + } +} + +.lightMode { + .host-metric-traces-header { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } + } + + .host-metric-traces-table { + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-vanilla-300); + + .ant-table-thead > tr > th { + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .ant-table-thead > tr > th:has(.hostname-column-header) { + background: var(--bg-vanilla-100); + } + + .ant-table-cell { + background: var(--bg-vanilla-100); + color: var(--bg-ink-500); + } + + .ant-table-cell:has(.hostname-column-value) { + background: var(--bg-vanilla-100); + } + + .hostname-column-value { + color: var(--bg-ink-300); + } + + .ant-table-tbody > tr:hover > td { + background: rgba(0, 0, 0, 0.04); + } + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx new file mode 100644 index 0000000000..999b2275a5 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx @@ -0,0 +1,199 @@ +import './ClusterTraces.styles.scss'; + +import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; +import { ResizeTable } from 'components/ResizeTable'; +import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { QueryParams } from 'constants/query'; +import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; +import NoLogs from 'container/NoLogs/NoLogs'; +import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; +import { ErrorText } from 'container/TimeSeriesView/styles'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import TraceExplorerControls from 'container/TracesExplorer/Controls'; +import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; +import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { Pagination } from 'hooks/queryPagination'; +import useUrlQueryData from 'hooks/useUrlQueryData'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; + +import { getClusterTracesQueryPayload, selectedColumns } from './constants'; + +interface Props { + timeRange: { + startTime: number; + endTime: number; + }; + isModalTimeSelection: boolean; + handleTimeChange: ( + interval: Time | CustomTimeType, + dateTimeRange?: [number, number], + ) => void; + handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; + tracesFilters: IBuilderQuery['filters']; + selectedInterval: Time; +} + +function ClusterTraces({ + timeRange, + isModalTimeSelection, + handleTimeChange, + handleChangeTracesFilters, + tracesFilters, + selectedInterval, +}: Props): JSX.Element { + const [traces, setTraces] = useState<any[]>([]); + const [offset] = useState<number>(0); + + const { currentQuery } = useQueryBuilder(); + const updatedCurrentQuery = useMemo( + () => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: [ + { + ...currentQuery.builder.queryData[0], + dataSource: DataSource.TRACES, + aggregateOperator: 'noop', + aggregateAttribute: { + ...currentQuery.builder.queryData[0].aggregateAttribute, + }, + filters: { + items: [], + op: 'AND', + }, + }, + ], + }, + }), + [currentQuery], + ); + + const query = updatedCurrentQuery?.builder?.queryData[0] || null; + + const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( + QueryParams.pagination, + ); + + const queryPayload = useMemo( + () => + getClusterTracesQueryPayload( + timeRange.startTime, + timeRange.endTime, + paginationQueryData?.offset || offset, + tracesFilters, + ), + [ + timeRange.startTime, + timeRange.endTime, + offset, + tracesFilters, + paginationQueryData, + ], + ); + + const { data, isLoading, isFetching, isError } = useQuery({ + queryKey: [ + 'hostMetricTraces', + timeRange.startTime, + timeRange.endTime, + offset, + tracesFilters, + DEFAULT_ENTITY_VERSION, + paginationQueryData, + ], + queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), + enabled: !!queryPayload, + }); + + const traceListColumns = getListColumns(selectedColumns); + + useEffect(() => { + if (data?.payload?.data?.newResult?.data?.result) { + const currentData = data.payload.data.newResult.data.result; + if (currentData.length > 0 && currentData[0].list) { + if (offset === 0) { + setTraces(currentData[0].list ?? []); + } else { + setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); + } + } + } + }, [data, offset]); + + const isDataEmpty = + !isLoading && !isFetching && !isError && traces.length === 0; + const hasAdditionalFilters = tracesFilters.items.length > 1; + + const totalCount = + data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; + + return ( + <div className="cluster-metric-traces"> + <div className="cluster-metric-traces-header"> + <div className="filter-section"> + {query && ( + <QueryBuilderSearch + query={query} + onChange={handleChangeTracesFilters} + disableNavigationShortcuts + /> + )} + </div> + <div className="datetime-section"> + <DateTimeSelectionV2 + showAutoRefresh={false} + showRefreshText={false} + hideShareModal + isModalTimeSelection={isModalTimeSelection} + onTimeChange={handleTimeChange} + defaultRelativeTime="5m" + modalSelectedInterval={selectedInterval} + /> + </div> + </div> + + {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} + + {isLoading && traces.length === 0 && <TracesLoading />} + + {isDataEmpty && !hasAdditionalFilters && ( + <NoLogs dataSource={DataSource.TRACES} /> + )} + + {isDataEmpty && hasAdditionalFilters && ( + <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> + )} + + {!isError && traces.length > 0 && ( + <div className="cluster-traces-table"> + <TraceExplorerControls + isLoading={isFetching} + totalCount={totalCount} + perPageOptions={PER_PAGE_OPTIONS} + showSizeChanger={false} + /> + <ResizeTable + tableLayout="fixed" + pagination={false} + scroll={{ x: true }} + loading={isFetching} + dataSource={traces} + columns={traceListColumns} + /> + </div> + )} + </div> + ); +} + +export default ClusterTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts new file mode 100644 index 0000000000..52b89abe8e --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts @@ -0,0 +1,200 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { + BaseAutocompleteData, + DataTypes, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { nanoToMilli } from 'utils/timeUtils'; + +export const columns = [ + { + dataIndex: 'timestamp', + key: 'timestamp', + title: 'Timestamp', + width: 200, + render: (timestamp: string): string => new Date(timestamp).toLocaleString(), + }, + { + title: 'Service Name', + dataIndex: ['data', 'serviceName'], + key: 'serviceName-string-tag', + width: 150, + }, + { + title: 'Name', + dataIndex: ['data', 'name'], + key: 'name-string-tag', + width: 145, + }, + { + title: 'Duration', + dataIndex: ['data', 'durationNano'], + key: 'durationNano-float64-tag', + width: 145, + render: (duration: number): string => `${nanoToMilli(duration)}ms`, + }, + { + title: 'HTTP Method', + dataIndex: ['data', 'httpMethod'], + key: 'httpMethod-string-tag', + width: 145, + }, + { + title: 'Status Code', + dataIndex: ['data', 'responseStatusCode'], + key: 'responseStatusCode-string-tag', + width: 145, + }, +]; + +export const selectedColumns: BaseAutocompleteData[] = [ + { + key: 'timestamp', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'serviceName', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'name', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'durationNano', + dataType: DataTypes.Float64, + type: 'tag', + isColumn: true, + }, + { + key: 'httpMethod', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, + { + key: 'responseStatusCode', + dataType: DataTypes.String, + type: 'tag', + isColumn: true, + }, +]; + +export const getClusterTracesQueryPayload = ( + start: number, + end: number, + offset = 0, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + query: { + promql: [], + clickhouse_sql: [], + builder: { + queryData: [ + { + dataSource: DataSource.TRACES, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.EMPTY, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + }, + ], + queryFormulas: [], + }, + id: '572f1d91-6ac0-46c0-b726-c21488b34434', + queryType: EQueryType.QUERY_BUILDER, + }, + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + start, + end, + params: { + dataSource: DataSource.TRACES, + }, + tableParams: { + pagination: { + limit: 10, + offset, + }, + selectColumns: [ + { + key: 'serviceName', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'serviceName--string--tag--true', + isIndexed: false, + }, + { + key: 'name', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'name--string--tag--true', + isIndexed: false, + }, + { + key: 'durationNano', + dataType: 'float64', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'durationNano--float64--tag--true', + isIndexed: false, + }, + { + key: 'httpMethod', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'httpMethod--string--tag--true', + isIndexed: false, + }, + { + key: 'responseStatusCode', + dataType: 'string', + type: 'tag', + isColumn: true, + isJSON: false, + id: 'responseStatusCode--string--tag--true', + isIndexed: false, + }, + ], + }, +}); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts new file mode 100644 index 0000000000..bc16a3b372 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts @@ -0,0 +1,3 @@ +import ClusterTraces from './ClusterTraces'; + +export default ClusterTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts new file mode 100644 index 0000000000..38877088f1 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts @@ -0,0 +1,5 @@ +export const QUERY_KEYS = { + K8S_OBJECT_KIND: 'k8s.object.kind', + K8S_OBJECT_NAME: 'k8s.object.name', + K8S_CLUSTER_NAME: 'k8s.cluster.name', +}; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/index.ts new file mode 100644 index 0000000000..a183c3be4f --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/index.ts @@ -0,0 +1,3 @@ +import ClusterDetails from './ClusterDetails'; + +export default ClusterDetails; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.styles.scss new file mode 100644 index 0000000000..a7c5686eb0 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.styles.scss @@ -0,0 +1,17 @@ +.infra-monitoring-container { + .clusters-list-table { + .expanded-table-container { + padding-left: 40px; + } + + .ant-table-cell { + min-width: 223px !important; + max-width: 223px !important; + } + + .ant-table-row-expand-icon-cell { + min-width: 30px !important; + max-width: 30px !important; + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx new file mode 100644 index 0000000000..412771f673 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx @@ -0,0 +1,501 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; +import './K8sClustersList.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Button, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { ColumnType, SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; +import { K8sClustersListPayload } from 'api/infraMonitoring/getK8sClustersList'; +import { useGetK8sClustersList } from 'hooks/infraMonitoring/useGetK8sClustersList'; +import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { + K8sCategory, + K8sEntityToAggregateAttributeMapping, +} from '../constants'; +import K8sHeader from '../K8sHeader'; +import LoadingContainer from '../LoadingContainer'; +import ClusterDetails from './ClusterDetails'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sClustersListColumns, + getK8sClustersListQuery, + K8sClustersRowData, +} from './utils'; + +// eslint-disable-next-line sonarjs/cognitive-complexity +function K8sClustersList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>({ columnName: 'cpu', order: 'desc' }); + + const [selectedClusterName, setselectedClusterName] = useState<string | null>( + null, + ); + + const pageSize = 10; + + const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]); + + const [ + selectedRowData, + setSelectedRowData, + ] = useState<K8sClustersRowData | null>(null); + + const [groupByOptions, setGroupByOptions] = useState< + { value: string; label: string }[] + >([]); + + const createFiltersForSelectedRowData = ( + selectedRowData: K8sClustersRowData, + groupBy: IBuilderQuery['groupBy'], + ): IBuilderQuery['filters'] => { + const baseFilters: IBuilderQuery['filters'] = { + items: [], + op: 'and', + }; + + if (!selectedRowData) return baseFilters; + + const { groupedByMeta } = selectedRowData; + + for (const key of groupBy) { + baseFilters.items.push({ + key: { + key: key.key, + type: null, + }, + op: '=', + value: groupedByMeta[key.key], + id: key.key, + }); + } + + return baseFilters; + }; + + const fetchGroupedByRowDataQuery = useMemo(() => { + if (!selectedRowData) return null; + + const baseQuery = getK8sClustersListQuery(); + + const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); + + return { + ...baseQuery, + limit: 10, + offset: 0, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); + + const { + data: groupedByRowData, + isFetching: isFetchingGroupedByRowData, + isLoading: isLoadingGroupedByRowData, + isError: isErrorGroupedByRowData, + refetch: fetchGroupedByRowData, + } = useGetK8sClustersList( + fetchGroupedByRowDataQuery as K8sClustersListPayload, + { + queryKey: ['clusterList', fetchGroupedByRowDataQuery], + enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, + }, + ); + + const { currentQuery } = useQueryBuilder(); + + const { + data: groupByFiltersData, + isLoading: isLoadingGroupByFilters, + } = useGetAggregateKeys( + { + dataSource: currentQuery.builder.queryData[0].dataSource, + aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES], + aggregateOperator: 'noop', + searchText: '', + tagType: '', + }, + { + queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'], + }, + true, + K8sCategory.NODES, + ); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + const query = useMemo(() => { + const baseQuery = getK8sClustersListQuery(); + const queryPayload = { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters: queryFilters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + if (groupBy.length > 0) { + queryPayload.groupBy = groupBy; + } + return queryPayload; + }, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]); + + const formattedGroupedByClustersData = useMemo( + () => + formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy), + [groupedByRowData, groupBy], + ); + + const { data, isFetching, isLoading, isError } = useGetK8sClustersList( + query as K8sClustersListPayload, + { + queryKey: ['clusterList', query], + enabled: !!query, + }, + ); + + const clustersData = useMemo(() => data?.payload?.data?.records || [], [data]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedClustersData = useMemo( + () => formatDataForTable(clustersData, groupBy), + [clustersData, groupBy], + ); + + const columns = useMemo(() => getK8sClustersListColumns(groupBy), [groupBy]); + + const handleGroupByRowClick = (record: K8sClustersRowData): void => { + setSelectedRowData(record); + + if (expandedRowKeys.includes(record.key)) { + setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key)); + } else { + setExpandedRowKeys([record.key]); + } + }; + + useEffect(() => { + if (selectedRowData) { + fetchGroupedByRowData(); + } + }, [selectedRowData, fetchGroupedByRowData]); + + const handleTableChange: TableProps<K8sClustersRowData>['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record<string, (string | number | boolean)[] | null>, + sorter: + | SorterResult<K8sClustersRowData> + | SorterResult<K8sClustersRowData>[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const { handleChangeQueryData } = useQueryOperations({ + index: 0, + query: currentQuery.builder.queryData[0], + entityVersion: '', + }); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + handleChangeQueryData('filters', value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + }, + [handleChangeQueryData], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + const selectedClusterData = useMemo(() => { + if (!selectedClusterName) return null; + return ( + clustersData.find( + (cluster) => cluster.meta.k8s_cluster_name === selectedClusterName, + ) || null + ); + }, [selectedClusterName, clustersData]); + + const handleRowClick = (record: K8sClustersRowData): void => { + if (groupBy.length === 0) { + setSelectedRowData(null); + setselectedClusterName(record.key); + } else { + handleGroupByRowClick(record); + } + + logEvent('Infra Monitoring: K8s cluster list item clicked', { + clusterName: record.clusterName, + }); + }; + + const nestedColumns = useMemo(() => getK8sClustersListColumns([]), []); + + const isGroupedByAttribute = groupBy.length > 0; + + const handleExpandedRowViewAllClick = (): void => { + if (!selectedRowData) return; + + const filters = createFiltersForSelectedRowData(selectedRowData, groupBy); + + handleFiltersChange(filters); + + setCurrentPage(1); + setSelectedRowData(null); + setGroupBy([]); + setOrderBy(null); + }; + + const expandedRowRender = (): JSX.Element => ( + <div className="expanded-table-container"> + {isErrorGroupedByRowData && ( + <Typography>{groupedByRowData?.error || 'Something went wrong'}</Typography> + )} + {isFetchingGroupedByRowData || isLoadingGroupedByRowData ? ( + <LoadingContainer /> + ) : ( + <div className="expanded-table"> + <Table + columns={nestedColumns as ColumnType<K8sClustersRowData>[]} + dataSource={formattedGroupedByClustersData} + pagination={false} + scroll={{ x: true }} + tableLayout="fixed" + size="small" + loading={{ + spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + showHeader={false} + /> + + {groupedByRowData?.payload?.data?.total && + groupedByRowData?.payload?.data?.total > 10 ? ( + <div className="expanded-table-footer"> + <Button + type="default" + size="small" + className="periscope-btn secondary" + onClick={handleExpandedRowViewAllClick} + > + View All + </Button> + </div> + ) : null} + </div> + )} + </div> + ); + + const expandRowIconRenderer = ({ + expanded, + onExpand, + record, + }: { + expanded: boolean; + onExpand: ( + record: K8sClustersRowData, + e: React.MouseEvent<HTMLButtonElement>, + ) => void; + record: K8sClustersRowData; + }): JSX.Element | null => { + if (!isGroupedByAttribute) { + return null; + } + + return expanded ? ( + <Button + className="periscope-btn ghost" + onClick={(e: React.MouseEvent<HTMLButtonElement>): void => + onExpand(record, e) + } + role="button" + > + <ChevronDown size={14} /> + </Button> + ) : ( + <Button + className="periscope-btn ghost" + onClick={(e: React.MouseEvent<HTMLButtonElement>): void => + onExpand(record, e) + } + role="button" + > + <ChevronRight size={14} /> + </Button> + ); + }; + + const handleCloseClusterDetail = (): void => { + setselectedClusterName(null); + }; + + const handleGroupByChange = useCallback( + (value: IBuilderQuery['groupBy']) => { + const groupBy = []; + + for (let index = 0; index < value.length; index++) { + const element = (value[index] as unknown) as string; + + const key = groupByFiltersData?.payload?.attributeKeys?.find( + (key) => key.key === element, + ); + + if (key) { + groupBy.push(key); + } + } + + setGroupBy(groupBy); + setExpandedRowKeys([]); + }, + [groupByFiltersData], + ); + + useEffect(() => { + if (groupByFiltersData?.payload) { + setGroupByOptions( + groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ + value: filter.key, + label: filter.key, + })) || [], + ); + } + }, [groupByFiltersData]); + + return ( + <div className="k8s-list"> + <K8sHeader + isFiltersVisible={isFiltersVisible} + handleFilterVisibilityChange={handleFilterVisibilityChange} + defaultAddedColumns={defaultAddedColumns} + handleFiltersChange={handleFiltersChange} + groupByOptions={groupByOptions} + isLoadingGroupByFilters={isLoadingGroupByFilters} + handleGroupByChange={handleGroupByChange} + selectedGroupBy={groupBy} + entity={K8sCategory.NODES} + /> + {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} + + <Table + className="k8s-list-table clusters-list-table" + dataSource={isFetching || isLoading ? [] : formattedClustersData} + columns={columns} + pagination={{ + current: currentPage, + pageSize, + total: totalCount, + showSizeChanger: false, + hideOnSinglePage: true, + }} + scroll={{ x: true }} + loading={{ + spinning: isFetching || isLoading, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + locale={{ + emptyText: + isFetching || isLoading ? null : ( + <div className="no-filtered-hosts-message-container"> + <div className="no-filtered-hosts-message-content"> + <img + src="/Icons/emptyState.svg" + alt="thinking-emoji" + className="empty-state-svg" + /> + + <Typography.Text className="no-filtered-hosts-message"> + This query had no results. Edit your query and try again! + </Typography.Text> + </div> + </div> + ), + }} + 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, + }} + /> + + <ClusterDetails + cluster={selectedClusterData} + isModalTimeSelection + onClose={handleCloseClusterDetail} + /> + </div> + ); +} + +export default K8sClustersList; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx new file mode 100644 index 0000000000..dc2583ecde --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx @@ -0,0 +1,195 @@ +import { Color } from '@signozhq/design-tokens'; +import { Tag, Tooltip } from 'antd'; +import { ColumnType } from 'antd/es/table'; +import { + K8sClustersData, + K8sClustersListPayload, +} from 'api/infraMonitoring/getK8sClustersList'; +import { Group } from 'lucide-react'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; + +import { formatBytes, ValidateColumnValueWrapper } from '../commonUtils'; +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Cluster Name', + value: 'clusterName', + id: 'cluster', + canRemove: false, + }, + { + label: 'CPU Usage (cores)', + value: 'cpu', + id: 'cpu', + canRemove: false, + }, + { + label: 'CPU Allocatable (cores)', + value: 'cpu_allocatable', + id: 'cpu_allocatable', + canRemove: false, + }, + { + label: 'Mem Usage', + value: 'memory', + id: 'memory', + canRemove: false, + }, + { + label: 'Mem Allocatable', + value: 'memory_allocatable', + id: 'memory_allocatable', + canRemove: false, + }, +]; + +export interface K8sClustersRowData { + key: string; + clusterUID: string; + clusterName: React.ReactNode; + cpu: React.ReactNode; + memory: React.ReactNode; + cpu_allocatable: React.ReactNode; + memory_allocatable: React.ReactNode; + groupedByMeta?: any; +} + +const clusterGroupColumnConfig = { + title: ( + <div className="column-header pod-group-header"> + <Group size={14} /> CLUSTER GROUP + </div> + ), + dataIndex: 'clusterGroup', + key: 'clusterGroup', + ellipsis: true, + width: 150, + align: 'left', + sorter: false, +}; + +export const getK8sClustersListQuery = (): K8sClustersListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +const columnsConfig = [ + { + title: <div className="column-header-left">Cluster Name</div>, + dataIndex: 'clusterName', + key: 'clusterName', + ellipsis: true, + width: 150, + sorter: false, + align: 'left', + }, + { + title: <div className="column-header-left">CPU Utilization (cores)</div>, + dataIndex: 'cpu', + key: 'cpu', + width: 80, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">CPU Allocatable (cores)</div>, + dataIndex: 'cpu_allocatable', + key: 'cpu_allocatable', + width: 80, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">Memory Utilization (bytes)</div>, + dataIndex: 'memory', + key: 'memory', + width: 80, + sorter: true, + align: 'left', + }, + { + title: <div className="column-header-left">Memory Allocatable (bytes)</div>, + dataIndex: 'memory_allocatable', + key: 'memory_allocatable', + width: 80, + sorter: true, + align: 'left', + }, +]; + +export const getK8sClustersListColumns = ( + groupBy: IBuilderQuery['groupBy'], +): ColumnType<K8sClustersRowData>[] => { + if (groupBy.length > 0) { + const filteredColumns = [...columnsConfig].filter( + (column) => column.key !== 'clusterName', + ); + filteredColumns.unshift(clusterGroupColumnConfig); + return filteredColumns as ColumnType<K8sClustersRowData>[]; + } + + return columnsConfig as ColumnType<K8sClustersRowData>[]; +}; + +const getGroupByEle = ( + cluster: K8sClustersData, + groupBy: IBuilderQuery['groupBy'], +): React.ReactNode => { + const groupByValues: string[] = []; + + groupBy.forEach((group) => { + groupByValues.push(cluster.meta[group.key as keyof typeof cluster.meta]); + }); + + return ( + <div className="pod-group"> + {groupByValues.map((value) => ( + <Tag key={value} color={Color.BG_SLATE_400} className="pod-group-tag-item"> + {value === '' ? '<no-value>' : value} + </Tag> + ))} + </div> + ); +}; + +export const formatDataForTable = ( + data: K8sClustersData[], + groupBy: IBuilderQuery['groupBy'], +): K8sClustersRowData[] => + data.map((cluster) => ({ + key: cluster.meta.k8s_cluster_name, + clusterUID: cluster.clusterUID, + clusterName: ( + <Tooltip title={cluster.meta.k8s_cluster_name}> + {cluster.meta.k8s_cluster_name} + </Tooltip> + ), + cpu: ( + <ValidateColumnValueWrapper value={cluster.cpuUsage}> + {cluster.cpuUsage} + </ValidateColumnValueWrapper> + ), + memory: ( + <ValidateColumnValueWrapper value={cluster.memoryUsage}> + {formatBytes(cluster.memoryUsage)} + </ValidateColumnValueWrapper> + ), + cpu_allocatable: ( + <ValidateColumnValueWrapper value={cluster.cpuAllocatable}> + {cluster.cpuAllocatable} + </ValidateColumnValueWrapper> + ), + memory_allocatable: ( + <ValidateColumnValueWrapper value={cluster.memoryAllocatable}> + {formatBytes(cluster.memoryAllocatable)} + </ValidateColumnValueWrapper> + ), + clusterGroup: getGroupByEle(cluster, groupBy), + meta: cluster.meta, + ...cluster.meta, + groupedByMeta: cluster.meta, + })); diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index 24c54cbd0f..7cb3e76a00 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -7,12 +7,20 @@ import { Collapse, Tooltip, Typography } from 'antd'; import QuickFilters from 'components/QuickFilters/QuickFilters'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; -import { Computer, Container, FilePenLine, Workflow } from 'lucide-react'; +import { + Boxes, + Computer, + Container, + FilePenLine, + Workflow, +} from 'lucide-react'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import { useState } from 'react'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; +import K8sClustersList from './Clusters/K8sClustersList'; import { + ClustersQuickFiltersConfig, DeploymentsQuickFiltersConfig, K8sCategories, NamespaceQuickFiltersConfig, @@ -114,26 +122,26 @@ export default function InfraMonitoringK8s(): JSX.Element { /> ), }, - // { - // label: ( - // <div className="k8s-quick-filters-category-label"> - // <div className="k8s-quick-filters-category-label-container"> - // <Boxes size={14} className="k8s-quick-filters-category-label-icon" /> - // <Typography.Text>Clusters</Typography.Text> - // </div> - // </div> - // ), - // key: K8sCategories.CLUSTERS, - // showArrow: false, - // children: ( - // <QuickFilters - // source="infra-monitoring" - // config={ClustersQuickFiltersConfig} - // handleFilterVisibilityChange={handleFilterVisibilityChange} - // onFilterChange={handleFilterChange} - // /> - // ), - // }, + { + label: ( + <div className="k8s-quick-filters-category-label"> + <div className="k8s-quick-filters-category-label-container"> + <Boxes size={14} className="k8s-quick-filters-category-label-icon" /> + <Typography.Text>Clusters</Typography.Text> + </div> + </div> + ), + key: K8sCategories.CLUSTERS, + showArrow: false, + children: ( + <QuickFilters + source="infra-monitoring" + config={ClustersQuickFiltersConfig} + handleFilterVisibilityChange={handleFilterVisibilityChange} + onFilterChange={handleFilterChange} + /> + ), + }, // { // label: ( // <div className="k8s-quick-filters-category-label"> @@ -333,6 +341,12 @@ export default function InfraMonitoringK8s(): JSX.Element { /> )} + {selectedCategory === K8sCategories.CLUSTERS && ( + <K8sClustersList + isFiltersVisible={showFilters} + handleFilterVisibilityChange={handleFilterVisibilityChange} + /> + )} </div> </div> </div> diff --git a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx index e1b588b4f7..605ba7d3d9 100644 --- a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx @@ -1,5 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react/require-default-props */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable prefer-destructuring */ import { Color } from '@signozhq/design-tokens'; import { Tooltip, Typography } from 'antd'; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sClustersList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sClustersList.ts new file mode 100644 index 0000000000..9b248688c7 --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sClustersList.ts @@ -0,0 +1,54 @@ +import { + getK8sClustersList, + K8sClustersListPayload, + K8sClustersListResponse, +} from 'api/infraMonitoring/getK8sClustersList'; +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 UseGetK8sClustersList = ( + requestData: K8sClustersListPayload, + + options?: UseQueryOptions< + SuccessResponse<K8sClustersListResponse> | ErrorResponse, + Error + >, + + headers?: Record<string, string>, +) => UseQueryResult< + SuccessResponse<K8sClustersListResponse> | ErrorResponse, + Error +>; + +export const useGetK8sClustersList: UseGetK8sClustersList = ( + requestData, + + options, + + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_CLUSTER_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery< + SuccessResponse<K8sClustersListResponse> | ErrorResponse, + Error + >({ + queryFn: ({ signal }) => getK8sClustersList(requestData, signal, headers), + + ...options, + + queryKey, + }); +}; From 12a865135fa397c11ee76e8547e2dd24c62c00ad Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Fri, 17 Jan 2025 22:49:00 +0530 Subject: [PATCH 07/20] chore: refactor pods --- .../entityDetails.styles.scss | 576 ++++++++++++++++++ .../entityEvents.styles.scss} | 21 +- .../entityLogs.styles.scss} | 10 +- .../entityMetrics.styles.scss} | 12 +- .../entityTraces.styles.scss} | 23 +- .../utils.tsx} | 104 +++- .../Pods/PodDetails/Events/Events.tsx | 27 +- .../PodDetails/Events/NoEventsContainer.tsx | 16 - .../Pods/PodDetails/Events/constants.ts | 65 -- .../Pods/PodDetails/Metrics/Metrics.tsx | 8 +- .../{ => PodDetails/Metrics}/constants.ts | 0 .../Pods/PodDetails/PodDetails.styles.scss | 247 -------- .../Pods/PodDetails/PodDetails.tsx | 22 +- .../PodDetails/PodLogs/NoLogsContainer.tsx | 16 - .../Pods/PodDetails/PodLogs/PodLogs.tsx | 27 +- .../PodLogs/PodLogsDetailedView.tsx | 6 +- .../Pods/PodDetails/PodLogs/constants.ts | 65 -- .../Pods/PodDetails/PodTraces/PodTraces.tsx | 15 +- .../Pods/PodDetails/constants.ts | 7 - 19 files changed, 766 insertions(+), 501 deletions(-) create mode 100644 frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityDetails.styles.scss rename frontend/src/container/InfraMonitoringK8s/{Pods/PodDetails/Events/Events.styles.scss => EntityDetailsUtils/entityEvents.styles.scss} (94%) rename frontend/src/container/InfraMonitoringK8s/{Pods/PodDetails/PodLogs/PodLogs.styles.scss => EntityDetailsUtils/entityLogs.styles.scss} (94%) rename frontend/src/container/InfraMonitoringK8s/{Pods/PodDetails/Metrics/Metrics.styles.scss => EntityDetailsUtils/entityMetrics.styles.scss} (76%) rename frontend/src/container/InfraMonitoringK8s/{Pods/PodDetails/PodTraces/PodTraces.styles.scss => EntityDetailsUtils/entityTraces.styles.scss} (88%) rename frontend/src/container/InfraMonitoringK8s/{Pods/PodDetails/PodTraces/constants.ts => EntityDetailsUtils/utils.tsx} (63%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/NoEventsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/constants.ts rename frontend/src/container/InfraMonitoringK8s/Pods/{ => PodDetails/Metrics}/constants.ts (100%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/NoLogsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityDetails.styles.scss new file mode 100644 index 0000000000..1fd8c08dab --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityDetails.styles.scss @@ -0,0 +1,576 @@ +// Entity Details +.entity-detail-drawer { + border-left: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); + + .ant-drawer-header { + padding: 8px 16px; + border-bottom: none; + + align-items: stretch; + + border-bottom: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + } + + .ant-drawer-close { + margin-inline-end: 0px; + } + + .ant-drawer-body { + display: flex; + flex-direction: column; + padding: 16px; + } + + .title { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .radio-button { + display: flex; + align-items: center; + justify-content: center; + padding-top: var(--padding-1); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .entity-detail-drawer__entity { + .entity-details-grid { + .labels-row, + .values-row { + display: grid; + grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; + gap: 30px; + align-items: center; + } + + .labels-row { + margin-bottom: 8px; + } + + .entity-details-metadata-label { + color: var(--text-vanilla-400); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + } + + .entity-details-metadata-value { + color: var(--text-vanilla-400); + font-family: 'Geist Mono'; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .status-tag { + margin: 0; + + &.active { + color: var(--success-500); + background: var(--success-100); + border-color: var(--success-500); + } + + &.inactive { + color: var(--error-500); + background: var(--error-100); + border-color: var(--error-500); + } + } + + .progress-container { + width: 158px; + .ant-progress { + margin: 0; + + .ant-progress-text { + font-weight: 600; + } + } + } + + .ant-card { + &.ant-card-bordered { + border: 1px solid var(--bg-slate-500) !important; + } + } + } + } + + .tabs-and-search { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; + + .action-btn { + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + } + } + + .views-tabs-container { + margin-top: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + + .views-tabs { + color: var(--text-vanilla-400); + + .view-title { + display: flex; + gap: var(--margin-2); + align-items: center; + justify-content: center; + font-size: var(--font-size-xs); + font-style: normal; + font-weight: var(--font-weight-normal); + } + + .tab { + border: 1px solid var(--bg-slate-400); + width: 114px; + } + + .tab::before { + background: var(--bg-slate-400); + } + + .selected_view { + background: var(--bg-slate-300); + color: var(--text-vanilla-100); + border: 1px solid var(--bg-slate-400); + } + + .selected_view::before { + background: var(--bg-slate-400); + } + } + + .compass-button { + width: 30px; + height: 30px; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + } + .ant-drawer-close { + padding: 0px; + } +} + +.lightMode { + .ant-drawer-header { + border-bottom: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + } + + .entity-detail-drawer { + .title { + color: var(--text-ink-300); + } + + .entity-detail-drawer__entity { + .ant-typography { + color: var(--text-ink-300); + background: transparent; + } + } + + .radio-button { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .views-tabs { + .tab { + background: var(--bg-vanilla-100); + } + + .selected_view { + background: var(--bg-vanilla-300); + border: 1px solid var(--bg-slate-300); + color: var(--text-ink-400); + } + + .selected_view::before { + background: var(--bg-vanilla-300); + border-left: 1px solid var(--bg-slate-300); + } + } + + .compass-button { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); + } + + .tabs-and-search { + .action-btn { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + } + } +} + +.entity-metric-traces { + margin-top: 1rem; + + .entity-metric-traces-header { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + + gap: 8px; + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + } + + .entity-metric-traces-table { + .ant-table-content { + overflow: hidden !important; + } + + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + + .ant-table-thead > tr > th { + padding: 12px; + font-weight: 500; + font-size: 12px; + line-height: 18px; + + background: rgba(171, 189, 255, 0.01); + border-bottom: none; + + color: var(--Vanilla-400, #c0c1c3); + font-family: Inter; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.44px; + text-transform: uppercase; + + &::before { + background-color: transparent; + } + } + + .ant-table-thead > tr > th:has(.entityname-column-header) { + background: var(--bg-ink-400); + } + + .ant-table-cell { + padding: 12px; + font-size: 13px; + line-height: 20px; + color: var(--bg-vanilla-100); + background: rgba(171, 189, 255, 0.01); + } + + .ant-table-cell:has(.entityname-column-value) { + background: var(--bg-ink-400); + } + + .entityname-column-value { + color: var(--bg-vanilla-100); + font-family: 'Geist Mono'; + font-style: normal; + font-weight: 600; + line-height: 20px; /* 142.857% */ + letter-spacing: -0.07px; + } + + .status-cell { + .active-tag { + color: var(--bg-forest-500); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .progress-container { + .ant-progress-bg { + height: 8px !important; + border-radius: 4px; + } + } + + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.04); + } + + .ant-table-cell:first-child { + text-align: justify; + } + + .ant-table-cell:nth-child(2) { + padding-left: 16px; + padding-right: 16px; + } + + .ant-table-cell:nth-child(n + 3) { + padding-right: 24px; + } + .column-header-right { + text-align: right; + } + .ant-table-tbody > tr > td { + border-bottom: none; + } + + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + background-color: transparent; + } + + .ant-empty-normal { + visibility: hidden; + } + } + + .ant-table-container::after { + content: none; + } + } +} + +.lightMode { + .entity-metric-traces-header { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } + } + + .entity-metric-traces-table { + .ant-table { + border-radius: 3px; + border: 1px solid var(--bg-vanilla-300); + + .ant-table-thead > tr > th { + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .ant-table-thead > tr > th:has(.entityname-column-header) { + background: var(--bg-vanilla-100); + } + + .ant-table-cell { + background: var(--bg-vanilla-100); + color: var(--bg-ink-500); + } + + .ant-table-cell:has(.entityname-column-value) { + background: var(--bg-vanilla-100); + } + + .entityname-column-value { + color: var(--bg-ink-300); + } + + .ant-table-tbody > tr:hover > td { + background: rgba(0, 0, 0, 0.04); + } + } + } +} + +.entity-metrics-logs-container { + margin-top: 1rem; + + .filter-section { + flex: 1; + + .ant-select-selector { + border-radius: 2px; + border: 1px solid var(--bg-slate-400) !important; + background-color: var(--bg-ink-300) !important; + + input { + font-size: 12px; + } + + .ant-tag .ant-typography { + font-size: 12px; + } + } + } + + .entity-metrics-logs-header { + display: flex; + justify-content: space-between; + gap: 8px; + + padding: 12px; + border-radius: 3px; + border: 1px solid var(--bg-slate-500); + } + + .entity-metrics-logs { + margin-top: 1rem; + + .virtuoso-list { + overflow-y: hidden !important; + + &::-webkit-scrollbar { + width: 0.3rem; + height: 0.3rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--bg-slate-300); + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--bg-slate-200); + } + + .ant-row { + width: fit-content; + } + } + + .skeleton-container { + height: 100%; + padding: 16px; + } + } +} + +.entity-metrics-logs-list-container { + flex: 1; + height: calc(100vh - 272px) !important; + display: flex; + height: 100%; + + .raw-log-content { + width: 100%; + text-wrap: inherit; + word-wrap: break-word; + } +} + +.entity-metrics-logs-list-card { + width: 100%; + margin-top: 12px; + + .ant-card-body { + padding: 0; + + height: 100%; + width: 100%; + } +} + +.logs-loading-skeleton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 0; + + .ant-skeleton-input-sm { + height: 18px; + } +} + +.no-logs-found { + height: 50vh; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + padding: 24px; + box-sizing: border-box; + + .ant-typography { + display: flex; + align-items: center; + gap: 16px; + } +} + +.lightMode { + .filter-section { + border-top: 1px solid var(--bg-vanilla-300); + border-bottom: 1px solid var(--bg-vanilla-300); + + .ant-select-selector { + border-color: var(--bg-vanilla-300) !important; + background-color: var(--bg-vanilla-100) !important; + color: var(--bg-ink-200); + } + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityEvents.styles.scss similarity index 94% rename from frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityEvents.styles.scss index 54ed42d565..a3be71803b 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityEvents.styles.scss @@ -1,4 +1,5 @@ -.pod-events-container { +// Events +.entity-events-container { margin-top: 1rem; .filter-section { @@ -19,7 +20,7 @@ } } - .pod-events-header { + .entity-events-header { display: flex; justify-content: space-between; gap: 8px; @@ -29,7 +30,7 @@ border: 1px solid var(--bg-slate-500); } - .pod-events { + .entity-events { margin-top: 1rem; .virtuoso-list { @@ -87,7 +88,7 @@ } } - .ant-table-thead > tr > th:has(.hostname-column-header) { + .ant-table-thead > tr > th:has(.entityname-column-header) { background: var(--bg-ink-400); } @@ -100,11 +101,11 @@ border-bottom: none; } - .ant-table-cell:has(.hostname-column-value) { + .ant-table-cell:has(.entityname-column-value) { background: var(--bg-ink-400); } - .hostname-column-value { + .entityname-column-value { color: var(--bg-vanilla-100); font-family: 'Geist Mono'; font-style: normal; @@ -190,7 +191,7 @@ } } -.pod-events-list-container { +.entity-events-list-container { flex: 1; height: calc(100vh - 300px) !important; display: flex; @@ -203,7 +204,7 @@ } } -.pod-events-list-card { +.entity-events-list-card { width: 100%; margin-top: 12px; @@ -271,7 +272,7 @@ } } -.pod-events-footer { +.entity-events-footer { display: flex; justify-content: flex-end; align-items: center; @@ -297,7 +298,7 @@ } .periscope-btn { - &.ghost { + &.gentity { border: none; background: transparent; } diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityLogs.styles.scss similarity index 94% rename from frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityLogs.styles.scss index b65cf1446e..b4c518a319 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityLogs.styles.scss @@ -1,4 +1,4 @@ -.pod-logs-container { +.entity-logs-container { margin-top: 1rem; .filter-section { @@ -19,7 +19,7 @@ } } - .pod-logs-header { + .entity-logs-header { display: flex; justify-content: space-between; gap: 8px; @@ -29,7 +29,7 @@ border: 1px solid var(--bg-slate-500); } - .pod-logs { + .entity-logs { margin-top: 1rem; .virtuoso-list { @@ -64,7 +64,7 @@ } } -.pod-logs-list-container { +.entity-logs-list-container { flex: 1; height: calc(100vh - 272px) !important; display: flex; @@ -77,7 +77,7 @@ } } -.pod-logs-list-card { +.entity-logs-list-card { width: 100%; margin-top: 12px; diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityMetrics.styles.scss similarity index 76% rename from frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityMetrics.styles.scss index 3978680c09..3af4091b60 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityMetrics.styles.scss @@ -1,11 +1,5 @@ -.empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.host-metrics-container { +// Metrics +.entity-metrics-container { margin-top: 1rem; } @@ -20,7 +14,7 @@ border: 1px solid var(--bg-slate-500); } -.host-metrics-card { +.entity-metrics-card { margin: 8px 0 1rem 0; height: 300px; padding: 10px; diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityTraces.styles.scss similarity index 88% rename from frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityTraces.styles.scss index a2d461a3a4..9c90114edc 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityTraces.styles.scss @@ -1,7 +1,8 @@ -.pod-metric-traces { +// Traces +.entity-metric-traces { margin-top: 1rem; - .pod-metric-traces-header { + .entity-metric-traces-header { display: flex; justify-content: space-between; margin-bottom: 1rem; @@ -30,7 +31,7 @@ } } - .pod-metric-traces-table { + .entity-metric-traces-table { .ant-table-content { overflow: hidden !important; } @@ -62,7 +63,7 @@ } } - .ant-table-thead > tr > th:has(.hostname-column-header) { + .ant-table-thead > tr > th:has(.entityname-column-header) { background: var(--bg-ink-400); } @@ -74,11 +75,11 @@ background: rgba(171, 189, 255, 0.01); } - .ant-table-cell:has(.hostname-column-value) { + .ant-table-cell:has(.entityname-column-value) { background: var(--bg-ink-400); } - .hostname-column-value { + .entityname-column-value { color: var(--bg-vanilla-100); font-family: 'Geist Mono'; font-style: normal; @@ -145,7 +146,7 @@ } .lightMode { - .host-metric-traces-header { + .entity-metric-traces-header { .filter-section { border-top: 1px solid var(--bg-vanilla-300); border-bottom: 1px solid var(--bg-vanilla-300); @@ -158,7 +159,7 @@ } } - .host-metric-traces-table { + .entity-metric-traces-table { .ant-table { border-radius: 3px; border: 1px solid var(--bg-vanilla-300); @@ -168,7 +169,7 @@ color: var(--text-ink-300); } - .ant-table-thead > tr > th:has(.hostname-column-header) { + .ant-table-thead > tr > th:has(.entityname-column-header) { background: var(--bg-vanilla-100); } @@ -177,11 +178,11 @@ color: var(--bg-ink-500); } - .ant-table-cell:has(.hostname-column-value) { + .ant-table-cell:has(.entityname-column-value) { background: var(--bg-vanilla-100); } - .hostname-column-value { + .entityname-column-value { color: var(--bg-ink-300); } diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/constants.ts b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx similarity index 63% rename from frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/constants.ts rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx index 99c6037177..cb08d3b9a8 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx @@ -1,5 +1,8 @@ +import { Color } from '@signozhq/design-tokens'; +import { Typography } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { Ghost } from 'lucide-react'; import { BaseAutocompleteData, DataTypes, @@ -8,8 +11,103 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; import { nanoToMilli } from 'utils/timeUtils'; +import { v4 as uuidv4 } from 'uuid'; -export const columns = [ +import { K8sCategory } from '../constants'; + +export const QUERY_KEYS = { + K8S_OBJECT_KIND: 'k8s.object.kind', + K8S_OBJECT_NAME: 'k8s.object.name', + K8S_POD_NAME: 'k8s.pod.name', + K8S_NAMESPACE_NAME: 'k8s.namespace.name', + K8S_CLUSTER_NAME: 'k8s.cluster.name', +}; + +/** + * Returns the payload configuration to fetch events for a K8s entity + */ +export const getEntityEventsOrLogsQueryPayload = ( + start: number, + end: number, + filters: IBuilderQuery['filters'], +): GetQueryResultsProps => ({ + graphType: PANEL_TYPES.LIST, + selectedTime: 'GLOBAL_TIME', + query: { + clickhouse_sql: [], + promql: [], + builder: { + queryData: [ + { + dataSource: DataSource.LOGS, + queryName: 'A', + aggregateOperator: 'noop', + aggregateAttribute: { + id: '------false', + dataType: DataTypes.String, + key: '', + isColumn: false, + type: '', + isJSON: false, + }, + timeAggregation: 'rate', + spaceAggregation: 'sum', + functions: [], + filters, + expression: 'A', + disabled: false, + stepInterval: 60, + having: [], + limit: null, + orderBy: [ + { + columnName: 'timestamp', + order: 'desc', + }, + ], + groupBy: [], + legend: '', + reduceTo: 'avg', + offset: 0, + pageSize: 100, + }, + ], + queryFormulas: [], + }, + id: uuidv4(), + queryType: EQueryType.QUERY_BUILDER, + }, + params: { + lastLogLineTimestamp: null, + }, + start, + end, +}); + +/** + * Empty state container for entity details + */ +export function EntityDetailsEmptyContainer({ + view, + category, +}: { + view: 'logs' | 'traces' | 'events'; + category: K8sCategory; +}): React.ReactElement { + const label = category.slice(0, category.length); + + return ( + <div className="no-logs-found"> + <Typography.Text type="secondary"> + <Ghost size={24} color={Color.BG_AMBER_500} /> + {`No ${view} found for this ${label} + in the selected time range.`} + </Typography.Text> + </div> + ); +} + +export const entityTracesColumns = [ { dataIndex: 'timestamp', key: 'timestamp', @@ -50,7 +148,7 @@ export const columns = [ }, ]; -export const selectedColumns: BaseAutocompleteData[] = [ +export const selectedEntityTracesColumns: BaseAutocompleteData[] = [ { key: 'timestamp', dataType: DataTypes.String, @@ -89,7 +187,7 @@ export const selectedColumns: BaseAutocompleteData[] = [ }, ]; -export const getPodTracesQueryPayload = ( +export const getEntityTracesQueryPayload = ( start: number, end: number, offset = 0, diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx index e2be874e6d..6f138122f7 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './Events.styles.scss'; +import '../../../EntityDetailsUtils/entityEvents.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Table, TableColumnsType } from 'antd'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; import LogsError from 'container/LogsError/LogsError'; import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; @@ -25,8 +26,10 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { v4 } from 'uuid'; -import { getPodsEventsQueryPayload } from './constants'; -import NoEventsContainer from './NoEventsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, +} from '../../../EntityDetailsUtils/utils'; interface EventDataType { key: string; @@ -110,7 +113,7 @@ export default function Events({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; const queryPayload = useMemo(() => { - const basePayload = getPodsEventsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -277,8 +280,8 @@ export default function Events({ ); return ( - <div className="pod-events-container"> - <div className="pod-events-header"> + <div className="entity-events-container"> + <div className="entity-events-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -304,14 +307,14 @@ export default function Events({ {isLoading && <LoadingContainer />} {!isLoading && !isError && formattedPodEvents.length === 0 && ( - <NoEventsContainer /> + <EntityDetailsEmptyContainer category={K8sCategory.PODS} view="events" /> )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && formattedPodEvents.length > 0 && ( - <div className="pod-events-list-container"> - <div className="pod-events-list-card"> + <div className="entity-events-list-container"> + <div className="entity-events-list-card"> <Table<EventDataType> loading={isLoading && page > 1} columns={columns} @@ -329,9 +332,9 @@ export default function Events({ )} {!isError && formattedPodEvents.length > 0 && ( - <div className="pod-events-footer"> + <div className="entity-events-footer"> <Button - className="pod-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handlePrev} disabled={page === 1 || isFetching || isLoading} @@ -341,7 +344,7 @@ export default function Events({ </Button> <Button - className="pod-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handleNext} disabled={hasReachedEndOfEvents || isFetching || isLoading} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/NoEventsContainer.tsx deleted file mode 100644 index 006ac4b437..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/NoEventsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoEventsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this pod - in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/constants.ts deleted file mode 100644 index 7b434effa9..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getPodsEventsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx index 2135e7b610..eb03cfea44 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx @@ -1,4 +1,4 @@ -import './Metrics.styles.scss'; +import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; import { Card, Col, Row, Skeleton, Typography } from 'antd'; import { K8sPodsData } from 'api/infraMonitoring/getK8sPodsList'; @@ -20,7 +20,7 @@ import { useQueries, UseQueryResult } from 'react-query'; import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; -import { getPodQueryPayload, podWidgetInfo } from '../../constants'; +import { getPodQueryPayload, podWidgetInfo } from './constants'; interface MetricsTabProps { timeRange: { @@ -123,11 +123,11 @@ function Metrics({ /> </div> </div> - <Row gutter={24} className="host-metrics-container"> + <Row gutter={24} className="entity-metrics-container"> {queries.map((query, idx) => ( <Col span={12} key={podWidgetInfo[idx].title}> <Typography.Text>{podWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="host-metrics-card" ref={graphRef}> + <Card bordered className="entity-metrics-card" ref={graphRef}> {renderCardContent(query, idx)} </Card> </Col> diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/constants.ts b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/constants.ts similarity index 100% rename from frontend/src/container/InfraMonitoringK8s/Pods/constants.ts rename to frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/constants.ts diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.styles.scss deleted file mode 100644 index 5c2446014e..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.styles.scss +++ /dev/null @@ -1,247 +0,0 @@ -.pod-detail-drawer { - border-left: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); - - .ant-drawer-header { - padding: 8px 16px; - border-bottom: none; - - align-items: stretch; - - border-bottom: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - } - - .ant-drawer-close { - margin-inline-end: 0px; - } - - .ant-drawer-body { - display: flex; - flex-direction: column; - padding: 16px; - } - - .title { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 14px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .radio-button { - display: flex; - align-items: center; - justify-content: center; - padding-top: var(--padding-1); - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .pod-detail-drawer__pod { - .pod-details-grid { - .labels-row, - .values-row { - display: grid; - grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; - gap: 30px; - align-items: center; - } - - .labels-row { - margin-bottom: 8px; - } - - .pod-details-metadata-label { - color: var(--text-vanilla-400); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 500; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - } - - .pod-details-metadata-value { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .status-tag { - margin: 0; - - &.active { - color: var(--success-500); - background: var(--success-100); - border-color: var(--success-500); - } - - &.inactive { - color: var(--error-500); - background: var(--error-100); - border-color: var(--error-500); - } - } - - .progress-container { - width: 158px; - .ant-progress { - margin: 0; - - .ant-progress-text { - font-weight: 600; - } - } - } - - .ant-card { - &.ant-card-bordered { - border: 1px solid var(--bg-slate-500) !important; - } - } - } - } - - .tabs-and-search { - display: flex; - justify-content: space-between; - align-items: center; - margin: 16px 0; - - .action-btn { - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: center; - } - } - - .views-tabs-container { - margin-top: 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; - - .views-tabs { - color: var(--text-vanilla-400); - - .view-title { - display: flex; - gap: var(--margin-2); - align-items: center; - justify-content: center; - font-size: var(--font-size-xs); - font-style: normal; - font-weight: var(--font-weight-normal); - } - - .tab { - border: 1px solid var(--bg-slate-400); - width: 114px; - } - - .tab::before { - background: var(--bg-slate-400); - } - - .selected_view { - background: var(--bg-slate-300); - color: var(--text-vanilla-100); - border: 1px solid var(--bg-slate-400); - } - - .selected_view::before { - background: var(--bg-slate-400); - } - } - - .compass-button { - width: 30px; - height: 30px; - - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - } - .ant-drawer-close { - padding: 0px; - } -} - -.lightMode { - .ant-drawer-header { - border-bottom: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - } - - .pod-detail-drawer { - .title { - color: var(--text-ink-300); - } - - .pod-detail-drawer__pod { - .ant-typography { - color: var(--text-ink-300); - background: transparent; - } - } - - .radio-button { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .views-tabs { - .tab { - background: var(--bg-vanilla-100); - } - - .selected_view { - background: var(--bg-vanilla-300); - border: 1px solid var(--bg-slate-300); - color: var(--text-ink-400); - } - - .selected_view::before { - background: var(--bg-vanilla-300); - border-left: 1px solid var(--bg-slate-300); - } - } - - .compass-button { - border: 1px solid var(--bg-vanilla-300); - background: var(--bg-vanilla-100); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .tabs-and-search { - .action-btn { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index d13207106f..3039a1739f 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -1,6 +1,6 @@ /* eslint-disable sonarjs/no-identical-functions */ /* eslint-disable sonarjs/no-duplicate-string */ -import './PodDetails.styles.scss'; +import '../../EntityDetailsUtils/entityDetails.styles.scss'; import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; @@ -13,6 +13,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils'; import { CustomTimeType, @@ -44,7 +45,6 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import { QUERY_KEYS } from './constants'; import Events from './Events/Events'; import Metrics from './Metrics/Metrics'; import { PodDetailProps } from './PodDetail.interfaces'; @@ -423,49 +423,49 @@ function PodDetails({ overscrollBehavior: 'contain', background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, }} - className="pod-detail-drawer" + className="entity-detail-drawer" destroyOnClose closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} > {pod && ( <> - <div className="pod-detail-drawer__pod"> - <div className="pod-details-grid"> + <div className="entity-detail-drawer__entity"> + <div className="entity-details-grid"> <div className="labels-row"> <Typography.Text type="secondary" - className="pod-details-metadata-label" + className="entity-details-metadata-label" > NAMESPACE </Typography.Text> <Typography.Text type="secondary" - className="pod-details-metadata-label" + className="entity-details-metadata-label" > Cluster Name </Typography.Text> <Typography.Text type="secondary" - className="pod-details-metadata-label" + className="entity-details-metadata-label" > Node </Typography.Text> </div> <div className="values-row"> - <Typography.Text className="pod-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={pod.meta.k8s_namespace_name}> {pod.meta.k8s_namespace_name} </Tooltip> </Typography.Text> - <Typography.Text className="pod-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={pod.meta.k8s_cluster_name}> {pod.meta.k8s_cluster_name} </Tooltip> </Typography.Text> - <Typography.Text className="pod-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={pod.meta.k8s_node_name}> {pod.meta.k8s_node_name} </Tooltip> diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/NoLogsContainer.tsx deleted file mode 100644 index b46213cbf3..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/NoLogsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoLogsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this pod in - the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx index 2ed0a9858b..673daedad6 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './PodLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import { Card } from 'antd'; import RawLogView from 'components/Logs/RawLogView'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LogsError from 'container/LogsError/LogsError'; import { LogsLoading } from 'container/LogsLoading/LogsLoading'; import { FontSize } from 'container/OptionsMenu/types'; @@ -22,9 +23,11 @@ import { } from 'types/api/queryBuilder/queryBuilderData'; import { v4 } from 'uuid'; -import { QUERY_KEYS } from '../constants'; -import { getPodLogsQueryPayload } from './constants'; -import NoLogsContainer from './NoLogsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, + QUERY_KEYS, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -67,7 +70,7 @@ function PodLogs({ }, [filters]); const queryPayload = useMemo(() => { - const basePayload = getPodLogsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -183,11 +186,11 @@ function PodLogs({ const renderContent = useMemo( () => ( - <Card bordered={false} className="pod-logs-list-card"> + <Card bordered={false} className="entity-logs-list-card"> <OverlayScrollbar isVirtuoso> <Virtuoso - className="pod-logs-virtuoso" - key="pod-logs-virtuoso" + className="entity-logs-virtuoso" + key="entity-logs-virtuoso" data={logs} endReached={loadMoreLogs} totalCount={logs.length} @@ -204,12 +207,14 @@ function PodLogs({ ); return ( - <div className="pod-logs"> + <div className="entity-logs"> {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {!isLoading && !isError && logs.length === 0 && ( + <EntityDetailsEmptyContainer category={K8sCategory.PODS} view="logs" /> + )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && logs.length > 0 && ( - <div className="pod-logs-list-container">{renderContent}</div> + <div className="entity-logs-list-container">{renderContent}</div> )} </div> ); diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx index 69e22c1c6c..573c54cc4c 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx @@ -1,4 +1,4 @@ -import './PodLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; @@ -64,8 +64,8 @@ function PodLogsDetailedView({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; return ( - <div className="host-metrics-logs-container"> - <div className="host-metrics-logs-header"> + <div className="entity-metrics-logs-container"> + <div className="entity-metrics-logs-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/constants.ts deleted file mode 100644 index 00a09f7d16..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getPodLogsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx index 53b12908c8..46dd65e368 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx @@ -1,4 +1,4 @@ -import './PodTraces.styles.scss'; +import '../../../EntityDetailsUtils/entityTraces.styles.scss'; import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; import { ResizeTable } from 'components/ResizeTable'; @@ -25,7 +25,10 @@ import { useQuery } from 'react-query'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import { getPodTracesQueryPayload, selectedColumns } from './constants'; +import { + getEntityTracesQueryPayload, + selectedEntityTracesColumns, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -86,7 +89,7 @@ function PodTraces({ const queryPayload = useMemo( () => - getPodTracesQueryPayload( + getEntityTracesQueryPayload( timeRange.startTime, timeRange.endTime, paginationQueryData?.offset || offset, @@ -115,7 +118,7 @@ function PodTraces({ enabled: !!queryPayload, }); - const traceListColumns = getListColumns(selectedColumns); + const traceListColumns = getListColumns(selectedEntityTracesColumns); useEffect(() => { if (data?.payload?.data?.newResult?.data?.result) { @@ -138,8 +141,8 @@ function PodTraces({ data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; return ( - <div className="host-metric-traces"> - <div className="host-metric-traces-header"> + <div className="entity-metric-traces"> + <div className="entity-metric-traces-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts deleted file mode 100644 index 6a9b1f852f..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const QUERY_KEYS = { - K8S_OBJECT_KIND: 'k8s.object.kind', - K8S_OBJECT_NAME: 'k8s.object.name', - K8S_POD_NAME: 'k8s.pod.name', - K8S_NAMESPACE_NAME: 'k8s.namespace.name', - K8S_CLUSTER_NAME: 'k8s.cluster.name', -}; From 73d41c6be99999a6f7a63c322f667e027158f01d Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Fri, 17 Jan 2025 23:05:13 +0530 Subject: [PATCH 08/20] chore: refactor nodes --- .../EntityDetailsUtils/utils.tsx | 1 + .../NodeDetails/Events/NoEventsContainer.tsx | 16 - .../NodeDetails/Events/NodeEvents.styles.scss | 289 ------------------ .../Nodes/NodeDetails/Events/NodeEvents.tsx | 27 +- .../Nodes/NodeDetails/Events/constants.ts | 65 ---- .../NodeDetails/Logs/NoLogsContainer.tsx | 16 - .../NodeDetails/Logs/NodeLogs.styles.scss | 133 -------- .../Nodes/NodeDetails/Logs/NodeLogs.tsx | 27 +- .../NodeDetails/Logs/NodeLogsDetailedView.tsx | 7 +- .../Nodes/NodeDetails/Logs/constants.ts | 65 ---- .../Metrics/NodeMetrics.styles.scss | 45 --- .../Nodes/NodeDetails/Metrics/NodeMetrics.tsx | 6 +- .../Nodes/NodeDetails/NodeDetails.styles.scss | 247 --------------- .../Nodes/NodeDetails/NodeDetails.tsx | 18 +- .../NodeDetails/Traces/NodeTraces.styles.scss | 193 ------------ .../Nodes/NodeDetails/Traces/NodeTraces.tsx | 19 +- .../Nodes/NodeDetails/Traces/constants.ts | 200 ------------ .../Nodes/NodeDetails/constants.ts | 6 - 18 files changed, 59 insertions(+), 1321 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NoEventsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NoLogsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx index cb08d3b9a8..4f9e0a0004 100644 --- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx @@ -21,6 +21,7 @@ export const QUERY_KEYS = { K8S_POD_NAME: 'k8s.pod.name', K8S_NAMESPACE_NAME: 'k8s.namespace.name', K8S_CLUSTER_NAME: 'k8s.cluster.name', + K8S_NODE_NAME: 'k8s.node.name', }; /** diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NoEventsContainer.tsx deleted file mode 100644 index 0fafb37cae..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NoEventsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoEventsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this node - in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.styles.scss deleted file mode 100644 index 807ec2910a..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.styles.scss +++ /dev/null @@ -1,289 +0,0 @@ -.node-events-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .node-events-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .node-events { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } - - .ant-table { - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgb(18, 19, 23); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.nodename-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgb(18, 19, 23); - border-bottom: none; - } - - .ant-table-cell:has(.nodename-column-value) { - background: var(--bg-ink-400); - } - - .nodename-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-pagination { - position: fixed; - bottom: 0; - width: calc(100% - 64px); - background: rgb(18, 19, 23); - padding: 16px; - margin: 0; - - // this is to offset intercom icon till we improve the design - padding-right: 72px; - - .ant-pagination-item { - border-radius: 4px; - - &-active { - background: var(--bg-robin-500); - border-color: var(--bg-robin-500); - - a { - color: var(--bg-ink-500) !important; - } - } - } - } -} - -.node-events-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.node-events-list-card { - width: 100%; - margin-top: 12px; - - .ant-table-wrapper { - height: 100%; - overflow-y: auto; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} - -.periscope-btn-icon { - cursor: pointer; -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx index 65109b0dca..ed3f067015 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './NodeEvents.styles.scss'; +import '../../../EntityDetailsUtils/entityEvents.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Table, TableColumnsType } from 'antd'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; import LogsError from 'container/LogsError/LogsError'; import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; @@ -25,8 +26,10 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { v4 } from 'uuid'; -import { getNodesEventsQueryPayload } from './constants'; -import NoEventsContainer from './NoEventsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, +} from '../../../EntityDetailsUtils/utils'; interface EventDataType { key: string; @@ -110,7 +113,7 @@ export default function Events({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; const queryPayload = useMemo(() => { - const basePayload = getNodesEventsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -277,8 +280,8 @@ export default function Events({ ); return ( - <div className="node-events-container"> - <div className="node-events-header"> + <div className="entity-events-container"> + <div className="entity-events-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -304,14 +307,14 @@ export default function Events({ {isLoading && <LoadingContainer />} {!isLoading && !isError && formattedNodeEvents.length === 0 && ( - <NoEventsContainer /> + <EntityDetailsEmptyContainer category={K8sCategory.NODES} view="events" /> )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && formattedNodeEvents.length > 0 && ( - <div className="node-events-list-container"> - <div className="node-events-list-card"> + <div className="entity-events-list-container"> + <div className="entity-events-list-card"> <Table<EventDataType> loading={isLoading && page > 1} columns={columns} @@ -329,9 +332,9 @@ export default function Events({ )} {!isError && formattedNodeEvents.length > 0 && ( - <div className="node-events-footer"> + <div className="entity-events-footer"> <Button - className="node-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handlePrev} disabled={page === 1 || isFetching || isLoading} @@ -341,7 +344,7 @@ export default function Events({ </Button> <Button - className="node-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handleNext} disabled={hasReachedEndOfEvents || isFetching || isLoading} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/constants.ts deleted file mode 100644 index d4c5a13a35..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getNodesEventsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NoLogsContainer.tsx deleted file mode 100644 index 6b4e96053f..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NoLogsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoLogsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this node - in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.styles.scss deleted file mode 100644 index 71f6b7f3f8..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.styles.scss +++ /dev/null @@ -1,133 +0,0 @@ -.node-logs-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .node-logs-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .node-logs { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } -} - -.node-logs-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.node-logs-list-card { - width: 100%; - margin-top: 12px; - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx index c9880228f1..3a3ffe770e 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './NodeLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import { Card } from 'antd'; import RawLogView from 'components/Logs/RawLogView'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LogsError from 'container/LogsError/LogsError'; import { LogsLoading } from 'container/LogsLoading/LogsLoading'; import { FontSize } from 'container/OptionsMenu/types'; @@ -22,9 +23,11 @@ import { } from 'types/api/queryBuilder/queryBuilderData'; import { v4 } from 'uuid'; -import { QUERY_KEYS } from '../constants'; -import { getNodeLogsQueryPayload } from './constants'; -import NoLogsContainer from './NoLogsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, + QUERY_KEYS, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -65,7 +68,7 @@ function PodLogs({ }, [filters]); const queryPayload = useMemo(() => { - const basePayload = getNodeLogsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -181,11 +184,11 @@ function PodLogs({ const renderContent = useMemo( () => ( - <Card bordered={false} className="node-logs-list-card"> + <Card bordered={false} className="entity-logs-list-card"> <OverlayScrollbar isVirtuoso> <Virtuoso - className="node-logs-virtuoso" - key="node-logs-virtuoso" + className="entity-logs-virtuoso" + key="entity-logs-virtuoso" data={logs} endReached={loadMoreLogs} totalCount={logs.length} @@ -202,12 +205,14 @@ function PodLogs({ ); return ( - <div className="node-logs"> + <div className="entity-logs"> {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {!isLoading && !isError && logs.length === 0 && ( + <EntityDetailsEmptyContainer category={K8sCategory.NODES} view="logs" /> + )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && logs.length > 0 && ( - <div className="node-logs-list-container">{renderContent}</div> + <div className="entity-logs-list-container">{renderContent}</div> )} </div> ); diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx index 5bbec59ade..09ce9a7fdd 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx @@ -1,4 +1,5 @@ -import './NodeLogs.styles.scss'; +/* eslint-disable no-nested-ternary */ +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; @@ -64,8 +65,8 @@ function NodeLogsDetailedView({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; return ( - <div className="node-logs-container"> - <div className="node-logs-header"> + <div className="entity-logs-container"> + <div className="entity-logs-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/constants.ts deleted file mode 100644 index 64488e4db4..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getNodeLogsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.styles.scss deleted file mode 100644 index bb24b9a5fe..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.styles.scss +++ /dev/null @@ -1,45 +0,0 @@ -.empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.node-metrics-container { - margin-top: 1rem; -} - -.metrics-header { - display: flex; - justify-content: flex-end; - margin-top: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); -} - -.node-metrics-card { - margin: 8px 0 1rem 0; - height: 300px; - padding: 10px; - - border: 1px solid var(--bg-slate-500); - - .ant-card-body { - padding: 0; - } - - .chart-container { - width: 100%; - height: 100%; - } - - .no-data-container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx index 46d2c78cc1..7fb026f4c5 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx @@ -1,4 +1,4 @@ -import './NodeMetrics.styles.scss'; +import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; import { Card, Col, Row, Skeleton, Typography } from 'antd'; import { K8sNodesData } from 'api/infraMonitoring/getK8sNodesList'; @@ -123,11 +123,11 @@ function NodeMetrics({ /> </div> </div> - <Row gutter={24} className="node-metrics-container"> + <Row gutter={24} className="entity-metrics-container"> {queries.map((query, idx) => ( <Col span={12} key={nodeWidgetInfo[idx].title}> <Typography.Text>{nodeWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="node-metrics-card" ref={graphRef}> + <Card bordered className="entity-metrics-card" ref={graphRef}> {renderCardContent(query, idx)} </Card> </Col> diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.styles.scss deleted file mode 100644 index 76014ba387..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.styles.scss +++ /dev/null @@ -1,247 +0,0 @@ -.node-detail-drawer { - border-left: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); - - .ant-drawer-header { - padding: 8px 16px; - border-bottom: none; - - align-items: stretch; - - border-bottom: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - } - - .ant-drawer-close { - margin-inline-end: 0px; - } - - .ant-drawer-body { - display: flex; - flex-direction: column; - padding: 16px; - } - - .title { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 14px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .radio-button { - display: flex; - align-items: center; - justify-content: center; - padding-top: var(--padding-1); - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .node-detail-drawer__node { - .node-details-grid { - .labels-row, - .values-row { - display: grid; - grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; - gap: 30px; - align-items: center; - } - - .labels-row { - margin-bottom: 8px; - } - - .node-details-metadata-label { - color: var(--text-vanilla-400); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 500; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - } - - .node-details-metadata-value { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .status-tag { - margin: 0; - - &.active { - color: var(--success-500); - background: var(--success-100); - border-color: var(--success-500); - } - - &.inactive { - color: var(--error-500); - background: var(--error-100); - border-color: var(--error-500); - } - } - - .progress-container { - width: 158px; - .ant-progress { - margin: 0; - - .ant-progress-text { - font-weight: 600; - } - } - } - - .ant-card { - &.ant-card-bordered { - border: 1px solid var(--bg-slate-500) !important; - } - } - } - } - - .tabs-and-search { - display: flex; - justify-content: space-between; - align-items: center; - margin: 16px 0; - - .action-btn { - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: center; - } - } - - .views-tabs-container { - margin-top: 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; - - .views-tabs { - color: var(--text-vanilla-400); - - .view-title { - display: flex; - gap: var(--margin-2); - align-items: center; - justify-content: center; - font-size: var(--font-size-xs); - font-style: normal; - font-weight: var(--font-weight-normal); - } - - .tab { - border: 1px solid var(--bg-slate-400); - width: 114px; - } - - .tab::before { - background: var(--bg-slate-400); - } - - .selected_view { - background: var(--bg-slate-300); - color: var(--text-vanilla-100); - border: 1px solid var(--bg-slate-400); - } - - .selected_view::before { - background: var(--bg-slate-400); - } - } - - .compass-button { - width: 30px; - height: 30px; - - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - } - .ant-drawer-close { - padding: 0px; - } -} - -.lightMode { - .ant-drawer-header { - border-bottom: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - } - - .node-detail-drawer { - .title { - color: var(--text-ink-300); - } - - .node-detail-drawer__node { - .ant-typography { - color: var(--text-ink-300); - background: transparent; - } - } - - .radio-button { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .views-tabs { - .tab { - background: var(--bg-vanilla-100); - } - - .selected_view { - background: var(--bg-vanilla-300); - border: 1px solid var(--bg-slate-300); - color: var(--text-ink-400); - } - - .selected_view::before { - background: var(--bg-vanilla-300); - border-left: 1px solid var(--bg-slate-300); - } - } - - .compass-button { - border: 1px solid var(--bg-vanilla-300); - background: var(--bg-vanilla-100); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .tabs-and-search { - .action-btn { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 33ecd20c36..1ebf9d2da6 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-identical-functions */ -import './NodeDetails.styles.scss'; +import '../../EntityDetailsUtils/entityDetails.styles.scss'; import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; @@ -43,7 +43,7 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import { QUERY_KEYS } from './constants'; +import { QUERY_KEYS } from '../../EntityDetailsUtils/utils'; import NodeEvents from './Events'; import NodeLogs from './Logs'; import NodeMetrics from './Metrics'; @@ -402,35 +402,35 @@ function NodeDetails({ overscrollBehavior: 'contain', background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, }} - className="node-detail-drawer" + className="entity-detail-drawer" destroyOnClose closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} > {node && ( <> - <div className="node-detail-drawer__node"> - <div className="node-details-grid"> + <div className="entity-detail-drawer__entity"> + <div className="entity-details-grid"> <div className="labels-row"> <Typography.Text type="secondary" - className="node-details-metadata-label" + className="entity-details-metadata-label" > Node Name </Typography.Text> <Typography.Text type="secondary" - className="node-details-metadata-label" + className="entity-details-metadata-label" > Cluster Name </Typography.Text> </div> <div className="values-row"> - <Typography.Text className="node-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={node.meta.k8s_node_name}> {node.meta.k8s_node_name} </Tooltip> </Typography.Text> - <Typography.Text className="node-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title="Cluster name">{node.meta.k8s_cluster_name}</Tooltip> </Typography.Text> </div> diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.styles.scss deleted file mode 100644 index ab082d210f..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.styles.scss +++ /dev/null @@ -1,193 +0,0 @@ -.node-metric-traces { - margin-top: 1rem; - - .node-metric-traces-header { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - } - - .node-metric-traces-table { - .ant-table-content { - overflow: hidden !important; - } - - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgba(171, 189, 255, 0.01); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgba(171, 189, 255, 0.01); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-ink-400); - } - - .hostname-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-table-container::after { - content: none; - } - } -} - -.lightMode { - .host-metric-traces-header { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } - } - - .host-metric-traces-table { - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-vanilla-300); - - .ant-table-thead > tr > th { - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-vanilla-100); - } - - .ant-table-cell { - background: var(--bg-vanilla-100); - color: var(--bg-ink-500); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-vanilla-100); - } - - .hostname-column-value { - color: var(--bg-ink-300); - } - - .ant-table-tbody > tr:hover > td { - background: rgba(0, 0, 0, 0.04); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx index 00e3533f2d..ccd80b87c2 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx @@ -1,4 +1,4 @@ -import './NodeTraces.styles.scss'; +import '../../../EntityDetailsUtils/entityTraces.styles.scss'; import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; import { ResizeTable } from 'components/ResizeTable'; @@ -25,7 +25,10 @@ import { useQuery } from 'react-query'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import { getNodeTracesQueryPayload, selectedColumns } from './constants'; +import { + getEntityTracesQueryPayload, + selectedEntityTracesColumns, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -86,7 +89,7 @@ function NodeTraces({ const queryPayload = useMemo( () => - getNodeTracesQueryPayload( + getEntityTracesQueryPayload( timeRange.startTime, timeRange.endTime, paginationQueryData?.offset || offset, @@ -103,7 +106,7 @@ function NodeTraces({ const { data, isLoading, isFetching, isError } = useQuery({ queryKey: [ - 'hostMetricTraces', + 'nodeMetricTraces', timeRange.startTime, timeRange.endTime, offset, @@ -115,7 +118,7 @@ function NodeTraces({ enabled: !!queryPayload, }); - const traceListColumns = getListColumns(selectedColumns); + const traceListColumns = getListColumns(selectedEntityTracesColumns); useEffect(() => { if (data?.payload?.data?.newResult?.data?.result) { @@ -138,8 +141,8 @@ function NodeTraces({ data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; return ( - <div className="node-metric-traces"> - <div className="node-metric-traces-header"> + <div className="entity-metric-traces"> + <div className="entity-metric-traces-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -175,7 +178,7 @@ function NodeTraces({ )} {!isError && traces.length > 0 && ( - <div className="node-traces-table"> + <div className="entity-traces-table"> <TraceExplorerControls isLoading={isFetching} totalCount={totalCount} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/constants.ts deleted file mode 100644 index f1b5950503..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/constants.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { - BaseAutocompleteData, - DataTypes, -} from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { nanoToMilli } from 'utils/timeUtils'; - -export const columns = [ - { - dataIndex: 'timestamp', - key: 'timestamp', - title: 'Timestamp', - width: 200, - render: (timestamp: string): string => new Date(timestamp).toLocaleString(), - }, - { - title: 'Service Name', - dataIndex: ['data', 'serviceName'], - key: 'serviceName-string-tag', - width: 150, - }, - { - title: 'Name', - dataIndex: ['data', 'name'], - key: 'name-string-tag', - width: 145, - }, - { - title: 'Duration', - dataIndex: ['data', 'durationNano'], - key: 'durationNano-float64-tag', - width: 145, - render: (duration: number): string => `${nanoToMilli(duration)}ms`, - }, - { - title: 'HTTP Method', - dataIndex: ['data', 'httpMethod'], - key: 'httpMethod-string-tag', - width: 145, - }, - { - title: 'Status Code', - dataIndex: ['data', 'responseStatusCode'], - key: 'responseStatusCode-string-tag', - width: 145, - }, -]; - -export const selectedColumns: BaseAutocompleteData[] = [ - { - key: 'timestamp', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'serviceName', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'name', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'durationNano', - dataType: DataTypes.Float64, - type: 'tag', - isColumn: true, - }, - { - key: 'httpMethod', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'responseStatusCode', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, -]; - -export const getNodeTracesQueryPayload = ( - start: number, - end: number, - offset = 0, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - query: { - promql: [], - clickhouse_sql: [], - builder: { - queryData: [ - { - dataSource: DataSource.TRACES, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.EMPTY, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - }, - ], - queryFormulas: [], - }, - id: '572f1d91-6ac0-46c0-b726-c21488b34434', - queryType: EQueryType.QUERY_BUILDER, - }, - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - start, - end, - params: { - dataSource: DataSource.TRACES, - }, - tableParams: { - pagination: { - limit: 10, - offset, - }, - selectColumns: [ - { - key: 'serviceName', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'serviceName--string--tag--true', - isIndexed: false, - }, - { - key: 'name', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'name--string--tag--true', - isIndexed: false, - }, - { - key: 'durationNano', - dataType: 'float64', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'durationNano--float64--tag--true', - isIndexed: false, - }, - { - key: 'httpMethod', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'httpMethod--string--tag--true', - isIndexed: false, - }, - { - key: 'responseStatusCode', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'responseStatusCode--string--tag--true', - isIndexed: false, - }, - ], - }, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts deleted file mode 100644 index e771ffa91f..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const QUERY_KEYS = { - K8S_OBJECT_KIND: 'k8s.object.kind', - K8S_OBJECT_NAME: 'k8s.object.name', - K8S_NODE_NAME: 'k8s.node.name', - K8S_CLUSTER_NAME: 'k8s.cluster.name', -}; From 47fb4d710fca3277d2d0016661bafc61dfbeb756 Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 00:36:32 +0530 Subject: [PATCH 09/20] chore: refactor namespaces --- .../Events/NamespaceEvents.styles.scss | 289 ------------------ .../Events/NamespaceEvents.tsx | 31 +- .../Events/NoEventsContainer.tsx | 16 - .../NamespaceDetails/Events/constants.ts | 65 ---- .../Logs/NamespaceLogs.styles.scss | 133 -------- .../NamespaceDetails/Logs/NamespaceLogs.tsx | 30 +- .../Logs/NamespaceLogsDetailedView.tsx | 6 +- .../NamespaceDetails/Logs/NoLogsContainer.tsx | 16 - .../NamespaceDetails/Logs/constants.ts | 65 ---- .../Metrics/NamespaceMetrics.styles.scss | 45 --- .../Metrics/NamespaceMetrics.tsx | 6 +- .../NamespaceDetails/NamespaceDetails.tsx | 16 +- .../Traces/NamespaceTraces.styles.scss | 193 ------------ .../Traces/NamespaceTraces.tsx | 17 +- .../NamespaceDetails/Traces/constants.ts | 200 ------------ .../Namespaces/NamespaceDetails/constants.ts | 6 - 16 files changed, 61 insertions(+), 1073 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss deleted file mode 100644 index 6aa2b22fb9..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.styles.scss +++ /dev/null @@ -1,289 +0,0 @@ -.namespace-events-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .namespace-events-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .namespace-events { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } - - .ant-table { - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgb(18, 19, 23); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.namespacename-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgb(18, 19, 23); - border-bottom: none; - } - - .ant-table-cell:has(.namespacename-column-value) { - background: var(--bg-ink-400); - } - - .namespacename-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-pagination { - position: fixed; - bottom: 0; - width: calc(100% - 64px); - background: rgb(18, 19, 23); - padding: 16px; - margin: 0; - - // this is to offset intercom icon till we improve the design - padding-right: 72px; - - .ant-pagination-item { - border-radius: 4px; - - &-active { - background: var(--bg-robin-500); - border-color: var(--bg-robin-500); - - a { - color: var(--bg-ink-500) !important; - } - } - } - } -} - -.namespace-events-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.namespace-events-list-card { - width: 100%; - margin-top: 12px; - - .ant-table-wrapper { - height: 100%; - overflow-y: auto; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} - -.periscope-btn-icon { - cursor: pointer; -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx index 00e2770dc4..51c9c84f96 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx @@ -1,10 +1,15 @@ /* eslint-disable no-nested-ternary */ -import './NamespaceEvents.styles.scss'; +import '../../../EntityDetailsUtils/entityEvents.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Table, TableColumnsType } from 'antd'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, +} from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; import LogsError from 'container/LogsError/LogsError'; import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; @@ -25,9 +30,6 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { v4 } from 'uuid'; -import { getNamespacesEventsQueryPayload } from './constants'; -import NoEventsContainer from './NoEventsContainer'; - interface EventDataType { key: string; timestamp: string; @@ -110,7 +112,7 @@ export default function Events({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; const queryPayload = useMemo(() => { - const basePayload = getNamespacesEventsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -283,8 +285,8 @@ export default function Events({ ); return ( - <div className="namespace-events-container"> - <div className="namespace-events-header"> + <div className="entity-events-container"> + <div className="entity-events-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -310,14 +312,17 @@ export default function Events({ {isLoading && <LoadingContainer />} {!isLoading && !isError && formattedNamespaceEvents.length === 0 && ( - <NoEventsContainer /> + <EntityDetailsEmptyContainer + category={K8sCategory.NAMESPACES} + view="events" + /> )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && formattedNamespaceEvents.length > 0 && ( - <div className="namespace-events-list-container"> - <div className="namespace-events-list-card"> + <div className="entity-events-list-container"> + <div className="entity-events-list-card"> <Table<EventDataType> loading={isLoading && page > 1} columns={columns} @@ -335,9 +340,9 @@ export default function Events({ )} {!isError && formattedNamespaceEvents.length > 0 && ( - <div className="namespace-events-footer"> + <div className="entity-events-footer"> <Button - className="namespace-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handlePrev} disabled={page === 1 || isFetching || isLoading} @@ -347,7 +352,7 @@ export default function Events({ </Button> <Button - className="namespace-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handleNext} disabled={hasReachedEndOfEvents || isFetching || isLoading} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx deleted file mode 100644 index c64c048277..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NoEventsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoEventsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this - namespace in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts deleted file mode 100644 index 4016dbeaa1..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getNamespacesEventsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss deleted file mode 100644 index 4a7171344d..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.styles.scss +++ /dev/null @@ -1,133 +0,0 @@ -.namespace-logs-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .namespace-logs-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .namespace-logs { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } -} - -.namespace-logs-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.namespace-logs-list-card { - width: 100%; - margin-top: 12px; - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx index 36175a962d..d443c52613 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './NamespaceLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import { Card } from 'antd'; import RawLogView from 'components/Logs/RawLogView'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LogsError from 'container/LogsError/LogsError'; import { LogsLoading } from 'container/LogsLoading/LogsLoading'; import { FontSize } from 'container/OptionsMenu/types'; @@ -22,9 +23,11 @@ import { } from 'types/api/queryBuilder/queryBuilderData'; import { v4 } from 'uuid'; -import { QUERY_KEYS } from '../constants'; -import { getNamespaceLogsQueryPayload } from './constants'; -import NoLogsContainer from './NoLogsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, + QUERY_KEYS, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -63,7 +66,7 @@ function PodLogs({ }, [filters]); const queryPayload = useMemo(() => { - const basePayload = getNamespaceLogsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -179,11 +182,11 @@ function PodLogs({ const renderContent = useMemo( () => ( - <Card bordered={false} className="namespace-logs-list-card"> + <Card bordered={false} className="entity-logs-list-card"> <OverlayScrollbar isVirtuoso> <Virtuoso - className="namespace-logs-virtuoso" - key="namespace-logs-virtuoso" + className="entity-logs-virtuoso" + key="entity-logs-virtuoso" data={logs} endReached={loadMoreLogs} totalCount={logs.length} @@ -200,12 +203,17 @@ function PodLogs({ ); return ( - <div className="namespace-logs"> + <div className="entity-logs"> {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {!isLoading && !isError && logs.length === 0 && ( + <EntityDetailsEmptyContainer + category={K8sCategory.NAMESPACES} + view="logs" + /> + )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && logs.length > 0 && ( - <div className="namespace-logs-list-container">{renderContent}</div> + <div className="entity-logs-list-container">{renderContent}</div> )} </div> ); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx index f70c8986c7..5eccdbb9a0 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx @@ -1,4 +1,4 @@ -import './NamespaceLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; @@ -64,8 +64,8 @@ function NamespaceLogsDetailedView({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; return ( - <div className="namespace-logs-container"> - <div className="namespace-logs-header"> + <div className="entity-logs-container"> + <div className="entity-logs-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx deleted file mode 100644 index ba16e99b05..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NoLogsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoLogsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this - namespace in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts deleted file mode 100644 index 48d222c6dc..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getNamespaceLogsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss deleted file mode 100644 index 7425085ae6..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.styles.scss +++ /dev/null @@ -1,45 +0,0 @@ -.empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.namespace-metrics-container { - margin-top: 1rem; -} - -.metrics-header { - display: flex; - justify-content: flex-end; - margin-top: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); -} - -.namespace-metrics-card { - margin: 8px 0 1rem 0; - height: 300px; - padding: 10px; - - border: 1px solid var(--bg-slate-500); - - .ant-card-body { - padding: 0; - } - - .chart-container { - width: 100%; - height: 100%; - } - - .no-data-container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx index 0e3e91d703..0a3d70a9d7 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx @@ -1,4 +1,4 @@ -import './NamespaceMetrics.styles.scss'; +import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; import { Card, Col, Row, Skeleton, Typography } from 'antd'; import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList'; @@ -150,11 +150,11 @@ function NamespaceMetrics({ /> </div> </div> - <Row gutter={24} className="namespace-metrics-container"> + <Row gutter={24} className="entity-metrics-container"> {queries.map((query, idx) => ( <Col span={12} key={namespaceWidgetInfo[idx].title}> <Typography.Text>{namespaceWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="namespace-metrics-card" ref={graphRef}> + <Card bordered className="entity-metrics-card" ref={graphRef}> {renderCardContent(query, idx)} </Card> </Col> diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx index 791e55efed..acf71edd3b 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -12,6 +12,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { CustomTimeType, Time, @@ -42,7 +43,6 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import { QUERY_KEYS } from './constants'; import NamespaceEvents from './Events'; import NamespaceLogs from './Logs'; import NamespaceMetrics from './Metrics'; @@ -403,35 +403,35 @@ function NamespaceDetails({ overscrollBehavior: 'contain', background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, }} - className="namespace-detail-drawer" + className="entity-detail-drawer" destroyOnClose closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} > {namespace && ( <> - <div className="namespace-detail-drawer__namespace"> - <div className="namespace-details-grid"> + <div className="entity-detail-drawer__entity"> + <div className="entity-details-grid"> <div className="labels-row"> <Typography.Text type="secondary" - className="namespace-details-metadata-label" + className="entity-details-metadata-label" > Namespace Name </Typography.Text> <Typography.Text type="secondary" - className="namespace-details-metadata-label" + className="entity-details-metadata-label" > Cluster Name </Typography.Text> </div> <div className="values-row"> - <Typography.Text className="namespace-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={namespace.namespaceName}> {namespace.namespaceName} </Tooltip> </Typography.Text> - <Typography.Text className="namespace-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title="Cluster name"> {namespace.meta.k8s_cluster_name} </Tooltip> diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss deleted file mode 100644 index e3d1e6bf1d..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.styles.scss +++ /dev/null @@ -1,193 +0,0 @@ -.namespace-metric-traces { - margin-top: 1rem; - - .namespace-metric-traces-header { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - } - - .namespace-metric-traces-table { - .ant-table-content { - overflow: hidden !important; - } - - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgba(171, 189, 255, 0.01); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgba(171, 189, 255, 0.01); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-ink-400); - } - - .hostname-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-table-container::after { - content: none; - } - } -} - -.lightMode { - .host-metric-traces-header { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } - } - - .host-metric-traces-table { - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-vanilla-300); - - .ant-table-thead > tr > th { - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-vanilla-100); - } - - .ant-table-cell { - background: var(--bg-vanilla-100); - color: var(--bg-ink-500); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-vanilla-100); - } - - .hostname-column-value { - color: var(--bg-ink-300); - } - - .ant-table-tbody > tr:hover > td { - background: rgba(0, 0, 0, 0.04); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx index b535c1d5c6..be1a48a93a 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx @@ -1,4 +1,4 @@ -import './NamespaceTraces.styles.scss'; +import '../../../EntityDetailsUtils/entityTraces.styles.scss'; import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; import { ResizeTable } from 'components/ResizeTable'; @@ -25,7 +25,10 @@ import { useQuery } from 'react-query'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import { getNamespaceTracesQueryPayload, selectedColumns } from './constants'; +import { + getEntityTracesQueryPayload, + selectedEntityTracesColumns, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -86,7 +89,7 @@ function NamespaceTraces({ const queryPayload = useMemo( () => - getNamespaceTracesQueryPayload( + getEntityTracesQueryPayload( timeRange.startTime, timeRange.endTime, paginationQueryData?.offset || offset, @@ -115,7 +118,7 @@ function NamespaceTraces({ enabled: !!queryPayload, }); - const traceListColumns = getListColumns(selectedColumns); + const traceListColumns = getListColumns(selectedEntityTracesColumns); useEffect(() => { if (data?.payload?.data?.newResult?.data?.result) { @@ -138,8 +141,8 @@ function NamespaceTraces({ data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; return ( - <div className="namespace-metric-traces"> - <div className="namespace-metric-traces-header"> + <div className="entity-metric-traces"> + <div className="entity-metric-traces-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -175,7 +178,7 @@ function NamespaceTraces({ )} {!isError && traces.length > 0 && ( - <div className="namespace-traces-table"> + <div className="entity-traces-table"> <TraceExplorerControls isLoading={isFetching} totalCount={totalCount} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts deleted file mode 100644 index b29db78f45..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/constants.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { - BaseAutocompleteData, - DataTypes, -} from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { nanoToMilli } from 'utils/timeUtils'; - -export const columns = [ - { - dataIndex: 'timestamp', - key: 'timestamp', - title: 'Timestamp', - width: 200, - render: (timestamp: string): string => new Date(timestamp).toLocaleString(), - }, - { - title: 'Service Name', - dataIndex: ['data', 'serviceName'], - key: 'serviceName-string-tag', - width: 150, - }, - { - title: 'Name', - dataIndex: ['data', 'name'], - key: 'name-string-tag', - width: 145, - }, - { - title: 'Duration', - dataIndex: ['data', 'durationNano'], - key: 'durationNano-float64-tag', - width: 145, - render: (duration: number): string => `${nanoToMilli(duration)}ms`, - }, - { - title: 'HTTP Method', - dataIndex: ['data', 'httpMethod'], - key: 'httpMethod-string-tag', - width: 145, - }, - { - title: 'Status Code', - dataIndex: ['data', 'responseStatusCode'], - key: 'responseStatusCode-string-tag', - width: 145, - }, -]; - -export const selectedColumns: BaseAutocompleteData[] = [ - { - key: 'timestamp', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'serviceName', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'name', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'durationNano', - dataType: DataTypes.Float64, - type: 'tag', - isColumn: true, - }, - { - key: 'httpMethod', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'responseStatusCode', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, -]; - -export const getNamespaceTracesQueryPayload = ( - start: number, - end: number, - offset = 0, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - query: { - promql: [], - clickhouse_sql: [], - builder: { - queryData: [ - { - dataSource: DataSource.TRACES, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.EMPTY, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - }, - ], - queryFormulas: [], - }, - id: '572f1d91-6ac0-46c0-b726-c21488b34434', - queryType: EQueryType.QUERY_BUILDER, - }, - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - start, - end, - params: { - dataSource: DataSource.TRACES, - }, - tableParams: { - pagination: { - limit: 10, - offset, - }, - selectColumns: [ - { - key: 'serviceName', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'serviceName--string--tag--true', - isIndexed: false, - }, - { - key: 'name', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'name--string--tag--true', - isIndexed: false, - }, - { - key: 'durationNano', - dataType: 'float64', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'durationNano--float64--tag--true', - isIndexed: false, - }, - { - key: 'httpMethod', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'httpMethod--string--tag--true', - isIndexed: false, - }, - { - key: 'responseStatusCode', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'responseStatusCode--string--tag--true', - isIndexed: false, - }, - ], - }, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts deleted file mode 100644 index 70bdcbf74d..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const QUERY_KEYS = { - K8S_OBJECT_KIND: 'k8s.object.kind', - K8S_OBJECT_NAME: 'k8s.object.name', - K8S_NAMESPACE_NAME: 'k8s.namespace.name', - K8S_CLUSTER_NAME: 'k8s.cluster.name', -}; From ce7573482a5e6f60672e3bc17ede25a32657341e Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 12:17:03 +0530 Subject: [PATCH 10/20] chore: refactor clusters --- .../ClusterDetails/ClusterDetails.styles.scss | 247 --------------- .../ClusterDetails/ClusterDetails.tsx | 18 +- .../Events/ClusterEvents.styles.scss | 289 ------------------ .../ClusterDetails/Events/ClusterEvents.tsx | 30 +- .../Events/NoEventsContainer.tsx | 16 - .../ClusterDetails/Events/constants.ts | 65 ---- .../Logs/ClusterLogs.styles.scss | 133 -------- .../ClusterDetails/Logs/ClusterLogs.tsx | 27 +- .../Logs/ClusterLogsDetailedView.tsx | 6 +- .../ClusterDetails/Logs/NoLogsContainer.tsx | 16 - .../Clusters/ClusterDetails/Logs/constants.ts | 65 ---- .../Metrics/ClusterMetrics.styles.scss | 45 --- .../ClusterDetails/Metrics/ClusterMetrics.tsx | 8 +- .../Traces/ClusterTraces.styles.scss | 193 ------------ .../ClusterDetails/Traces/ClusterTraces.tsx | 17 +- .../ClusterDetails/Traces/constants.ts | 200 ------------ .../Clusters/ClusterDetails/constants.ts | 5 - 17 files changed, 60 insertions(+), 1320 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss deleted file mode 100644 index 52309b5da5..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss +++ /dev/null @@ -1,247 +0,0 @@ -.cluster-detail-drawer { - border-left: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); - - .ant-drawer-header { - padding: 8px 16px; - border-bottom: none; - - align-items: stretch; - - border-bottom: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - } - - .ant-drawer-close { - margin-inline-end: 0px; - } - - .ant-drawer-body { - display: flex; - flex-direction: column; - padding: 16px; - } - - .title { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 14px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .radio-button { - display: flex; - align-items: center; - justify-content: center; - padding-top: var(--padding-1); - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .cluster-detail-drawer__cluster { - .cluster-details-grid { - .labels-row, - .values-row { - display: grid; - grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; - gap: 30px; - align-items: center; - } - - .labels-row { - margin-bottom: 8px; - } - - .cluster-details-metadata-label { - color: var(--text-vanilla-400); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 500; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - } - - .cluster-details-metadata-value { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .status-tag { - margin: 0; - - &.active { - color: var(--success-500); - background: var(--success-100); - border-color: var(--success-500); - } - - &.inactive { - color: var(--error-500); - background: var(--error-100); - border-color: var(--error-500); - } - } - - .progress-container { - width: 158px; - .ant-progress { - margin: 0; - - .ant-progress-text { - font-weight: 600; - } - } - } - - .ant-card { - &.ant-card-bordered { - border: 1px solid var(--bg-slate-500) !important; - } - } - } - } - - .tabs-and-search { - display: flex; - justify-content: space-between; - align-items: center; - margin: 16px 0; - - .action-btn { - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: center; - } - } - - .views-tabs-container { - margin-top: 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; - - .views-tabs { - color: var(--text-vanilla-400); - - .view-title { - display: flex; - gap: var(--margin-2); - align-items: center; - justify-content: center; - font-size: var(--font-size-xs); - font-style: normal; - font-weight: var(--font-weight-normal); - } - - .tab { - border: 1px solid var(--bg-slate-400); - width: 114px; - } - - .tab::before { - background: var(--bg-slate-400); - } - - .selected_view { - background: var(--bg-slate-300); - color: var(--text-vanilla-100); - border: 1px solid var(--bg-slate-400); - } - - .selected_view::before { - background: var(--bg-slate-400); - } - } - - .compass-button { - width: 30px; - height: 30px; - - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - } - .ant-drawer-close { - padding: 0px; - } -} - -.lightMode { - .ant-drawer-header { - border-bottom: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - } - - .cluster-detail-drawer { - .title { - color: var(--text-ink-300); - } - - .cluster-detail-drawer__cluster { - .ant-typography { - color: var(--text-ink-300); - background: transparent; - } - } - - .radio-button { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .views-tabs { - .tab { - background: var(--bg-vanilla-100); - } - - .selected_view { - background: var(--bg-vanilla-300); - border: 1px solid var(--bg-slate-300); - color: var(--text-ink-400); - } - - .selected_view::before { - background: var(--bg-vanilla-300); - border-left: 1px solid var(--bg-slate-300); - } - } - - .compass-button { - border: 1px solid var(--bg-vanilla-300); - background: var(--bg-vanilla-100); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .tabs-and-search { - .action-btn { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index c044948d06..c873f5ff96 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-identical-functions */ -import './ClusterDetails.styles.scss'; +import '../../EntityDetailsUtils/entityDetails.styles.scss'; import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; @@ -12,6 +12,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { CustomTimeType, Time, @@ -43,7 +44,6 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import { ClusterDetailsProps } from './ClusterDetails.interfaces'; -import { QUERY_KEYS } from './constants'; import ClusterEvents from './Events'; import ClusterLogs from './Logs'; import ClusterMetrics from './Metrics'; @@ -393,35 +393,35 @@ function ClusterDetails({ overscrollBehavior: 'contain', background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, }} - className="cluster-detail-drawer" + className="entity-detail-drawer" destroyOnClose closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} > {cluster && ( <> - <div className="cluster-detail-drawer__cluster"> - <div className="cluster-details-grid"> + <div className="entity-detail-drawer__entity"> + <div className="entity-details-grid"> <div className="labels-row"> <Typography.Text type="secondary" - className="cluster-details-metadata-label" + className="entity-details-metadata-label" > Cluster Name </Typography.Text> <Typography.Text type="secondary" - className="cluster-details-metadata-label" + className="entity-details-metadata-label" > Cluster Name </Typography.Text> </div> <div className="values-row"> - <Typography.Text className="cluster-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={cluster.meta.k8s_cluster_name}> {cluster.meta.k8s_cluster_name} </Tooltip> </Typography.Text> - <Typography.Text className="cluster-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title="Cluster name">{cluster.meta.k8s_cluster_name}</Tooltip> </Typography.Text> </div> diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss deleted file mode 100644 index 3d76fe1772..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss +++ /dev/null @@ -1,289 +0,0 @@ -.cluster-events-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .cluster-events-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .cluster-events { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } - - .ant-table { - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgb(18, 19, 23); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.clustername-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgb(18, 19, 23); - border-bottom: none; - } - - .ant-table-cell:has(.clustername-column-value) { - background: var(--bg-ink-400); - } - - .clustername-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-pagination { - position: fixed; - bottom: 0; - width: calc(100% - 64px); - background: rgb(18, 19, 23); - padding: 16px; - margin: 0; - - // this is to offset intercom icon till we improve the design - padding-right: 72px; - - .ant-pagination-item { - border-radius: 4px; - - &-active { - background: var(--bg-robin-500); - border-color: var(--bg-robin-500); - - a { - color: var(--bg-ink-500) !important; - } - } - } - } -} - -.cluster-events-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.cluster-events-list-card { - width: 100%; - margin-top: 12px; - - .ant-table-wrapper { - height: 100%; - overflow-y: auto; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} - -.periscope-btn-icon { - cursor: pointer; -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx index c2b013749b..bdf44c8ac9 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './ClusterEvents.styles.scss'; +import '../../../EntityDetailsUtils/entityEvents.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Table, TableColumnsType } from 'antd'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; import LogsError from 'container/LogsError/LogsError'; import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; @@ -25,8 +26,10 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { v4 } from 'uuid'; -import { getClustersEventsQueryPayload } from './constants'; -import NoEventsContainer from './NoEventsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, +} from '../../../EntityDetailsUtils/utils'; interface EventDataType { key: string; @@ -110,7 +113,7 @@ export default function Events({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; const queryPayload = useMemo(() => { - const basePayload = getClustersEventsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -277,8 +280,8 @@ export default function Events({ ); return ( - <div className="cluster-events-container"> - <div className="cluster-events-header"> + <div className="entity-events-container"> + <div className="entity-events-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -304,14 +307,17 @@ export default function Events({ {isLoading && <LoadingContainer />} {!isLoading && !isError && formattedClusterEvents.length === 0 && ( - <NoEventsContainer /> + <EntityDetailsEmptyContainer + category={K8sCategory.CLUSTERS} + view="events" + /> )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && formattedClusterEvents.length > 0 && ( - <div className="cluster-events-list-container"> - <div className="cluster-events-list-card"> + <div className="entity-events-list-container"> + <div className="entity-events-list-card"> <Table<EventDataType> loading={isLoading && page > 1} columns={columns} @@ -329,9 +335,9 @@ export default function Events({ )} {!isError && formattedClusterEvents.length > 0 && ( - <div className="cluster-events-footer"> + <div className="entity-events-footer"> <Button - className="cluster-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handlePrev} disabled={page === 1 || isFetching || isLoading} @@ -341,7 +347,7 @@ export default function Events({ </Button> <Button - className="cluster-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handleNext} disabled={hasReachedEndOfEvents || isFetching || isLoading} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx deleted file mode 100644 index c253774f68..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/NoEventsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoEventsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this - cluster in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts deleted file mode 100644 index 5b27953b35..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getClustersEventsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss deleted file mode 100644 index c04a4a49c0..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.styles.scss +++ /dev/null @@ -1,133 +0,0 @@ -.cluster-logs-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .cluster-logs-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .cluster-logs { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } -} - -.cluster-logs-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.cluster-logs-list-card { - width: 100%; - margin-top: 12px; - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx index 0f9ad02709..0e6487d7b8 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './ClusterLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import { Card } from 'antd'; import RawLogView from 'components/Logs/RawLogView'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LogsError from 'container/LogsError/LogsError'; import { LogsLoading } from 'container/LogsLoading/LogsLoading'; import { FontSize } from 'container/OptionsMenu/types'; @@ -22,9 +23,11 @@ import { } from 'types/api/queryBuilder/queryBuilderData'; import { v4 } from 'uuid'; -import { QUERY_KEYS } from '../constants'; -import { getClusterLogsQueryPayload } from './constants'; -import NoLogsContainer from './NoLogsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, + QUERY_KEYS, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -63,7 +66,7 @@ function PodLogs({ }, [filters]); const queryPayload = useMemo(() => { - const basePayload = getClusterLogsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -179,11 +182,11 @@ function PodLogs({ const renderContent = useMemo( () => ( - <Card bordered={false} className="cluster-logs-list-card"> + <Card bordered={false} className="entity-logs-list-card"> <OverlayScrollbar isVirtuoso> <Virtuoso - className="cluster-logs-virtuoso" - key="cluster-logs-virtuoso" + className="entity-logs-virtuoso" + key="entity-logs-virtuoso" data={logs} endReached={loadMoreLogs} totalCount={logs.length} @@ -200,12 +203,14 @@ function PodLogs({ ); return ( - <div className="cluster-logs"> + <div className="entity-logs"> {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {!isLoading && !isError && logs.length === 0 && ( + <EntityDetailsEmptyContainer category={K8sCategory.CLUSTERS} view="logs" /> + )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && logs.length > 0 && ( - <div className="cluster-logs-list-container">{renderContent}</div> + <div className="entity-logs-list-container">{renderContent}</div> )} </div> ); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx index d5f08bc7d9..d21b5a6121 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx @@ -1,4 +1,4 @@ -import './ClusterLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; @@ -64,8 +64,8 @@ function ClusterLogsDetailedView({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; return ( - <div className="cluster-logs-container"> - <div className="cluster-logs-header"> + <div className="entity-logs-container"> + <div className="entity-logs-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx deleted file mode 100644 index ebcecff969..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/NoLogsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoLogsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this - cluster in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts deleted file mode 100644 index ae2f7c6cfb..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getClusterLogsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss deleted file mode 100644 index 804fca7485..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.styles.scss +++ /dev/null @@ -1,45 +0,0 @@ -.empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.cluster-metrics-container { - margin-top: 1rem; -} - -.metrics-header { - display: flex; - justify-content: flex-end; - margin-top: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); -} - -.cluster-metrics-card { - margin: 8px 0 1rem 0; - height: 300px; - padding: 10px; - - border: 1px solid var(--bg-slate-500); - - .ant-card-body { - padding: 0; - } - - .chart-container { - width: 100%; - height: 100%; - } - - .no-data-container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx index dd71739913..0e4f84a373 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx @@ -1,4 +1,4 @@ -import './ClusterMetrics.styles.scss'; +import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; import { Card, Col, Row, Skeleton, Typography } from 'antd'; import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList'; @@ -56,7 +56,7 @@ function ClusterMetrics({ const queries = useQueries( queryPayloads.map((payload) => ({ - queryKey: ['cluster-metrics', payload, ENTITY_VERSION_V4, 'NODE'], + queryKey: ['entity-metrics', payload, ENTITY_VERSION_V4, 'NODE'], queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => GetMetricQueryRange(payload, ENTITY_VERSION_V4), enabled: !!payload, @@ -149,11 +149,11 @@ function ClusterMetrics({ /> </div> </div> - <Row gutter={24} className="cluster-metrics-container"> + <Row gutter={24} className="entity-metrics-container"> {queries.map((query, idx) => ( <Col span={12} key={clusterWidgetInfo[idx].title}> <Typography.Text>{clusterWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="cluster-metrics-card" ref={graphRef}> + <Card bordered className="entity-metrics-card" ref={graphRef}> {renderCardContent(query, idx)} </Card> </Col> diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss deleted file mode 100644 index 8f6c5c01db..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.styles.scss +++ /dev/null @@ -1,193 +0,0 @@ -.cluster-metric-traces { - margin-top: 1rem; - - .cluster-metric-traces-header { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - } - - .cluster-metric-traces-table { - .ant-table-content { - overflow: hidden !important; - } - - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgba(171, 189, 255, 0.01); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgba(171, 189, 255, 0.01); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-ink-400); - } - - .hostname-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-table-container::after { - content: none; - } - } -} - -.lightMode { - .host-metric-traces-header { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } - } - - .host-metric-traces-table { - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-vanilla-300); - - .ant-table-thead > tr > th { - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-vanilla-100); - } - - .ant-table-cell { - background: var(--bg-vanilla-100); - color: var(--bg-ink-500); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-vanilla-100); - } - - .hostname-column-value { - color: var(--bg-ink-300); - } - - .ant-table-tbody > tr:hover > td { - background: rgba(0, 0, 0, 0.04); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx index 999b2275a5..b0dff95d49 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx @@ -1,4 +1,4 @@ -import './ClusterTraces.styles.scss'; +import '../../../EntityDetailsUtils/entityTraces.styles.scss'; import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; import { ResizeTable } from 'components/ResizeTable'; @@ -25,7 +25,10 @@ import { useQuery } from 'react-query'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import { getClusterTracesQueryPayload, selectedColumns } from './constants'; +import { + getEntityTracesQueryPayload, + selectedEntityTracesColumns, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -86,7 +89,7 @@ function ClusterTraces({ const queryPayload = useMemo( () => - getClusterTracesQueryPayload( + getEntityTracesQueryPayload( timeRange.startTime, timeRange.endTime, paginationQueryData?.offset || offset, @@ -115,7 +118,7 @@ function ClusterTraces({ enabled: !!queryPayload, }); - const traceListColumns = getListColumns(selectedColumns); + const traceListColumns = getListColumns(selectedEntityTracesColumns); useEffect(() => { if (data?.payload?.data?.newResult?.data?.result) { @@ -138,8 +141,8 @@ function ClusterTraces({ data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; return ( - <div className="cluster-metric-traces"> - <div className="cluster-metric-traces-header"> + <div className="entity-metric-traces"> + <div className="entity-metric-traces-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -175,7 +178,7 @@ function ClusterTraces({ )} {!isError && traces.length > 0 && ( - <div className="cluster-traces-table"> + <div className="entity-traces-table"> <TraceExplorerControls isLoading={isFetching} totalCount={totalCount} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts deleted file mode 100644 index 52b89abe8e..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/constants.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { - BaseAutocompleteData, - DataTypes, -} from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { nanoToMilli } from 'utils/timeUtils'; - -export const columns = [ - { - dataIndex: 'timestamp', - key: 'timestamp', - title: 'Timestamp', - width: 200, - render: (timestamp: string): string => new Date(timestamp).toLocaleString(), - }, - { - title: 'Service Name', - dataIndex: ['data', 'serviceName'], - key: 'serviceName-string-tag', - width: 150, - }, - { - title: 'Name', - dataIndex: ['data', 'name'], - key: 'name-string-tag', - width: 145, - }, - { - title: 'Duration', - dataIndex: ['data', 'durationNano'], - key: 'durationNano-float64-tag', - width: 145, - render: (duration: number): string => `${nanoToMilli(duration)}ms`, - }, - { - title: 'HTTP Method', - dataIndex: ['data', 'httpMethod'], - key: 'httpMethod-string-tag', - width: 145, - }, - { - title: 'Status Code', - dataIndex: ['data', 'responseStatusCode'], - key: 'responseStatusCode-string-tag', - width: 145, - }, -]; - -export const selectedColumns: BaseAutocompleteData[] = [ - { - key: 'timestamp', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'serviceName', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'name', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'durationNano', - dataType: DataTypes.Float64, - type: 'tag', - isColumn: true, - }, - { - key: 'httpMethod', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'responseStatusCode', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, -]; - -export const getClusterTracesQueryPayload = ( - start: number, - end: number, - offset = 0, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - query: { - promql: [], - clickhouse_sql: [], - builder: { - queryData: [ - { - dataSource: DataSource.TRACES, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.EMPTY, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - }, - ], - queryFormulas: [], - }, - id: '572f1d91-6ac0-46c0-b726-c21488b34434', - queryType: EQueryType.QUERY_BUILDER, - }, - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - start, - end, - params: { - dataSource: DataSource.TRACES, - }, - tableParams: { - pagination: { - limit: 10, - offset, - }, - selectColumns: [ - { - key: 'serviceName', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'serviceName--string--tag--true', - isIndexed: false, - }, - { - key: 'name', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'name--string--tag--true', - isIndexed: false, - }, - { - key: 'durationNano', - dataType: 'float64', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'durationNano--float64--tag--true', - isIndexed: false, - }, - { - key: 'httpMethod', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'httpMethod--string--tag--true', - isIndexed: false, - }, - { - key: 'responseStatusCode', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'responseStatusCode--string--tag--true', - isIndexed: false, - }, - ], - }, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts deleted file mode 100644 index 38877088f1..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const QUERY_KEYS = { - K8S_OBJECT_KIND: 'k8s.object.kind', - K8S_OBJECT_NAME: 'k8s.object.name', - K8S_CLUSTER_NAME: 'k8s.cluster.name', -}; From bf6f1417948da205afe8c0e862d93c1ff560e79d Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 12:30:31 +0530 Subject: [PATCH 11/20] chore: refactor deployments --- .../DeploymentDetails.styles.scss | 247 --------------- .../DeploymentDetails/DeploymentDetails.tsx | 22 +- .../Events/DeploymentEvents.styles.scss | 289 ------------------ .../Events/DeploymentEvents.tsx | 30 +- .../Events/NoEventsContainer.tsx | 16 - .../DeploymentDetails/Events/constants.ts | 65 ---- .../Logs/DeploymentLogs.styles.scss | 133 -------- .../DeploymentDetails/Logs/DeploymentLogs.tsx | 30 +- .../Logs/DeploymentLogsDetailedView.tsx | 6 +- .../Logs/NoLogsContainer.tsx | 16 - .../DeploymentDetails/Logs/constants.ts | 65 ---- .../Metrics/DeploymentMetrics.styles.scss | 45 --- .../Metrics/DeploymentMetrics.tsx | 8 +- .../Traces/DeploymentTraces.styles.scss | 193 ------------ .../Traces/DeploymentTraces.tsx | 19 +- .../DeploymentDetails/Traces/constants.ts | 200 ------------ .../DeploymentDetails/constants.ts | 6 - .../EntityDetailsUtils/utils.tsx | 1 + 18 files changed, 67 insertions(+), 1324 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss deleted file mode 100644 index 22d2934367..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.styles.scss +++ /dev/null @@ -1,247 +0,0 @@ -.deployment-detail-drawer { - border-left: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2); - - .ant-drawer-header { - padding: 8px 16px; - border-bottom: none; - - align-items: stretch; - - border-bottom: 1px solid var(--bg-slate-500); - background: var(--bg-ink-400); - } - - .ant-drawer-close { - margin-inline-end: 0px; - } - - .ant-drawer-body { - display: flex; - flex-direction: column; - padding: 16px; - } - - .title { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 14px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .radio-button { - display: flex; - align-items: center; - justify-content: center; - padding-top: var(--padding-1); - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .deployment-detail-drawer__deployment { - .deployment-details-grid { - .labels-row, - .values-row { - display: grid; - grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr; - gap: 30px; - align-items: center; - } - - .labels-row { - margin-bottom: 8px; - } - - .deployment-details-metadata-label { - color: var(--text-vanilla-400); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 500; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - } - - .deployment-details-metadata-value { - color: var(--text-vanilla-400); - font-family: 'Geist Mono'; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .status-tag { - margin: 0; - - &.active { - color: var(--success-500); - background: var(--success-100); - border-color: var(--success-500); - } - - &.inactive { - color: var(--error-500); - background: var(--error-100); - border-color: var(--error-500); - } - } - - .progress-container { - width: 158px; - .ant-progress { - margin: 0; - - .ant-progress-text { - font-weight: 600; - } - } - } - - .ant-card { - &.ant-card-bordered { - border: 1px solid var(--bg-slate-500) !important; - } - } - } - } - - .tabs-and-search { - display: flex; - justify-content: space-between; - align-items: center; - margin: 16px 0; - - .action-btn { - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: center; - } - } - - .views-tabs-container { - margin-top: 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; - - .views-tabs { - color: var(--text-vanilla-400); - - .view-title { - display: flex; - gap: var(--margin-2); - align-items: center; - justify-content: center; - font-size: var(--font-size-xs); - font-style: normal; - font-weight: var(--font-weight-normal); - } - - .tab { - border: 1px solid var(--bg-slate-400); - width: 114px; - } - - .tab::before { - background: var(--bg-slate-400); - } - - .selected_view { - background: var(--bg-slate-300); - color: var(--text-vanilla-100); - border: 1px solid var(--bg-slate-400); - } - - .selected_view::before { - background: var(--bg-slate-400); - } - } - - .compass-button { - width: 30px; - height: 30px; - - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - } - .ant-drawer-close { - padding: 0px; - } -} - -.lightMode { - .ant-drawer-header { - border-bottom: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - } - - .deployment-detail-drawer { - .title { - color: var(--text-ink-300); - } - - .deployment-detail-drawer__deployment { - .ant-typography { - color: var(--text-ink-300); - background: transparent; - } - } - - .radio-button { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .views-tabs { - .tab { - background: var(--bg-vanilla-100); - } - - .selected_view { - background: var(--bg-vanilla-300); - border: 1px solid var(--bg-slate-300); - color: var(--text-ink-400); - } - - .selected_view::before { - background: var(--bg-vanilla-300); - border-left: 1px solid var(--bg-slate-300); - } - } - - .compass-button { - border: 1px solid var(--bg-vanilla-300); - background: var(--bg-vanilla-100); - box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); - } - - .tabs-and-search { - .action-btn { - border: 1px solid var(--bg-vanilla-400); - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index e59d2f5f17..81670cba0a 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -1,5 +1,5 @@ /* eslint-disable sonarjs/no-identical-functions */ -import './DeploymentDetails.styles.scss'; +import '../../EntityDetailsUtils/entityDetails.styles.scss'; import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; @@ -12,6 +12,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { CustomTimeType, Time, @@ -42,7 +43,6 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import { QUERY_KEYS } from './constants'; import { DeploymentDetailsProps } from './DeploymentDetails.interfaces'; import DeploymentEvents from './Events'; import DeploymentLogs from './Logs'; @@ -420,46 +420,46 @@ function DeploymentDetails({ overscrollBehavior: 'contain', background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, }} - className="deployment-detail-drawer" + className="entity-detail-drawer" destroyOnClose closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />} > {deployment && ( <> - <div className="deployment-detail-drawer__deployment"> - <div className="deployment-details-grid"> + <div className="entity-detail-drawer__entity"> + <div className="entity-details-grid"> <div className="labels-row"> <Typography.Text type="secondary" - className="deployment-details-metadata-label" + className="entity-details-metadata-label" > Deployment Name </Typography.Text> <Typography.Text type="secondary" - className="deployment-details-metadata-label" + className="entity-details-metadata-label" > Cluster Name </Typography.Text> <Typography.Text type="secondary" - className="deployment-details-metadata-label" + className="entity-details-metadata-label" > Namespace Name </Typography.Text> </div> <div className="values-row"> - <Typography.Text className="deployment-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={deployment.meta.k8s_deployment_name}> {deployment.meta.k8s_deployment_name} </Tooltip> </Typography.Text> - <Typography.Text className="deployment-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={deployment.meta.k8s_cluster_name}> {deployment.meta.k8s_cluster_name} </Tooltip> </Typography.Text> - <Typography.Text className="deployment-details-metadata-value"> + <Typography.Text className="entity-details-metadata-value"> <Tooltip title={deployment.meta.k8s_namespace_name}> {deployment.meta.k8s_namespace_name} </Tooltip> diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss deleted file mode 100644 index d7b54bc629..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.styles.scss +++ /dev/null @@ -1,289 +0,0 @@ -.deployment-events-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .deployment-events-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .deployment-events { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } - - .ant-table { - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgb(18, 19, 23); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.deploymentname-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgb(18, 19, 23); - border-bottom: none; - } - - .ant-table-cell:has(.deploymentname-column-value) { - background: var(--bg-ink-400); - } - - .deploymentname-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-pagination { - position: fixed; - bottom: 0; - width: calc(100% - 64px); - background: rgb(18, 19, 23); - padding: 16px; - margin: 0; - - // this is to offset intercom icon till we improve the design - padding-right: 72px; - - .ant-pagination-item { - border-radius: 4px; - - &-active { - background: var(--bg-robin-500); - border-color: var(--bg-robin-500); - - a { - color: var(--bg-ink-500) !important; - } - } - } - } -} - -.deployment-events-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.deployment-events-list-card { - width: 100%; - margin-top: 12px; - - .ant-table-wrapper { - height: 100%; - overflow-y: auto; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} - -.periscope-btn-icon { - cursor: pointer; -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx index e9b95e83ea..05b6520f51 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './DeploymentEvents.styles.scss'; +import '../../../EntityDetailsUtils/entityEvents.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Table, TableColumnsType } from 'antd'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; import LogsError from 'container/LogsError/LogsError'; import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; @@ -25,8 +26,10 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { v4 } from 'uuid'; -import { getDeploymentsEventsQueryPayload } from './constants'; -import NoEventsContainer from './NoEventsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, +} from '../../../EntityDetailsUtils/utils'; interface EventDataType { key: string; @@ -110,7 +113,7 @@ export default function Events({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; const queryPayload = useMemo(() => { - const basePayload = getDeploymentsEventsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -283,8 +286,8 @@ export default function Events({ ); return ( - <div className="deployment-events-container"> - <div className="deployment-events-header"> + <div className="entity-events-container"> + <div className="entity-events-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -310,14 +313,17 @@ export default function Events({ {isLoading && <LoadingContainer />} {!isLoading && !isError && formattedDeploymentEvents.length === 0 && ( - <NoEventsContainer /> + <EntityDetailsEmptyContainer + category={K8sCategory.DEPLOYMENTS} + view="events" + /> )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && formattedDeploymentEvents.length > 0 && ( - <div className="deployment-events-list-container"> - <div className="deployment-events-list-card"> + <div className="entity-events-list-container"> + <div className="entity-events-list-card"> <Table<EventDataType> loading={isLoading && page > 1} columns={columns} @@ -335,9 +341,9 @@ export default function Events({ )} {!isError && formattedDeploymentEvents.length > 0 && ( - <div className="deployment-events-footer"> + <div className="entity-events-footer"> <Button - className="deployment-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handlePrev} disabled={page === 1 || isFetching || isLoading} @@ -347,7 +353,7 @@ export default function Events({ </Button> <Button - className="deployment-events-footer-button periscope-btn ghost" + className="entity-events-footer-button periscope-btn ghost" type="link" onClick={handleNext} disabled={hasReachedEndOfEvents || isFetching || isLoading} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx deleted file mode 100644 index 779968c842..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/NoEventsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoEventsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this - deployment in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts deleted file mode 100644 index 0012d25ad5..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getDeploymentsEventsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss deleted file mode 100644 index fd91f49f65..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.styles.scss +++ /dev/null @@ -1,133 +0,0 @@ -.deployment-logs-container { - margin-top: 1rem; - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - - .deployment-logs-header { - display: flex; - justify-content: space-between; - gap: 8px; - - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - } - - .deployment-logs { - margin-top: 1rem; - - .virtuoso-list { - overflow-y: hidden !important; - - &::-webkit-scrollbar { - width: 0.3rem; - height: 0.3rem; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background: var(--bg-slate-300); - } - - &::-webkit-scrollbar-thumb:hover { - background: var(--bg-slate-200); - } - - .ant-row { - width: fit-content; - } - } - - .skeleton-container { - height: 100%; - padding: 16px; - } - } -} - -.deployment-logs-list-container { - flex: 1; - height: calc(100vh - 272px) !important; - display: flex; - height: 100%; - - .raw-log-content { - width: 100%; - text-wrap: inherit; - word-wrap: break-word; - } -} - -.deployment-logs-list-card { - width: 100%; - margin-top: 12px; - - .ant-card-body { - padding: 0; - - height: 100%; - width: 100%; - } -} - -.logs-loading-skeleton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0; - - .ant-skeleton-input-sm { - height: 18px; - } -} - -.no-logs-found { - height: 50vh; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - - padding: 24px; - box-sizing: border-box; - - .ant-typography { - display: flex; - align-items: center; - gap: 16px; - } -} - -.lightMode { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx index 9936ab7483..a60a235005 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx @@ -1,10 +1,11 @@ /* eslint-disable no-nested-ternary */ -import './DeploymentLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import { Card } from 'antd'; import RawLogView from 'components/Logs/RawLogView'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import LogsError from 'container/LogsError/LogsError'; import { LogsLoading } from 'container/LogsLoading/LogsLoading'; import { FontSize } from 'container/OptionsMenu/types'; @@ -22,9 +23,11 @@ import { } from 'types/api/queryBuilder/queryBuilderData'; import { v4 } from 'uuid'; -import { QUERY_KEYS } from '../constants'; -import { getDeploymentLogsQueryPayload } from './constants'; -import NoLogsContainer from './NoLogsContainer'; +import { + EntityDetailsEmptyContainer, + getEntityEventsOrLogsQueryPayload, + QUERY_KEYS, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -65,7 +68,7 @@ function PodLogs({ }, [filters]); const queryPayload = useMemo(() => { - const basePayload = getDeploymentLogsQueryPayload( + const basePayload = getEntityEventsOrLogsQueryPayload( timeRange.startTime, timeRange.endTime, filters, @@ -181,11 +184,11 @@ function PodLogs({ const renderContent = useMemo( () => ( - <Card bordered={false} className="deployment-logs-list-card"> + <Card bordered={false} className="entity-logs-list-card"> <OverlayScrollbar isVirtuoso> <Virtuoso - className="deployment-logs-virtuoso" - key="deployment-logs-virtuoso" + className="entity-logs-virtuoso" + key="entity-logs-virtuoso" data={logs} endReached={loadMoreLogs} totalCount={logs.length} @@ -202,12 +205,17 @@ function PodLogs({ ); return ( - <div className="deployment-logs"> + <div className="entity-logs"> {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && <NoLogsContainer />} + {!isLoading && !isError && logs.length === 0 && ( + <EntityDetailsEmptyContainer + category={K8sCategory.DEPLOYMENTS} + view="logs" + /> + )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && logs.length > 0 && ( - <div className="deployment-logs-list-container">{renderContent}</div> + <div className="entity-logs-list-container">{renderContent}</div> )} </div> ); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx index 1c1e59bebc..3fbadf20bc 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx @@ -1,4 +1,4 @@ -import './DeploymentLogs.styles.scss'; +import '../../../EntityDetailsUtils/entityLogs.styles.scss'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; @@ -64,8 +64,8 @@ function DeploymentLogsDetailedView({ const query = updatedCurrentQuery?.builder?.queryData[0] || null; return ( - <div className="deployment-logs-container"> - <div className="deployment-logs-header"> + <div className="entity-logs-container"> + <div className="entity-logs-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx deleted file mode 100644 index 99a791b620..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/NoLogsContainer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Color } from '@signozhq/design-tokens'; -import { Typography } from 'antd'; -import { Ghost } from 'lucide-react'; - -const { Text } = Typography; - -export default function NoLogsContainer(): React.ReactElement { - return ( - <div className="no-logs-found"> - <Text type="secondary"> - <Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this - deployment in the selected time range. - </Text> - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts deleted file mode 100644 index fb620033cf..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/constants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 as uuidv4 } from 'uuid'; - -export const getDeploymentLogsQueryPayload = ( - start: number, - end: number, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - query: { - clickhouse_sql: [], - promql: [], - builder: { - queryData: [ - { - dataSource: DataSource.LOGS, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.String, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - offset: 0, - pageSize: 100, - }, - ], - queryFormulas: [], - }, - id: uuidv4(), - queryType: EQueryType.QUERY_BUILDER, - }, - params: { - lastLogLineTimestamp: null, - }, - start, - end, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss deleted file mode 100644 index c94162f9d9..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.styles.scss +++ /dev/null @@ -1,45 +0,0 @@ -.empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.deployment-metrics-container { - margin-top: 1rem; -} - -.metrics-header { - display: flex; - justify-content: flex-end; - margin-top: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); -} - -.deployment-metrics-card { - margin: 8px 0 1rem 0; - height: 300px; - padding: 10px; - - border: 1px solid var(--bg-slate-500); - - .ant-card-body { - padding: 0; - } - - .chart-container { - width: 100%; - height: 100%; - } - - .no-data-container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx index fcbb986cae..cdc8978f8c 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx @@ -1,4 +1,4 @@ -import './DeploymentMetrics.styles.scss'; +import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; import { Card, Col, Row, Skeleton, Typography } from 'antd'; import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList'; @@ -55,7 +55,7 @@ function DeploymentMetrics({ const queries = useQueries( queryPayloads.map((payload) => ({ - queryKey: ['deployment-metrics', payload, ENTITY_VERSION_V4, 'NODE'], + queryKey: ['deploymentMetrics', payload, ENTITY_VERSION_V4, 'NODE'], queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => GetMetricQueryRange(payload, ENTITY_VERSION_V4), enabled: !!payload, @@ -128,11 +128,11 @@ function DeploymentMetrics({ /> </div> </div> - <Row gutter={24} className="deployment-metrics-container"> + <Row gutter={24} className="entity-metrics-container"> {queries.map((query, idx) => ( <Col span={12} key={deploymentWidgetInfo[idx].title}> <Typography.Text>{deploymentWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="deployment-metrics-card" ref={graphRef}> + <Card bordered className="entity-metrics-card" ref={graphRef}> {renderCardContent(query, idx)} </Card> </Col> diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss deleted file mode 100644 index 059e07b4f2..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.styles.scss +++ /dev/null @@ -1,193 +0,0 @@ -.deployment-metric-traces { - margin-top: 1rem; - - .deployment-metric-traces-header { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; - - gap: 8px; - padding: 12px; - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .filter-section { - flex: 1; - - .ant-select-selector { - border-radius: 2px; - border: 1px solid var(--bg-slate-400) !important; - background-color: var(--bg-ink-300) !important; - - input { - font-size: 12px; - } - - .ant-tag .ant-typography { - font-size: 12px; - } - } - } - } - - .deployment-metric-traces-table { - .ant-table-content { - overflow: hidden !important; - } - - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-slate-500); - - .ant-table-thead > tr > th { - padding: 12px; - font-weight: 500; - font-size: 12px; - line-height: 18px; - - background: rgba(171, 189, 255, 0.01); - border-bottom: none; - - color: var(--Vanilla-400, #c0c1c3); - font-family: Inter; - font-size: 11px; - font-style: normal; - font-weight: 600; - line-height: 18px; /* 163.636% */ - letter-spacing: 0.44px; - text-transform: uppercase; - - &::before { - background-color: transparent; - } - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-ink-400); - } - - .ant-table-cell { - padding: 12px; - font-size: 13px; - line-height: 20px; - color: var(--bg-vanilla-100); - background: rgba(171, 189, 255, 0.01); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-ink-400); - } - - .hostname-column-value { - color: var(--bg-vanilla-100); - font-family: 'Geist Mono'; - font-style: normal; - font-weight: 600; - line-height: 20px; /* 142.857% */ - letter-spacing: -0.07px; - } - - .status-cell { - .active-tag { - color: var(--bg-forest-500); - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - } - } - - .progress-container { - .ant-progress-bg { - height: 8px !important; - border-radius: 4px; - } - } - - .ant-table-tbody > tr:hover > td { - background: rgba(255, 255, 255, 0.04); - } - - .ant-table-cell:first-child { - text-align: justify; - } - - .ant-table-cell:nth-child(2) { - padding-left: 16px; - padding-right: 16px; - } - - .ant-table-cell:nth-child(n + 3) { - padding-right: 24px; - } - .column-header-right { - text-align: right; - } - .ant-table-tbody > tr > td { - border-bottom: none; - } - - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: transparent; - } - - .ant-empty-normal { - visibility: hidden; - } - } - - .ant-table-container::after { - content: none; - } - } -} - -.lightMode { - .host-metric-traces-header { - .filter-section { - border-top: 1px solid var(--bg-vanilla-300); - border-bottom: 1px solid var(--bg-vanilla-300); - - .ant-select-selector { - border-color: var(--bg-vanilla-300) !important; - background-color: var(--bg-vanilla-100) !important; - color: var(--bg-ink-200); - } - } - } - - .host-metric-traces-table { - .ant-table { - border-radius: 3px; - border: 1px solid var(--bg-vanilla-300); - - .ant-table-thead > tr > th { - background: var(--bg-vanilla-100); - color: var(--text-ink-300); - } - - .ant-table-thead > tr > th:has(.hostname-column-header) { - background: var(--bg-vanilla-100); - } - - .ant-table-cell { - background: var(--bg-vanilla-100); - color: var(--bg-ink-500); - } - - .ant-table-cell:has(.hostname-column-value) { - background: var(--bg-vanilla-100); - } - - .hostname-column-value { - color: var(--bg-ink-300); - } - - .ant-table-tbody > tr:hover > td { - background: rgba(0, 0, 0, 0.04); - } - } - } -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx index 0c9d137380..b023fbeae1 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx @@ -1,4 +1,4 @@ -import './DeploymentTraces.styles.scss'; +import '../../../EntityDetailsUtils/entityTraces.styles.scss'; import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; import { ResizeTable } from 'components/ResizeTable'; @@ -25,7 +25,10 @@ import { useQuery } from 'react-query'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import { getDeploymentTracesQueryPayload, selectedColumns } from './constants'; +import { + getEntityTracesQueryPayload, + selectedEntityTracesColumns, +} from '../../../EntityDetailsUtils/utils'; interface Props { timeRange: { @@ -86,7 +89,7 @@ function DeploymentTraces({ const queryPayload = useMemo( () => - getDeploymentTracesQueryPayload( + getEntityTracesQueryPayload( timeRange.startTime, timeRange.endTime, paginationQueryData?.offset || offset, @@ -103,7 +106,7 @@ function DeploymentTraces({ const { data, isLoading, isFetching, isError } = useQuery({ queryKey: [ - 'hostMetricTraces', + 'deploymentMetricTraces', timeRange.startTime, timeRange.endTime, offset, @@ -115,7 +118,7 @@ function DeploymentTraces({ enabled: !!queryPayload, }); - const traceListColumns = getListColumns(selectedColumns); + const traceListColumns = getListColumns(selectedEntityTracesColumns); useEffect(() => { if (data?.payload?.data?.newResult?.data?.result) { @@ -138,8 +141,8 @@ function DeploymentTraces({ data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; return ( - <div className="deployment-metric-traces"> - <div className="deployment-metric-traces-header"> + <div className="entity-metric-traces"> + <div className="entity-metric-traces-header"> <div className="filter-section"> {query && ( <QueryBuilderSearch @@ -175,7 +178,7 @@ function DeploymentTraces({ )} {!isError && traces.length > 0 && ( - <div className="deployment-traces-table"> + <div className="entity-traces-table"> <TraceExplorerControls isLoading={isFetching} totalCount={totalCount} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts deleted file mode 100644 index d592b4db74..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/constants.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; -import { - BaseAutocompleteData, - DataTypes, -} from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { EQueryType } from 'types/common/dashboard'; -import { DataSource } from 'types/common/queryBuilder'; -import { nanoToMilli } from 'utils/timeUtils'; - -export const columns = [ - { - dataIndex: 'timestamp', - key: 'timestamp', - title: 'Timestamp', - width: 200, - render: (timestamp: string): string => new Date(timestamp).toLocaleString(), - }, - { - title: 'Service Name', - dataIndex: ['data', 'serviceName'], - key: 'serviceName-string-tag', - width: 150, - }, - { - title: 'Name', - dataIndex: ['data', 'name'], - key: 'name-string-tag', - width: 145, - }, - { - title: 'Duration', - dataIndex: ['data', 'durationNano'], - key: 'durationNano-float64-tag', - width: 145, - render: (duration: number): string => `${nanoToMilli(duration)}ms`, - }, - { - title: 'HTTP Method', - dataIndex: ['data', 'httpMethod'], - key: 'httpMethod-string-tag', - width: 145, - }, - { - title: 'Status Code', - dataIndex: ['data', 'responseStatusCode'], - key: 'responseStatusCode-string-tag', - width: 145, - }, -]; - -export const selectedColumns: BaseAutocompleteData[] = [ - { - key: 'timestamp', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'serviceName', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'name', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'durationNano', - dataType: DataTypes.Float64, - type: 'tag', - isColumn: true, - }, - { - key: 'httpMethod', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, - { - key: 'responseStatusCode', - dataType: DataTypes.String, - type: 'tag', - isColumn: true, - }, -]; - -export const getDeploymentTracesQueryPayload = ( - start: number, - end: number, - offset = 0, - filters: IBuilderQuery['filters'], -): GetQueryResultsProps => ({ - query: { - promql: [], - clickhouse_sql: [], - builder: { - queryData: [ - { - dataSource: DataSource.TRACES, - queryName: 'A', - aggregateOperator: 'noop', - aggregateAttribute: { - id: '------false', - dataType: DataTypes.EMPTY, - key: '', - isColumn: false, - type: '', - isJSON: false, - }, - timeAggregation: 'rate', - spaceAggregation: 'sum', - functions: [], - filters, - expression: 'A', - disabled: false, - stepInterval: 60, - having: [], - limit: null, - orderBy: [ - { - columnName: 'timestamp', - order: 'desc', - }, - ], - groupBy: [], - legend: '', - reduceTo: 'avg', - }, - ], - queryFormulas: [], - }, - id: '572f1d91-6ac0-46c0-b726-c21488b34434', - queryType: EQueryType.QUERY_BUILDER, - }, - graphType: PANEL_TYPES.LIST, - selectedTime: 'GLOBAL_TIME', - start, - end, - params: { - dataSource: DataSource.TRACES, - }, - tableParams: { - pagination: { - limit: 10, - offset, - }, - selectColumns: [ - { - key: 'serviceName', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'serviceName--string--tag--true', - isIndexed: false, - }, - { - key: 'name', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'name--string--tag--true', - isIndexed: false, - }, - { - key: 'durationNano', - dataType: 'float64', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'durationNano--float64--tag--true', - isIndexed: false, - }, - { - key: 'httpMethod', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'httpMethod--string--tag--true', - isIndexed: false, - }, - { - key: 'responseStatusCode', - dataType: 'string', - type: 'tag', - isColumn: true, - isJSON: false, - id: 'responseStatusCode--string--tag--true', - isIndexed: false, - }, - ], - }, -}); diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts deleted file mode 100644 index dc94ffe217..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const QUERY_KEYS = { - K8S_OBJECT_KIND: 'k8s.object.kind', - K8S_OBJECT_NAME: 'k8s.object.name', - K8S_DEPLOYMENT_NAME: 'k8s.deployment.name', - K8S_NAMESPACE_NAME: 'k8s.namespace.name', -}; diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx index 4f9e0a0004..68c696d586 100644 --- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/utils.tsx @@ -22,6 +22,7 @@ export const QUERY_KEYS = { K8S_NAMESPACE_NAME: 'k8s.namespace.name', K8S_CLUSTER_NAME: 'k8s.cluster.name', K8S_NODE_NAME: 'k8s.node.name', + K8S_DEPLOYMENT_NAME: 'k8s.deployment.name', }; /** From e68ca3d1b83a35551f4320c855b1d0aca704c4ed Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 13:28:39 +0530 Subject: [PATCH 12/20] chore: refactor events --- .../ClusterDetails/ClusterDetails.tsx | 5 +- .../ClusterDetails/Events/ClusterEvents.tsx | 366 ----------------- .../Clusters/ClusterDetails/Events/index.ts | 3 - .../DeploymentDetails/DeploymentDetails.tsx | 5 +- .../Events/DeploymentEvents.tsx | 372 ------------------ .../DeploymentDetails/Events/index.ts | 3 - .../EntityEvents.tsx} | 36 +- .../Events/NamespaceEvents.tsx | 371 ----------------- .../NamespaceDetails/Events/index.ts | 3 - .../NamespaceDetails/NamespaceDetails.tsx | 5 +- .../Nodes/NodeDetails/Events/index.ts | 3 - .../Nodes/NodeDetails/NodeDetails.tsx | 7 +- .../Pods/PodDetails/Events/Events.tsx | 363 ----------------- .../Pods/PodDetails/PodDetails.tsx | 7 +- 14 files changed, 42 insertions(+), 1507 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts rename frontend/src/container/InfraMonitoringK8s/{Nodes/NodeDetails/Events/NodeEvents.tsx => EntityDetailsUtils/EntityEvents.tsx} (90%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index c873f5ff96..b5501720ef 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -12,6 +12,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { CustomTimeType, @@ -43,8 +44,8 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; +import ClusterEvents from '../../EntityDetailsUtils/EntityEvents'; import { ClusterDetailsProps } from './ClusterDetails.interfaces'; -import ClusterEvents from './Events'; import ClusterLogs from './Logs'; import ClusterMetrics from './Metrics'; import ClusterTraces from './Traces'; @@ -527,6 +528,8 @@ function ClusterDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} + category={K8sCategory.CLUSTERS} + queryKey="clusterEvents" /> )} </> diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx deleted file mode 100644 index bdf44c8ac9..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.tsx +++ /dev/null @@ -1,366 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityEvents.styles.scss'; - -import { Color } from '@signozhq/design-tokens'; -import { Button, Table, TableColumnsType } from 'antd'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; -import LogsError from 'container/LogsError/LogsError'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isArray } from 'lodash-es'; -import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, -} from '../../../EntityDetailsUtils/utils'; - -interface EventDataType { - key: string; - timestamp: string; - body: string; - id: string; - attributes_bool?: Record<string, boolean>; - attributes_number?: Record<string, number>; - attributes_string?: Record<string, string>; - resources_string?: Record<string, string>; - scope_name?: string; - scope_string?: Record<string, string>; - scope_version?: string; - severity_number?: number; - severity_text?: string; - span_id?: string; - trace_flags?: number; - trace_id?: string; - severity?: string; -} - -interface IClusterEventsProps { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; -} - -const EventsPageSize = 10; - -export default function Events({ - timeRange, - handleChangeEventFilters, - filters, - isModalTimeSelection, - handleTimeChange, - selectedInterval, -}: IClusterEventsProps): JSX.Element { - const { currentQuery } = useQueryBuilder(); - - const [formattedClusterEvents, setFormattedClusterEvents] = useState< - EventDataType[] - >([]); - - const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); - - const [page, setPage] = useState(1); - - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 10; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const { data: eventsData, isLoading, isFetching, isError } = useQuery({ - queryKey: ['clusterEvents', timeRange.startTime, timeRange.endTime, filters], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const columns: TableColumnsType<EventDataType> = [ - { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, - { - title: 'Timestamp', - dataIndex: 'timestamp', - width: 200, - ellipsis: true, - key: 'timestamp', - }, - { title: 'Body', dataIndex: 'body', key: 'body' }, - ]; - - useEffect(() => { - if (eventsData?.payload?.data?.newResult?.data?.result) { - const responsePayload = - eventsData?.payload.data.newResult.data.result[0].list || []; - - const formattedData = responsePayload?.map( - (event): EventDataType => ({ - timestamp: event.timestamp, - severity: event.data.severity_text, - body: event.data.body, - id: event.data.id, - key: event.data.id, - resources_string: event.data.resources_string, - attributes_string: event.data.attributes_string, - }), - ); - - setFormattedClusterEvents(formattedData); - - if ( - !responsePayload || - (responsePayload && - isArray(responsePayload) && - responsePayload.length < EventsPageSize) - ) { - setHasReachedEndOfEvents(true); - } else { - setHasReachedEndOfEvents(false); - } - } - }, [eventsData]); - - const handleExpandRow = (record: EventDataType): JSX.Element => ( - <EventContents - data={{ ...record.attributes_string, ...record.resources_string }} - /> - ); - - const handlePrev = (): void => { - if (!formattedClusterEvents.length) return; - - setPage(page - 1); - - const firstEvent = formattedClusterEvents[0]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '>', - value: firstEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeEventFilters(newFilters); - }; - - const handleNext = (): void => { - if (!formattedClusterEvents.length) return; - - setPage(page + 1); - const lastEvent = formattedClusterEvents[formattedClusterEvents.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeEventFilters(newFilters); - }; - - const handleExpandRowIcon = ({ - expanded, - onExpand, - record, - }: { - expanded: boolean; - onExpand: ( - record: EventDataType, - e: React.MouseEvent<HTMLElement, MouseEvent>, - ) => void; - record: EventDataType; - }): JSX.Element => - expanded ? ( - <ChevronDown - className="periscope-btn-icon" - size={14} - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ) : ( - <ChevronRight - className="periscope-btn-icon" - size={14} - // eslint-disable-next-line sonarjs/no-identical-functions - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ); - - return ( - <div className="entity-events-container"> - <div className="entity-events-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeEventFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isLoading && <LoadingContainer />} - - {!isLoading && !isError && formattedClusterEvents.length === 0 && ( - <EntityDetailsEmptyContainer - category={K8sCategory.CLUSTERS} - view="events" - /> - )} - - {isError && !isLoading && <LogsError />} - - {!isLoading && !isError && formattedClusterEvents.length > 0 && ( - <div className="entity-events-list-container"> - <div className="entity-events-list-card"> - <Table<EventDataType> - loading={isLoading && page > 1} - columns={columns} - expandable={{ - expandedRowRender: handleExpandRow, - rowExpandable: (record): boolean => record.body !== 'Not Expandable', - expandIcon: handleExpandRowIcon, - }} - dataSource={formattedClusterEvents} - pagination={false} - rowKey={(record): string => record.id} - /> - </div> - </div> - )} - - {!isError && formattedClusterEvents.length > 0 && ( - <div className="entity-events-footer"> - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handlePrev} - disabled={page === 1 || isFetching || isLoading} - > - {!isFetching && <ChevronLeft size={14} />} - Prev - </Button> - - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handleNext} - disabled={hasReachedEndOfEvents || isFetching || isLoading} - > - Next - {!isFetching && <ChevronRight size={14} />} - </Button> - - {(isFetching || isLoading) && ( - <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> - )} - </div> - )} - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts deleted file mode 100644 index 31af59a90e..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import ClusterEvents from './ClusterEvents'; - -export default ClusterEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index 81670cba0a..7a155b9649 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -12,6 +12,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { CustomTimeType, @@ -43,8 +44,8 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; +import DeploymentEvents from '../../EntityDetailsUtils/EntityEvents'; import { DeploymentDetailsProps } from './DeploymentDetails.interfaces'; -import DeploymentEvents from './Events'; import DeploymentLogs from './Logs'; import DeploymentMetrics from './Metrics'; import DeploymentTraces from './Traces'; @@ -567,6 +568,8 @@ function DeploymentDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} + category={K8sCategory.DEPLOYMENTS} + queryKey="deploymentEvents" /> )} </> diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx deleted file mode 100644 index 05b6520f51..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/DeploymentEvents.tsx +++ /dev/null @@ -1,372 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityEvents.styles.scss'; - -import { Color } from '@signozhq/design-tokens'; -import { Button, Table, TableColumnsType } from 'antd'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; -import LogsError from 'container/LogsError/LogsError'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isArray } from 'lodash-es'; -import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, -} from '../../../EntityDetailsUtils/utils'; - -interface EventDataType { - key: string; - timestamp: string; - body: string; - id: string; - attributes_bool?: Record<string, boolean>; - attributes_number?: Record<string, number>; - attributes_string?: Record<string, string>; - resources_string?: Record<string, string>; - scope_name?: string; - scope_string?: Record<string, string>; - scope_version?: string; - severity_number?: number; - severity_text?: string; - span_id?: string; - trace_flags?: number; - trace_id?: string; - severity?: string; -} - -interface IDeploymentEventsProps { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; -} - -const EventsPageSize = 10; - -export default function Events({ - timeRange, - handleChangeEventFilters, - filters, - isModalTimeSelection, - handleTimeChange, - selectedInterval, -}: IDeploymentEventsProps): JSX.Element { - const { currentQuery } = useQueryBuilder(); - - const [formattedDeploymentEvents, setFormattedDeploymentEvents] = useState< - EventDataType[] - >([]); - - const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); - - const [page, setPage] = useState(1); - - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 10; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const { data: eventsData, isLoading, isFetching, isError } = useQuery({ - queryKey: [ - 'deploymentEvents', - timeRange.startTime, - timeRange.endTime, - filters, - ], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const columns: TableColumnsType<EventDataType> = [ - { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, - { - title: 'Timestamp', - dataIndex: 'timestamp', - width: 200, - ellipsis: true, - key: 'timestamp', - }, - { title: 'Body', dataIndex: 'body', key: 'body' }, - ]; - - useEffect(() => { - if (eventsData?.payload?.data?.newResult?.data?.result) { - const responsePayload = - eventsData?.payload.data.newResult.data.result[0].list || []; - - const formattedData = responsePayload?.map( - (event): EventDataType => ({ - timestamp: event.timestamp, - severity: event.data.severity_text, - body: event.data.body, - id: event.data.id, - key: event.data.id, - resources_string: event.data.resources_string, - attributes_string: event.data.attributes_string, - }), - ); - - setFormattedDeploymentEvents(formattedData); - - if ( - !responsePayload || - (responsePayload && - isArray(responsePayload) && - responsePayload.length < EventsPageSize) - ) { - setHasReachedEndOfEvents(true); - } else { - setHasReachedEndOfEvents(false); - } - } - }, [eventsData]); - - const handleExpandRow = (record: EventDataType): JSX.Element => ( - <EventContents - data={{ ...record.attributes_string, ...record.resources_string }} - /> - ); - - const handlePrev = (): void => { - if (!formattedDeploymentEvents.length) return; - - setPage(page - 1); - - const firstEvent = formattedDeploymentEvents[0]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '>', - value: firstEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeEventFilters(newFilters); - }; - - const handleNext = (): void => { - if (!formattedDeploymentEvents.length) return; - - setPage(page + 1); - const lastEvent = - formattedDeploymentEvents[formattedDeploymentEvents.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeEventFilters(newFilters); - }; - - const handleExpandRowIcon = ({ - expanded, - onExpand, - record, - }: { - expanded: boolean; - onExpand: ( - record: EventDataType, - e: React.MouseEvent<HTMLElement, MouseEvent>, - ) => void; - record: EventDataType; - }): JSX.Element => - expanded ? ( - <ChevronDown - className="periscope-btn-icon" - size={14} - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ) : ( - <ChevronRight - className="periscope-btn-icon" - size={14} - // eslint-disable-next-line sonarjs/no-identical-functions - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ); - - return ( - <div className="entity-events-container"> - <div className="entity-events-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeEventFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isLoading && <LoadingContainer />} - - {!isLoading && !isError && formattedDeploymentEvents.length === 0 && ( - <EntityDetailsEmptyContainer - category={K8sCategory.DEPLOYMENTS} - view="events" - /> - )} - - {isError && !isLoading && <LogsError />} - - {!isLoading && !isError && formattedDeploymentEvents.length > 0 && ( - <div className="entity-events-list-container"> - <div className="entity-events-list-card"> - <Table<EventDataType> - loading={isLoading && page > 1} - columns={columns} - expandable={{ - expandedRowRender: handleExpandRow, - rowExpandable: (record): boolean => record.body !== 'Not Expandable', - expandIcon: handleExpandRowIcon, - }} - dataSource={formattedDeploymentEvents} - pagination={false} - rowKey={(record): string => record.id} - /> - </div> - </div> - )} - - {!isError && formattedDeploymentEvents.length > 0 && ( - <div className="entity-events-footer"> - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handlePrev} - disabled={page === 1 || isFetching || isLoading} - > - {!isFetching && <ChevronLeft size={14} />} - Prev - </Button> - - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handleNext} - disabled={hasReachedEndOfEvents || isFetching || isLoading} - > - Next - {!isFetching && <ChevronRight size={14} />} - </Button> - - {(isFetching || isLoading) && ( - <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> - )} - </div> - )} - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts deleted file mode 100644 index 6780621713..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Events/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DeploymentEvents from './DeploymentEvents'; - -export default DeploymentEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents.tsx similarity index 90% rename from frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents.tsx index ed3f067015..f65dc6e178 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/NodeEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityEvents.styles.scss'; +import './entityEvents.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Table, TableColumnsType } from 'antd'; @@ -29,7 +29,7 @@ import { v4 } from 'uuid'; import { EntityDetailsEmptyContainer, getEntityEventsOrLogsQueryPayload, -} from '../../../EntityDetailsUtils/utils'; +} from './utils'; interface EventDataType { key: string; @@ -51,7 +51,7 @@ interface EventDataType { severity?: string; } -interface INodeEventsProps { +interface IEntityEventsProps { timeRange: { startTime: number; endTime: number; @@ -64,6 +64,8 @@ interface INodeEventsProps { dateTimeRange?: [number, number], ) => void; selectedInterval: Time; + category: K8sCategory; + queryKey: string; } const EventsPageSize = 10; @@ -75,10 +77,12 @@ export default function Events({ isModalTimeSelection, handleTimeChange, selectedInterval, -}: INodeEventsProps): JSX.Element { + category, + queryKey, +}: IEntityEventsProps): JSX.Element { const { currentQuery } = useQueryBuilder(); - const [formattedNodeEvents, setFormattedNodeEvents] = useState< + const [formattedEntityEvents, setFormattedEntityEvents] = useState< EventDataType[] >([]); @@ -128,7 +132,7 @@ export default function Events({ }, [timeRange.startTime, timeRange.endTime, filters]); const { data: eventsData, isLoading, isFetching, isError } = useQuery({ - queryKey: ['nodeEvents', timeRange.startTime, timeRange.endTime, filters], + queryKey: [queryKey, timeRange.startTime, timeRange.endTime, filters], queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), enabled: !!queryPayload, }); @@ -162,7 +166,7 @@ export default function Events({ }), ); - setFormattedNodeEvents(formattedData); + setFormattedEntityEvents(formattedData); if ( !responsePayload || @@ -184,11 +188,11 @@ export default function Events({ ); const handlePrev = (): void => { - if (!formattedNodeEvents.length) return; + if (!formattedEntityEvents.length) return; setPage(page - 1); - const firstEvent = formattedNodeEvents[0]; + const firstEvent = formattedEntityEvents[0]; const newItems = [ ...filters.items.filter((item) => item.key?.key !== 'id'), @@ -214,10 +218,10 @@ export default function Events({ }; const handleNext = (): void => { - if (!formattedNodeEvents.length) return; + if (!formattedEntityEvents.length) return; setPage(page + 1); - const lastEvent = formattedNodeEvents[formattedNodeEvents.length - 1]; + const lastEvent = formattedEntityEvents[formattedEntityEvents.length - 1]; const newItems = [ ...filters.items.filter((item) => item.key?.key !== 'id'), @@ -306,13 +310,13 @@ export default function Events({ {isLoading && <LoadingContainer />} - {!isLoading && !isError && formattedNodeEvents.length === 0 && ( - <EntityDetailsEmptyContainer category={K8sCategory.NODES} view="events" /> + {!isLoading && !isError && formattedEntityEvents.length === 0 && ( + <EntityDetailsEmptyContainer category={category} view="events" /> )} {isError && !isLoading && <LogsError />} - {!isLoading && !isError && formattedNodeEvents.length > 0 && ( + {!isLoading && !isError && formattedEntityEvents.length > 0 && ( <div className="entity-events-list-container"> <div className="entity-events-list-card"> <Table<EventDataType> @@ -323,7 +327,7 @@ export default function Events({ rowExpandable: (record): boolean => record.body !== 'Not Expandable', expandIcon: handleExpandRowIcon, }} - dataSource={formattedNodeEvents} + dataSource={formattedEntityEvents} pagination={false} rowKey={(record): string => record.id} /> @@ -331,7 +335,7 @@ export default function Events({ </div> )} - {!isError && formattedNodeEvents.length > 0 && ( + {!isError && formattedEntityEvents.length > 0 && ( <div className="entity-events-footer"> <Button className="entity-events-footer-button periscope-btn ghost" diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx deleted file mode 100644 index 51c9c84f96..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/NamespaceEvents.tsx +++ /dev/null @@ -1,371 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityEvents.styles.scss'; - -import { Color } from '@signozhq/design-tokens'; -import { Button, Table, TableColumnsType } from 'antd'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, -} from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; -import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; -import LogsError from 'container/LogsError/LogsError'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isArray } from 'lodash-es'; -import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 } from 'uuid'; - -interface EventDataType { - key: string; - timestamp: string; - body: string; - id: string; - attributes_bool?: Record<string, boolean>; - attributes_number?: Record<string, number>; - attributes_string?: Record<string, string>; - resources_string?: Record<string, string>; - scope_name?: string; - scope_string?: Record<string, string>; - scope_version?: string; - severity_number?: number; - severity_text?: string; - span_id?: string; - trace_flags?: number; - trace_id?: string; - severity?: string; -} - -interface INamespaceEventsProps { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; -} - -const EventsPageSize = 10; - -export default function Events({ - timeRange, - handleChangeEventFilters, - filters, - isModalTimeSelection, - handleTimeChange, - selectedInterval, -}: INamespaceEventsProps): JSX.Element { - const { currentQuery } = useQueryBuilder(); - - const [formattedNamespaceEvents, setFormattedNamespaceEvents] = useState< - EventDataType[] - >([]); - - const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); - - const [page, setPage] = useState(1); - - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 10; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const { data: eventsData, isLoading, isFetching, isError } = useQuery({ - queryKey: [ - 'namespaceEvents', - timeRange.startTime, - timeRange.endTime, - filters, - ], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const columns: TableColumnsType<EventDataType> = [ - { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, - { - title: 'Timestamp', - dataIndex: 'timestamp', - width: 200, - ellipsis: true, - key: 'timestamp', - }, - { title: 'Body', dataIndex: 'body', key: 'body' }, - ]; - - useEffect(() => { - if (eventsData?.payload?.data?.newResult?.data?.result) { - const responsePayload = - eventsData?.payload.data.newResult.data.result[0].list || []; - - const formattedData = responsePayload?.map( - (event): EventDataType => ({ - timestamp: event.timestamp, - severity: event.data.severity_text, - body: event.data.body, - id: event.data.id, - key: event.data.id, - resources_string: event.data.resources_string, - attributes_string: event.data.attributes_string, - }), - ); - - setFormattedNamespaceEvents(formattedData); - - if ( - !responsePayload || - (responsePayload && - isArray(responsePayload) && - responsePayload.length < EventsPageSize) - ) { - setHasReachedEndOfEvents(true); - } else { - setHasReachedEndOfEvents(false); - } - } - }, [eventsData]); - - const handleExpandRow = (record: EventDataType): JSX.Element => ( - <EventContents - data={{ ...record.attributes_string, ...record.resources_string }} - /> - ); - - const handlePrev = (): void => { - if (!formattedNamespaceEvents.length) return; - - setPage(page - 1); - - const firstEvent = formattedNamespaceEvents[0]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '>', - value: firstEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeEventFilters(newFilters); - }; - - const handleNext = (): void => { - if (!formattedNamespaceEvents.length) return; - - setPage(page + 1); - const lastEvent = - formattedNamespaceEvents[formattedNamespaceEvents.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeEventFilters(newFilters); - }; - - const handleExpandRowIcon = ({ - expanded, - onExpand, - record, - }: { - expanded: boolean; - onExpand: ( - record: EventDataType, - e: React.MouseEvent<HTMLElement, MouseEvent>, - ) => void; - record: EventDataType; - }): JSX.Element => - expanded ? ( - <ChevronDown - className="periscope-btn-icon" - size={14} - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ) : ( - <ChevronRight - className="periscope-btn-icon" - size={14} - // eslint-disable-next-line sonarjs/no-identical-functions - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ); - - return ( - <div className="entity-events-container"> - <div className="entity-events-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeEventFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isLoading && <LoadingContainer />} - - {!isLoading && !isError && formattedNamespaceEvents.length === 0 && ( - <EntityDetailsEmptyContainer - category={K8sCategory.NAMESPACES} - view="events" - /> - )} - - {isError && !isLoading && <LogsError />} - - {!isLoading && !isError && formattedNamespaceEvents.length > 0 && ( - <div className="entity-events-list-container"> - <div className="entity-events-list-card"> - <Table<EventDataType> - loading={isLoading && page > 1} - columns={columns} - expandable={{ - expandedRowRender: handleExpandRow, - rowExpandable: (record): boolean => record.body !== 'Not Expandable', - expandIcon: handleExpandRowIcon, - }} - dataSource={formattedNamespaceEvents} - pagination={false} - rowKey={(record): string => record.id} - /> - </div> - </div> - )} - - {!isError && formattedNamespaceEvents.length > 0 && ( - <div className="entity-events-footer"> - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handlePrev} - disabled={page === 1 || isFetching || isLoading} - > - {!isFetching && <ChevronLeft size={14} />} - Prev - </Button> - - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handleNext} - disabled={hasReachedEndOfEvents || isFetching || isLoading} - > - Next - {!isFetching && <ChevronRight size={14} />} - </Button> - - {(isFetching || isLoading) && ( - <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> - )} - </div> - )} - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts deleted file mode 100644 index 2ce2229591..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Events/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NamespaceEvents from './NamespaceEvents'; - -export default NamespaceEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx index acf71edd3b..6789bb4cd6 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -12,6 +12,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { CustomTimeType, @@ -43,7 +44,7 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import NamespaceEvents from './Events'; +import NamespaceEvents from '../../EntityDetailsUtils/EntityEvents'; import NamespaceLogs from './Logs'; import NamespaceMetrics from './Metrics'; import { NamespaceDetailsProps } from './NamespaceDetails.interfaces'; @@ -539,6 +540,8 @@ function NamespaceDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} + category={K8sCategory.NAMESPACES} + queryKey="namespaceEvents" /> )} </> diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/index.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/index.ts deleted file mode 100644 index ce518fd1b7..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Events/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NodeEvents from './NodeEvents'; - -export default NodeEvents; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 1ebf9d2da6..1c39ede479 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -12,6 +12,8 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; +import Events from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents'; import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils'; import { CustomTimeType, @@ -44,7 +46,6 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import { QUERY_KEYS } from '../../EntityDetailsUtils/utils'; -import NodeEvents from './Events'; import NodeLogs from './Logs'; import NodeMetrics from './Metrics'; import { NodeDetailsProps } from './NodeDetails.interfaces'; @@ -529,13 +530,15 @@ function NodeDetails({ /> )} {selectedView === VIEW_TYPES.EVENTS && ( - <NodeEvents + <Events timeRange={modalTimeRange} handleChangeEventFilters={handleChangeEventsFilters} filters={eventsFilters} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} + category={K8sCategory.NODES} + queryKey="nodeEvents" /> )} </> diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx deleted file mode 100644 index 6f138122f7..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Events/Events.tsx +++ /dev/null @@ -1,363 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityEvents.styles.scss'; - -import { Color } from '@signozhq/design-tokens'; -import { Button, Table, TableColumnsType } from 'antd'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { EventContents } from 'container/InfraMonitoringK8s/commonUtils'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer'; -import LogsError from 'container/LogsError/LogsError'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isArray } from 'lodash-es'; -import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, -} from '../../../EntityDetailsUtils/utils'; - -interface EventDataType { - key: string; - timestamp: string; - body: string; - id: string; - attributes_bool?: Record<string, boolean>; - attributes_number?: Record<string, number>; - attributes_string?: Record<string, string>; - resources_string?: Record<string, string>; - scope_name?: string; - scope_string?: Record<string, string>; - scope_version?: string; - severity_number?: number; - severity_text?: string; - span_id?: string; - trace_flags?: number; - trace_id?: string; - severity?: string; -} - -interface IPodEventsProps { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; -} - -const EventsPageSize = 10; - -export default function Events({ - timeRange, - handleChangeLogFilters, - filters, - isModalTimeSelection, - handleTimeChange, - selectedInterval, -}: IPodEventsProps): JSX.Element { - const { currentQuery } = useQueryBuilder(); - - const [formattedPodEvents, setFormattedPodEvents] = useState<EventDataType[]>( - [], - ); - - const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false); - - const [page, setPage] = useState(1); - - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 10; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const { data: eventsData, isLoading, isFetching, isError } = useQuery({ - queryKey: ['podEvents', timeRange.startTime, timeRange.endTime, filters], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const columns: TableColumnsType<EventDataType> = [ - { title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 }, - { - title: 'Timestamp', - dataIndex: 'timestamp', - width: 200, - ellipsis: true, - key: 'timestamp', - }, - { title: 'Body', dataIndex: 'body', key: 'body' }, - ]; - - useEffect(() => { - if (eventsData?.payload?.data?.newResult?.data?.result) { - const responsePayload = - eventsData?.payload.data.newResult.data.result[0].list || []; - - const formattedData = responsePayload?.map( - (event): EventDataType => ({ - timestamp: event.timestamp, - severity: event.data.severity_text, - body: event.data.body, - id: event.data.id, - key: event.data.id, - resources_string: event.data.resources_string, - attributes_string: event.data.attributes_string, - }), - ); - - setFormattedPodEvents(formattedData); - - if ( - !responsePayload || - (responsePayload && - isArray(responsePayload) && - responsePayload.length < EventsPageSize) - ) { - setHasReachedEndOfEvents(true); - } else { - setHasReachedEndOfEvents(false); - } - } - }, [eventsData]); - - const handleExpandRow = (record: EventDataType): JSX.Element => ( - <EventContents - data={{ ...record.attributes_string, ...record.resources_string }} - /> - ); - - const handlePrev = (): void => { - if (!formattedPodEvents.length) return; - - setPage(page - 1); - - const firstEvent = formattedPodEvents[0]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '>', - value: firstEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeLogFilters(newFilters); - }; - - const handleNext = (): void => { - if (!formattedPodEvents.length) return; - - setPage(page + 1); - const lastEvent = formattedPodEvents[formattedPodEvents.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastEvent.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeLogFilters(newFilters); - }; - - const handleExpandRowIcon = ({ - expanded, - onExpand, - record, - }: { - expanded: boolean; - onExpand: ( - record: EventDataType, - e: React.MouseEvent<HTMLElement, MouseEvent>, - ) => void; - record: EventDataType; - }): JSX.Element => - expanded ? ( - <ChevronDown - className="periscope-btn-icon" - size={14} - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ) : ( - <ChevronRight - className="periscope-btn-icon" - size={14} - // eslint-disable-next-line sonarjs/no-identical-functions - onClick={(e): void => - onExpand( - record, - (e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>, - ) - } - /> - ); - - return ( - <div className="entity-events-container"> - <div className="entity-events-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeLogFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isLoading && <LoadingContainer />} - - {!isLoading && !isError && formattedPodEvents.length === 0 && ( - <EntityDetailsEmptyContainer category={K8sCategory.PODS} view="events" /> - )} - - {isError && !isLoading && <LogsError />} - - {!isLoading && !isError && formattedPodEvents.length > 0 && ( - <div className="entity-events-list-container"> - <div className="entity-events-list-card"> - <Table<EventDataType> - loading={isLoading && page > 1} - columns={columns} - expandable={{ - expandedRowRender: handleExpandRow, - rowExpandable: (record): boolean => record.body !== 'Not Expandable', - expandIcon: handleExpandRowIcon, - }} - dataSource={formattedPodEvents} - pagination={false} - rowKey={(record): string => record.id} - /> - </div> - </div> - )} - - {!isError && formattedPodEvents.length > 0 && ( - <div className="entity-events-footer"> - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handlePrev} - disabled={page === 1 || isFetching || isLoading} - > - {!isFetching && <ChevronLeft size={14} />} - Prev - </Button> - - <Button - className="entity-events-footer-button periscope-btn ghost" - type="link" - onClick={handleNext} - disabled={hasReachedEndOfEvents || isFetching || isLoading} - > - Next - {!isFetching && <ChevronRight size={14} />} - </Button> - - {(isFetching || isLoading) && ( - <Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} /> - )} - </div> - )} - </div> - ); -} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index 3039a1739f..d3e85b6f06 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -13,6 +13,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils'; import { @@ -45,7 +46,7 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import Events from './Events/Events'; +import Events from '../../EntityDetailsUtils/EntityEvents'; import Metrics from './Metrics/Metrics'; import { PodDetailProps } from './PodDetail.interfaces'; import PodLogsDetailedView from './PodLogs/PodLogsDetailedView'; @@ -572,9 +573,11 @@ function PodDetails({ timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} - handleChangeLogFilters={handleChangeEventsFilters} + handleChangeEventFilters={handleChangeEventsFilters} filters={eventsFilters} selectedInterval={selectedInterval} + category={K8sCategory.PODS} + queryKey="podEvents" /> )} </> From 03786504f77db61ef3b278d1a947c88cc00001d1 Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 13:47:44 +0530 Subject: [PATCH 13/20] chore: refactor traces --- .../ClusterDetails/ClusterDetails.tsx | 3 +- .../ClusterDetails/Traces/ClusterTraces.tsx | 202 ------------------ .../Clusters/ClusterDetails/Traces/index.ts | 3 - .../DeploymentDetails/DeploymentDetails.tsx | 3 +- .../Traces/DeploymentTraces.tsx | 202 ------------------ .../DeploymentDetails/Traces/index.ts | 3 - .../EntityTraces.tsx} | 12 +- .../NamespaceDetails/NamespaceDetails.tsx | 3 +- .../Traces/NamespaceTraces.tsx | 202 ------------------ .../NamespaceDetails/Traces/index.ts | 3 - .../Nodes/NodeDetails/NodeDetails.tsx | 3 +- .../Nodes/NodeDetails/Traces/index.ts | 3 - .../Pods/PodDetails/PodDetails.tsx | 3 +- .../Pods/PodDetails/PodTraces/PodTraces.tsx | 202 ------------------ 14 files changed, 17 insertions(+), 830 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts rename frontend/src/container/InfraMonitoringK8s/{Nodes/NodeDetails/Traces/NodeTraces.tsx => EntityDetailsUtils/EntityTraces.tsx} (96%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index b5501720ef..65fe990df2 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -45,10 +45,10 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import ClusterEvents from '../../EntityDetailsUtils/EntityEvents'; +import ClusterTraces from '../../EntityDetailsUtils/EntityTraces'; import { ClusterDetailsProps } from './ClusterDetails.interfaces'; import ClusterLogs from './Logs'; import ClusterMetrics from './Metrics'; -import ClusterTraces from './Traces'; function ClusterDetails({ cluster, @@ -518,6 +518,7 @@ function ClusterDetails({ handleChangeTracesFilters={handleChangeTracesFilters} tracesFilters={tracesFilters} selectedInterval={selectedInterval} + queryKey="clusterTraces" /> )} {selectedView === VIEW_TYPES.EVENTS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx deleted file mode 100644 index b0dff95d49..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/ClusterTraces.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import '../../../EntityDetailsUtils/entityTraces.styles.scss'; - -import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; -import { ResizeTable } from 'components/ResizeTable'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { QueryParams } from 'constants/query'; -import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; -import NoLogs from 'container/NoLogs/NoLogs'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import { ErrorText } from 'container/TimeSeriesView/styles'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import TraceExplorerControls from 'container/TracesExplorer/Controls'; -import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; -import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { Pagination } from 'hooks/queryPagination'; -import useUrlQueryData from 'hooks/useUrlQueryData'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import { - getEntityTracesQueryPayload, - selectedEntityTracesColumns, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; - tracesFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function ClusterTraces({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeTracesFilters, - tracesFilters, - selectedInterval, -}: Props): JSX.Element { - const [traces, setTraces] = useState<any[]>([]); - const [offset] = useState<number>(0); - - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.TRACES, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( - QueryParams.pagination, - ); - - const queryPayload = useMemo( - () => - getEntityTracesQueryPayload( - timeRange.startTime, - timeRange.endTime, - paginationQueryData?.offset || offset, - tracesFilters, - ), - [ - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - paginationQueryData, - ], - ); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: [ - 'hostMetricTraces', - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - DEFAULT_ENTITY_VERSION, - paginationQueryData, - ], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const traceListColumns = getListColumns(selectedEntityTracesColumns); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - if (currentData.length > 0 && currentData[0].list) { - if (offset === 0) { - setTraces(currentData[0].list ?? []); - } else { - setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); - } - } - } - }, [data, offset]); - - const isDataEmpty = - !isLoading && !isFetching && !isError && traces.length === 0; - const hasAdditionalFilters = tracesFilters.items.length > 1; - - const totalCount = - data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; - - return ( - <div className="entity-metric-traces"> - <div className="entity-metric-traces-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeTracesFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} - - {isLoading && traces.length === 0 && <TracesLoading />} - - {isDataEmpty && !hasAdditionalFilters && ( - <NoLogs dataSource={DataSource.TRACES} /> - )} - - {isDataEmpty && hasAdditionalFilters && ( - <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> - )} - - {!isError && traces.length > 0 && ( - <div className="entity-traces-table"> - <TraceExplorerControls - isLoading={isFetching} - totalCount={totalCount} - perPageOptions={PER_PAGE_OPTIONS} - showSizeChanger={false} - /> - <ResizeTable - tableLayout="fixed" - pagination={false} - scroll={{ x: true }} - loading={isFetching} - dataSource={traces} - columns={traceListColumns} - /> - </div> - )} - </div> - ); -} - -export default ClusterTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts deleted file mode 100644 index bc16a3b372..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Traces/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import ClusterTraces from './ClusterTraces'; - -export default ClusterTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index 7a155b9649..eab10cea92 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -45,10 +45,10 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import DeploymentEvents from '../../EntityDetailsUtils/EntityEvents'; +import DeploymentTraces from '../../EntityDetailsUtils/EntityTraces'; import { DeploymentDetailsProps } from './DeploymentDetails.interfaces'; import DeploymentLogs from './Logs'; import DeploymentMetrics from './Metrics'; -import DeploymentTraces from './Traces'; function DeploymentDetails({ deployment, @@ -558,6 +558,7 @@ function DeploymentDetails({ handleChangeTracesFilters={handleChangeTracesFilters} tracesFilters={tracesFilters} selectedInterval={selectedInterval} + queryKey="deploymentTraces" /> )} {selectedView === VIEW_TYPES.EVENTS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx deleted file mode 100644 index b023fbeae1..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/DeploymentTraces.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import '../../../EntityDetailsUtils/entityTraces.styles.scss'; - -import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; -import { ResizeTable } from 'components/ResizeTable'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { QueryParams } from 'constants/query'; -import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; -import NoLogs from 'container/NoLogs/NoLogs'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import { ErrorText } from 'container/TimeSeriesView/styles'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import TraceExplorerControls from 'container/TracesExplorer/Controls'; -import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; -import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { Pagination } from 'hooks/queryPagination'; -import useUrlQueryData from 'hooks/useUrlQueryData'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import { - getEntityTracesQueryPayload, - selectedEntityTracesColumns, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; - tracesFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function DeploymentTraces({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeTracesFilters, - tracesFilters, - selectedInterval, -}: Props): JSX.Element { - const [traces, setTraces] = useState<any[]>([]); - const [offset] = useState<number>(0); - - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.TRACES, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( - QueryParams.pagination, - ); - - const queryPayload = useMemo( - () => - getEntityTracesQueryPayload( - timeRange.startTime, - timeRange.endTime, - paginationQueryData?.offset || offset, - tracesFilters, - ), - [ - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - paginationQueryData, - ], - ); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: [ - 'deploymentMetricTraces', - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - DEFAULT_ENTITY_VERSION, - paginationQueryData, - ], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const traceListColumns = getListColumns(selectedEntityTracesColumns); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - if (currentData.length > 0 && currentData[0].list) { - if (offset === 0) { - setTraces(currentData[0].list ?? []); - } else { - setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); - } - } - } - }, [data, offset]); - - const isDataEmpty = - !isLoading && !isFetching && !isError && traces.length === 0; - const hasAdditionalFilters = tracesFilters.items.length > 1; - - const totalCount = - data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; - - return ( - <div className="entity-metric-traces"> - <div className="entity-metric-traces-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeTracesFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} - - {isLoading && traces.length === 0 && <TracesLoading />} - - {isDataEmpty && !hasAdditionalFilters && ( - <NoLogs dataSource={DataSource.TRACES} /> - )} - - {isDataEmpty && hasAdditionalFilters && ( - <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> - )} - - {!isError && traces.length > 0 && ( - <div className="entity-traces-table"> - <TraceExplorerControls - isLoading={isFetching} - totalCount={totalCount} - perPageOptions={PER_PAGE_OPTIONS} - showSizeChanger={false} - /> - <ResizeTable - tableLayout="fixed" - pagination={false} - scroll={{ x: true }} - loading={isFetching} - dataSource={traces} - columns={traceListColumns} - /> - </div> - )} - </div> - ); -} - -export default DeploymentTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts deleted file mode 100644 index 2f1f125a99..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Traces/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DeploymentTraces from './DeploymentTraces'; - -export default DeploymentTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces.tsx similarity index 96% rename from frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces.tsx index ccd80b87c2..a7187eadd5 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/NodeTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces.tsx @@ -1,4 +1,4 @@ -import '../../../EntityDetailsUtils/entityTraces.styles.scss'; +import './entityTraces.styles.scss'; import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; import { ResizeTable } from 'components/ResizeTable'; @@ -28,7 +28,7 @@ import { DataSource } from 'types/common/queryBuilder'; import { getEntityTracesQueryPayload, selectedEntityTracesColumns, -} from '../../../EntityDetailsUtils/utils'; +} from './utils'; interface Props { timeRange: { @@ -43,15 +43,17 @@ interface Props { handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; tracesFilters: IBuilderQuery['filters']; selectedInterval: Time; + queryKey: string; } -function NodeTraces({ +function EntityTraces({ timeRange, isModalTimeSelection, handleTimeChange, handleChangeTracesFilters, tracesFilters, selectedInterval, + queryKey, }: Props): JSX.Element { const [traces, setTraces] = useState<any[]>([]); const [offset] = useState<number>(0); @@ -106,7 +108,7 @@ function NodeTraces({ const { data, isLoading, isFetching, isError } = useQuery({ queryKey: [ - 'nodeMetricTraces', + queryKey, timeRange.startTime, timeRange.endTime, offset, @@ -199,4 +201,4 @@ function NodeTraces({ ); } -export default NodeTraces; +export default EntityTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx index 6789bb4cd6..ef88d1056a 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -45,10 +45,10 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import NamespaceEvents from '../../EntityDetailsUtils/EntityEvents'; +import NamespaceTraces from '../../EntityDetailsUtils/EntityTraces'; import NamespaceLogs from './Logs'; import NamespaceMetrics from './Metrics'; import { NamespaceDetailsProps } from './NamespaceDetails.interfaces'; -import NamespaceTraces from './Traces'; function NamespaceDetails({ namespace, @@ -530,6 +530,7 @@ function NamespaceDetails({ handleChangeTracesFilters={handleChangeTracesFilters} tracesFilters={tracesFilters} selectedInterval={selectedInterval} + queryKey="namespaceTraces" /> )} {selectedView === VIEW_TYPES.EVENTS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx deleted file mode 100644 index be1a48a93a..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/NamespaceTraces.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import '../../../EntityDetailsUtils/entityTraces.styles.scss'; - -import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; -import { ResizeTable } from 'components/ResizeTable'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { QueryParams } from 'constants/query'; -import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; -import NoLogs from 'container/NoLogs/NoLogs'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import { ErrorText } from 'container/TimeSeriesView/styles'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import TraceExplorerControls from 'container/TracesExplorer/Controls'; -import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; -import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { Pagination } from 'hooks/queryPagination'; -import useUrlQueryData from 'hooks/useUrlQueryData'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import { - getEntityTracesQueryPayload, - selectedEntityTracesColumns, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; - tracesFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function NamespaceTraces({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeTracesFilters, - tracesFilters, - selectedInterval, -}: Props): JSX.Element { - const [traces, setTraces] = useState<any[]>([]); - const [offset] = useState<number>(0); - - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.TRACES, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( - QueryParams.pagination, - ); - - const queryPayload = useMemo( - () => - getEntityTracesQueryPayload( - timeRange.startTime, - timeRange.endTime, - paginationQueryData?.offset || offset, - tracesFilters, - ), - [ - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - paginationQueryData, - ], - ); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: [ - 'hostMetricTraces', - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - DEFAULT_ENTITY_VERSION, - paginationQueryData, - ], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const traceListColumns = getListColumns(selectedEntityTracesColumns); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - if (currentData.length > 0 && currentData[0].list) { - if (offset === 0) { - setTraces(currentData[0].list ?? []); - } else { - setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); - } - } - } - }, [data, offset]); - - const isDataEmpty = - !isLoading && !isFetching && !isError && traces.length === 0; - const hasAdditionalFilters = tracesFilters.items.length > 1; - - const totalCount = - data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; - - return ( - <div className="entity-metric-traces"> - <div className="entity-metric-traces-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeTracesFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} - - {isLoading && traces.length === 0 && <TracesLoading />} - - {isDataEmpty && !hasAdditionalFilters && ( - <NoLogs dataSource={DataSource.TRACES} /> - )} - - {isDataEmpty && hasAdditionalFilters && ( - <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> - )} - - {!isError && traces.length > 0 && ( - <div className="entity-traces-table"> - <TraceExplorerControls - isLoading={isFetching} - totalCount={totalCount} - perPageOptions={PER_PAGE_OPTIONS} - showSizeChanger={false} - /> - <ResizeTable - tableLayout="fixed" - pagination={false} - scroll={{ x: true }} - loading={isFetching} - dataSource={traces} - columns={traceListColumns} - /> - </div> - )} - </div> - ); -} - -export default NamespaceTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts deleted file mode 100644 index 6aa2bfd70b..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Traces/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NamespaceTraces from './NamespaceTraces'; - -export default NamespaceTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 1c39ede479..736c97ded6 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -45,11 +45,11 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; +import NodeTraces from '../../EntityDetailsUtils/EntityTraces'; import { QUERY_KEYS } from '../../EntityDetailsUtils/utils'; import NodeLogs from './Logs'; import NodeMetrics from './Metrics'; import { NodeDetailsProps } from './NodeDetails.interfaces'; -import NodeTraces from './Traces'; function NodeDetails({ node, @@ -527,6 +527,7 @@ function NodeDetails({ handleChangeTracesFilters={handleChangeTracesFilters} tracesFilters={tracesFilters} selectedInterval={selectedInterval} + queryKey="nodeTraces" /> )} {selectedView === VIEW_TYPES.EVENTS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/index.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/index.ts deleted file mode 100644 index a5bc66c59b..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Traces/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NodeTraces from './NodeTraces'; - -export default NodeTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index d3e85b6f06..6ae54bcffb 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -47,10 +47,10 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import Events from '../../EntityDetailsUtils/EntityEvents'; +import PodTraces from '../../EntityDetailsUtils/EntityTraces'; import Metrics from './Metrics/Metrics'; import { PodDetailProps } from './PodDetail.interfaces'; import PodLogsDetailedView from './PodLogs/PodLogsDetailedView'; -import PodTraces from './PodTraces/PodTraces'; const TimeRangeOffset = 1000000000; @@ -565,6 +565,7 @@ function PodDetails({ handleChangeTracesFilters={handleChangeTracesFilters} tracesFilters={tracesFilters} selectedInterval={selectedInterval} + queryKey="podTraces" /> )} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx deleted file mode 100644 index 46dd65e368..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodTraces/PodTraces.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import '../../../EntityDetailsUtils/entityTraces.styles.scss'; - -import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils'; -import { ResizeTable } from 'components/ResizeTable'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { QueryParams } from 'constants/query'; -import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch'; -import NoLogs from 'container/NoLogs/NoLogs'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import { ErrorText } from 'container/TimeSeriesView/styles'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import TraceExplorerControls from 'container/TracesExplorer/Controls'; -import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs'; -import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { Pagination } from 'hooks/queryPagination'; -import useUrlQueryData from 'hooks/useUrlQueryData'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import { - getEntityTracesQueryPayload, - selectedEntityTracesColumns, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void; - tracesFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function PodTraces({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeTracesFilters, - tracesFilters, - selectedInterval, -}: Props): JSX.Element { - const [traces, setTraces] = useState<any[]>([]); - const [offset] = useState<number>(0); - - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.TRACES, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( - QueryParams.pagination, - ); - - const queryPayload = useMemo( - () => - getEntityTracesQueryPayload( - timeRange.startTime, - timeRange.endTime, - paginationQueryData?.offset || offset, - tracesFilters, - ), - [ - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - paginationQueryData, - ], - ); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: [ - 'podTraces', - timeRange.startTime, - timeRange.endTime, - offset, - tracesFilters, - DEFAULT_ENTITY_VERSION, - paginationQueryData, - ], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - }); - - const traceListColumns = getListColumns(selectedEntityTracesColumns); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - if (currentData.length > 0 && currentData[0].list) { - if (offset === 0) { - setTraces(currentData[0].list ?? []); - } else { - setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]); - } - } - } - }, [data, offset]); - - const isDataEmpty = - !isLoading && !isFetching && !isError && traces.length === 0; - const hasAdditionalFilters = tracesFilters.items.length > 1; - - const totalCount = - data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0; - - return ( - <div className="entity-metric-traces"> - <div className="entity-metric-traces-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeTracesFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - - {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} - - {isLoading && traces.length === 0 && <TracesLoading />} - - {isDataEmpty && !hasAdditionalFilters && ( - <NoLogs dataSource={DataSource.TRACES} /> - )} - - {isDataEmpty && hasAdditionalFilters && ( - <EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" /> - )} - - {!isError && traces.length > 0 && ( - <div className="pod-traces-table"> - <TraceExplorerControls - isLoading={isFetching} - totalCount={totalCount} - perPageOptions={PER_PAGE_OPTIONS} - showSizeChanger={false} - /> - <ResizeTable - tableLayout="fixed" - pagination={false} - scroll={{ x: true }} - loading={isFetching} - dataSource={traces} - columns={traceListColumns} - /> - </div> - )} - </div> - ); -} - -export default PodTraces; From 3a88a6b56dee8a1eafe03df6285fa16402bb805c Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 14:28:57 +0530 Subject: [PATCH 14/20] chore: refactor metrics --- .../ClusterDetails/ClusterDetails.tsx | 9 +- .../Clusters/ClusterDetails/Metrics/index.ts | 3 - .../ClusterDetails/{Metrics => }/constants.ts | 2 +- .../DeploymentDetails/DeploymentDetails.tsx | 12 +- .../Metrics/DeploymentMetrics.tsx | 145 --------------- .../DeploymentDetails/Metrics/index.ts | 3 - .../{Metrics => }/constants.ts | 2 +- .../EntityMetrics.tsx} | 59 +++++-- .../Metrics/NamespaceMetrics.tsx | 167 ------------------ .../NamespaceDetails/Metrics/index.ts | 3 - .../NamespaceDetails/NamespaceDetails.tsx | 12 +- .../{Metrics => }/constants.ts | 3 +- .../Nodes/NodeDetails/Metrics/NodeMetrics.tsx | 140 --------------- .../Nodes/NodeDetails/Metrics/index.ts | 3 - .../Nodes/NodeDetails/NodeDetails.tsx | 9 +- .../NodeDetails/{Metrics => }/constants.ts | 2 +- .../Pods/PodDetails/Metrics/Metrics.tsx | 140 --------------- .../Pods/PodDetails/PodDetails.tsx | 9 +- .../PodDetails/{Metrics => }/constants.ts | 2 +- 19 files changed, 88 insertions(+), 637 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts rename frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/{Metrics => }/constants.ts (99%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts rename frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/{Metrics => }/constants.ts (99%) rename frontend/src/container/InfraMonitoringK8s/{Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx => EntityDetailsUtils/EntityMetrics.tsx} (77%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts rename frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/{Metrics => }/constants.ts (99%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/index.ts rename frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/{Metrics => }/constants.ts (99%) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx rename frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/{Metrics => }/constants.ts (99%) diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index 65fe990df2..1e259e9e24 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -45,10 +45,11 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import ClusterEvents from '../../EntityDetailsUtils/EntityEvents'; +import ClusterMetrics from '../../EntityDetailsUtils/EntityMetrics'; import ClusterTraces from '../../EntityDetailsUtils/EntityTraces'; import { ClusterDetailsProps } from './ClusterDetails.interfaces'; +import { clusterWidgetInfo, getClusterMetricsQueryPayload } from './constants'; import ClusterLogs from './Logs'; -import ClusterMetrics from './Metrics'; function ClusterDetails({ cluster, @@ -497,7 +498,11 @@ function ClusterDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} - cluster={cluster} + entity={cluster} + entityWidgetInfo={clusterWidgetInfo} + getEntityQueryPayload={getClusterMetricsQueryPayload} + category={K8sCategory.CLUSTERS} + queryKey="clusterMetrics" /> )} {selectedView === VIEW_TYPES.LOGS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts deleted file mode 100644 index cf254e714f..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import ClusterMetrics from './ClusterMetrics'; - -export default ClusterMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts rename to frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts index 595fb4bdd9..9d60aca7a4 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/constants.ts @@ -42,7 +42,7 @@ export const clusterWidgetInfo = [ }, ]; -export const getClusterQueryPayload = ( +export const getClusterMetricsQueryPayload = ( cluster: K8sClustersData, start: number, end: number, diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index eab10cea92..d11520fb3f 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -45,10 +45,14 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import DeploymentEvents from '../../EntityDetailsUtils/EntityEvents'; +import DeploymentMetrics from '../../EntityDetailsUtils/EntityMetrics'; import DeploymentTraces from '../../EntityDetailsUtils/EntityTraces'; +import { + deploymentWidgetInfo, + getDeploymentMetricsQueryPayload, +} from './constants'; import { DeploymentDetailsProps } from './DeploymentDetails.interfaces'; import DeploymentLogs from './Logs'; -import DeploymentMetrics from './Metrics'; function DeploymentDetails({ deployment, @@ -537,7 +541,11 @@ function DeploymentDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} - deployment={deployment} + entity={deployment} + entityWidgetInfo={deploymentWidgetInfo} + getEntityQueryPayload={getDeploymentMetricsQueryPayload} + category={K8sCategory.DEPLOYMENTS} + queryKey="deploymentMetrics" /> )} {selectedView === VIEW_TYPES.LOGS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx deleted file mode 100644 index cdc8978f8c..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/DeploymentMetrics.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; - -import { Card, Col, Row, Skeleton, Typography } from 'antd'; -import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList'; -import cx from 'classnames'; -import Uplot from 'components/Uplot'; -import { ENTITY_VERSION_V4 } from 'constants/app'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { useResizeObserver } from 'hooks/useDimensions'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; -import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; -import { useMemo, useRef } from 'react'; -import { useQueries, UseQueryResult } from 'react-query'; -import { SuccessResponse } from 'types/api'; -import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; - -import { deploymentWidgetInfo, getDeploymentQueryPayload } from './constants'; - -interface DeploymentMetricsProps { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; - deployment: K8sDeploymentsData; -} - -function DeploymentMetrics({ - selectedInterval, - deployment, - timeRange, - handleTimeChange, - isModalTimeSelection, -}: DeploymentMetricsProps): JSX.Element { - const queryPayloads = useMemo( - () => - getDeploymentQueryPayload( - deployment, - timeRange.startTime, - timeRange.endTime, - ), - [deployment, timeRange.startTime, timeRange.endTime], - ); - - const queries = useQueries( - queryPayloads.map((payload) => ({ - queryKey: ['deploymentMetrics', payload, ENTITY_VERSION_V4, 'NODE'], - queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => - GetMetricQueryRange(payload, ENTITY_VERSION_V4), - enabled: !!payload, - })), - ); - - const isDarkMode = useIsDarkMode(); - const graphRef = useRef<HTMLDivElement>(null); - const dimensions = useResizeObserver(graphRef); - - const chartData = useMemo( - () => queries.map(({ data }) => getUPlotChartData(data?.payload)), - [queries], - ); - - const options = useMemo( - () => - queries.map(({ data }, idx) => - getUPlotChartOptions({ - apiResponse: data?.payload, - isDarkMode, - dimensions, - yAxisUnit: deploymentWidgetInfo[idx].yAxisUnit, - softMax: null, - softMin: null, - minTimeScale: timeRange.startTime, - maxTimeScale: timeRange.endTime, - }), - ), - [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], - ); - - const renderCardContent = ( - query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, - idx: number, - ): JSX.Element => { - if (query.isLoading) { - return <Skeleton />; - } - - if (query.error) { - const errorMessage = - (query.error as Error)?.message || 'Something went wrong'; - return <div>{errorMessage}</div>; - } - return ( - <div - className={cx('chart-container', { - 'no-data-container': - !query.isLoading && !query?.data?.payload?.data?.result?.length, - })} - > - <Uplot options={options[idx]} data={chartData[idx]} /> - </div> - ); - }; - - return ( - <> - <div className="metrics-header"> - <div className="metrics-datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - isModalTimeSelection={isModalTimeSelection} - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <Row gutter={24} className="entity-metrics-container"> - {queries.map((query, idx) => ( - <Col span={12} key={deploymentWidgetInfo[idx].title}> - <Typography.Text>{deploymentWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="entity-metrics-card" ref={graphRef}> - {renderCardContent(query, idx)} - </Card> - </Col> - ))} - </Row> - </> - ); -} - -export default DeploymentMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts deleted file mode 100644 index 729438baaa..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DeploymentMetrics from './DeploymentMetrics'; - -export default DeploymentMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts rename to frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts index 75f1934240..c6481481b1 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Metrics/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/constants.ts @@ -26,7 +26,7 @@ export const deploymentWidgetInfo = [ }, ]; -export const getDeploymentQueryPayload = ( +export const getDeploymentMetricsQueryPayload = ( deployment: K8sDeploymentsData, start: number, end: number, diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics.tsx similarity index 77% rename from frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics.tsx index 0e4f84a373..b8a8bbe172 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Metrics/ClusterMetrics.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics.tsx @@ -1,7 +1,6 @@ -import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; +import './entityMetrics.styles.scss'; import { Card, Col, Row, Skeleton, Typography } from 'antd'; -import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList'; import cx from 'classnames'; import Uplot from 'components/Uplot'; import { ENTITY_VERSION_V4 } from 'constants/app'; @@ -10,6 +9,7 @@ import { getMetricsTableData, MetricsTable, } from 'container/InfraMonitoringK8s/commonUtils'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import { CustomTimeType, @@ -17,7 +17,10 @@ import { } from 'container/TopNav/DateTimeSelectionV2/config'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { + GetMetricQueryRange, + GetQueryResultsProps, +} from 'lib/dashboard/getQueryResults'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { useMemo, useRef } from 'react'; @@ -26,9 +29,7 @@ import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { Options } from 'uplot'; -import { clusterWidgetInfo, getClusterQueryPayload } from './constants'; - -interface ClusterMetricsProps { +interface EntityMetricsProps<T> { timeRange: { startTime: number; endTime: number; @@ -39,24 +40,39 @@ interface ClusterMetricsProps { dateTimeRange?: [number, number], ) => void; selectedInterval: Time; - cluster: K8sClustersData; + entity: T; + entityWidgetInfo: { + title: string; + yAxisUnit: string; + }[]; + getEntityQueryPayload: ( + node: T, + start: number, + end: number, + ) => GetQueryResultsProps[]; + queryKey: string; + category: K8sCategory; } -function ClusterMetrics({ +function EntityMetrics<T>({ selectedInterval, - cluster, + entity, timeRange, handleTimeChange, isModalTimeSelection, -}: ClusterMetricsProps): JSX.Element { + entityWidgetInfo, + getEntityQueryPayload, + queryKey, + category, +}: EntityMetricsProps<T>): JSX.Element { const queryPayloads = useMemo( - () => getClusterQueryPayload(cluster, timeRange.startTime, timeRange.endTime), - [cluster, timeRange.startTime, timeRange.endTime], + () => getEntityQueryPayload(entity, timeRange.startTime, timeRange.endTime), + [getEntityQueryPayload, entity, timeRange.startTime, timeRange.endTime], ); const queries = useQueries( queryPayloads.map((payload) => ({ - queryKey: ['entity-metrics', payload, ENTITY_VERSION_V4, 'NODE'], + queryKey: [queryKey, payload, ENTITY_VERSION_V4, category], queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => GetMetricQueryRange(payload, ENTITY_VERSION_V4), enabled: !!payload, @@ -89,14 +105,21 @@ function ClusterMetrics({ apiResponse: data?.payload, isDarkMode, dimensions, - yAxisUnit: clusterWidgetInfo[idx].yAxisUnit, + yAxisUnit: entityWidgetInfo[idx].yAxisUnit, softMax: null, softMin: null, minTimeScale: timeRange.startTime, maxTimeScale: timeRange.endTime, }); }), - [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], + [ + queries, + isDarkMode, + dimensions, + entityWidgetInfo, + timeRange.startTime, + timeRange.endTime, + ], ); const renderCardContent = ( @@ -151,8 +174,8 @@ function ClusterMetrics({ </div> <Row gutter={24} className="entity-metrics-container"> {queries.map((query, idx) => ( - <Col span={12} key={clusterWidgetInfo[idx].title}> - <Typography.Text>{clusterWidgetInfo[idx].title}</Typography.Text> + <Col span={12} key={entityWidgetInfo[idx].title}> + <Typography.Text>{entityWidgetInfo[idx].title}</Typography.Text> <Card bordered className="entity-metrics-card" ref={graphRef}> {renderCardContent(query, idx)} </Card> @@ -163,4 +186,4 @@ function ClusterMetrics({ ); } -export default ClusterMetrics; +export default EntityMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx deleted file mode 100644 index 0a3d70a9d7..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/NamespaceMetrics.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; - -import { Card, Col, Row, Skeleton, Typography } from 'antd'; -import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList'; -import cx from 'classnames'; -import Uplot from 'components/Uplot'; -import { ENTITY_VERSION_V4 } from 'constants/app'; -import { PANEL_TYPES } from 'constants/queryBuilder'; -import { - getMetricsTableData, - MetricsTable, -} from 'container/InfraMonitoringK8s/commonUtils'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { useResizeObserver } from 'hooks/useDimensions'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; -import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; -import { useMemo, useRef } from 'react'; -import { useQueries, UseQueryResult } from 'react-query'; -import { SuccessResponse } from 'types/api'; -import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; -import { Options } from 'uplot'; - -import { getNamespaceQueryPayload, namespaceWidgetInfo } from './constants'; - -interface NamespaceMetricsProps { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; - namespace: K8sNamespacesData; -} - -function NamespaceMetrics({ - selectedInterval, - namespace, - timeRange, - handleTimeChange, - isModalTimeSelection, -}: NamespaceMetricsProps): JSX.Element { - const queryPayloads = useMemo( - () => - getNamespaceQueryPayload(namespace, timeRange.startTime, timeRange.endTime), - [namespace, timeRange.startTime, timeRange.endTime], - ); - - const queries = useQueries( - queryPayloads.map((payload) => ({ - queryKey: ['namespace-metrics', payload, ENTITY_VERSION_V4, 'NAMESPACE'], - queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => - GetMetricQueryRange(payload, ENTITY_VERSION_V4), - enabled: !!payload, - })), - ); - - const isDarkMode = useIsDarkMode(); - const graphRef = useRef<HTMLDivElement>(null); - const dimensions = useResizeObserver(graphRef); - - const chartData = useMemo( - () => - queries.map(({ data }) => { - const panelType = (data?.params as any)?.compositeQuery?.panelType; - return panelType === PANEL_TYPES.TABLE - ? getMetricsTableData(data) - : getUPlotChartData(data?.payload); - }), - [queries], - ); - - const options = useMemo( - () => - queries.map(({ data }, idx) => { - const panelType = (data?.params as any)?.compositeQuery?.panelType; - if (panelType === PANEL_TYPES.TABLE) { - return null; - } - return getUPlotChartOptions({ - apiResponse: data?.payload, - isDarkMode, - dimensions, - yAxisUnit: namespaceWidgetInfo[idx].yAxisUnit, - softMax: null, - softMin: null, - minTimeScale: timeRange.startTime, - maxTimeScale: timeRange.endTime, - }); - }), - [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], - ); - - const renderCardContent = ( - query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, - idx: number, - ): JSX.Element => { - if (query.isLoading) { - return <Skeleton />; - } - - if (query.error) { - const errorMessage = - (query.error as Error)?.message || 'Something went wrong'; - return <div>{errorMessage}</div>; - } - - const { panelType } = (query.data?.params as any).compositeQuery; - - return ( - <div - className={cx('chart-container', { - 'no-data-container': - !query.isLoading && !query?.data?.payload?.data?.result?.length, - })} - > - {panelType === PANEL_TYPES.TABLE ? ( - <MetricsTable - rows={chartData[idx][0].rows} - columns={chartData[idx][0].columns} - /> - ) : ( - <Uplot options={options[idx] as Options} data={chartData[idx]} /> - )} - </div> - ); - }; - - return ( - <> - <div className="metrics-header"> - <div className="metrics-datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - isModalTimeSelection={isModalTimeSelection} - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <Row gutter={24} className="entity-metrics-container"> - {queries.map((query, idx) => ( - <Col span={12} key={namespaceWidgetInfo[idx].title}> - <Typography.Text>{namespaceWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="entity-metrics-card" ref={graphRef}> - {renderCardContent(query, idx)} - </Card> - </Col> - ))} - </Row> - </> - ); -} - -export default NamespaceMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts deleted file mode 100644 index 7dcd578b80..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NamespaceMetrics from './NamespaceMetrics'; - -export default NamespaceMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx index ef88d1056a..6a73f67650 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -45,9 +45,13 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import NamespaceEvents from '../../EntityDetailsUtils/EntityEvents'; +import NamespaceMetrics from '../../EntityDetailsUtils/EntityMetrics'; import NamespaceTraces from '../../EntityDetailsUtils/EntityTraces'; +import { + getNamespaceMetricsQueryPayload, + namespaceWidgetInfo, +} from './constants'; import NamespaceLogs from './Logs'; -import NamespaceMetrics from './Metrics'; import { NamespaceDetailsProps } from './NamespaceDetails.interfaces'; function NamespaceDetails({ @@ -509,7 +513,11 @@ function NamespaceDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} - namespace={namespace} + entity={namespace} + entityWidgetInfo={namespaceWidgetInfo} + getEntityQueryPayload={getNamespaceMetricsQueryPayload} + category={K8sCategory.NAMESPACES} + queryKey="namespaceMetrics" /> )} {selectedView === VIEW_TYPES.LOGS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts rename to frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts index 23c451c866..f7b32c5e65 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Metrics/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/constants.ts @@ -34,6 +34,7 @@ export const namespaceWidgetInfo = [ }, { title: 'StatefulSets', + yAxisUnit: '', }, { title: 'ReplicaSets', @@ -49,7 +50,7 @@ export const namespaceWidgetInfo = [ }, ]; -export const getNamespaceQueryPayload = ( +export const getNamespaceMetricsQueryPayload = ( namespace: K8sNamespacesData, start: number, end: number, diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx deleted file mode 100644 index 7fb026f4c5..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/NodeMetrics.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; - -import { Card, Col, Row, Skeleton, Typography } from 'antd'; -import { K8sNodesData } from 'api/infraMonitoring/getK8sNodesList'; -import cx from 'classnames'; -import Uplot from 'components/Uplot'; -import { ENTITY_VERSION_V4 } from 'constants/app'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { useResizeObserver } from 'hooks/useDimensions'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; -import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; -import { useMemo, useRef } from 'react'; -import { useQueries, UseQueryResult } from 'react-query'; -import { SuccessResponse } from 'types/api'; -import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; - -import { getNodeQueryPayload, nodeWidgetInfo } from './constants'; - -interface NodeMetricsProps { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; - node: K8sNodesData; -} - -function NodeMetrics({ - selectedInterval, - node, - timeRange, - handleTimeChange, - isModalTimeSelection, -}: NodeMetricsProps): JSX.Element { - const queryPayloads = useMemo( - () => getNodeQueryPayload(node, timeRange.startTime, timeRange.endTime), - [node, timeRange.startTime, timeRange.endTime], - ); - - const queries = useQueries( - queryPayloads.map((payload) => ({ - queryKey: ['node-metrics', payload, ENTITY_VERSION_V4, 'NODE'], - queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => - GetMetricQueryRange(payload, ENTITY_VERSION_V4), - enabled: !!payload, - })), - ); - - const isDarkMode = useIsDarkMode(); - const graphRef = useRef<HTMLDivElement>(null); - const dimensions = useResizeObserver(graphRef); - - const chartData = useMemo( - () => queries.map(({ data }) => getUPlotChartData(data?.payload)), - [queries], - ); - - const options = useMemo( - () => - queries.map(({ data }, idx) => - getUPlotChartOptions({ - apiResponse: data?.payload, - isDarkMode, - dimensions, - yAxisUnit: nodeWidgetInfo[idx].yAxisUnit, - softMax: null, - softMin: null, - minTimeScale: timeRange.startTime, - maxTimeScale: timeRange.endTime, - }), - ), - [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], - ); - - const renderCardContent = ( - query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, - idx: number, - ): JSX.Element => { - if (query.isLoading) { - return <Skeleton />; - } - - if (query.error) { - const errorMessage = - (query.error as Error)?.message || 'Something went wrong'; - return <div>{errorMessage}</div>; - } - return ( - <div - className={cx('chart-container', { - 'no-data-container': - !query.isLoading && !query?.data?.payload?.data?.result?.length, - })} - > - <Uplot options={options[idx]} data={chartData[idx]} /> - </div> - ); - }; - - return ( - <> - <div className="metrics-header"> - <div className="metrics-datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - isModalTimeSelection={isModalTimeSelection} - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <Row gutter={24} className="entity-metrics-container"> - {queries.map((query, idx) => ( - <Col span={12} key={nodeWidgetInfo[idx].title}> - <Typography.Text>{nodeWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="entity-metrics-card" ref={graphRef}> - {renderCardContent(query, idx)} - </Card> - </Col> - ))} - </Row> - </> - ); -} - -export default NodeMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/index.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/index.ts deleted file mode 100644 index 3c2c065a5f..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NodeMetrics from './NodeMetrics'; - -export default NodeMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 736c97ded6..789240601f 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -45,10 +45,11 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; +import NodeMetrics from '../../EntityDetailsUtils/EntityMetrics'; import NodeTraces from '../../EntityDetailsUtils/EntityTraces'; import { QUERY_KEYS } from '../../EntityDetailsUtils/utils'; +import { getNodeMetricsQueryPayload, nodeWidgetInfo } from './constants'; import NodeLogs from './Logs'; -import NodeMetrics from './Metrics'; import { NodeDetailsProps } from './NodeDetails.interfaces'; function NodeDetails({ @@ -506,7 +507,11 @@ function NodeDetails({ isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} selectedInterval={selectedInterval} - node={node} + entity={node} + entityWidgetInfo={nodeWidgetInfo} + getEntityQueryPayload={getNodeMetricsQueryPayload} + category={K8sCategory.NODES} + queryKey="nodeMetrics" /> )} {selectedView === VIEW_TYPES.LOGS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/constants.ts rename to frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts index 47a3f508b4..e491d03d3c 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Metrics/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/constants.ts @@ -50,7 +50,7 @@ export const nodeWidgetInfo = [ }, ]; -export const getNodeQueryPayload = ( +export const getNodeMetricsQueryPayload = ( node: K8sNodesData, start: number, end: number, diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx deleted file mode 100644 index eb03cfea44..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/Metrics.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import '../../../EntityDetailsUtils/entityMetrics.styles.scss'; - -import { Card, Col, Row, Skeleton, Typography } from 'antd'; -import { K8sPodsData } from 'api/infraMonitoring/getK8sPodsList'; -import cx from 'classnames'; -import Uplot from 'components/Uplot'; -import { ENTITY_VERSION_V4 } from 'constants/app'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { useResizeObserver } from 'hooks/useDimensions'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; -import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; -import { useMemo, useRef } from 'react'; -import { useQueries, UseQueryResult } from 'react-query'; -import { SuccessResponse } from 'types/api'; -import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; - -import { getPodQueryPayload, podWidgetInfo } from './constants'; - -interface MetricsTabProps { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - selectedInterval: Time; - pod: K8sPodsData; -} - -function Metrics({ - selectedInterval, - pod, - timeRange, - handleTimeChange, - isModalTimeSelection, -}: MetricsTabProps): JSX.Element { - const queryPayloads = useMemo( - () => getPodQueryPayload(pod, timeRange.startTime, timeRange.endTime), - [pod, timeRange.startTime, timeRange.endTime], - ); - - const queries = useQueries( - queryPayloads.map((payload) => ({ - queryKey: ['pod-metrics', payload, ENTITY_VERSION_V4, 'POD'], - queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => - GetMetricQueryRange(payload, ENTITY_VERSION_V4), - enabled: !!payload, - })), - ); - - const isDarkMode = useIsDarkMode(); - const graphRef = useRef<HTMLDivElement>(null); - const dimensions = useResizeObserver(graphRef); - - const chartData = useMemo( - () => queries.map(({ data }) => getUPlotChartData(data?.payload)), - [queries], - ); - - const options = useMemo( - () => - queries.map(({ data }, idx) => - getUPlotChartOptions({ - apiResponse: data?.payload, - isDarkMode, - dimensions, - yAxisUnit: podWidgetInfo[idx].yAxisUnit, - softMax: null, - softMin: null, - minTimeScale: timeRange.startTime, - maxTimeScale: timeRange.endTime, - }), - ), - [queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime], - ); - - const renderCardContent = ( - query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>, - idx: number, - ): JSX.Element => { - if (query.isLoading) { - return <Skeleton />; - } - - if (query.error) { - const errorMessage = - (query.error as Error)?.message || 'Something went wrong'; - return <div>{errorMessage}</div>; - } - return ( - <div - className={cx('chart-container', { - 'no-data-container': - !query.isLoading && !query?.data?.payload?.data?.result?.length, - })} - > - <Uplot options={options[idx]} data={chartData[idx]} /> - </div> - ); - }; - - return ( - <> - <div className="metrics-header"> - <div className="metrics-datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - isModalTimeSelection={isModalTimeSelection} - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <Row gutter={24} className="entity-metrics-container"> - {queries.map((query, idx) => ( - <Col span={12} key={podWidgetInfo[idx].title}> - <Typography.Text>{podWidgetInfo[idx].title}</Typography.Text> - <Card bordered className="entity-metrics-card" ref={graphRef}> - {renderCardContent(query, idx)} - </Card> - </Col> - ))} - </Row> - </> - ); -} - -export default Metrics; diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index 6ae54bcffb..07916cee84 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -47,8 +47,9 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import Events from '../../EntityDetailsUtils/EntityEvents'; +import Metrics from '../../EntityDetailsUtils/EntityMetrics'; import PodTraces from '../../EntityDetailsUtils/EntityTraces'; -import Metrics from './Metrics/Metrics'; +import { getPodMetricsQueryPayload, podWidgetInfo } from './constants'; import { PodDetailProps } from './PodDetail.interfaces'; import PodLogsDetailedView from './PodLogs/PodLogsDetailedView'; @@ -540,11 +541,15 @@ function PodDetails({ {selectedView === VIEW_TYPES.METRICS && ( <Metrics - pod={pod} + entity={pod} selectedInterval={selectedInterval} timeRange={modalTimeRange} handleTimeChange={handleTimeChange} isModalTimeSelection={isModalTimeSelection} + entityWidgetInfo={podWidgetInfo} + getEntityQueryPayload={getPodMetricsQueryPayload} + category={K8sCategory.PODS} + queryKey="podMetrics" /> )} {selectedView === VIEW_TYPES.LOGS && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/constants.ts b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/constants.ts rename to frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts index 7a18381c93..4f8579af71 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/Metrics/constants.ts +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/constants.ts @@ -62,7 +62,7 @@ export const podWidgetInfo = [ }, ]; -export const getPodQueryPayload = ( +export const getPodMetricsQueryPayload = ( pod: K8sPodsData, start: number, end: number, From 8b29d086437572105a7a78aaae4ca3f85fcd7297 Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 15:16:32 +0530 Subject: [PATCH 15/20] chore: reorganize folder structure --- .../Clusters/ClusterDetails/ClusterDetails.tsx | 3 ++- .../DeploymentDetails/DeploymentDetails.tsx | 3 ++- .../{ => EntityEvents}/EntityEvents.tsx | 2 +- .../{ => EntityEvents}/entityEvents.styles.scss | 0 .../EntityDetailsUtils/EntityEvents/index.ts | 3 +++ .../{ => EntityMetrics}/EntityMetrics.tsx | 0 .../{ => EntityMetrics}/entityMetrics.styles.scss | 0 .../EntityDetailsUtils/EntityMetrics/index.ts | 3 +++ .../{ => EntityTraces}/EntityTraces.tsx | 2 +- .../{ => EntityTraces}/entityTraces.styles.scss | 0 .../EntityDetailsUtils/EntityTraces/index.ts | 3 +++ .../NamespaceDetails/NamespaceDetails.tsx | 3 ++- .../Nodes/NodeDetails/NodeDetails.tsx | 7 ++++--- .../Pods/PodDetails/PodDetails.tsx | 13 +++++++------ 14 files changed, 28 insertions(+), 14 deletions(-) rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityEvents}/EntityEvents.tsx (99%) rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityEvents}/entityEvents.styles.scss (100%) create mode 100644 frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/index.ts rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityMetrics}/EntityMetrics.tsx (100%) rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityMetrics}/entityMetrics.styles.scss (100%) create mode 100644 frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/index.ts rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityTraces}/EntityTraces.tsx (99%) rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityTraces}/entityTraces.styles.scss (100%) create mode 100644 frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/index.ts diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index 1e259e9e24..52f6774ead 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -5,6 +5,7 @@ import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; import logEvent from 'api/common/logEvent'; +import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList'; import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; import { QueryParams } from 'constants/query'; import { @@ -493,7 +494,7 @@ function ClusterDetails({ )} </div> {selectedView === VIEW_TYPES.METRICS && ( - <ClusterMetrics + <ClusterMetrics<K8sClustersData> timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index d11520fb3f..514836d761 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -5,6 +5,7 @@ import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; import logEvent from 'api/common/logEvent'; +import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList'; import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; import { QueryParams } from 'constants/query'; import { @@ -536,7 +537,7 @@ function DeploymentDetails({ )} </div> {selectedView === VIEW_TYPES.METRICS && ( - <DeploymentMetrics + <DeploymentMetrics<K8sDeploymentsData> timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx index f65dc6e178..67b4722ba3 100644 --- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx @@ -29,7 +29,7 @@ import { v4 } from 'uuid'; import { EntityDetailsEmptyContainer, getEntityEventsOrLogsQueryPayload, -} from './utils'; +} from '../utils'; interface EventDataType { key: string; diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/entityEvents.styles.scss similarity index 100% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityEvents.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/entityEvents.styles.scss diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/index.ts b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/index.ts new file mode 100644 index 0000000000..01ca4fa6dd --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/index.ts @@ -0,0 +1,3 @@ +import Events from './EntityEvents'; + +export default Events; diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/EntityMetrics.tsx similarity index 100% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/EntityMetrics.tsx diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityMetrics.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/entityMetrics.styles.scss similarity index 100% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityMetrics.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/entityMetrics.styles.scss diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/index.ts b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/index.ts new file mode 100644 index 0000000000..71e23e4da9 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/index.ts @@ -0,0 +1,3 @@ +import EntityMetrics from './EntityMetrics'; + +export default EntityMetrics; diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx similarity index 99% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx index a7187eadd5..189f047c88 100644 --- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx @@ -28,7 +28,7 @@ import { DataSource } from 'types/common/queryBuilder'; import { getEntityTracesQueryPayload, selectedEntityTracesColumns, -} from './utils'; +} from '../utils'; interface Props { timeRange: { diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityTraces.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/entityTraces.styles.scss similarity index 100% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityTraces.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/entityTraces.styles.scss diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/index.ts b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/index.ts new file mode 100644 index 0000000000..890b543828 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/index.ts @@ -0,0 +1,3 @@ +import EntityTraces from './EntityTraces'; + +export default EntityTraces; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx index 6a73f67650..dc58d43b79 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -5,6 +5,7 @@ import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; import logEvent from 'api/common/logEvent'; +import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList'; import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; import { QueryParams } from 'constants/query'; import { @@ -508,7 +509,7 @@ function NamespaceDetails({ )} </div> {selectedView === VIEW_TYPES.METRICS && ( - <NamespaceMetrics + <NamespaceMetrics<K8sNamespacesData> timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 789240601f..30a3a34712 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -5,6 +5,7 @@ import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; import logEvent from 'api/common/logEvent'; +import { K8sNodesData } from 'api/infraMonitoring/getK8sNodesList'; import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; import { QueryParams } from 'constants/query'; import { @@ -13,7 +14,7 @@ import { } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import Events from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents'; +import NodeEvents from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents'; import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils'; import { CustomTimeType, @@ -502,7 +503,7 @@ function NodeDetails({ )} </div> {selectedView === VIEW_TYPES.METRICS && ( - <NodeMetrics + <NodeMetrics<K8sNodesData> timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} @@ -536,7 +537,7 @@ function NodeDetails({ /> )} {selectedView === VIEW_TYPES.EVENTS && ( - <Events + <NodeEvents timeRange={modalTimeRange} handleChangeEventFilters={handleChangeEventsFilters} filters={eventsFilters} diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index 07916cee84..17a4f20def 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -6,6 +6,7 @@ import { Color, Spacing } from '@signozhq/design-tokens'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; import logEvent from 'api/common/logEvent'; +import { K8sPodsData } from 'api/infraMonitoring/getK8sPodsList'; import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; import { QueryParams } from 'constants/query'; import { @@ -46,12 +47,12 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; -import Events from '../../EntityDetailsUtils/EntityEvents'; -import Metrics from '../../EntityDetailsUtils/EntityMetrics'; +import PodEvents from '../../EntityDetailsUtils/EntityEvents'; +import PodMetrics from '../../EntityDetailsUtils/EntityMetrics'; import PodTraces from '../../EntityDetailsUtils/EntityTraces'; import { getPodMetricsQueryPayload, podWidgetInfo } from './constants'; import { PodDetailProps } from './PodDetail.interfaces'; -import PodLogsDetailedView from './PodLogs/PodLogsDetailedView'; +import PodLogs from './PodLogs/PodLogsDetailedView'; const TimeRangeOffset = 1000000000; @@ -540,7 +541,7 @@ function PodDetails({ </div> {selectedView === VIEW_TYPES.METRICS && ( - <Metrics + <PodMetrics<K8sPodsData> entity={pod} selectedInterval={selectedInterval} timeRange={modalTimeRange} @@ -553,7 +554,7 @@ function PodDetails({ /> )} {selectedView === VIEW_TYPES.LOGS && ( - <PodLogsDetailedView + <PodLogs timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} @@ -575,7 +576,7 @@ function PodDetails({ )} {selectedView === VIEW_TYPES.EVENTS && ( - <Events + <PodEvents timeRange={modalTimeRange} isModalTimeSelection={isModalTimeSelection} handleTimeChange={handleTimeChange} From 905ca26ff9aee8043f1e108b4bada9593bc63d38 Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sat, 18 Jan 2025 15:31:12 +0530 Subject: [PATCH 16/20] chore: refactor logs --- .../ClusterDetails/ClusterDetails.tsx | 5 +- .../Logs/ClusterLogsDetailedView.tsx | 99 -------- .../Clusters/ClusterDetails/Logs/index.ts | 3 - .../DeploymentDetails/DeploymentDetails.tsx | 8 +- .../DeploymentDetails/Logs/DeploymentLogs.tsx | 224 ------------------ .../DeploymentDetails/Logs/index.ts | 3 - .../EntityLogs/EntityLogs.tsx} | 22 +- .../EntityLogs/EntityLogsDetailedView.tsx} | 20 +- .../{ => EntityLogs}/entityLogs.styles.scss | 0 .../EntityDetailsUtils/EntityLogs/index.ts | 3 + .../NamespaceDetails/Logs/NamespaceLogs.tsx | 222 ----------------- .../Logs/NamespaceLogsDetailedView.tsx | 99 -------- .../Namespaces/NamespaceDetails/Logs/index.ts | 3 - .../NamespaceDetails/NamespaceDetails.tsx | 5 +- .../Nodes/NodeDetails/Logs/NodeLogs.tsx | 221 ----------------- .../NodeDetails/Logs/NodeLogsDetailedView.tsx | 100 -------- .../Nodes/NodeDetails/Logs/index.ts | 3 - .../Nodes/NodeDetails/NodeDetails.tsx | 5 +- .../Pods/PodDetails/PodDetails.tsx | 9 +- .../Pods/PodDetails/PodLogs/PodLogs.tsx | 223 ----------------- .../PodLogs/PodLogsDetailedView.tsx | 99 -------- 21 files changed, 58 insertions(+), 1318 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts rename frontend/src/container/InfraMonitoringK8s/{Clusters/ClusterDetails/Logs/ClusterLogs.tsx => EntityDetailsUtils/EntityLogs/EntityLogs.tsx} (92%) rename frontend/src/container/InfraMonitoringK8s/{Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx => EntityDetailsUtils/EntityLogs/EntityLogsDetailedView.tsx} (84%) rename frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/{ => EntityLogs}/entityLogs.styles.scss (100%) create mode 100644 frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/index.ts delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx delete mode 100644 frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index 52f6774ead..ccd6d033ad 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -46,11 +46,11 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import ClusterEvents from '../../EntityDetailsUtils/EntityEvents'; +import ClusterLogs from '../../EntityDetailsUtils/EntityLogs'; import ClusterMetrics from '../../EntityDetailsUtils/EntityMetrics'; import ClusterTraces from '../../EntityDetailsUtils/EntityTraces'; import { ClusterDetailsProps } from './ClusterDetails.interfaces'; import { clusterWidgetInfo, getClusterMetricsQueryPayload } from './constants'; -import ClusterLogs from './Logs'; function ClusterDetails({ cluster, @@ -514,6 +514,9 @@ function ClusterDetails({ handleChangeLogFilters={handleChangeLogFilters} logFilters={logFilters} selectedInterval={selectedInterval} + queryKey="clusterLogs" + category={K8sCategory.CLUSTERS} + queryKeyFilters={[QUERY_KEYS.K8S_CLUSTER_NAME]} /> )} {selectedView === VIEW_TYPES.TRACES && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx deleted file mode 100644 index d21b5a6121..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogsDetailedView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { useMemo } from 'react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import ClusterLogs from './ClusterLogs'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; - logFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function ClusterLogsDetailedView({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeLogFilters, - logFilters, - selectedInterval, -}: Props): JSX.Element { - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - return ( - <div className="entity-logs-container"> - <div className="entity-logs-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeLogFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <ClusterLogs - timeRange={timeRange} - handleChangeLogFilters={handleChangeLogFilters} - filters={logFilters} - /> - </div> - ); -} - -export default ClusterLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts deleted file mode 100644 index 5210b7ab20..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import ClusterLogs from './ClusterLogsDetailedView'; - -export default ClusterLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index 514836d761..85044e5495 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -46,6 +46,7 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import DeploymentEvents from '../../EntityDetailsUtils/EntityEvents'; +import DeploymentLogs from '../../EntityDetailsUtils/EntityLogs'; import DeploymentMetrics from '../../EntityDetailsUtils/EntityMetrics'; import DeploymentTraces from '../../EntityDetailsUtils/EntityTraces'; import { @@ -53,7 +54,6 @@ import { getDeploymentMetricsQueryPayload, } from './constants'; import { DeploymentDetailsProps } from './DeploymentDetails.interfaces'; -import DeploymentLogs from './Logs'; function DeploymentDetails({ deployment, @@ -557,6 +557,12 @@ function DeploymentDetails({ handleChangeLogFilters={handleChangeLogFilters} logFilters={logFilters} selectedInterval={selectedInterval} + queryKeyFilters={[ + QUERY_KEYS.K8S_DEPLOYMENT_NAME, + QUERY_KEYS.K8S_NAMESPACE_NAME, + ]} + queryKey="deploymentLogs" + category={K8sCategory.DEPLOYMENTS} /> )} {selectedView === VIEW_TYPES.TRACES && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx deleted file mode 100644 index a60a235005..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogs.tsx +++ /dev/null @@ -1,224 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import { Card } from 'antd'; -import RawLogView from 'components/Logs/RawLogView'; -import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LogsError from 'container/LogsError/LogsError'; -import { LogsLoading } from 'container/LogsLoading/LogsLoading'; -import { FontSize } from 'container/OptionsMenu/types'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isEqual } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { Virtuoso } from 'react-virtuoso'; -import { ILog } from 'types/api/logs/log'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { - IBuilderQuery, - TagFilterItem, -} from 'types/api/queryBuilder/queryBuilderData'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, - QUERY_KEYS, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; -} - -function PodLogs({ - timeRange, - handleChangeLogFilters, - filters, -}: Props): JSX.Element { - const [logs, setLogs] = useState<ILog[]>([]); - const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); - const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); - const [resetLogsList, setResetLogsList] = useState<boolean>(false); - - useEffect(() => { - const newRestFilters = filters.items.filter( - (item) => - item.key?.key !== 'id' && - ![QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( - item.key?.key ?? '', - ), - ); - - const areFiltersSame = isEqual(restFilters, newRestFilters); - - if (!areFiltersSame) { - setResetLogsList(true); - } - - setRestFilters(newRestFilters); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filters]); - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 100; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const [isPaginating, setIsPaginating] = useState(false); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: ['deploymentLogs', timeRange.startTime, timeRange.endTime, filters], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - keepPreviousData: isPaginating, - }); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - - if (resetLogsList) { - const currentLogs: ILog[] = - currentData[0].list?.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs(currentLogs); - - setResetLogsList(false); - } - - if (currentData.length > 0 && currentData[0].list) { - const currentLogs: ILog[] = - currentData[0].list.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs((prev) => [...prev, ...currentLogs]); - } else { - setHasReachedEndOfLogs(true); - } - } - }, [data, restFilters, isPaginating, resetLogsList]); - - const getItemContent = useCallback( - (_: number, logToRender: ILog): JSX.Element => ( - <RawLogView - isReadOnly - isTextOverflowEllipsisDisabled - key={logToRender.id} - data={logToRender} - linesPerRow={5} - fontSize={FontSize.MEDIUM} - /> - ), - [], - ); - - const loadMoreLogs = useCallback(() => { - if (!logs.length) return; - - setIsPaginating(true); - const lastLog = logs[logs.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastLog.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeLogFilters(newFilters); - }, [logs, filters, handleChangeLogFilters]); - - useEffect(() => { - setIsPaginating(false); - }, [data]); - - const renderFooter = useCallback( - (): JSX.Element | null => ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {isFetching ? ( - <div className="logs-loading-skeleton"> Loading more logs ... </div> - ) : hasReachedEndOfLogs ? ( - <div className="logs-loading-skeleton"> *** End *** </div> - ) : null} - </> - ), - [isFetching, hasReachedEndOfLogs], - ); - - const renderContent = useMemo( - () => ( - <Card bordered={false} className="entity-logs-list-card"> - <OverlayScrollbar isVirtuoso> - <Virtuoso - className="entity-logs-virtuoso" - key="entity-logs-virtuoso" - data={logs} - endReached={loadMoreLogs} - totalCount={logs.length} - itemContent={getItemContent} - overscan={200} - components={{ - Footer: renderFooter, - }} - /> - </OverlayScrollbar> - </Card> - ), - [logs, loadMoreLogs, getItemContent, renderFooter], - ); - - return ( - <div className="entity-logs"> - {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && ( - <EntityDetailsEmptyContainer - category={K8sCategory.DEPLOYMENTS} - view="logs" - /> - )} - {isError && !isLoading && <LogsError />} - {!isLoading && !isError && logs.length > 0 && ( - <div className="entity-logs-list-container">{renderContent}</div> - )} - </div> - ); -} - -export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts deleted file mode 100644 index 47b16ec9b6..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DeploymentLogs from './DeploymentLogsDetailedView'; - -export default DeploymentLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx similarity index 92% rename from frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx index 0e6487d7b8..cb7d4d63c5 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Logs/ClusterLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; +import './entityLogs.styles.scss'; import { Card } from 'antd'; import RawLogView from 'components/Logs/RawLogView'; @@ -26,8 +26,7 @@ import { v4 } from 'uuid'; import { EntityDetailsEmptyContainer, getEntityEventsOrLogsQueryPayload, - QUERY_KEYS, -} from '../../../EntityDetailsUtils/utils'; +} from '../utils'; interface Props { timeRange: { @@ -36,12 +35,18 @@ interface Props { }; handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; filters: IBuilderQuery['filters']; + queryKey: string; + category: K8sCategory; + queryKeyFilters: Array<string>; } -function PodLogs({ +function EntityLogs({ timeRange, handleChangeLogFilters, filters, + queryKey, + category, + queryKeyFilters, }: Props): JSX.Element { const [logs, setLogs] = useState<ILog[]>([]); const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); @@ -51,8 +56,7 @@ function PodLogs({ useEffect(() => { const newRestFilters = filters.items.filter( (item) => - item.key?.key !== 'id' && - ![QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''), + item.key?.key !== 'id' && !queryKeyFilters.includes(item.key?.key ?? ''), ); const areFiltersSame = isEqual(restFilters, newRestFilters); @@ -83,7 +87,7 @@ function PodLogs({ const [isPaginating, setIsPaginating] = useState(false); const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: ['clusterLogs', timeRange.startTime, timeRange.endTime, filters], + queryKey: [queryKey, timeRange.startTime, timeRange.endTime, filters], queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), enabled: !!queryPayload, keepPreviousData: isPaginating, @@ -206,7 +210,7 @@ function PodLogs({ <div className="entity-logs"> {isLoading && <LogsLoading />} {!isLoading && !isError && logs.length === 0 && ( - <EntityDetailsEmptyContainer category={K8sCategory.CLUSTERS} view="logs" /> + <EntityDetailsEmptyContainer category={category} view="logs" /> )} {isError && !isLoading && <LogsError />} {!isLoading && !isError && logs.length > 0 && ( @@ -216,4 +220,4 @@ function PodLogs({ ); } -export default PodLogs; +export default EntityLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogsDetailedView.tsx similarity index 84% rename from frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogsDetailedView.tsx index 3fbadf20bc..bab5c68eb6 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/Logs/DeploymentLogsDetailedView.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogsDetailedView.tsx @@ -1,5 +1,6 @@ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; +import './entityLogs.styles.scss'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import { @@ -11,7 +12,7 @@ import { useMemo } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -import DeploymentLogs from './DeploymentLogs'; +import EntityLogs from './EntityLogs'; interface Props { timeRange: { @@ -26,15 +27,21 @@ interface Props { handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; logFilters: IBuilderQuery['filters']; selectedInterval: Time; + queryKey: string; + category: K8sCategory; + queryKeyFilters: Array<string>; } -function DeploymentLogsDetailedView({ +function EntityLogsDetailedView({ timeRange, isModalTimeSelection, handleTimeChange, handleChangeLogFilters, logFilters, selectedInterval, + queryKey, + category, + queryKeyFilters, }: Props): JSX.Element { const { currentQuery } = useQueryBuilder(); const updatedCurrentQuery = useMemo( @@ -87,13 +94,16 @@ function DeploymentLogsDetailedView({ /> </div> </div> - <DeploymentLogs + <EntityLogs timeRange={timeRange} handleChangeLogFilters={handleChangeLogFilters} filters={logFilters} + queryKey={queryKey} + category={category} + queryKeyFilters={queryKeyFilters} /> </div> ); } -export default DeploymentLogsDetailedView; +export default EntityLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityLogs.styles.scss b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/entityLogs.styles.scss similarity index 100% rename from frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/entityLogs.styles.scss rename to frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/entityLogs.styles.scss diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/index.ts b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/index.ts new file mode 100644 index 0000000000..5bd0f406d1 --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/index.ts @@ -0,0 +1,3 @@ +import EntityLogs from './EntityLogsDetailedView'; + +export default EntityLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx deleted file mode 100644 index d443c52613..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogs.tsx +++ /dev/null @@ -1,222 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import { Card } from 'antd'; -import RawLogView from 'components/Logs/RawLogView'; -import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LogsError from 'container/LogsError/LogsError'; -import { LogsLoading } from 'container/LogsLoading/LogsLoading'; -import { FontSize } from 'container/OptionsMenu/types'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isEqual } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { Virtuoso } from 'react-virtuoso'; -import { ILog } from 'types/api/logs/log'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { - IBuilderQuery, - TagFilterItem, -} from 'types/api/queryBuilder/queryBuilderData'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, - QUERY_KEYS, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; -} - -function PodLogs({ - timeRange, - handleChangeLogFilters, - filters, -}: Props): JSX.Element { - const [logs, setLogs] = useState<ILog[]>([]); - const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); - const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); - const [resetLogsList, setResetLogsList] = useState<boolean>(false); - - useEffect(() => { - const newRestFilters = filters.items.filter( - (item) => - item.key?.key !== 'id' && - ![QUERY_KEYS.K8S_NAMESPACE_NAME].includes(item.key?.key ?? ''), - ); - - const areFiltersSame = isEqual(restFilters, newRestFilters); - - if (!areFiltersSame) { - setResetLogsList(true); - } - - setRestFilters(newRestFilters); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filters]); - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 100; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const [isPaginating, setIsPaginating] = useState(false); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: ['namespaceLogs', timeRange.startTime, timeRange.endTime, filters], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - keepPreviousData: isPaginating, - }); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - - if (resetLogsList) { - const currentLogs: ILog[] = - currentData[0].list?.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs(currentLogs); - - setResetLogsList(false); - } - - if (currentData.length > 0 && currentData[0].list) { - const currentLogs: ILog[] = - currentData[0].list.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs((prev) => [...prev, ...currentLogs]); - } else { - setHasReachedEndOfLogs(true); - } - } - }, [data, restFilters, isPaginating, resetLogsList]); - - const getItemContent = useCallback( - (_: number, logToRender: ILog): JSX.Element => ( - <RawLogView - isReadOnly - isTextOverflowEllipsisDisabled - key={logToRender.id} - data={logToRender} - linesPerRow={5} - fontSize={FontSize.MEDIUM} - /> - ), - [], - ); - - const loadMoreLogs = useCallback(() => { - if (!logs.length) return; - - setIsPaginating(true); - const lastLog = logs[logs.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastLog.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeLogFilters(newFilters); - }, [logs, filters, handleChangeLogFilters]); - - useEffect(() => { - setIsPaginating(false); - }, [data]); - - const renderFooter = useCallback( - (): JSX.Element | null => ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {isFetching ? ( - <div className="logs-loading-skeleton"> Loading more logs ... </div> - ) : hasReachedEndOfLogs ? ( - <div className="logs-loading-skeleton"> *** End *** </div> - ) : null} - </> - ), - [isFetching, hasReachedEndOfLogs], - ); - - const renderContent = useMemo( - () => ( - <Card bordered={false} className="entity-logs-list-card"> - <OverlayScrollbar isVirtuoso> - <Virtuoso - className="entity-logs-virtuoso" - key="entity-logs-virtuoso" - data={logs} - endReached={loadMoreLogs} - totalCount={logs.length} - itemContent={getItemContent} - overscan={200} - components={{ - Footer: renderFooter, - }} - /> - </OverlayScrollbar> - </Card> - ), - [logs, loadMoreLogs, getItemContent, renderFooter], - ); - - return ( - <div className="entity-logs"> - {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && ( - <EntityDetailsEmptyContainer - category={K8sCategory.NAMESPACES} - view="logs" - /> - )} - {isError && !isLoading && <LogsError />} - {!isLoading && !isError && logs.length > 0 && ( - <div className="entity-logs-list-container">{renderContent}</div> - )} - </div> - ); -} - -export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx deleted file mode 100644 index 5eccdbb9a0..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/NamespaceLogsDetailedView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { useMemo } from 'react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import NamespaceLogs from './NamespaceLogs'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; - logFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function NamespaceLogsDetailedView({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeLogFilters, - logFilters, - selectedInterval, -}: Props): JSX.Element { - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - return ( - <div className="entity-logs-container"> - <div className="entity-logs-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeLogFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <NamespaceLogs - timeRange={timeRange} - handleChangeLogFilters={handleChangeLogFilters} - filters={logFilters} - /> - </div> - ); -} - -export default NamespaceLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts deleted file mode 100644 index eb3fc025c5..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/Logs/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NamespaceLogs from './NamespaceLogsDetailedView'; - -export default NamespaceLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx index dc58d43b79..82b181455f 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx @@ -46,13 +46,13 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import NamespaceEvents from '../../EntityDetailsUtils/EntityEvents'; +import NamespaceLogs from '../../EntityDetailsUtils/EntityLogs'; import NamespaceMetrics from '../../EntityDetailsUtils/EntityMetrics'; import NamespaceTraces from '../../EntityDetailsUtils/EntityTraces'; import { getNamespaceMetricsQueryPayload, namespaceWidgetInfo, } from './constants'; -import NamespaceLogs from './Logs'; import { NamespaceDetailsProps } from './NamespaceDetails.interfaces'; function NamespaceDetails({ @@ -529,6 +529,9 @@ function NamespaceDetails({ handleChangeLogFilters={handleChangeLogFilters} logFilters={logFilters} selectedInterval={selectedInterval} + queryKey="namespaceLogs" + category={K8sCategory.NAMESPACES} + queryKeyFilters={[QUERY_KEYS.K8S_NAMESPACE_NAME]} /> )} {selectedView === VIEW_TYPES.TRACES && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx deleted file mode 100644 index 3a3ffe770e..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogs.tsx +++ /dev/null @@ -1,221 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import { Card } from 'antd'; -import RawLogView from 'components/Logs/RawLogView'; -import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LogsError from 'container/LogsError/LogsError'; -import { LogsLoading } from 'container/LogsLoading/LogsLoading'; -import { FontSize } from 'container/OptionsMenu/types'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isEqual } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { Virtuoso } from 'react-virtuoso'; -import { ILog } from 'types/api/logs/log'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { - IBuilderQuery, - TagFilterItem, -} from 'types/api/queryBuilder/queryBuilderData'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, - QUERY_KEYS, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; -} - -function PodLogs({ - timeRange, - handleChangeLogFilters, - filters, -}: Props): JSX.Element { - const [logs, setLogs] = useState<ILog[]>([]); - const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); - const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); - const [resetLogsList, setResetLogsList] = useState<boolean>(false); - - useEffect(() => { - const newRestFilters = filters.items.filter( - (item) => - item.key?.key !== 'id' && - ![QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( - item.key?.key ?? '', - ), - ); - - const areFiltersSame = isEqual(restFilters, newRestFilters); - - if (!areFiltersSame) { - setResetLogsList(true); - } - - setRestFilters(newRestFilters); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filters]); - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 100; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const [isPaginating, setIsPaginating] = useState(false); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: ['nodeLogs', timeRange.startTime, timeRange.endTime, filters], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - keepPreviousData: isPaginating, - }); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - - if (resetLogsList) { - const currentLogs: ILog[] = - currentData[0].list?.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs(currentLogs); - - setResetLogsList(false); - } - - if (currentData.length > 0 && currentData[0].list) { - const currentLogs: ILog[] = - currentData[0].list.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs((prev) => [...prev, ...currentLogs]); - } else { - setHasReachedEndOfLogs(true); - } - } - }, [data, restFilters, isPaginating, resetLogsList]); - - const getItemContent = useCallback( - (_: number, logToRender: ILog): JSX.Element => ( - <RawLogView - isReadOnly - isTextOverflowEllipsisDisabled - key={logToRender.id} - data={logToRender} - linesPerRow={5} - fontSize={FontSize.MEDIUM} - /> - ), - [], - ); - - const loadMoreLogs = useCallback(() => { - if (!logs.length) return; - - setIsPaginating(true); - const lastLog = logs[logs.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastLog.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeLogFilters(newFilters); - }, [logs, filters, handleChangeLogFilters]); - - useEffect(() => { - setIsPaginating(false); - }, [data]); - - const renderFooter = useCallback( - (): JSX.Element | null => ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {isFetching ? ( - <div className="logs-loading-skeleton"> Loading more logs ... </div> - ) : hasReachedEndOfLogs ? ( - <div className="logs-loading-skeleton"> *** End *** </div> - ) : null} - </> - ), - [isFetching, hasReachedEndOfLogs], - ); - - const renderContent = useMemo( - () => ( - <Card bordered={false} className="entity-logs-list-card"> - <OverlayScrollbar isVirtuoso> - <Virtuoso - className="entity-logs-virtuoso" - key="entity-logs-virtuoso" - data={logs} - endReached={loadMoreLogs} - totalCount={logs.length} - itemContent={getItemContent} - overscan={200} - components={{ - Footer: renderFooter, - }} - /> - </OverlayScrollbar> - </Card> - ), - [logs, loadMoreLogs, getItemContent, renderFooter], - ); - - return ( - <div className="entity-logs"> - {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && ( - <EntityDetailsEmptyContainer category={K8sCategory.NODES} view="logs" /> - )} - {isError && !isLoading && <LogsError />} - {!isLoading && !isError && logs.length > 0 && ( - <div className="entity-logs-list-container">{renderContent}</div> - )} - </div> - ); -} - -export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx deleted file mode 100644 index 09ce9a7fdd..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/NodeLogsDetailedView.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { useMemo } from 'react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import NodeLogs from './NodeLogs'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; - logFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function NodeLogsDetailedView({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeLogFilters, - logFilters, - selectedInterval, -}: Props): JSX.Element { - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - return ( - <div className="entity-logs-container"> - <div className="entity-logs-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeLogFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <NodeLogs - timeRange={timeRange} - handleChangeLogFilters={handleChangeLogFilters} - filters={logFilters} - /> - </div> - ); -} - -export default NodeLogsDetailedView; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/index.ts b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/index.ts deleted file mode 100644 index 977dda6f01..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/Logs/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NodeLogs from './NodeLogsDetailedView'; - -export default NodeLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 30a3a34712..372f236329 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -46,11 +46,11 @@ import { import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; +import NodeLogs from '../../EntityDetailsUtils/EntityLogs'; import NodeMetrics from '../../EntityDetailsUtils/EntityMetrics'; import NodeTraces from '../../EntityDetailsUtils/EntityTraces'; import { QUERY_KEYS } from '../../EntityDetailsUtils/utils'; import { getNodeMetricsQueryPayload, nodeWidgetInfo } from './constants'; -import NodeLogs from './Logs'; import { NodeDetailsProps } from './NodeDetails.interfaces'; function NodeDetails({ @@ -523,6 +523,9 @@ function NodeDetails({ handleChangeLogFilters={handleChangeLogFilters} logFilters={logFilters} selectedInterval={selectedInterval} + queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]} + queryKey="nodeLogs" + category={K8sCategory.NODES} /> )} {selectedView === VIEW_TYPES.TRACES && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index 17a4f20def..c671134d50 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -48,11 +48,11 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import PodEvents from '../../EntityDetailsUtils/EntityEvents'; +import PodLogs from '../../EntityDetailsUtils/EntityLogs'; import PodMetrics from '../../EntityDetailsUtils/EntityMetrics'; import PodTraces from '../../EntityDetailsUtils/EntityTraces'; import { getPodMetricsQueryPayload, podWidgetInfo } from './constants'; import { PodDetailProps } from './PodDetail.interfaces'; -import PodLogs from './PodLogs/PodLogsDetailedView'; const TimeRangeOffset = 1000000000; @@ -561,6 +561,13 @@ function PodDetails({ handleChangeLogFilters={handleChangeLogFilters} logFilters={logFilters} selectedInterval={selectedInterval} + queryKeyFilters={[ + QUERY_KEYS.K8S_POD_NAME, + QUERY_KEYS.K8S_CLUSTER_NAME, + QUERY_KEYS.K8S_NAMESPACE_NAME, + ]} + queryKey="podLogs" + category={K8sCategory.PODS} /> )} {selectedView === VIEW_TYPES.TRACES && ( diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx deleted file mode 100644 index 673daedad6..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogs.tsx +++ /dev/null @@ -1,223 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import { Card } from 'antd'; -import RawLogView from 'components/Logs/RawLogView'; -import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; -import { DEFAULT_ENTITY_VERSION } from 'constants/app'; -import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; -import LogsError from 'container/LogsError/LogsError'; -import { LogsLoading } from 'container/LogsLoading/LogsLoading'; -import { FontSize } from 'container/OptionsMenu/types'; -import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; -import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; -import { isEqual } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { Virtuoso } from 'react-virtuoso'; -import { ILog } from 'types/api/logs/log'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { - IBuilderQuery, - TagFilterItem, -} from 'types/api/queryBuilder/queryBuilderData'; -import { v4 } from 'uuid'; - -import { - EntityDetailsEmptyContainer, - getEntityEventsOrLogsQueryPayload, - QUERY_KEYS, -} from '../../../EntityDetailsUtils/utils'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void; - filters: IBuilderQuery['filters']; -} - -function PodLogs({ - timeRange, - handleChangeLogFilters, - filters, -}: Props): JSX.Element { - const [logs, setLogs] = useState<ILog[]>([]); - const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); - const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]); - const [resetLogsList, setResetLogsList] = useState<boolean>(false); - - useEffect(() => { - const newRestFilters = filters.items.filter( - (item) => - item.key?.key !== 'id' && - ![ - QUERY_KEYS.K8S_POD_NAME, - QUERY_KEYS.K8S_CLUSTER_NAME, - QUERY_KEYS.K8S_NAMESPACE_NAME, - ].includes(item.key?.key || ''), - ); - - const areFiltersSame = isEqual(restFilters, newRestFilters); - - if (!areFiltersSame) { - setResetLogsList(true); - } - - setRestFilters(newRestFilters); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filters]); - - const queryPayload = useMemo(() => { - const basePayload = getEntityEventsOrLogsQueryPayload( - timeRange.startTime, - timeRange.endTime, - filters, - ); - - basePayload.query.builder.queryData[0].pageSize = 100; - basePayload.query.builder.queryData[0].orderBy = [ - { columnName: 'timestamp', order: ORDERBY_FILTERS.DESC }, - ]; - - return basePayload; - }, [timeRange.startTime, timeRange.endTime, filters]); - - const [isPaginating, setIsPaginating] = useState(false); - - const { data, isLoading, isFetching, isError } = useQuery({ - queryKey: ['podLogs', timeRange.startTime, timeRange.endTime, filters], - queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION), - enabled: !!queryPayload, - keepPreviousData: isPaginating, - }); - - useEffect(() => { - if (data?.payload?.data?.newResult?.data?.result) { - const currentData = data.payload.data.newResult.data.result; - - if (resetLogsList) { - const currentLogs: ILog[] = - currentData[0].list?.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs(currentLogs); - - setResetLogsList(false); - } - - if (currentData.length > 0 && currentData[0].list) { - const currentLogs: ILog[] = - currentData[0].list.map((item) => ({ - ...item.data, - timestamp: item.timestamp, - })) || []; - - setLogs((prev) => [...prev, ...currentLogs]); - } else { - setHasReachedEndOfLogs(true); - } - } - }, [data, restFilters, isPaginating, resetLogsList]); - - const getItemContent = useCallback( - (_: number, logToRender: ILog): JSX.Element => ( - <RawLogView - isReadOnly - isTextOverflowEllipsisDisabled - key={logToRender.id} - data={logToRender} - linesPerRow={5} - fontSize={FontSize.MEDIUM} - /> - ), - [], - ); - - const loadMoreLogs = useCallback(() => { - if (!logs.length) return; - - setIsPaginating(true); - const lastLog = logs[logs.length - 1]; - - const newItems = [ - ...filters.items.filter((item) => item.key?.key !== 'id'), - { - id: v4(), - key: { - key: 'id', - type: '', - dataType: DataTypes.String, - isColumn: true, - }, - op: '<', - value: lastLog.id, - }, - ]; - - const newFilters = { - op: 'AND', - items: newItems, - } as IBuilderQuery['filters']; - - handleChangeLogFilters(newFilters); - }, [logs, filters, handleChangeLogFilters]); - - useEffect(() => { - setIsPaginating(false); - }, [data]); - - const renderFooter = useCallback( - (): JSX.Element | null => ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {isFetching ? ( - <div className="logs-loading-skeleton"> Loading more logs ... </div> - ) : hasReachedEndOfLogs ? ( - <div className="logs-loading-skeleton"> *** End *** </div> - ) : null} - </> - ), - [isFetching, hasReachedEndOfLogs], - ); - - const renderContent = useMemo( - () => ( - <Card bordered={false} className="entity-logs-list-card"> - <OverlayScrollbar isVirtuoso> - <Virtuoso - className="entity-logs-virtuoso" - key="entity-logs-virtuoso" - data={logs} - endReached={loadMoreLogs} - totalCount={logs.length} - itemContent={getItemContent} - overscan={200} - components={{ - Footer: renderFooter, - }} - /> - </OverlayScrollbar> - </Card> - ), - [logs, loadMoreLogs, getItemContent, renderFooter], - ); - - return ( - <div className="entity-logs"> - {isLoading && <LogsLoading />} - {!isLoading && !isError && logs.length === 0 && ( - <EntityDetailsEmptyContainer category={K8sCategory.PODS} view="logs" /> - )} - {isError && !isLoading && <LogsError />} - {!isLoading && !isError && logs.length > 0 && ( - <div className="entity-logs-list-container">{renderContent}</div> - )} - </div> - ); -} - -export default PodLogs; diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx deleted file mode 100644 index 573c54cc4c..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodLogs/PodLogsDetailedView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import '../../../EntityDetailsUtils/entityLogs.styles.scss'; - -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; -import { - CustomTimeType, - Time, -} from 'container/TopNav/DateTimeSelectionV2/config'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; -import { useMemo } from 'react'; -import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; - -import PodLogs from './PodLogs'; - -interface Props { - timeRange: { - startTime: number; - endTime: number; - }; - isModalTimeSelection: boolean; - handleTimeChange: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; - handleChangeLogFilters: (value: IBuilderQuery['filters']) => void; - logFilters: IBuilderQuery['filters']; - selectedInterval: Time; -} - -function PodLogsDetailedView({ - timeRange, - isModalTimeSelection, - handleTimeChange, - handleChangeLogFilters, - logFilters, - selectedInterval, -}: Props): JSX.Element { - const { currentQuery } = useQueryBuilder(); - const updatedCurrentQuery = useMemo( - () => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: [ - { - ...currentQuery.builder.queryData[0], - dataSource: DataSource.LOGS, - aggregateOperator: 'noop', - aggregateAttribute: { - ...currentQuery.builder.queryData[0].aggregateAttribute, - }, - filters: { - items: [], - op: 'AND', - }, - }, - ], - }, - }), - [currentQuery], - ); - - const query = updatedCurrentQuery?.builder?.queryData[0] || null; - - return ( - <div className="entity-metrics-logs-container"> - <div className="entity-metrics-logs-header"> - <div className="filter-section"> - {query && ( - <QueryBuilderSearch - query={query} - onChange={handleChangeLogFilters} - disableNavigationShortcuts - /> - )} - </div> - <div className="datetime-section"> - <DateTimeSelectionV2 - showAutoRefresh={false} - showRefreshText={false} - hideShareModal - isModalTimeSelection={isModalTimeSelection} - onTimeChange={handleTimeChange} - defaultRelativeTime="5m" - modalSelectedInterval={selectedInterval} - /> - </div> - </div> - <PodLogs - timeRange={timeRange} - handleChangeLogFilters={handleChangeLogFilters} - filters={logFilters} - /> - </div> - ); -} - -export default PodLogsDetailedView; From 0d07e6d515614848d8a62341df38b58591ffbc37 Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sun, 19 Jan 2025 13:25:47 +0530 Subject: [PATCH 17/20] chore: listing bug fixes --- .../getInfraAttributeValues.ts | 4 +- .../api/infraMonitoring/getK8sClustersList.ts | 4 +- .../infraMonitoring/getK8sDeploymentsList.ts | 4 +- .../infraMonitoring/getK8sNamespacesList.ts | 4 +- .../ClusterDetails/ClusterDetails.tsx | 47 +++--- .../Clusters/K8sClustersList.tsx | 32 ++-- .../InfraMonitoringK8s/Clusters/utils.tsx | 3 +- .../DeploymentDetails/DeploymentDetails.tsx | 47 +++--- .../K8sDeploymentsList.styles.scss | 50 ++++++- .../Deployments/K8sDeploymentsList.tsx | 140 +++++++++--------- .../InfraMonitoringK8s/Deployments/utils.tsx | 39 +++-- .../InfraMonitoringK8s.styles.scss | 4 + .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 5 +- .../Namespaces/K8sNamespacesList.styles.scss | 2 +- .../Namespaces/K8sNamespacesList.tsx | 138 +++++++++-------- .../InfraMonitoringK8s/Namespaces/utils.tsx | 5 +- .../Nodes/NodeDetails/NodeDetails.tsx | 2 +- .../InfraMonitoringK8s/Pods/K8sPodLists.tsx | 2 +- .../Pods/PodDetails/PodDetails.tsx | 2 +- .../InfraMonitoringK8s/commonUtils.tsx | 18 +++ .../InfraMonitoringK8s/entityDetailUtils.ts | 18 --- 21 files changed, 321 insertions(+), 249 deletions(-) delete mode 100644 frontend/src/container/InfraMonitoringK8s/entityDetailUtils.ts diff --git a/frontend/src/api/infraMonitoring/getInfraAttributeValues.ts b/frontend/src/api/infraMonitoring/getInfraAttributeValues.ts index c1a9531b84..0e6b17ce11 100644 --- a/frontend/src/api/infraMonitoring/getInfraAttributeValues.ts +++ b/frontend/src/api/infraMonitoring/getInfraAttributeValues.ts @@ -1,4 +1,4 @@ -import { ApiBaseInstance } from 'api'; +import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import createQueryParams from 'lib/createQueryParams'; @@ -19,7 +19,7 @@ export const getInfraAttributesValues = async ({ SuccessResponse<IAttributeValuesResponse> | ErrorResponse > => { try { - const response = await ApiBaseInstance.get( + const response = await axios.get( `/hosts/attribute_values?${createQueryParams({ dataSource, attributeKey, diff --git a/frontend/src/api/infraMonitoring/getK8sClustersList.ts b/frontend/src/api/infraMonitoring/getK8sClustersList.ts index f799676568..2da1b214b3 100644 --- a/frontend/src/api/infraMonitoring/getK8sClustersList.ts +++ b/frontend/src/api/infraMonitoring/getK8sClustersList.ts @@ -1,4 +1,4 @@ -import { ApiBaseInstance } from 'api'; +import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; @@ -46,7 +46,7 @@ export const getK8sClustersList = async ( headers?: Record<string, string>, ): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => { try { - const response = await ApiBaseInstance.post('/clusters/list', props, { + const response = await axios.post('/clusters/list', props, { signal, headers, }); diff --git a/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts b/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts index 9ce4643687..b991ce22e7 100644 --- a/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts +++ b/frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts @@ -1,4 +1,4 @@ -import { ApiBaseInstance } from 'api'; +import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; @@ -52,7 +52,7 @@ export const getK8sDeploymentsList = async ( headers?: Record<string, string>, ): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => { try { - const response = await ApiBaseInstance.post('/deployments/list', props, { + const response = await axios.post('/deployments/list', props, { signal, headers, }); diff --git a/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts b/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts index 69835fe5b0..bba2249f4e 100644 --- a/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts +++ b/frontend/src/api/infraMonitoring/getK8sNamespacesList.ts @@ -1,4 +1,4 @@ -import { ApiBaseInstance } from 'api'; +import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; @@ -44,7 +44,7 @@ export const getK8sNamespacesList = async ( headers?: Record<string, string>, ): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => { try { - const response = await ApiBaseInstance.post('/namespaces/list', props, { + const response = await axios.post('/namespaces/list', props, { signal, headers, }); diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx index ccd6d033ad..17080f7d8e 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx @@ -13,6 +13,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils'; import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { @@ -227,11 +228,13 @@ function ClusterDetails({ return { op: 'AND', - items: [ - ...primaryFilters, - ...newFilters, - ...(paginationFilter ? [paginationFilter] : []), - ].filter((item): item is TagFilterItem => item !== undefined), + items: filterDuplicateFilters( + [ + ...primaryFilters, + ...newFilters, + ...(paginationFilter ? [paginationFilter] : []), + ].filter((item): item is TagFilterItem => item !== undefined), + ), }; }); }, @@ -252,12 +255,14 @@ function ClusterDetails({ return { op: 'AND', - items: [ - ...primaryFilters, - ...value.items.filter( - (item) => item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME, - ), - ].filter((item): item is TagFilterItem => item !== undefined), + items: filterDuplicateFilters( + [ + ...primaryFilters, + ...value.items.filter( + (item) => item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + ), }; }); }, @@ -281,15 +286,17 @@ function ClusterDetails({ return { op: 'AND', - items: [ - clusterKindFilter, - clusterNameFilter, - ...value.items.filter( - (item) => - item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && - item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, - ), - ].filter((item): item is TagFilterItem => item !== undefined), + items: filterDuplicateFilters( + [ + clusterKindFilter, + clusterNameFilter, + ...value.items.filter( + (item) => + item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && + item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + ), }; }); }, diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx index 412771f673..0434112a23 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx @@ -45,9 +45,11 @@ import { function K8sClustersList({ isFiltersVisible, handleFilterVisibilityChange, + quickFiltersLastUpdated, }: { isFiltersVisible: boolean; handleFilterVisibilityChange: () => void; + quickFiltersLastUpdated: number; }): JSX.Element { const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( (state) => state.globalTime, @@ -79,12 +81,28 @@ function K8sClustersList({ { value: string; label: string }[] >([]); + const { currentQuery } = useQueryBuilder(); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + // Reset pagination every time quick filters are changed + useEffect(() => { + setCurrentPage(1); + }, [quickFiltersLastUpdated]); + const createFiltersForSelectedRowData = ( selectedRowData: K8sClustersRowData, groupBy: IBuilderQuery['groupBy'], ): IBuilderQuery['filters'] => { const baseFilters: IBuilderQuery['filters'] = { - items: [], + items: [...queryFilters.items], op: 'and', }; @@ -123,6 +141,7 @@ function K8sClustersList({ end: Math.floor(maxTime / 1000000), orderBy, }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); const { @@ -139,8 +158,6 @@ function K8sClustersList({ }, ); - const { currentQuery } = useQueryBuilder(); - const { data: groupByFiltersData, isLoading: isLoadingGroupByFilters, @@ -159,15 +176,6 @@ function K8sClustersList({ K8sCategory.NODES, ); - const queryFilters = useMemo( - () => - currentQuery?.builder?.queryData[0]?.filters || { - items: [], - op: 'and', - }, - [currentQuery?.builder?.queryData], - ); - const query = useMemo(() => { const baseQuery = getK8sClustersListQuery(); const queryPayload = { diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx index dc2583ecde..374fa42f06 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx @@ -57,7 +57,7 @@ export interface K8sClustersRowData { const clusterGroupColumnConfig = { title: ( - <div className="column-header pod-group-header"> + <div className="column-header entity-group-header"> <Group size={14} /> CLUSTER GROUP </div> ), @@ -67,6 +67,7 @@ const clusterGroupColumnConfig = { width: 150, align: 'left', sorter: false, + className: 'column entity-group-header', }; export const getK8sClustersListQuery = (): K8sClustersListPayload => ({ diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx index 85044e5495..d4f4189cf3 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx @@ -13,6 +13,7 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils'; import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; import { @@ -249,11 +250,13 @@ function DeploymentDetails({ return { op: 'AND', - items: [ - ...primaryFilters, - ...newFilters, - ...(paginationFilter ? [paginationFilter] : []), - ].filter((item): item is TagFilterItem => item !== undefined), + items: filterDuplicateFilters( + [ + ...primaryFilters, + ...newFilters, + ...(paginationFilter ? [paginationFilter] : []), + ].filter((item): item is TagFilterItem => item !== undefined), + ), }; }); }, @@ -279,12 +282,14 @@ function DeploymentDetails({ return { op: 'AND', - items: [ - ...primaryFilters, - ...value.items.filter( - (item) => item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME, - ), - ].filter((item): item is TagFilterItem => item !== undefined), + items: filterDuplicateFilters( + [ + ...primaryFilters, + ...value.items.filter( + (item) => item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + ), }; }); }, @@ -311,15 +316,17 @@ function DeploymentDetails({ return { op: 'AND', - items: [ - deploymentKindFilter, - deploymentNameFilter, - ...value.items.filter( - (item) => - item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && - item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, - ), - ].filter((item): item is TagFilterItem => item !== undefined), + items: filterDuplicateFilters( + [ + deploymentKindFilter, + deploymentNameFilter, + ...value.items.filter( + (item) => + item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND && + item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME, + ), + ].filter((item): item is TagFilterItem => item !== undefined), + ), }; }); }, diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss index 24a522df86..e5c3027873 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.styles.scss @@ -4,14 +4,54 @@ padding-left: 20px; } - .ant-table-cell { - min-width: 180px !important; - max-width: 180px !important; - } - .ant-table-row-expand-icon-cell { min-width: 30px !important; max-width: 30px !important; } } } + +.ant-table-cell { + &:has(.deployment-name-header) { + min-width: 250px !important; + max-width: 250px !important; + } +} + +.ant-table-cell { + &:has(.namespace-name-header) { + min-width: 220px !important; + max-width: 220px !important; + } +} + +.ant-table-cell { + &:has(.med-col) { + min-width: 200px !important; + max-width: 200px !important; + } +} + +.ant-table-cell { + &:has(.small-col) { + min-width: 120px !important; + max-width: 120px !important; + } +} + +.progress-container { + display: flex !important; + justify-content: start !important; +} + +.expanded-deployments-list-table { + .ant-table-cell { + min-width: 180px !important; + max-width: 180px !important; + } + + .ant-table-row-expand-icon-cell { + min-width: 30px !important; + max-width: 30px !important; + } +} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx index 713c5495db..76f9942eb5 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx @@ -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 classNames from 'classnames'; import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; @@ -45,9 +46,11 @@ import { function K8sDeploymentsList({ isFiltersVisible, handleFilterVisibilityChange, + quickFiltersLastUpdated, }: { isFiltersVisible: boolean; handleFilterVisibilityChange: () => void; + quickFiltersLastUpdated: number; }): JSX.Element { const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( (state) => state.globalTime, @@ -79,12 +82,28 @@ function K8sDeploymentsList({ { value: string; label: string }[] >([]); + const { currentQuery } = useQueryBuilder(); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + // Reset pagination every time quick filters are changed + useEffect(() => { + setCurrentPage(1); + }, [quickFiltersLastUpdated]); + const createFiltersForSelectedRowData = ( selectedRowData: K8sDeploymentsRowData, groupBy: IBuilderQuery['groupBy'], ): IBuilderQuery['filters'] => { const baseFilters: IBuilderQuery['filters'] = { - items: [], + items: [...queryFilters.items], op: 'and', }; @@ -123,6 +142,7 @@ function K8sDeploymentsList({ end: Math.floor(maxTime / 1000000), orderBy, }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); const { @@ -139,8 +159,6 @@ function K8sDeploymentsList({ }, ); - const { currentQuery } = useQueryBuilder(); - const { data: groupByFiltersData, isLoading: isLoadingGroupByFilters, @@ -159,15 +177,6 @@ function K8sDeploymentsList({ K8sCategory.NODES, ); - const queryFilters = useMemo( - () => - currentQuery?.builder?.queryData[0]?.filters || { - items: [], - op: 'and', - }, - [currentQuery?.builder?.queryData], - ); - const query = useMemo(() => { const baseQuery = getK8sDeploymentsListQuery(); const queryPayload = { @@ -398,18 +407,6 @@ function K8sDeploymentsList({ setselectedDeploymentUID(null); }; - const showsDeploymentsTable = - !isError && - !isLoading && - !isFetching && - !(formattedDeploymentsData.length === 0 && queryFilters.items.length > 0); - - const showNoFilteredDeploymentsMessage = - !isFetching && - !isLoading && - formattedDeploymentsData.length === 0 && - queryFilters.items.length > 0; - const handleGroupByChange = useCallback( (value: IBuilderQuery['groupBy']) => { const groupBy = []; @@ -458,54 +455,55 @@ function K8sDeploymentsList({ /> {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} - {showNoFilteredDeploymentsMessage && ( - <div className="no-filtered-hosts-message-container"> - <div className="no-filtered-hosts-message-content"> - <img - src="/Icons/emptyState.svg" - alt="thinking-emoji" - className="empty-state-svg" - /> - - <Typography.Text className="no-filtered-hosts-message"> - This query had no results. Edit your query and try again! - </Typography.Text> - </div> - </div> - )} + <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: false, + hideOnSinglePage: true, + }} + scroll={{ x: true }} + loading={{ + spinning: isFetching || isLoading, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + locale={{ + emptyText: + isFetching || isLoading ? null : ( + <div className="no-filtered-hosts-message-container"> + <div className="no-filtered-hosts-message-content"> + <img + src="/Icons/emptyState.svg" + alt="thinking-emoji" + className="empty-state-svg" + /> + + <Typography.Text className="no-filtered-hosts-message"> + This query had no results. Edit your query and try again! + </Typography.Text> + </div> + </div> + ), + }} + 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, + }} + /> - {(isFetching || isLoading) && <LoadingContainer />} - - {showsDeploymentsTable && ( - <Table - className="k8s-list-table deployments-list-table" - dataSource={isFetching || isLoading ? [] : formattedDeploymentsData} - columns={columns} - pagination={{ - current: currentPage, - pageSize, - total: totalCount, - showSizeChanger: false, - hideOnSinglePage: true, - }} - scroll={{ x: true }} - loading={{ - spinning: isFetching || isLoading, - indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, - }} - 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, - }} - /> - )} <DeploymentDetails deployment={selectedDeploymentData} isModalTimeSelection diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx index 23b533b05b..eb9e744a69 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -97,7 +97,7 @@ export interface K8sDeploymentsRowData { const deploymentGroupColumnConfig = { title: ( - <div className="column-header pod-group-header"> + <div className="column-header entity-group-header"> <Group size={14} /> DEPLOYMENT GROUP </div> ), @@ -107,6 +107,7 @@ const deploymentGroupColumnConfig = { width: 150, align: 'left', sorter: false, + className: 'column entity-group-header', }; export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ @@ -119,7 +120,11 @@ export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({ const columnsConfig = [ { - title: <div className="column-header-left">Deployment Name</div>, + title: ( + <div className="column-header-left deployment-name-header"> + Deployment Name + </div> + ), dataIndex: 'deploymentName', key: 'deploymentName', ellipsis: true, @@ -128,7 +133,11 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">Namespace Name</div>, + title: ( + <div className="column-header-left namespace-name-header"> + Namespace Name + </div> + ), dataIndex: 'namespaceName', key: 'namespaceName', ellipsis: true, @@ -137,7 +146,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">Available</div>, + title: <div className="column-header-left small-col">Available</div>, dataIndex: 'availableReplicas', key: 'availableReplicas', width: 100, @@ -145,7 +154,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">Desired</div>, + title: <div className="column-header-left small-col">Desired</div>, dataIndex: 'desiredReplicas', key: 'desiredReplicas', width: 80, @@ -153,7 +162,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">CPU Req Usage (%)</div>, + title: <div className="column-header-left med-col">CPU Req Usage (%)</div>, dataIndex: 'cpu_request', key: 'cpu_request', width: 80, @@ -161,7 +170,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">CPU Limit Usage (%)</div>, + title: <div className="column-header-left med-col">CPU Limit Usage (%)</div>, dataIndex: 'cpu_limit', key: 'cpu_limit', width: 50, @@ -169,7 +178,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">CPU Usage (cores)</div>, + title: <div className="column-header- small-col">CPU Usage (cores)</div>, dataIndex: 'cpu', key: 'cpu', width: 80, @@ -177,7 +186,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">Mem Req Usage (%)</div>, + title: <div className="column-header-left med-col">Mem Req Usage (%)</div>, dataIndex: 'memory_request', key: 'memory_request', width: 50, @@ -185,7 +194,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">Mem Limit Usage (%)</div>, + title: <div className="column-header-left med-col">Mem Limit Usage (%)</div>, dataIndex: 'memory_limit', key: 'memory_limit', width: 80, @@ -193,7 +202,7 @@ const columnsConfig = [ align: 'left', }, { - title: <div className="column-header-left">Mem Usage</div>, + title: <div className="column-header-left small-col">Mem Usage</div>, dataIndex: 'memory', key: 'memory', width: 80, @@ -273,14 +282,14 @@ export const formatDataForTable = ( cpu_request: ( <ValidateColumnValueWrapper value={deployment.cpuRequest}> <div className="progress-container"> - <EntityProgressBar value={deployment.cpuRequest} /> + <EntityProgressBar value={deployment.cpuRequest} type="request" /> </div> </ValidateColumnValueWrapper> ), cpu_limit: ( <ValidateColumnValueWrapper value={deployment.cpuLimit}> <div className="progress-container"> - <EntityProgressBar value={deployment.cpuLimit} /> + <EntityProgressBar value={deployment.cpuLimit} type="limit" /> </div> </ValidateColumnValueWrapper> ), @@ -292,14 +301,14 @@ export const formatDataForTable = ( memory_request: ( <ValidateColumnValueWrapper value={deployment.memoryRequest}> <div className="progress-container"> - <EntityProgressBar value={deployment.memoryRequest} /> + <EntityProgressBar value={deployment.memoryRequest} type="request" /> </div> </ValidateColumnValueWrapper> ), memory_limit: ( <ValidateColumnValueWrapper value={deployment.memoryLimit}> <div className="progress-container"> - <EntityProgressBar value={deployment.memoryLimit} /> + <EntityProgressBar value={deployment.memoryLimit} type="limit" /> </div> </ValidateColumnValueWrapper> ), diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.styles.scss b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.styles.scss index aa2afd0c6b..76ae436183 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.styles.scss @@ -876,3 +876,7 @@ } } } + +.entity-group-header { + width: 300px !important; +} diff --git a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index 7cb3e76a00..5ba6995673 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -107,7 +107,7 @@ export default function InfraMonitoringK8s(): JSX.Element { size={14} className="k8s-quick-filters-category-label-icon" /> - <Typography.Text>Namespace</Typography.Text> + <Typography.Text>Namespaces</Typography.Text> </div> </div> ), @@ -331,6 +331,7 @@ export default function InfraMonitoringK8s(): JSX.Element { <K8sDeploymentsList isFiltersVisible={showFilters} handleFilterVisibilityChange={handleFilterVisibilityChange} + quickFiltersLastUpdated={quickFiltersLastUpdated} /> )} @@ -338,6 +339,7 @@ export default function InfraMonitoringK8s(): JSX.Element { <K8sNamespacesList isFiltersVisible={showFilters} handleFilterVisibilityChange={handleFilterVisibilityChange} + quickFiltersLastUpdated={quickFiltersLastUpdated} /> )} @@ -345,6 +347,7 @@ export default function InfraMonitoringK8s(): JSX.Element { <K8sClustersList isFiltersVisible={showFilters} handleFilterVisibilityChange={handleFilterVisibilityChange} + quickFiltersLastUpdated={quickFiltersLastUpdated} /> )} </div> diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss index 231314989b..9181a24e21 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.styles.scss @@ -1,7 +1,7 @@ .infra-monitoring-container { .namespaces-list-table { .expanded-table-container { - padding-left: 40px; + padding-left: 80px; } .ant-table-cell { diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx index 9f5b43c1d5..a137989340 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx @@ -45,9 +45,11 @@ import { function K8sNamespacesList({ isFiltersVisible, handleFilterVisibilityChange, + quickFiltersLastUpdated, }: { isFiltersVisible: boolean; handleFilterVisibilityChange: () => void; + quickFiltersLastUpdated: number; }): JSX.Element { const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( (state) => state.globalTime, @@ -79,12 +81,28 @@ function K8sNamespacesList({ { value: string; label: string }[] >([]); + const { currentQuery } = useQueryBuilder(); + + const queryFilters = useMemo( + () => + currentQuery?.builder?.queryData[0]?.filters || { + items: [], + op: 'and', + }, + [currentQuery?.builder?.queryData], + ); + + // Reset pagination every time quick filters are changed + useEffect(() => { + setCurrentPage(1); + }, [quickFiltersLastUpdated]); + const createFiltersForSelectedRowData = ( selectedRowData: K8sNamespacesRowData, groupBy: IBuilderQuery['groupBy'], ): IBuilderQuery['filters'] => { const baseFilters: IBuilderQuery['filters'] = { - items: [], + items: [...queryFilters.items], op: 'and', }; @@ -123,6 +141,7 @@ function K8sNamespacesList({ end: Math.floor(maxTime / 1000000), orderBy, }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [minTime, maxTime, orderBy, selectedRowData, groupBy]); const { @@ -139,8 +158,6 @@ function K8sNamespacesList({ }, ); - const { currentQuery } = useQueryBuilder(); - const { data: groupByFiltersData, isLoading: isLoadingGroupByFilters, @@ -159,15 +176,6 @@ function K8sNamespacesList({ K8sCategory.NODES, ); - const queryFilters = useMemo( - () => - currentQuery?.builder?.queryData[0]?.filters || { - items: [], - op: 'and', - }, - [currentQuery?.builder?.queryData], - ); - const query = useMemo(() => { const baseQuery = getK8sNamespacesListQuery(); const queryPayload = { @@ -396,18 +404,6 @@ function K8sNamespacesList({ setselectedNamespaceUID(null); }; - const showsNamespacesTable = - !isError && - !isLoading && - !isFetching && - !(formattedNamespacesData.length === 0 && queryFilters.items.length > 0); - - const showNoFilteredNamespacesMessage = - !isFetching && - !isLoading && - formattedNamespacesData.length === 0 && - queryFilters.items.length > 0; - const handleGroupByChange = useCallback( (value: IBuilderQuery['groupBy']) => { const groupBy = []; @@ -456,54 +452,52 @@ function K8sNamespacesList({ /> {isError && <Typography>{data?.error || 'Something went wrong'}</Typography>} - {showNoFilteredNamespacesMessage && ( - <div className="no-filtered-hosts-message-container"> - <div className="no-filtered-hosts-message-content"> - <img - src="/Icons/emptyState.svg" - alt="thinking-emoji" - className="empty-state-svg" - /> - - <Typography.Text className="no-filtered-hosts-message"> - This query had no results. Edit your query and try again! - </Typography.Text> - </div> - </div> - )} - - {(isFetching || isLoading) && <LoadingContainer />} - - {showsNamespacesTable && ( - <Table - className="k8s-list-table namespaces-list-table" - dataSource={isFetching || isLoading ? [] : formattedNamespacesData} - columns={columns} - pagination={{ - current: currentPage, - pageSize, - total: totalCount, - showSizeChanger: false, - hideOnSinglePage: true, - }} - scroll={{ x: true }} - loading={{ - spinning: isFetching || isLoading, - indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, - }} - 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, - }} - /> - )} + <Table + className="k8s-list-table namespaces-list-table" + dataSource={isFetching || isLoading ? [] : formattedNamespacesData} + columns={columns} + pagination={{ + current: currentPage, + pageSize, + total: totalCount, + showSizeChanger: false, + hideOnSinglePage: true, + }} + scroll={{ x: true }} + loading={{ + spinning: isFetching || isLoading, + indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />, + }} + locale={{ + emptyText: + isFetching || isLoading ? null : ( + <div className="no-filtered-hosts-message-container"> + <div className="no-filtered-hosts-message-content"> + <img + src="/Icons/emptyState.svg" + alt="thinking-emoji" + className="empty-state-svg" + /> + + <Typography.Text className="no-filtered-hosts-message"> + This query had no results. Edit your query and try again! + </Typography.Text> + </div> + </div> + ), + }} + 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, + }} + /> <NamespaceDetails namespace={selectedNamespaceData} isModalTimeSelection diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx index b0ba92d6d9..d1d491a78b 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx @@ -50,7 +50,7 @@ export interface K8sNamespacesRowData { const namespaceGroupColumnConfig = { title: ( - <div className="column-header pod-group-header"> + <div className="column-header entity-group-header"> <Group size={14} /> NAMESPACE GROUP </div> ), @@ -60,6 +60,7 @@ const namespaceGroupColumnConfig = { width: 150, align: 'left', sorter: false, + className: 'column entity-group-header', }; export const getK8sNamespacesListQuery = (): K8sNamespacesListPayload => ({ @@ -112,7 +113,7 @@ export const getK8sNamespacesListColumns = ( ): ColumnType<K8sNamespacesRowData>[] => { if (groupBy.length > 0) { const filteredColumns = [...columnsConfig].filter( - (column) => column.key !== 'namespaceName' && column.key !== 'clusterName', + (column) => column.key !== 'namespaceName', ); filteredColumns.unshift(namespaceGroupColumnConfig); return filteredColumns as ColumnType<K8sNamespacesRowData>[]; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx index 372f236329..7d7d3ae4d0 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx @@ -13,9 +13,9 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils'; import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import NodeEvents from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents'; -import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils'; import { CustomTimeType, Time, diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx index 4628143b1b..49adce37d8 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx @@ -170,7 +170,7 @@ function K8sPodsList({ selectedRowData: K8sPodsRowData, ): IBuilderQuery['filters'] => { const baseFilters: IBuilderQuery['filters'] = { - items: [...query.filters.items], + items: [...queryFilters.items], op: 'and', }; diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx index c671134d50..49bf0caa34 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx @@ -14,9 +14,9 @@ import { initialQueryState, } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils'; import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils'; -import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils'; import { CustomTimeType, Time, diff --git a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx index 605ba7d3d9..c76c795547 100644 --- a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx @@ -12,6 +12,7 @@ import { ResizeTable } from 'components/ResizeTable'; import FieldRenderer from 'container/LogDetailedView/FieldRenderer'; import { DataType } from 'container/LogDetailedView/TableView'; import { useMemo } from 'react'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { getInvalidValueTooltipText, K8sCategory } from './constants'; @@ -233,3 +234,20 @@ export function MetricsTable({ </div> ); } + +export const filterDuplicateFilters = ( + filters: TagFilterItem[], +): TagFilterItem[] => { + const uniqueFilters = []; + const seenIds = new Set(); + + // eslint-disable-next-line no-restricted-syntax + for (const filter of filters) { + if (!seenIds.has(filter.id)) { + seenIds.add(filter.id); + uniqueFilters.push(filter); + } + } + + return uniqueFilters; +}; diff --git a/frontend/src/container/InfraMonitoringK8s/entityDetailUtils.ts b/frontend/src/container/InfraMonitoringK8s/entityDetailUtils.ts deleted file mode 100644 index 66315adb4d..0000000000 --- a/frontend/src/container/InfraMonitoringK8s/entityDetailUtils.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; - -export const filterDuplicateFilters = ( - filters: TagFilterItem[], -): TagFilterItem[] => { - const uniqueFilters = []; - const seenIds = new Set(); - - // eslint-disable-next-line no-restricted-syntax - for (const filter of filters) { - if (!seenIds.has(filter.id)) { - seenIds.add(filter.id); - uniqueFilters.push(filter); - } - } - - return uniqueFilters; -}; From ba1b64ff7d1bba4cf49e2674fd5e95f2d2c3f9bb Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Sun, 19 Jan 2025 13:34:35 +0530 Subject: [PATCH 18/20] chore: fix metrics table height --- frontend/src/container/InfraMonitoringK8s/commonUtils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx index c76c795547..095a438b5c 100644 --- a/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/commonUtils.tsx @@ -228,7 +228,7 @@ export function MetricsTable({ columns={columnsData} tableLayout="fixed" pagination={{ pageSize: 10, showSizeChanger: false }} - scroll={{ y: 200 }} + scroll={{ y: 180 }} sticky /> </div> From 0cefb5af062d5bb8b31b450d75ee14b6b719908e Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Tue, 21 Jan 2025 20:32:26 +0530 Subject: [PATCH 19/20] chore: bugs fixes from testing --- .../InfraMonitoringK8s/Clusters/K8sClustersList.tsx | 4 +++- .../container/InfraMonitoringK8s/Clusters/utils.tsx | 6 +++--- .../Deployments/K8sDeploymentsList.tsx | 6 ++++-- .../InfraMonitoringK8s/Deployments/utils.tsx | 6 ++++-- .../EntityDetailsUtils/EntityLogs/EntityLogs.tsx | 12 ++++++++++++ .../Namespaces/K8sNamespacesList.tsx | 2 ++ .../InfraMonitoringK8s/Namespaces/utils.tsx | 4 ++-- .../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 3 ++- .../InfraMonitoringK8s/Pods/K8sPodLists.tsx | 3 ++- 9 files changed, 34 insertions(+), 12 deletions(-) diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx index 0434112a23..da29494f45 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx @@ -291,7 +291,7 @@ function K8sClustersList({ const handleRowClick = (record: K8sClustersRowData): void => { if (groupBy.length === 0) { setSelectedRowData(null); - setselectedClusterName(record.key); + setselectedClusterName(record.clusterUID); } else { handleGroupByRowClick(record); } @@ -418,6 +418,8 @@ function K8sClustersList({ } } + // Reset pagination on switching to groupBy + setCurrentPage(1); setGroupBy(groupBy); setExpandedRowKeys([]); }, diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx index 374fa42f06..f03804537e 100644 --- a/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx @@ -161,9 +161,9 @@ export const formatDataForTable = ( data: K8sClustersData[], groupBy: IBuilderQuery['groupBy'], ): K8sClustersRowData[] => - data.map((cluster) => ({ - key: cluster.meta.k8s_cluster_name, - clusterUID: cluster.clusterUID, + data.map((cluster, index) => ({ + key: index.toString(), + clusterUID: cluster.meta.k8s_cluster_name, clusterName: ( <Tooltip title={cluster.meta.k8s_cluster_name}> {cluster.meta.k8s_cluster_name} diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx index 76f9942eb5..5e5d0ff489 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx @@ -296,13 +296,13 @@ function K8sDeploymentsList({ const handleRowClick = (record: K8sDeploymentsRowData): void => { if (groupBy.length === 0) { setSelectedRowData(null); - setselectedDeploymentUID(record.key); + setselectedDeploymentUID(record.deploymentUID); } else { handleGroupByRowClick(record); } logEvent('Infra Monitoring: K8s deployment list item clicked', { - deploymentUID: record.key, + deploymentUID: record.deploymentName, }); }; @@ -423,6 +423,8 @@ function K8sDeploymentsList({ } } + // Reset pagination on switching to groupBy + setCurrentPage(1); setGroupBy(groupBy); setExpandedRowKeys([]); }, diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx index eb9e744a69..28e8ab79cc 100644 --- a/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx @@ -80,6 +80,7 @@ export const defaultAddedColumns: IEntityColumn[] = [ export interface K8sDeploymentsRowData { key: string; + deploymentUID: string; deploymentName: React.ReactNode; availableReplicas: React.ReactNode; desiredReplicas: React.ReactNode; @@ -252,8 +253,9 @@ export const formatDataForTable = ( data: K8sDeploymentsData[], groupBy: IBuilderQuery['groupBy'], ): K8sDeploymentsRowData[] => - data.map((deployment) => ({ - key: deployment.meta.k8s_deployment_name, + data.map((deployment, index) => ({ + key: index.toString(), + deploymentUID: deployment.meta.k8s_deployment_name, deploymentName: ( <Tooltip title={deployment.meta.k8s_deployment_name}> {deployment.meta.k8s_deployment_name} diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx index cb7d4d63c5..c73865eb80 100644 --- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx +++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs/EntityLogs.tsx @@ -132,6 +132,18 @@ function EntityLogs({ data={logToRender} linesPerRow={5} fontSize={FontSize.MEDIUM} + selectedFields={[ + { + dataType: 'string', + type: '', + name: 'body', + }, + { + dataType: 'string', + type: '', + name: 'timestamp', + }, + ]} /> ), [], diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx index a137989340..0469bff802 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx @@ -420,6 +420,8 @@ function K8sNamespacesList({ } } + // Reset pagination on switching to groupBy + setCurrentPage(1); setGroupBy(groupBy); setExpandedRowKeys([]); }, diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx index d1d491a78b..d566c86910 100644 --- a/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx @@ -147,8 +147,8 @@ export const formatDataForTable = ( data: K8sNamespacesData[], groupBy: IBuilderQuery['groupBy'], ): K8sNamespacesRowData[] => - data.map((namespace) => ({ - key: namespace.namespaceName, + data.map((namespace, index) => ({ + key: index.toString(), namespaceUID: namespace.namespaceName, namespaceName: namespace.namespaceName, clusterName: namespace.meta.k8s_cluster_name, diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx index 63cd557d32..32b0b7a9a3 100644 --- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -406,7 +406,8 @@ function K8sNodesList({ groupBy.push(key); } } - + // Reset pagination on switching to groupBy + setCurrentPage(1); setGroupBy(groupBy); setExpandedRowKeys([]); }, diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx index 49adce37d8..d8a6e5583e 100644 --- a/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx +++ b/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx @@ -298,8 +298,9 @@ function K8sPodsList({ } } + // Reset pagination on switching to groupBy + setCurrentPage(1); setGroupBy(groupBy); - setExpandedRowKeys([]); }, [groupByFiltersData], From a92bb73de487c751d2b199d7b849bfa1fdd1ec5c Mon Sep 17 00:00:00 2001 From: amlannandy <amlannandy5@gmail.com> Date: Tue, 21 Jan 2025 20:39:26 +0530 Subject: [PATCH 20/20] chore: fix issue in hosts logs --- .../HostMetricsLogs/HostMetricsLogs.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/components/HostMetricsDetail/HostMetricsLogs/HostMetricsLogs.tsx b/frontend/src/components/HostMetricsDetail/HostMetricsLogs/HostMetricsLogs.tsx index 3b8fc88a35..bb5c17a506 100644 --- a/frontend/src/components/HostMetricsDetail/HostMetricsLogs/HostMetricsLogs.tsx +++ b/frontend/src/components/HostMetricsDetail/HostMetricsLogs/HostMetricsLogs.tsx @@ -127,6 +127,18 @@ function HostMetricsLogs({ data={logToRender} linesPerRow={5} fontSize={FontSize.MEDIUM} + selectedFields={[ + { + dataType: 'string', + type: '', + name: 'body', + }, + { + dataType: 'string', + type: '', + name: 'timestamp', + }, + ]} /> ), [],