From 81e8e16025cd3cd9007489a6c118c76c9f60bbe8 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] feat: clusters implementation in k8s infra monitoring (#6628) --- .../ClusterDetails/ClusterDetails.styles.scss | 247 +++ .../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 + .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 8 + 21 files changed, 3857 insertions(+) create mode 100644 frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.styles.scss 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 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 00000000000..52309b5da54 --- /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/Events/ClusterEvents.styles.scss b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/Events/ClusterEvents.styles.scss new file mode 100644 index 00000000000..3d76fe17720 --- /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 00000000000..c2b013749b9 --- /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; + attributes_number?: Record; + attributes_string?: Record; + resources_string?: Record; + scope_name?: string; + scope_string?: Record; + 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 = [ + { 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 => ( + + ); + + 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, + ) => void; + record: EventDataType; + }): JSX.Element => + expanded ? ( + + onExpand( + record, + (e as unknown) as React.MouseEvent, + ) + } + /> + ) : ( + + onExpand( + record, + (e as unknown) as React.MouseEvent, + ) + } + /> + ); + + return ( +
+
+
+ {query && ( + + )} +
+
+ +
+
+ + {isLoading && } + + {!isLoading && !isError && formattedClusterEvents.length === 0 && ( + + )} + + {isError && !isLoading && } + + {!isLoading && !isError && formattedClusterEvents.length > 0 && ( +
+
+ + 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} + /> +
+
+ )} + + {!isError && formattedClusterEvents.length > 0 && ( +
+ + + + + {(isFetching || isLoading) && ( + + )} +
+ )} +
+ ); +} 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 00000000000..c253774f68b --- /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 ( +
+ + No events found for this + cluster in the selected time range. + +
+ ); +} 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 00000000000..5b27953b35f --- /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 00000000000..31af59a90e3 --- /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 00000000000..c04a4a49c02 --- /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 00000000000..0f9ad02709f --- /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([]); + const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false); + const [restFilters, setRestFilters] = useState([]); + const [resetLogsList, setResetLogsList] = useState(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 => ( + + ), + [], + ); + + 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 ? ( +
Loading more logs ...
+ ) : hasReachedEndOfLogs ? ( +
*** End ***
+ ) : null} + + ), + [isFetching, hasReachedEndOfLogs], + ); + + const renderContent = useMemo( + () => ( + + + + + + ), + [logs, loadMoreLogs, getItemContent, renderFooter], + ); + + return ( +
+ {isLoading && } + {!isLoading && !isError && logs.length === 0 && } + {isError && !isLoading && } + {!isLoading && !isError && logs.length > 0 && ( +
{renderContent}
+ )} +
+ ); +} + +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 00000000000..d5f08bc7d98 --- /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 ( +
+
+
+ {query && ( + + )} +
+
+ +
+
+ +
+ ); +} + +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 00000000000..ebcecff9698 --- /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 ( +
+ + No logs found for this + cluster in the selected time range. + +
+ ); +} 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 00000000000..ae2f7c6cfb0 --- /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 00000000000..5210b7ab20a --- /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 00000000000..804fca7485d --- /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 00000000000..dd717399133 --- /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> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + })), + ); + + const isDarkMode = useIsDarkMode(); + const graphRef = useRef(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, unknown>, + idx: number, + ): JSX.Element => { + if (query.isLoading) { + return ; + } + + if (query.error) { + const errorMessage = + (query.error as Error)?.message || 'Something went wrong'; + return
{errorMessage}
; + } + + const { panelType } = (query.data?.params as any).compositeQuery; + + return ( +
+ {panelType === PANEL_TYPES.TABLE ? ( + + ) : ( + + )} +
+ ); + }; + + return ( + <> +
+
+ +
+
+ + {queries.map((query, idx) => ( + + {clusterWidgetInfo[idx].title} + + {renderCardContent(query, idx)} + + + ))} + + + ); +} + +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 00000000000..595fb4bdd91 --- /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 00000000000..cf254e714fc --- /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 00000000000..8f6c5c01dbe --- /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 00000000000..999b2275a5c --- /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([]); + const [offset] = useState(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( + 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 ( +
+
+
+ {query && ( + + )} +
+
+ +
+
+ + {isError && {data?.error || 'Something went wrong'}} + + {isLoading && traces.length === 0 && } + + {isDataEmpty && !hasAdditionalFilters && ( + + )} + + {isDataEmpty && hasAdditionalFilters && ( + + )} + + {!isError && traces.length > 0 && ( +
+ + +
+ )} +
+ ); +} + +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 00000000000..52b89abe8ef --- /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 00000000000..bc16a3b372b --- /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/InfraMonitoringK8s.tsx b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx index b28883c0151..184948ad9ba 100644 --- a/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx +++ b/frontend/src/container/InfraMonitoringK8s/InfraMonitoringK8s.tsx @@ -396,6 +396,14 @@ export default function InfraMonitoringK8s(): JSX.Element { quickFiltersLastUpdated={quickFiltersLastUpdated} /> )} + + {selectedCategory === K8sCategories.CLUSTERS && ( + + )}