2025-08-05 20:45:21 +07:00
|
|
|
/* eslint-disable no-nested-ternary */
|
2025-03-05 15:23:23 +05:30
|
|
|
import './Summary.styles.scss';
|
|
|
|
|
|
2025-02-05 18:27:12 +05:30
|
|
|
import * as Sentry from '@sentry/react';
|
2025-06-10 11:42:49 +07:00
|
|
|
import logEvent from 'api/common/logEvent';
|
2025-08-17 15:19:04 +07:00
|
|
|
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
2025-06-10 11:18:12 +07:00
|
|
|
import { initialQueriesMap } from 'constants/queryBuilder';
|
2025-03-05 15:23:23 +05:30
|
|
|
import { usePageSize } from 'container/InfraMonitoringK8s/utils';
|
2025-08-05 20:45:21 +07:00
|
|
|
import NoLogs from 'container/NoLogs/NoLogs';
|
2025-03-05 15:23:23 +05:30
|
|
|
import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList';
|
|
|
|
|
import { useGetMetricsTreeMap } from 'hooks/metricsExplorer/useGetMetricsTreeMap';
|
2025-02-05 18:27:12 +05:30
|
|
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
2025-06-10 11:42:49 +07:00
|
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
2025-03-05 15:23:23 +05:30
|
|
|
import { useSelector } from 'react-redux';
|
2025-05-19 11:54:05 +07:00
|
|
|
import { useSearchParams } from 'react-router-dom-v5-compat';
|
2025-03-05 15:23:23 +05:30
|
|
|
import { AppState } from 'store/reducers';
|
|
|
|
|
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
2025-08-05 20:45:21 +07:00
|
|
|
import { DataSource } from 'types/common/queryBuilder';
|
2025-03-05 15:23:23 +05:30
|
|
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
|
|
|
|
2025-06-10 11:42:49 +07:00
|
|
|
import { MetricsExplorerEventKeys, MetricsExplorerEvents } from '../events';
|
2025-04-01 17:17:38 +05:30
|
|
|
import InspectModal from '../Inspect';
|
2025-03-11 12:02:08 +05:30
|
|
|
import MetricDetails from '../MetricDetails';
|
2025-08-05 20:45:21 +07:00
|
|
|
import { MetricsLoading } from '../MetricsLoading/MetricsLoading';
|
2025-05-19 11:54:05 +07:00
|
|
|
import {
|
|
|
|
|
IS_INSPECT_MODAL_OPEN_KEY,
|
|
|
|
|
IS_METRIC_DETAILS_OPEN_KEY,
|
|
|
|
|
SELECTED_METRIC_NAME_KEY,
|
2025-06-10 11:18:12 +07:00
|
|
|
SUMMARY_FILTERS_KEY,
|
2025-05-19 11:54:05 +07:00
|
|
|
} from './constants';
|
2025-03-05 15:23:23 +05:30
|
|
|
import MetricsSearch from './MetricsSearch';
|
|
|
|
|
import MetricsTable from './MetricsTable';
|
|
|
|
|
import MetricsTreemap from './MetricsTreemap';
|
|
|
|
|
import { OrderByPayload, TreemapViewType } from './types';
|
|
|
|
|
import {
|
|
|
|
|
convertNanoToMilliseconds,
|
|
|
|
|
formatDataForMetricsTable,
|
|
|
|
|
getMetricsListQuery,
|
|
|
|
|
} from './utils';
|
2025-02-05 18:27:12 +05:30
|
|
|
|
2025-04-23 17:36:33 +07:00
|
|
|
const DEFAULT_ORDER_BY: OrderByPayload = {
|
|
|
|
|
columnName: 'samples',
|
|
|
|
|
order: 'desc',
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-05 18:27:12 +05:30
|
|
|
function Summary(): JSX.Element {
|
2025-03-05 15:23:23 +05:30
|
|
|
const { pageSize, setPageSize } = usePageSize('metricsExplorer');
|
|
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
2025-04-23 17:36:33 +07:00
|
|
|
const [orderBy, setOrderBy] = useState<OrderByPayload>(DEFAULT_ORDER_BY);
|
2025-03-05 15:23:23 +05:30
|
|
|
const [heatmapView, setHeatmapView] = useState<TreemapViewType>(
|
2025-03-18 16:27:14 +05:30
|
|
|
TreemapViewType.TIMESERIES,
|
2025-03-05 15:23:23 +05:30
|
|
|
);
|
2025-05-19 11:54:05 +07:00
|
|
|
|
|
|
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
|
|
|
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(
|
|
|
|
|
() => searchParams.get(IS_METRIC_DETAILS_OPEN_KEY) === 'true' || false,
|
|
|
|
|
);
|
|
|
|
|
const [isInspectModalOpen, setIsInspectModalOpen] = useState(
|
|
|
|
|
() => searchParams.get(IS_INSPECT_MODAL_OPEN_KEY) === 'true' || false,
|
|
|
|
|
);
|
|
|
|
|
const [selectedMetricName, setSelectedMetricName] = useState(
|
|
|
|
|
() => searchParams.get(SELECTED_METRIC_NAME_KEY) || null,
|
2025-03-11 12:02:08 +05:30
|
|
|
);
|
2025-03-05 15:23:23 +05:30
|
|
|
|
|
|
|
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
|
|
|
|
(state) => state.globalTime,
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-10 11:18:12 +07:00
|
|
|
const queryFilters: TagFilter = useMemo(() => {
|
|
|
|
|
const encodedFilters = searchParams.get(SUMMARY_FILTERS_KEY);
|
|
|
|
|
if (encodedFilters) {
|
|
|
|
|
return JSON.parse(encodedFilters);
|
|
|
|
|
}
|
2025-04-23 17:36:33 +07:00
|
|
|
return {
|
2025-06-10 11:18:12 +07:00
|
|
|
items: [],
|
|
|
|
|
op: 'AND',
|
2025-04-23 17:36:33 +07:00
|
|
|
};
|
2025-06-10 11:18:12 +07:00
|
|
|
}, [searchParams]);
|
2025-04-23 17:36:33 +07:00
|
|
|
|
2025-06-10 11:42:49 +07:00
|
|
|
useEffect(() => {
|
|
|
|
|
logEvent(MetricsExplorerEvents.TabChanged, {
|
|
|
|
|
[MetricsExplorerEventKeys.Tab]: 'summary',
|
2025-07-07 23:34:17 +07:00
|
|
|
[MetricsExplorerEventKeys.TimeRange]: {
|
|
|
|
|
startTime: convertNanoToMilliseconds(minTime),
|
|
|
|
|
endTime: convertNanoToMilliseconds(maxTime),
|
|
|
|
|
},
|
2025-06-10 11:42:49 +07:00
|
|
|
});
|
2025-07-07 23:34:17 +07:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2025-06-10 11:42:49 +07:00
|
|
|
}, []);
|
|
|
|
|
|
2025-05-19 11:54:05 +07:00
|
|
|
// This is used to avoid the filters from being serialized with the id
|
2025-06-10 11:18:12 +07:00
|
|
|
const queryFiltersWithoutId = useMemo(() => {
|
2025-05-19 11:54:05 +07:00
|
|
|
const filtersWithoutId = {
|
2025-06-10 11:18:12 +07:00
|
|
|
...queryFilters,
|
|
|
|
|
items: queryFilters.items.map(({ id, ...rest }) => rest),
|
2025-05-19 11:54:05 +07:00
|
|
|
};
|
|
|
|
|
return JSON.stringify(filtersWithoutId);
|
2025-06-10 11:18:12 +07:00
|
|
|
}, [queryFilters]);
|
2025-03-05 15:23:23 +05:30
|
|
|
|
|
|
|
|
const metricsListQuery = useMemo(() => {
|
|
|
|
|
const baseQuery = getMetricsListQuery();
|
|
|
|
|
return {
|
|
|
|
|
...baseQuery,
|
|
|
|
|
limit: pageSize,
|
|
|
|
|
offset: (currentPage - 1) * pageSize,
|
|
|
|
|
filters: queryFilters,
|
|
|
|
|
start: convertNanoToMilliseconds(minTime),
|
|
|
|
|
end: convertNanoToMilliseconds(maxTime),
|
|
|
|
|
orderBy,
|
|
|
|
|
};
|
|
|
|
|
}, [queryFilters, minTime, maxTime, orderBy, pageSize, currentPage]);
|
|
|
|
|
|
|
|
|
|
const metricsTreemapQuery = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
limit: 100,
|
|
|
|
|
filters: queryFilters,
|
|
|
|
|
treemap: heatmapView,
|
|
|
|
|
start: convertNanoToMilliseconds(minTime),
|
|
|
|
|
end: convertNanoToMilliseconds(maxTime),
|
|
|
|
|
}),
|
|
|
|
|
[queryFilters, heatmapView, minTime, maxTime],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
data: metricsData,
|
|
|
|
|
isLoading: isMetricsLoading,
|
|
|
|
|
isFetching: isMetricsFetching,
|
2025-03-19 17:31:36 +05:30
|
|
|
isError: isMetricsError,
|
2025-03-05 15:23:23 +05:30
|
|
|
} = useGetMetricsList(metricsListQuery, {
|
2025-05-07 12:18:56 +07:00
|
|
|
enabled: !!metricsListQuery && !isInspectModalOpen,
|
2025-06-10 11:18:12 +07:00
|
|
|
queryKey: [
|
|
|
|
|
'metricsList',
|
|
|
|
|
queryFiltersWithoutId,
|
|
|
|
|
orderBy,
|
|
|
|
|
pageSize,
|
|
|
|
|
currentPage,
|
|
|
|
|
minTime,
|
|
|
|
|
maxTime,
|
|
|
|
|
],
|
2025-03-05 15:23:23 +05:30
|
|
|
});
|
|
|
|
|
|
2025-05-09 11:41:41 +07:00
|
|
|
const isListViewError = useMemo(
|
2025-05-09 20:25:43 +07:00
|
|
|
() => isMetricsError || !!(metricsData && metricsData.statusCode !== 200),
|
2025-05-09 11:41:41 +07:00
|
|
|
[isMetricsError, metricsData],
|
|
|
|
|
);
|
|
|
|
|
|
2025-03-05 15:23:23 +05:30
|
|
|
const {
|
|
|
|
|
data: treeMapData,
|
|
|
|
|
isLoading: isTreeMapLoading,
|
|
|
|
|
isFetching: isTreeMapFetching,
|
2025-03-19 17:31:36 +05:30
|
|
|
isError: isTreeMapError,
|
2025-03-05 15:23:23 +05:30
|
|
|
} = useGetMetricsTreeMap(metricsTreemapQuery, {
|
2025-05-07 12:18:56 +07:00
|
|
|
enabled: !!metricsTreemapQuery && !isInspectModalOpen,
|
2025-06-10 11:18:12 +07:00
|
|
|
queryKey: [
|
|
|
|
|
'metricsTreemap',
|
|
|
|
|
queryFiltersWithoutId,
|
|
|
|
|
heatmapView,
|
|
|
|
|
minTime,
|
|
|
|
|
maxTime,
|
|
|
|
|
],
|
2025-03-05 15:23:23 +05:30
|
|
|
});
|
|
|
|
|
|
2025-05-09 11:41:41 +07:00
|
|
|
const isProportionViewError = useMemo(
|
|
|
|
|
() => isTreeMapError || treeMapData?.statusCode !== 200,
|
|
|
|
|
[isTreeMapError, treeMapData],
|
|
|
|
|
);
|
|
|
|
|
|
2025-03-05 15:23:23 +05:30
|
|
|
const handleFilterChange = useCallback(
|
|
|
|
|
(value: TagFilter) => {
|
2025-05-19 11:54:05 +07:00
|
|
|
setSearchParams({
|
2025-06-10 11:18:12 +07:00
|
|
|
...Object.fromEntries(searchParams.entries()),
|
|
|
|
|
[SUMMARY_FILTERS_KEY]: JSON.stringify(value),
|
2025-05-19 11:54:05 +07:00
|
|
|
});
|
2025-03-05 15:23:23 +05:30
|
|
|
setCurrentPage(1);
|
2025-07-07 23:34:17 +07:00
|
|
|
if (value.items.length > 0) {
|
|
|
|
|
logEvent(MetricsExplorerEvents.FilterApplied, {
|
|
|
|
|
[MetricsExplorerEventKeys.Tab]: 'summary',
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-03-05 15:23:23 +05:30
|
|
|
},
|
2025-06-10 11:18:12 +07:00
|
|
|
[setSearchParams, searchParams],
|
2025-03-05 15:23:23 +05:30
|
|
|
);
|
|
|
|
|
|
2025-06-10 11:18:12 +07:00
|
|
|
const searchQuery = useMemo(
|
2025-03-05 15:23:23 +05:30
|
|
|
() => ({
|
2025-06-10 11:18:12 +07:00
|
|
|
...initialQueriesMap.metrics.builder.queryData[0],
|
|
|
|
|
filters: queryFilters,
|
2025-08-17 15:19:04 +07:00
|
|
|
filter: convertFiltersToExpression(queryFilters),
|
2025-03-05 15:23:23 +05:30
|
|
|
}),
|
2025-06-10 11:18:12 +07:00
|
|
|
[queryFilters],
|
2025-03-05 15:23:23 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const onPaginationChange = (page: number, pageSize: number): void => {
|
|
|
|
|
setCurrentPage(page);
|
|
|
|
|
setPageSize(pageSize);
|
2025-06-10 11:42:49 +07:00
|
|
|
logEvent(MetricsExplorerEvents.PageNumberChanged, {
|
|
|
|
|
[MetricsExplorerEventKeys.Tab]: 'summary',
|
|
|
|
|
[MetricsExplorerEventKeys.PageNumber]: page,
|
|
|
|
|
});
|
|
|
|
|
logEvent(MetricsExplorerEvents.PageSizeChanged, {
|
|
|
|
|
[MetricsExplorerEventKeys.Tab]: 'summary',
|
|
|
|
|
[MetricsExplorerEventKeys.PageSize]: pageSize,
|
|
|
|
|
});
|
2025-03-05 15:23:23 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formattedMetricsData = useMemo(
|
|
|
|
|
() => formatDataForMetricsTable(metricsData?.payload?.data?.metrics || []),
|
|
|
|
|
[metricsData],
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-10 11:42:49 +07:00
|
|
|
const openMetricDetails = (
|
|
|
|
|
metricName: string,
|
|
|
|
|
view: 'list' | 'treemap',
|
|
|
|
|
): void => {
|
2025-03-11 12:02:08 +05:30
|
|
|
setSelectedMetricName(metricName);
|
|
|
|
|
setIsMetricDetailsOpen(true);
|
2025-05-19 11:54:05 +07:00
|
|
|
setSearchParams({
|
2025-06-10 11:18:12 +07:00
|
|
|
...Object.fromEntries(searchParams.entries()),
|
2025-05-19 11:54:05 +07:00
|
|
|
[IS_METRIC_DETAILS_OPEN_KEY]: 'true',
|
|
|
|
|
[SELECTED_METRIC_NAME_KEY]: metricName,
|
|
|
|
|
});
|
2025-06-10 11:42:49 +07:00
|
|
|
logEvent(MetricsExplorerEvents.MetricClicked, {
|
|
|
|
|
[MetricsExplorerEventKeys.MetricName]: metricName,
|
|
|
|
|
[MetricsExplorerEventKeys.View]: view,
|
|
|
|
|
});
|
2025-03-11 12:02:08 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeMetricDetails = (): void => {
|
|
|
|
|
setSelectedMetricName(null);
|
|
|
|
|
setIsMetricDetailsOpen(false);
|
2025-05-19 11:54:05 +07:00
|
|
|
setSearchParams({
|
2025-06-10 11:18:12 +07:00
|
|
|
...Object.fromEntries(searchParams.entries()),
|
2025-05-19 11:54:05 +07:00
|
|
|
[IS_METRIC_DETAILS_OPEN_KEY]: 'false',
|
|
|
|
|
[SELECTED_METRIC_NAME_KEY]: '',
|
|
|
|
|
});
|
2025-03-11 12:02:08 +05:30
|
|
|
};
|
|
|
|
|
|
2025-04-01 17:17:38 +05:30
|
|
|
const openInspectModal = (metricName: string): void => {
|
|
|
|
|
setSelectedMetricName(metricName);
|
|
|
|
|
setIsInspectModalOpen(true);
|
|
|
|
|
setIsMetricDetailsOpen(false);
|
2025-05-19 11:54:05 +07:00
|
|
|
setSearchParams({
|
2025-06-10 11:18:12 +07:00
|
|
|
...Object.fromEntries(searchParams.entries()),
|
2025-05-19 11:54:05 +07:00
|
|
|
[IS_INSPECT_MODAL_OPEN_KEY]: 'true',
|
|
|
|
|
[SELECTED_METRIC_NAME_KEY]: metricName,
|
|
|
|
|
});
|
2025-04-01 17:17:38 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeInspectModal = (): void => {
|
|
|
|
|
setIsInspectModalOpen(false);
|
|
|
|
|
setSelectedMetricName(null);
|
2025-05-19 11:54:05 +07:00
|
|
|
setSearchParams({
|
2025-06-10 11:18:12 +07:00
|
|
|
...Object.fromEntries(searchParams.entries()),
|
2025-05-19 11:54:05 +07:00
|
|
|
[IS_INSPECT_MODAL_OPEN_KEY]: 'false',
|
|
|
|
|
[SELECTED_METRIC_NAME_KEY]: '',
|
|
|
|
|
});
|
2025-04-01 17:17:38 +05:30
|
|
|
};
|
|
|
|
|
|
2025-06-10 11:42:49 +07:00
|
|
|
const handleSetHeatmapView = (view: TreemapViewType): void => {
|
|
|
|
|
setHeatmapView(view);
|
|
|
|
|
logEvent(MetricsExplorerEvents.TreemapViewChanged, {
|
|
|
|
|
[MetricsExplorerEventKeys.Tab]: 'summary',
|
|
|
|
|
[MetricsExplorerEventKeys.ViewType]: view,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSetOrderBy = (orderBy: OrderByPayload): void => {
|
|
|
|
|
setOrderBy(orderBy);
|
|
|
|
|
logEvent(MetricsExplorerEvents.OrderByApplied, {
|
|
|
|
|
[MetricsExplorerEventKeys.Tab]: 'summary',
|
|
|
|
|
[MetricsExplorerEventKeys.ColumnName]: orderBy.columnName,
|
|
|
|
|
[MetricsExplorerEventKeys.Order]: orderBy.order,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-05 20:45:21 +07:00
|
|
|
const isMetricsListDataEmpty = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
formattedMetricsData.length === 0 && !isMetricsLoading && !isMetricsFetching,
|
|
|
|
|
[formattedMetricsData, isMetricsLoading, isMetricsFetching],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const isMetricsTreeMapDataEmpty = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
!treeMapData?.payload?.data[heatmapView]?.length &&
|
|
|
|
|
!isTreeMapLoading &&
|
|
|
|
|
!isTreeMapFetching,
|
|
|
|
|
[
|
|
|
|
|
treeMapData?.payload?.data,
|
|
|
|
|
heatmapView,
|
|
|
|
|
isTreeMapLoading,
|
|
|
|
|
isTreeMapFetching,
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
2025-02-05 18:27:12 +05:30
|
|
|
return (
|
|
|
|
|
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
2025-03-05 15:23:23 +05:30
|
|
|
<div className="metrics-explorer-summary-tab">
|
2025-05-19 11:54:05 +07:00
|
|
|
<MetricsSearch query={searchQuery} onChange={handleFilterChange} />
|
2025-08-05 20:45:21 +07:00
|
|
|
{isMetricsLoading || isTreeMapLoading ? (
|
|
|
|
|
<MetricsLoading />
|
|
|
|
|
) : isMetricsListDataEmpty && isMetricsTreeMapDataEmpty ? (
|
|
|
|
|
<NoLogs dataSource={DataSource.METRICS} />
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<MetricsTreemap
|
|
|
|
|
data={treeMapData?.payload}
|
|
|
|
|
isLoading={isTreeMapLoading || isTreeMapFetching}
|
|
|
|
|
isError={isProportionViewError}
|
|
|
|
|
viewType={heatmapView}
|
|
|
|
|
openMetricDetails={openMetricDetails}
|
|
|
|
|
setHeatmapView={handleSetHeatmapView}
|
|
|
|
|
/>
|
|
|
|
|
<MetricsTable
|
|
|
|
|
isLoading={isMetricsLoading || isMetricsFetching}
|
|
|
|
|
isError={isListViewError}
|
|
|
|
|
data={formattedMetricsData}
|
|
|
|
|
pageSize={pageSize}
|
|
|
|
|
currentPage={currentPage}
|
|
|
|
|
onPaginationChange={onPaginationChange}
|
|
|
|
|
setOrderBy={handleSetOrderBy}
|
|
|
|
|
totalCount={metricsData?.payload?.data?.total || 0}
|
|
|
|
|
openMetricDetails={openMetricDetails}
|
|
|
|
|
queryFilters={queryFilters}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2025-03-05 15:23:23 +05:30
|
|
|
</div>
|
2025-03-11 12:02:08 +05:30
|
|
|
{isMetricDetailsOpen && (
|
|
|
|
|
<MetricDetails
|
|
|
|
|
isOpen={isMetricDetailsOpen}
|
|
|
|
|
onClose={closeMetricDetails}
|
|
|
|
|
metricName={selectedMetricName}
|
|
|
|
|
isModalTimeSelection={false}
|
2025-04-01 17:17:38 +05:30
|
|
|
openInspectModal={openInspectModal}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{isInspectModalOpen && (
|
|
|
|
|
<InspectModal
|
|
|
|
|
isOpen={isInspectModalOpen}
|
|
|
|
|
onClose={closeInspectModal}
|
|
|
|
|
metricName={selectedMetricName}
|
2025-03-11 12:02:08 +05:30
|
|
|
/>
|
|
|
|
|
)}
|
2025-02-05 18:27:12 +05:30
|
|
|
</Sentry.ErrorBoundary>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Summary;
|