chore: summary view fixes

This commit is contained in:
amlannandy 2025-06-02 17:58:54 +07:00 committed by Amlan Kumar Nandy
parent 61b2f8cb31
commit f713040b9c
8 changed files with 104 additions and 168 deletions

View File

@ -10,24 +10,21 @@ import {
} from 'antd'; } from 'antd';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useGetMetricsListFilterValues } from 'hooks/metricsExplorer/useGetMetricsListFilterValues'; import { useGetMetricsListFilterValues } from 'hooks/metricsExplorer/useGetMetricsListFilterValues';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import useDebouncedFn from 'hooks/useDebouncedFunction'; import useDebouncedFn from 'hooks/useDebouncedFunction';
import { Search } from 'lucide-react'; import { Search } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom-v5-compat'; import { useSearchParams } from 'react-router-dom-v5-compat';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { COMPOSITE_QUERY_KEY } from './constants'; import { SUMMARY_FILTERS_KEY } from './constants';
function MetricNameSearch(): JSX.Element { function MetricNameSearch({
const { currentQuery } = useQueryBuilder(); queryFilters,
const { handleChangeQueryData } = useQueryOperations({ }: {
index: 0, queryFilters: TagFilter;
query: currentQuery.builder.queryData[0], }): JSX.Element {
entityVersion: '', const [searchParams, setSearchParams] = useSearchParams();
});
const [, setSearchParams] = useSearchParams();
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false); const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
const [searchString, setSearchString] = useState<string>(''); const [searchString, setSearchString] = useState<string>('');
@ -70,9 +67,9 @@ function MetricNameSearch(): JSX.Element {
const handleSelect = useCallback( const handleSelect = useCallback(
(selectedMetricName: string): void => { (selectedMetricName: string): void => {
const newFilter = { const newFilters = {
items: [ items: [
...currentQuery.builder.queryData[0].filters.items, ...queryFilters.items,
{ {
id: 'metric_name', id: 'metric_name',
op: 'CONTAINS', op: 'CONTAINS',
@ -84,27 +81,15 @@ function MetricNameSearch(): JSX.Element {
value: selectedMetricName, value: selectedMetricName,
}, },
], ],
op: 'AND', op: 'and',
}; };
const compositeQuery = {
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
filters: newFilter,
},
],
},
};
handleChangeQueryData('filters', newFilter);
setSearchParams({ setSearchParams({
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery), ...Object.fromEntries(searchParams.entries()),
[SUMMARY_FILTERS_KEY]: JSON.stringify(newFilters),
}); });
setIsPopoverOpen(false); setIsPopoverOpen(false);
}, },
[currentQuery, handleChangeQueryData, setSearchParams], [queryFilters.items, setSearchParams, searchParams],
); );
const metricNameFilterValues = useMemo( const metricNameFilterValues = useMemo(
@ -198,7 +183,7 @@ function MetricNameSearch(): JSX.Element {
const handleInputChange = useCallback( const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>): void => { (e: React.ChangeEvent<HTMLInputElement>): void => {
const value = e.target.value.trim().toLowerCase(); const value = e.target.value.trim();
setSearchString(value); setSearchString(value);
debouncedUpdate(value); debouncedUpdate(value);
}, },

View File

@ -1,26 +1,23 @@
import { Button, Menu, Popover, Tooltip } from 'antd'; import { Button, Menu, Popover, Tooltip } from 'antd';
import { MetricType } from 'api/metricsExplorer/getMetricsList'; import { MetricType } from 'api/metricsExplorer/getMetricsList';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { Search } from 'lucide-react'; import { Search } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom-v5-compat'; import { useSearchParams } from 'react-router-dom-v5-compat';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { import {
COMPOSITE_QUERY_KEY,
METRIC_TYPE_LABEL_MAP, METRIC_TYPE_LABEL_MAP,
METRIC_TYPE_VALUES_MAP, METRIC_TYPE_VALUES_MAP,
SUMMARY_FILTERS_KEY,
} from './constants'; } from './constants';
function MetricTypeSearch(): JSX.Element { function MetricTypeSearch({
const { currentQuery } = useQueryBuilder(); queryFilters,
const { handleChangeQueryData } = useQueryOperations({ }: {
index: 0, queryFilters: TagFilter;
query: currentQuery.builder.queryData[0], }): JSX.Element {
entityVersion: '', const [searchParams, setSearchParams] = useSearchParams();
});
const [, setSearchParams] = useSearchParams();
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false); const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
const menuItems = useMemo( const menuItems = useMemo(
@ -40,9 +37,9 @@ function MetricTypeSearch(): JSX.Element {
const handleSelect = useCallback( const handleSelect = useCallback(
(selectedMetricType: string): void => { (selectedMetricType: string): void => {
if (selectedMetricType !== 'all') { if (selectedMetricType !== 'all') {
const newFilter = { const newFilters = {
items: [ items: [
...currentQuery.builder.queryData[0].filters.items, ...queryFilters.items,
{ {
id: 'metric_type', id: 'metric_type',
op: '=', op: '=',
@ -56,49 +53,23 @@ function MetricTypeSearch(): JSX.Element {
], ],
op: 'AND', op: 'AND',
}; };
const compositeQuery = {
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
filters: newFilter,
},
],
},
};
handleChangeQueryData('filters', newFilter);
setSearchParams({ setSearchParams({
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery), ...Object.fromEntries(searchParams.entries()),
[SUMMARY_FILTERS_KEY]: JSON.stringify(newFilters),
}); });
} else { } else {
const newFilter = { const newFilters = {
items: currentQuery.builder.queryData[0].filters.items.filter( items: queryFilters.items.filter((item) => item.id !== 'metric_type'),
(item) => item.id !== 'metric_type',
),
op: 'AND', op: 'AND',
}; };
const compositeQuery = {
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
filters: newFilter,
},
],
},
};
handleChangeQueryData('filters', newFilter);
setSearchParams({ setSearchParams({
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery), ...Object.fromEntries(searchParams.entries()),
[SUMMARY_FILTERS_KEY]: JSON.stringify(newFilters),
}); });
} }
setIsPopoverOpen(false); setIsPopoverOpen(false);
}, },
[currentQuery, handleChangeQueryData, setSearchParams], [queryFilters.items, setSearchParams, searchParams],
); );
const menu = ( const menu = (

View File

@ -12,7 +12,7 @@ import { Info } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { MetricsListItemRowData, MetricsTableProps } from './types'; import { MetricsListItemRowData, MetricsTableProps } from './types';
import { metricsTableColumns } from './utils'; import { getMetricsTableColumns } from './utils';
function MetricsTable({ function MetricsTable({
isLoading, isLoading,
@ -24,6 +24,7 @@ function MetricsTable({
setOrderBy, setOrderBy,
totalCount, totalCount,
openMetricDetails, openMetricDetails,
queryFilters,
}: MetricsTableProps): JSX.Element { }: MetricsTableProps): JSX.Element {
const handleTableChange: TableProps<MetricsListItemRowData>['onChange'] = useCallback( const handleTableChange: TableProps<MetricsListItemRowData>['onChange'] = useCallback(
( (
@ -74,7 +75,7 @@ function MetricsTable({
), ),
}} }}
dataSource={data} dataSource={data}
columns={metricsTableColumns} columns={getMetricsTableColumns(queryFilters)}
locale={{ locale={{
emptyText: isLoading ? null : ( emptyText: isLoading ? null : (
<div <div

View File

@ -1,29 +1,25 @@
import './Summary.styles.scss'; import './Summary.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap } from 'constants/queryBuilder';
import { usePageSize } from 'container/InfraMonitoringK8s/utils'; import { usePageSize } from 'container/InfraMonitoringK8s/utils';
import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList'; import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList';
import { useGetMetricsTreeMap } from 'hooks/metricsExplorer/useGetMetricsTreeMap'; import { useGetMetricsTreeMap } from 'hooks/metricsExplorer/useGetMetricsTreeMap';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat'; import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import InspectModal from '../Inspect'; import InspectModal from '../Inspect';
import MetricDetails from '../MetricDetails'; import MetricDetails from '../MetricDetails';
import { import {
COMPOSITE_QUERY_KEY,
IS_INSPECT_MODAL_OPEN_KEY, IS_INSPECT_MODAL_OPEN_KEY,
IS_METRIC_DETAILS_OPEN_KEY, IS_METRIC_DETAILS_OPEN_KEY,
SELECTED_METRIC_NAME_KEY, SELECTED_METRIC_NAME_KEY,
SUMMARY_FILTERS_KEY,
} from './constants'; } from './constants';
import MetricsSearch from './MetricsSearch'; import MetricsSearch from './MetricsSearch';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
@ -63,57 +59,25 @@ function Summary(): JSX.Element {
(state) => state.globalTime, (state) => state.globalTime,
); );
const { currentQuery, updateAllQueriesOperators } = useQueryBuilder(); const queryFilters: TagFilter = useMemo(() => {
const encodedFilters = searchParams.get(SUMMARY_FILTERS_KEY);
const defaultQuery = useMemo(() => { if (encodedFilters) {
const query = updateAllQueriesOperators( return JSON.parse(encodedFilters);
initialQueriesMap.metrics, }
PANEL_TYPES.LIST,
DataSource.METRICS,
);
return { return {
...query, items: [],
builder: { op: 'AND',
...query.builder,
queryData: [
{
...query.builder.queryData[0],
orderBy: [DEFAULT_ORDER_BY],
},
],
},
}; };
}, [updateAllQueriesOperators]); }, [searchParams]);
useShareBuilderUrl(defaultQuery);
// This is used to avoid the filters from being serialized with the id // This is used to avoid the filters from being serialized with the id
const currentQueryFiltersString = useMemo(() => { const queryFiltersWithoutId = useMemo(() => {
const filters = currentQuery?.builder?.queryData[0]?.filters;
if (!filters) return '';
const filtersWithoutId = { const filtersWithoutId = {
...filters, ...queryFilters,
items: filters.items.map(({ id, ...rest }) => rest), items: queryFilters.items.map(({ id, ...rest }) => rest),
}; };
return JSON.stringify(filtersWithoutId); return JSON.stringify(filtersWithoutId);
}, [currentQuery]); }, [queryFilters]);
const queryFilters = useMemo(
() =>
currentQuery?.builder?.queryData[0]?.filters || {
items: [],
op: 'and',
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentQueryFiltersString],
);
const { handleChangeQueryData } = useQueryOperations({
index: 0,
query: currentQuery.builder.queryData[0],
entityVersion: '',
});
const metricsListQuery = useMemo(() => { const metricsListQuery = useMemo(() => {
const baseQuery = getMetricsListQuery(); const baseQuery = getMetricsListQuery();
@ -146,6 +110,15 @@ function Summary(): JSX.Element {
isError: isMetricsError, isError: isMetricsError,
} = useGetMetricsList(metricsListQuery, { } = useGetMetricsList(metricsListQuery, {
enabled: !!metricsListQuery && !isInspectModalOpen, enabled: !!metricsListQuery && !isInspectModalOpen,
queryKey: [
'metricsList',
queryFiltersWithoutId,
orderBy,
pageSize,
currentPage,
minTime,
maxTime,
],
}); });
const isListViewError = useMemo( const isListViewError = useMemo(
@ -160,6 +133,13 @@ function Summary(): JSX.Element {
isError: isTreeMapError, isError: isTreeMapError,
} = useGetMetricsTreeMap(metricsTreemapQuery, { } = useGetMetricsTreeMap(metricsTreemapQuery, {
enabled: !!metricsTreemapQuery && !isInspectModalOpen, enabled: !!metricsTreemapQuery && !isInspectModalOpen,
queryKey: [
'metricsTreemap',
queryFiltersWithoutId,
heatmapView,
minTime,
maxTime,
],
}); });
const isProportionViewError = useMemo( const isProportionViewError = useMemo(
@ -169,48 +149,23 @@ function Summary(): JSX.Element {
const handleFilterChange = useCallback( const handleFilterChange = useCallback(
(value: TagFilter) => { (value: TagFilter) => {
handleChangeQueryData('filters', value);
const compositeQuery = {
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
filters: value,
},
],
},
};
setSearchParams({ setSearchParams({
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery), ...Object.fromEntries(searchParams.entries()),
[SUMMARY_FILTERS_KEY]: JSON.stringify(value),
}); });
setCurrentPage(1); setCurrentPage(1);
}, },
[handleChangeQueryData, currentQuery, setSearchParams], [setSearchParams, searchParams],
); );
const updatedCurrentQuery = useMemo( const searchQuery = useMemo(
() => ({ () => ({
...currentQuery, ...initialQueriesMap.metrics.builder.queryData[0],
builder: { filters: queryFilters,
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
aggregateOperator: 'noop',
aggregateAttribute: {
...currentQuery.builder.queryData[0].aggregateAttribute,
},
},
],
},
}), }),
[currentQuery], [queryFilters],
); );
const searchQuery = updatedCurrentQuery?.builder?.queryData[0] || null;
const onPaginationChange = (page: number, pageSize: number): void => { const onPaginationChange = (page: number, pageSize: number): void => {
setCurrentPage(page); setCurrentPage(page);
setPageSize(pageSize); setPageSize(pageSize);
@ -225,6 +180,7 @@ function Summary(): JSX.Element {
setSelectedMetricName(metricName); setSelectedMetricName(metricName);
setIsMetricDetailsOpen(true); setIsMetricDetailsOpen(true);
setSearchParams({ setSearchParams({
...Object.fromEntries(searchParams.entries()),
[IS_METRIC_DETAILS_OPEN_KEY]: 'true', [IS_METRIC_DETAILS_OPEN_KEY]: 'true',
[SELECTED_METRIC_NAME_KEY]: metricName, [SELECTED_METRIC_NAME_KEY]: metricName,
}); });
@ -234,6 +190,7 @@ function Summary(): JSX.Element {
setSelectedMetricName(null); setSelectedMetricName(null);
setIsMetricDetailsOpen(false); setIsMetricDetailsOpen(false);
setSearchParams({ setSearchParams({
...Object.fromEntries(searchParams.entries()),
[IS_METRIC_DETAILS_OPEN_KEY]: 'false', [IS_METRIC_DETAILS_OPEN_KEY]: 'false',
[SELECTED_METRIC_NAME_KEY]: '', [SELECTED_METRIC_NAME_KEY]: '',
}); });
@ -244,19 +201,17 @@ function Summary(): JSX.Element {
setIsInspectModalOpen(true); setIsInspectModalOpen(true);
setIsMetricDetailsOpen(false); setIsMetricDetailsOpen(false);
setSearchParams({ setSearchParams({
...Object.fromEntries(searchParams.entries()),
[IS_INSPECT_MODAL_OPEN_KEY]: 'true', [IS_INSPECT_MODAL_OPEN_KEY]: 'true',
[SELECTED_METRIC_NAME_KEY]: metricName, [SELECTED_METRIC_NAME_KEY]: metricName,
}); });
}; };
const closeInspectModal = (): void => { const closeInspectModal = (): void => {
handleChangeQueryData('filters', {
items: [],
op: 'AND',
});
setIsInspectModalOpen(false); setIsInspectModalOpen(false);
setSelectedMetricName(null); setSelectedMetricName(null);
setSearchParams({ setSearchParams({
...Object.fromEntries(searchParams.entries()),
[IS_INSPECT_MODAL_OPEN_KEY]: 'false', [IS_INSPECT_MODAL_OPEN_KEY]: 'false',
[SELECTED_METRIC_NAME_KEY]: '', [SELECTED_METRIC_NAME_KEY]: '',
}); });
@ -284,6 +239,7 @@ function Summary(): JSX.Element {
setOrderBy={setOrderBy} setOrderBy={setOrderBy}
totalCount={metricsData?.payload?.data?.total || 0} totalCount={metricsData?.payload?.data?.total || 0}
openMetricDetails={openMetricDetails} openMetricDetails={openMetricDetails}
queryFilters={queryFilters}
/> />
</div> </div>
{isMetricDetailsOpen && ( {isMetricDetailsOpen && (

View File

@ -36,4 +36,4 @@ export const METRIC_TYPE_VALUES_MAP = {
export const IS_METRIC_DETAILS_OPEN_KEY = 'isMetricDetailsOpen'; export const IS_METRIC_DETAILS_OPEN_KEY = 'isMetricDetailsOpen';
export const IS_INSPECT_MODAL_OPEN_KEY = 'isInspectModalOpen'; export const IS_INSPECT_MODAL_OPEN_KEY = 'isInspectModalOpen';
export const SELECTED_METRIC_NAME_KEY = 'selectedMetricName'; export const SELECTED_METRIC_NAME_KEY = 'selectedMetricName';
export const COMPOSITE_QUERY_KEY = 'compositeQuery'; export const SUMMARY_FILTERS_KEY = 'summaryFilters';

View File

@ -15,6 +15,7 @@ export interface MetricsTableProps {
setOrderBy: Dispatch<SetStateAction<OrderByPayload>>; setOrderBy: Dispatch<SetStateAction<OrderByPayload>>;
totalCount: number; totalCount: number;
openMetricDetails: (metricName: string) => void; openMetricDetails: (metricName: string) => void;
queryFilters: TagFilter;
} }
export interface MetricsSearchProps { export interface MetricsSearchProps {

View File

@ -18,18 +18,21 @@ import {
Gauge, Gauge,
} from 'lucide-react'; } from 'lucide-react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { METRIC_TYPE_LABEL_MAP } from './constants'; import { METRIC_TYPE_LABEL_MAP } from './constants';
import MetricNameSearch from './MetricNameSearch'; import MetricNameSearch from './MetricNameSearch';
import MetricTypeSearch from './MetricTypeSearch'; import MetricTypeSearch from './MetricTypeSearch';
import { MetricsListItemRowData, TreemapTile, TreemapViewType } from './types'; import { MetricsListItemRowData, TreemapTile, TreemapViewType } from './types';
export const metricsTableColumns: ColumnType<MetricsListItemRowData>[] = [ export const getMetricsTableColumns = (
queryFilters: TagFilter,
): ColumnType<MetricsListItemRowData>[] => [
{ {
title: ( title: (
<div className="metric-name-column-header"> <div className="metric-name-column-header">
<span className="metric-name-column-header-text">METRIC</span> <span className="metric-name-column-header-text">METRIC</span>
<MetricNameSearch /> <MetricNameSearch queryFilters={queryFilters} />
</div> </div>
), ),
dataIndex: 'metric_name', dataIndex: 'metric_name',
@ -51,7 +54,7 @@ export const metricsTableColumns: ColumnType<MetricsListItemRowData>[] = [
title: ( title: (
<div className="metric-type-column-header"> <div className="metric-type-column-header">
<span className="metric-type-column-header-text">TYPE</span> <span className="metric-type-column-header-text">TYPE</span>
<MetricTypeSearch /> <MetricTypeSearch queryFilters={queryFilters} />
</div> </div>
), ),
dataIndex: 'metric_type', dataIndex: 'metric_type',

View File

@ -2,8 +2,13 @@ import './MetricsExplorerPage.styles.scss';
import RouteTab from 'components/RouteTab'; import RouteTab from 'components/RouteTab';
import { TabRoutes } from 'components/RouteTab/types'; import { TabRoutes } from 'components/RouteTab/types';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import history from 'lib/history'; import history from 'lib/history';
import { useMemo } from 'react';
import { useLocation } from 'react-use'; import { useLocation } from 'react-use';
import { DataSource } from 'types/common/queryBuilder';
import { Explorer, Summary, Views } from './constants'; import { Explorer, Summary, Views } from './constants';
@ -12,6 +17,20 @@ function MetricsExplorerPage(): JSX.Element {
const routes: TabRoutes[] = [Summary, Explorer, Views]; const routes: TabRoutes[] = [Summary, Explorer, Views];
const { updateAllQueriesOperators } = useQueryBuilder();
const defaultQuery = useMemo(
() =>
updateAllQueriesOperators(
initialQueriesMap[DataSource.METRICS],
PANEL_TYPES.LIST,
DataSource.METRICS,
),
[updateAllQueriesOperators],
);
useShareBuilderUrl(defaultQuery);
return ( return (
<div className="metrics-explorer-page"> <div className="metrics-explorer-page">
<RouteTab routes={routes} activeKey={pathname} history={history} /> <RouteTab routes={routes} activeKey={pathname} history={history} />