mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 19:28:59 +08:00
chore: persist the filters and time selection, modal open state in summary view (#7942)
This commit is contained in:
parent
6dbcc5fb9d
commit
2b28c5f2e2
@ -5,9 +5,9 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/es/table';
|
||||
import { Table, Tooltip, Typography } from 'antd';
|
||||
import { Progress } from 'antd/lib';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
|
||||
import { DataType } from 'container/LogDetailedView/TableView';
|
||||
|
@ -15,8 +15,11 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
import { COMPOSITE_QUERY_KEY } from './constants';
|
||||
|
||||
function MetricNameSearch(): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -24,6 +27,7 @@ function MetricNameSearch(): JSX.Element {
|
||||
query: currentQuery.builder.queryData[0],
|
||||
entityVersion: '',
|
||||
});
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
const [searchString, setSearchString] = useState<string>('');
|
||||
@ -66,7 +70,7 @@ function MetricNameSearch(): JSX.Element {
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(selectedMetricName: string): void => {
|
||||
handleChangeQueryData('filters', {
|
||||
const newFilter = {
|
||||
items: [
|
||||
...currentQuery.builder.queryData[0].filters.items,
|
||||
{
|
||||
@ -81,10 +85,26 @@ function MetricNameSearch(): JSX.Element {
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
const compositeQuery = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQuery.builder.queryData[0],
|
||||
filters: newFilter,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
handleChangeQueryData('filters', newFilter);
|
||||
setSearchParams({
|
||||
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery),
|
||||
});
|
||||
setIsPopoverOpen(false);
|
||||
},
|
||||
[currentQuery.builder.queryData, handleChangeQueryData],
|
||||
[currentQuery, handleChangeQueryData, setSearchParams],
|
||||
);
|
||||
|
||||
const metricNameFilterValues = useMemo(
|
||||
|
@ -4,8 +4,13 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { METRIC_TYPE_LABEL_MAP, METRIC_TYPE_VALUES_MAP } from './constants';
|
||||
import {
|
||||
COMPOSITE_QUERY_KEY,
|
||||
METRIC_TYPE_LABEL_MAP,
|
||||
METRIC_TYPE_VALUES_MAP,
|
||||
} from './constants';
|
||||
|
||||
function MetricTypeSearch(): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
@ -15,6 +20,7 @@ function MetricTypeSearch(): JSX.Element {
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
const menuItems = useMemo(
|
||||
@ -34,7 +40,7 @@ function MetricTypeSearch(): JSX.Element {
|
||||
const handleSelect = useCallback(
|
||||
(selectedMetricType: string): void => {
|
||||
if (selectedMetricType !== 'all') {
|
||||
handleChangeQueryData('filters', {
|
||||
const newFilter = {
|
||||
items: [
|
||||
...currentQuery.builder.queryData[0].filters.items,
|
||||
{
|
||||
@ -49,18 +55,50 @@ function MetricTypeSearch(): JSX.Element {
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
const compositeQuery = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQuery.builder.queryData[0],
|
||||
filters: newFilter,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
handleChangeQueryData('filters', newFilter);
|
||||
setSearchParams({
|
||||
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery),
|
||||
});
|
||||
} else {
|
||||
handleChangeQueryData('filters', {
|
||||
const newFilter = {
|
||||
items: currentQuery.builder.queryData[0].filters.items.filter(
|
||||
(item) => item.id !== 'metric_type',
|
||||
),
|
||||
op: 'AND',
|
||||
};
|
||||
const compositeQuery = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQuery.builder.queryData[0],
|
||||
filters: newFilter,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
handleChangeQueryData('filters', newFilter);
|
||||
setSearchParams({
|
||||
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery),
|
||||
});
|
||||
}
|
||||
setIsPopoverOpen(false);
|
||||
},
|
||||
[currentQuery.builder.queryData, handleChangeQueryData],
|
||||
[currentQuery, handleChangeQueryData, setSearchParams],
|
||||
);
|
||||
|
||||
const menu = (
|
||||
|
@ -1,32 +1,13 @@
|
||||
import { Select, Tooltip } from 'antd';
|
||||
import { Tooltip } from 'antd';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { HardHat, Info } from 'lucide-react';
|
||||
|
||||
import { TREEMAP_VIEW_OPTIONS } from './constants';
|
||||
import { MetricsSearchProps } from './types';
|
||||
|
||||
function MetricsSearch({
|
||||
query,
|
||||
onChange,
|
||||
heatmapView,
|
||||
setHeatmapView,
|
||||
}: MetricsSearchProps): JSX.Element {
|
||||
function MetricsSearch({ query, onChange }: MetricsSearchProps): JSX.Element {
|
||||
return (
|
||||
<div className="metrics-search-container">
|
||||
<div className="metrics-search-options">
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
options={TREEMAP_VIEW_OPTIONS}
|
||||
value={heatmapView}
|
||||
onChange={setHeatmapView}
|
||||
/>
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
/>
|
||||
</div>
|
||||
<div className="qb-search-container">
|
||||
<Tooltip
|
||||
title="Use filters to refine metrics based on attributes. Example: service_name=api - Shows all metrics associated with the API service"
|
||||
@ -41,6 +22,13 @@ function MetricsSearch({
|
||||
isMetricsExplorer
|
||||
/>
|
||||
</div>
|
||||
<div className="metrics-search-options">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Group } from '@visx/group';
|
||||
import { Treemap } from '@visx/hierarchy';
|
||||
import { Empty, Skeleton, Tooltip, Typography } from 'antd';
|
||||
import { Empty, Select, Skeleton, Tooltip, Typography } from 'antd';
|
||||
import { stratify, treemapBinary } from 'd3-hierarchy';
|
||||
import { Info } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
@ -10,6 +10,7 @@ import {
|
||||
TREEMAP_HEIGHT,
|
||||
TREEMAP_MARGINS,
|
||||
TREEMAP_SQUARE_PADDING,
|
||||
TREEMAP_VIEW_OPTIONS,
|
||||
} from './constants';
|
||||
import { MetricsTreemapProps, TreemapTile, TreemapViewType } from './types';
|
||||
import {
|
||||
@ -24,6 +25,7 @@ function MetricsTreemap({
|
||||
isLoading,
|
||||
isError,
|
||||
openMetricDetails,
|
||||
setHeatmapView,
|
||||
}: MetricsTreemapProps): JSX.Element {
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
|
||||
@ -55,7 +57,10 @@ function MetricsTreemap({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div data-testid="metrics-treemap-loading-state">
|
||||
<Skeleton style={{ width: treemapWidth, height: TREEMAP_HEIGHT }} active />
|
||||
<Skeleton
|
||||
style={{ width: treemapWidth, height: TREEMAP_HEIGHT + 55 }}
|
||||
active
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -90,13 +95,20 @@ function MetricsTreemap({
|
||||
data-testid="metrics-treemap-container"
|
||||
>
|
||||
<div className="metrics-treemap-title">
|
||||
<Typography.Title level={4}>Proportion View</Typography.Title>
|
||||
<Tooltip
|
||||
title="The treemap displays the proportion of samples/timeseries in the selected time range. Each tile represents a unique metric, and its size indicates the percentage of samples/timeseries it contributes to the total."
|
||||
placement="right"
|
||||
>
|
||||
<Info size={16} />
|
||||
</Tooltip>
|
||||
<div className="metrics-treemap-title-left">
|
||||
<Typography.Title level={4}>Proportion View</Typography.Title>
|
||||
<Tooltip
|
||||
title="The treemap displays the proportion of samples/timeseries in the selected time range. Each tile represents a unique metric, and its size indicates the percentage of samples/timeseries it contributes to the total."
|
||||
placement="right"
|
||||
>
|
||||
<Info size={16} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select
|
||||
options={TREEMAP_VIEW_OPTIONS}
|
||||
value={viewType}
|
||||
onChange={setHeatmapView}
|
||||
/>
|
||||
</div>
|
||||
<svg
|
||||
width={treemapWidth}
|
||||
|
@ -21,9 +21,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.metrics-treemap-title {
|
||||
justify-content: space-between;
|
||||
|
||||
.metrics-treemap-title-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
.metrics-search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.metrics-search-options {
|
||||
@ -35,6 +48,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
.lucide-info {
|
||||
cursor: pointer;
|
||||
|
@ -11,6 +11,7 @@ import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@ -18,6 +19,12 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import InspectModal from '../Inspect';
|
||||
import MetricDetails from '../MetricDetails';
|
||||
import {
|
||||
COMPOSITE_QUERY_KEY,
|
||||
IS_INSPECT_MODAL_OPEN_KEY,
|
||||
IS_METRIC_DETAILS_OPEN_KEY,
|
||||
SELECTED_METRIC_NAME_KEY,
|
||||
} from './constants';
|
||||
import MetricsSearch from './MetricsSearch';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import MetricsTreemap from './MetricsTreemap';
|
||||
@ -40,10 +47,16 @@ function Summary(): JSX.Element {
|
||||
const [heatmapView, setHeatmapView] = useState<TreemapViewType>(
|
||||
TreemapViewType.TIMESERIES,
|
||||
);
|
||||
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(false);
|
||||
const [isInspectModalOpen, setIsInspectModalOpen] = useState(false);
|
||||
const [selectedMetricName, setSelectedMetricName] = useState<string | null>(
|
||||
null,
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
@ -75,13 +88,25 @@ function Summary(): JSX.Element {
|
||||
|
||||
useShareBuilderUrl(defaultQuery);
|
||||
|
||||
// This is used to avoid the filters from being serialized with the id
|
||||
const currentQueryFiltersString = useMemo(() => {
|
||||
const filters = currentQuery?.builder?.queryData[0]?.filters;
|
||||
if (!filters) return '';
|
||||
const filtersWithoutId = {
|
||||
...filters,
|
||||
items: filters.items.map(({ id, ...rest }) => rest),
|
||||
};
|
||||
return JSON.stringify(filtersWithoutId);
|
||||
}, [currentQuery]);
|
||||
|
||||
const queryFilters = useMemo(
|
||||
() =>
|
||||
currentQuery?.builder?.queryData[0]?.filters || {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
[currentQuery],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[currentQueryFiltersString],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -145,9 +170,24 @@ function Summary(): JSX.Element {
|
||||
const handleFilterChange = useCallback(
|
||||
(value: TagFilter) => {
|
||||
handleChangeQueryData('filters', value);
|
||||
const compositeQuery = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQuery.builder.queryData[0],
|
||||
filters: value,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
setSearchParams({
|
||||
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery),
|
||||
});
|
||||
setCurrentPage(1);
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
[handleChangeQueryData, currentQuery, setSearchParams],
|
||||
);
|
||||
|
||||
const updatedCurrentQuery = useMemo(
|
||||
@ -184,17 +224,29 @@ function Summary(): JSX.Element {
|
||||
const openMetricDetails = (metricName: string): void => {
|
||||
setSelectedMetricName(metricName);
|
||||
setIsMetricDetailsOpen(true);
|
||||
setSearchParams({
|
||||
[IS_METRIC_DETAILS_OPEN_KEY]: 'true',
|
||||
[SELECTED_METRIC_NAME_KEY]: metricName,
|
||||
});
|
||||
};
|
||||
|
||||
const closeMetricDetails = (): void => {
|
||||
setSelectedMetricName(null);
|
||||
setIsMetricDetailsOpen(false);
|
||||
setSearchParams({
|
||||
[IS_METRIC_DETAILS_OPEN_KEY]: 'false',
|
||||
[SELECTED_METRIC_NAME_KEY]: '',
|
||||
});
|
||||
};
|
||||
|
||||
const openInspectModal = (metricName: string): void => {
|
||||
setSelectedMetricName(metricName);
|
||||
setIsInspectModalOpen(true);
|
||||
setIsMetricDetailsOpen(false);
|
||||
setSearchParams({
|
||||
[IS_INSPECT_MODAL_OPEN_KEY]: 'true',
|
||||
[SELECTED_METRIC_NAME_KEY]: metricName,
|
||||
});
|
||||
};
|
||||
|
||||
const closeInspectModal = (): void => {
|
||||
@ -204,23 +256,23 @@ function Summary(): JSX.Element {
|
||||
});
|
||||
setIsInspectModalOpen(false);
|
||||
setSelectedMetricName(null);
|
||||
setSearchParams({
|
||||
[IS_INSPECT_MODAL_OPEN_KEY]: 'false',
|
||||
[SELECTED_METRIC_NAME_KEY]: '',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<div className="metrics-explorer-summary-tab">
|
||||
<MetricsSearch
|
||||
query={searchQuery}
|
||||
onChange={handleFilterChange}
|
||||
heatmapView={heatmapView}
|
||||
setHeatmapView={setHeatmapView}
|
||||
/>
|
||||
<MetricsSearch query={searchQuery} onChange={handleFilterChange} />
|
||||
<MetricsTreemap
|
||||
data={treeMapData?.payload}
|
||||
isLoading={isTreeMapLoading || isTreeMapFetching}
|
||||
isError={isProportionViewError}
|
||||
viewType={heatmapView}
|
||||
openMetricDetails={openMetricDetails}
|
||||
setHeatmapView={setHeatmapView}
|
||||
/>
|
||||
<MetricsTable
|
||||
isLoading={isMetricsLoading || isMetricsFetching}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import * as useGetMetricsListFilterValues from 'hooks/metricsExplorer/useGetMetricsListFilterValues';
|
||||
import * as useQueryBuilderOperationsHooks from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import store from 'store';
|
||||
@ -28,7 +29,23 @@ const mockData: MetricsListItemRowData[] = [
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('react-router-dom-v5-compat', () => {
|
||||
const actual = jest.requireActual('react-router-dom-v5-compat');
|
||||
return {
|
||||
...actual,
|
||||
useSearchParams: jest.fn().mockReturnValue([{}, jest.fn()]),
|
||||
useNavigationType: (): any => 'PUSH',
|
||||
};
|
||||
});
|
||||
describe('MetricsTable', () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(useQueryBuilderOperationsHooks, 'useQueryOperations')
|
||||
.mockReturnValue({
|
||||
handleChangeQueryData: jest.fn(),
|
||||
} as any);
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(useGetMetricsListFilterValues, 'useGetMetricsListFilterValues')
|
||||
.mockReturnValue({
|
||||
|
@ -55,6 +55,7 @@ describe('MetricsTreemap', () => {
|
||||
}}
|
||||
openMetricDetails={jest.fn()}
|
||||
viewType={TreemapViewType.SAMPLES}
|
||||
setHeatmapView={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
@ -79,6 +80,7 @@ describe('MetricsTreemap', () => {
|
||||
}}
|
||||
openMetricDetails={jest.fn()}
|
||||
viewType={TreemapViewType.SAMPLES}
|
||||
setHeatmapView={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
@ -105,6 +107,7 @@ describe('MetricsTreemap', () => {
|
||||
}}
|
||||
openMetricDetails={jest.fn()}
|
||||
viewType={TreemapViewType.SAMPLES}
|
||||
setHeatmapView={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
@ -128,6 +131,7 @@ describe('MetricsTreemap', () => {
|
||||
data={null}
|
||||
openMetricDetails={jest.fn()}
|
||||
viewType={TreemapViewType.SAMPLES}
|
||||
setHeatmapView={jest.fn()}
|
||||
/>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
|
@ -0,0 +1,150 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MetricType } from 'api/metricsExplorer/getMetricsList';
|
||||
import ROUTES from 'constants/routes';
|
||||
import * as useGetMetricsListHooks from 'hooks/metricsExplorer/useGetMetricsList';
|
||||
import * as useGetMetricsTreeMapHooks from 'hooks/metricsExplorer/useGetMetricsTreeMap';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import store from 'store';
|
||||
|
||||
import Summary from '../Summary';
|
||||
import { TreemapViewType } from '../types';
|
||||
|
||||
jest.mock('uplot', () => {
|
||||
const paths = {
|
||||
spline: jest.fn(),
|
||||
bars: jest.fn(),
|
||||
};
|
||||
const uplotMock = jest.fn(() => ({
|
||||
paths,
|
||||
}));
|
||||
return {
|
||||
paths,
|
||||
default: uplotMock,
|
||||
};
|
||||
});
|
||||
jest.mock('d3-hierarchy', () => ({
|
||||
stratify: jest.fn().mockReturnValue({
|
||||
id: jest.fn().mockReturnValue({
|
||||
parentId: jest.fn().mockReturnValue(
|
||||
jest.fn().mockReturnValue({
|
||||
sum: jest.fn().mockReturnValue({
|
||||
descendants: jest.fn().mockReturnValue([]),
|
||||
eachBefore: jest.fn().mockReturnValue([]),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
treemapBinary: jest.fn(),
|
||||
}));
|
||||
jest.mock('react-use', () => ({
|
||||
useWindowSize: jest.fn().mockReturnValue({ width: 1000, height: 1000 }),
|
||||
}));
|
||||
jest.mock('react-router-dom-v5-compat', () => {
|
||||
const actual = jest.requireActual('react-router-dom-v5-compat');
|
||||
return {
|
||||
...actual,
|
||||
useSearchParams: jest.fn(),
|
||||
useNavigationType: (): any => 'PUSH',
|
||||
};
|
||||
});
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${ROUTES.METRICS_EXPLORER_BASE}`,
|
||||
}),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const mockMetricName = 'test-metric';
|
||||
jest.spyOn(useGetMetricsListHooks, 'useGetMetricsList').mockReturnValue({
|
||||
data: {
|
||||
payload: {
|
||||
status: 'success',
|
||||
data: {
|
||||
metrics: [
|
||||
{
|
||||
metric_name: mockMetricName,
|
||||
description: 'description for a test metric',
|
||||
type: MetricType.GAUGE,
|
||||
unit: 'count',
|
||||
lastReceived: '1715702400',
|
||||
[TreemapViewType.TIMESERIES]: 100,
|
||||
[TreemapViewType.SAMPLES]: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
} as any);
|
||||
jest.spyOn(useGetMetricsTreeMapHooks, 'useGetMetricsTreeMap').mockReturnValue({
|
||||
data: {
|
||||
payload: {
|
||||
status: 'success',
|
||||
data: {
|
||||
[TreemapViewType.TIMESERIES]: [
|
||||
{
|
||||
metric_name: mockMetricName,
|
||||
percentage: 100,
|
||||
total_value: 100,
|
||||
},
|
||||
],
|
||||
[TreemapViewType.SAMPLES]: [
|
||||
{
|
||||
metric_name: mockMetricName,
|
||||
percentage: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
} as any);
|
||||
const mockSetSearchParams = jest.fn();
|
||||
|
||||
describe('Summary', () => {
|
||||
it('persists inspect modal open state across page refresh', () => {
|
||||
(useSearchParams as jest.Mock).mockReturnValue([
|
||||
new URLSearchParams({
|
||||
isInspectModalOpen: 'true',
|
||||
selectedMetricName: 'test-metric',
|
||||
}),
|
||||
mockSetSearchParams,
|
||||
]);
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<Summary />
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText('Proportion View')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('persists metric details modal state across page refresh', () => {
|
||||
(useSearchParams as jest.Mock).mockReturnValue([
|
||||
new URLSearchParams({
|
||||
isMetricDetailsOpen: 'true',
|
||||
selectedMetricName: mockMetricName,
|
||||
}),
|
||||
mockSetSearchParams,
|
||||
]);
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<Summary />
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText('Proportion View')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -32,3 +32,8 @@ export const METRIC_TYPE_VALUES_MAP = {
|
||||
[MetricType.SUMMARY]: 'Summary',
|
||||
[MetricType.EXPONENTIAL_HISTOGRAM]: 'ExponentialHistogram',
|
||||
};
|
||||
|
||||
export const IS_METRIC_DETAILS_OPEN_KEY = 'isMetricDetailsOpen';
|
||||
export const IS_INSPECT_MODAL_OPEN_KEY = 'isInspectModalOpen';
|
||||
export const SELECTED_METRIC_NAME_KEY = 'selectedMetricName';
|
||||
export const COMPOSITE_QUERY_KEY = 'compositeQuery';
|
||||
|
@ -20,8 +20,6 @@ export interface MetricsTableProps {
|
||||
export interface MetricsSearchProps {
|
||||
query: IBuilderQuery;
|
||||
onChange: (value: TagFilter) => void;
|
||||
heatmapView: TreemapViewType;
|
||||
setHeatmapView: (value: TreemapViewType) => void;
|
||||
}
|
||||
|
||||
export interface MetricsTreemapProps {
|
||||
@ -30,6 +28,7 @@ export interface MetricsTreemapProps {
|
||||
isError: boolean;
|
||||
viewType: TreemapViewType;
|
||||
openMetricDetails: (metricName: string) => void;
|
||||
setHeatmapView: (value: TreemapViewType) => void;
|
||||
}
|
||||
|
||||
export interface OrderByPayload {
|
||||
|
Loading…
x
Reference in New Issue
Block a user