mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 03:25:57 +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 */
|
/* eslint-disable prefer-destructuring */
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Tooltip, Typography } from 'antd';
|
import { Table, Tooltip, Typography } from 'antd';
|
||||||
import Table, { ColumnsType } from 'antd/es/table';
|
|
||||||
import { Progress } from 'antd/lib';
|
import { Progress } from 'antd/lib';
|
||||||
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
|
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
|
||||||
import { DataType } from 'container/LogDetailedView/TableView';
|
import { DataType } from 'container/LogDetailedView/TableView';
|
||||||
|
@ -15,8 +15,11 @@ 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 { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
import { COMPOSITE_QUERY_KEY } from './constants';
|
||||||
|
|
||||||
function MetricNameSearch(): JSX.Element {
|
function MetricNameSearch(): JSX.Element {
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { currentQuery } = useQueryBuilder();
|
||||||
const { handleChangeQueryData } = useQueryOperations({
|
const { handleChangeQueryData } = useQueryOperations({
|
||||||
@ -24,6 +27,7 @@ function MetricNameSearch(): JSX.Element {
|
|||||||
query: currentQuery.builder.queryData[0],
|
query: currentQuery.builder.queryData[0],
|
||||||
entityVersion: '',
|
entityVersion: '',
|
||||||
});
|
});
|
||||||
|
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>('');
|
||||||
@ -66,7 +70,7 @@ function MetricNameSearch(): JSX.Element {
|
|||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(selectedMetricName: string): void => {
|
(selectedMetricName: string): void => {
|
||||||
handleChangeQueryData('filters', {
|
const newFilter = {
|
||||||
items: [
|
items: [
|
||||||
...currentQuery.builder.queryData[0].filters.items,
|
...currentQuery.builder.queryData[0].filters.items,
|
||||||
{
|
{
|
||||||
@ -81,10 +85,26 @@ function MetricNameSearch(): JSX.Element {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
op: 'AND',
|
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);
|
setIsPopoverOpen(false);
|
||||||
},
|
},
|
||||||
[currentQuery.builder.queryData, handleChangeQueryData],
|
[currentQuery, handleChangeQueryData, setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const metricNameFilterValues = useMemo(
|
const metricNameFilterValues = useMemo(
|
||||||
|
@ -4,8 +4,13 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|||||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
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 { 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 {
|
function MetricTypeSearch(): JSX.Element {
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { currentQuery } = useQueryBuilder();
|
||||||
@ -15,6 +20,7 @@ function MetricTypeSearch(): JSX.Element {
|
|||||||
entityVersion: '',
|
entityVersion: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [, setSearchParams] = useSearchParams();
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const menuItems = useMemo(
|
const menuItems = useMemo(
|
||||||
@ -34,7 +40,7 @@ function MetricTypeSearch(): JSX.Element {
|
|||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(selectedMetricType: string): void => {
|
(selectedMetricType: string): void => {
|
||||||
if (selectedMetricType !== 'all') {
|
if (selectedMetricType !== 'all') {
|
||||||
handleChangeQueryData('filters', {
|
const newFilter = {
|
||||||
items: [
|
items: [
|
||||||
...currentQuery.builder.queryData[0].filters.items,
|
...currentQuery.builder.queryData[0].filters.items,
|
||||||
{
|
{
|
||||||
@ -49,18 +55,50 @@ 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({
|
||||||
|
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
handleChangeQueryData('filters', {
|
const newFilter = {
|
||||||
items: currentQuery.builder.queryData[0].filters.items.filter(
|
items: currentQuery.builder.queryData[0].filters.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({
|
||||||
|
[COMPOSITE_QUERY_KEY]: JSON.stringify(compositeQuery),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsPopoverOpen(false);
|
setIsPopoverOpen(false);
|
||||||
},
|
},
|
||||||
[currentQuery.builder.queryData, handleChangeQueryData],
|
[currentQuery, handleChangeQueryData, setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
|
@ -1,32 +1,13 @@
|
|||||||
import { Select, Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||||
import { HardHat, Info } from 'lucide-react';
|
import { HardHat, Info } from 'lucide-react';
|
||||||
|
|
||||||
import { TREEMAP_VIEW_OPTIONS } from './constants';
|
|
||||||
import { MetricsSearchProps } from './types';
|
import { MetricsSearchProps } from './types';
|
||||||
|
|
||||||
function MetricsSearch({
|
function MetricsSearch({ query, onChange }: MetricsSearchProps): JSX.Element {
|
||||||
query,
|
|
||||||
onChange,
|
|
||||||
heatmapView,
|
|
||||||
setHeatmapView,
|
|
||||||
}: MetricsSearchProps): JSX.Element {
|
|
||||||
return (
|
return (
|
||||||
<div className="metrics-search-container">
|
<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">
|
<div className="qb-search-container">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Use filters to refine metrics based on attributes. Example: service_name=api - Shows all metrics associated with the API service"
|
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
|
isMetricsExplorer
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="metrics-search-options">
|
||||||
|
<DateTimeSelectionV2
|
||||||
|
showAutoRefresh={false}
|
||||||
|
showRefreshText={false}
|
||||||
|
hideShareModal
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Group } from '@visx/group';
|
import { Group } from '@visx/group';
|
||||||
import { Treemap } from '@visx/hierarchy';
|
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 { stratify, treemapBinary } from 'd3-hierarchy';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
TREEMAP_HEIGHT,
|
TREEMAP_HEIGHT,
|
||||||
TREEMAP_MARGINS,
|
TREEMAP_MARGINS,
|
||||||
TREEMAP_SQUARE_PADDING,
|
TREEMAP_SQUARE_PADDING,
|
||||||
|
TREEMAP_VIEW_OPTIONS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { MetricsTreemapProps, TreemapTile, TreemapViewType } from './types';
|
import { MetricsTreemapProps, TreemapTile, TreemapViewType } from './types';
|
||||||
import {
|
import {
|
||||||
@ -24,6 +25,7 @@ function MetricsTreemap({
|
|||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
openMetricDetails,
|
openMetricDetails,
|
||||||
|
setHeatmapView,
|
||||||
}: MetricsTreemapProps): JSX.Element {
|
}: MetricsTreemapProps): JSX.Element {
|
||||||
const { width: windowWidth } = useWindowSize();
|
const { width: windowWidth } = useWindowSize();
|
||||||
|
|
||||||
@ -55,7 +57,10 @@ function MetricsTreemap({
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div data-testid="metrics-treemap-loading-state">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -90,6 +95,7 @@ function MetricsTreemap({
|
|||||||
data-testid="metrics-treemap-container"
|
data-testid="metrics-treemap-container"
|
||||||
>
|
>
|
||||||
<div className="metrics-treemap-title">
|
<div className="metrics-treemap-title">
|
||||||
|
<div className="metrics-treemap-title-left">
|
||||||
<Typography.Title level={4}>Proportion View</Typography.Title>
|
<Typography.Title level={4}>Proportion View</Typography.Title>
|
||||||
<Tooltip
|
<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."
|
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."
|
||||||
@ -98,6 +104,12 @@ function MetricsTreemap({
|
|||||||
<Info size={16} />
|
<Info size={16} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<Select
|
||||||
|
options={TREEMAP_VIEW_OPTIONS}
|
||||||
|
value={viewType}
|
||||||
|
onChange={setHeatmapView}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<svg
|
<svg
|
||||||
width={treemapWidth}
|
width={treemapWidth}
|
||||||
height={TREEMAP_HEIGHT}
|
height={TREEMAP_HEIGHT}
|
||||||
|
@ -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 {
|
.metrics-search-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
||||||
.metrics-search-options {
|
.metrics-search-options {
|
||||||
@ -35,6 +48,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
.lucide-info {
|
.lucide-info {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -11,6 +11,7 @@ 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 { 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 { DataSource } from 'types/common/queryBuilder';
|
||||||
@ -18,6 +19,12 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
|
|
||||||
import InspectModal from '../Inspect';
|
import InspectModal from '../Inspect';
|
||||||
import MetricDetails from '../MetricDetails';
|
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 MetricsSearch from './MetricsSearch';
|
||||||
import MetricsTable from './MetricsTable';
|
import MetricsTable from './MetricsTable';
|
||||||
import MetricsTreemap from './MetricsTreemap';
|
import MetricsTreemap from './MetricsTreemap';
|
||||||
@ -40,10 +47,16 @@ function Summary(): JSX.Element {
|
|||||||
const [heatmapView, setHeatmapView] = useState<TreemapViewType>(
|
const [heatmapView, setHeatmapView] = useState<TreemapViewType>(
|
||||||
TreemapViewType.TIMESERIES,
|
TreemapViewType.TIMESERIES,
|
||||||
);
|
);
|
||||||
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(false);
|
|
||||||
const [isInspectModalOpen, setIsInspectModalOpen] = useState(false);
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [selectedMetricName, setSelectedMetricName] = useState<string | null>(
|
const [isMetricDetailsOpen, setIsMetricDetailsOpen] = useState(
|
||||||
null,
|
() => 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>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
@ -75,13 +88,25 @@ function Summary(): JSX.Element {
|
|||||||
|
|
||||||
useShareBuilderUrl(defaultQuery);
|
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(
|
const queryFilters = useMemo(
|
||||||
() =>
|
() =>
|
||||||
currentQuery?.builder?.queryData[0]?.filters || {
|
currentQuery?.builder?.queryData[0]?.filters || {
|
||||||
items: [],
|
items: [],
|
||||||
op: 'and',
|
op: 'and',
|
||||||
},
|
},
|
||||||
[currentQuery],
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[currentQueryFiltersString],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { handleChangeQueryData } = useQueryOperations({
|
const { handleChangeQueryData } = useQueryOperations({
|
||||||
@ -145,9 +170,24 @@ function Summary(): JSX.Element {
|
|||||||
const handleFilterChange = useCallback(
|
const handleFilterChange = useCallback(
|
||||||
(value: TagFilter) => {
|
(value: TagFilter) => {
|
||||||
handleChangeQueryData('filters', value);
|
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);
|
setCurrentPage(1);
|
||||||
},
|
},
|
||||||
[handleChangeQueryData],
|
[handleChangeQueryData, currentQuery, setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedCurrentQuery = useMemo(
|
const updatedCurrentQuery = useMemo(
|
||||||
@ -184,17 +224,29 @@ function Summary(): JSX.Element {
|
|||||||
const openMetricDetails = (metricName: string): void => {
|
const openMetricDetails = (metricName: string): void => {
|
||||||
setSelectedMetricName(metricName);
|
setSelectedMetricName(metricName);
|
||||||
setIsMetricDetailsOpen(true);
|
setIsMetricDetailsOpen(true);
|
||||||
|
setSearchParams({
|
||||||
|
[IS_METRIC_DETAILS_OPEN_KEY]: 'true',
|
||||||
|
[SELECTED_METRIC_NAME_KEY]: metricName,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeMetricDetails = (): void => {
|
const closeMetricDetails = (): void => {
|
||||||
setSelectedMetricName(null);
|
setSelectedMetricName(null);
|
||||||
setIsMetricDetailsOpen(false);
|
setIsMetricDetailsOpen(false);
|
||||||
|
setSearchParams({
|
||||||
|
[IS_METRIC_DETAILS_OPEN_KEY]: 'false',
|
||||||
|
[SELECTED_METRIC_NAME_KEY]: '',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const openInspectModal = (metricName: string): void => {
|
const openInspectModal = (metricName: string): void => {
|
||||||
setSelectedMetricName(metricName);
|
setSelectedMetricName(metricName);
|
||||||
setIsInspectModalOpen(true);
|
setIsInspectModalOpen(true);
|
||||||
setIsMetricDetailsOpen(false);
|
setIsMetricDetailsOpen(false);
|
||||||
|
setSearchParams({
|
||||||
|
[IS_INSPECT_MODAL_OPEN_KEY]: 'true',
|
||||||
|
[SELECTED_METRIC_NAME_KEY]: metricName,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeInspectModal = (): void => {
|
const closeInspectModal = (): void => {
|
||||||
@ -204,23 +256,23 @@ function Summary(): JSX.Element {
|
|||||||
});
|
});
|
||||||
setIsInspectModalOpen(false);
|
setIsInspectModalOpen(false);
|
||||||
setSelectedMetricName(null);
|
setSelectedMetricName(null);
|
||||||
|
setSearchParams({
|
||||||
|
[IS_INSPECT_MODAL_OPEN_KEY]: 'false',
|
||||||
|
[SELECTED_METRIC_NAME_KEY]: '',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||||
<div className="metrics-explorer-summary-tab">
|
<div className="metrics-explorer-summary-tab">
|
||||||
<MetricsSearch
|
<MetricsSearch query={searchQuery} onChange={handleFilterChange} />
|
||||||
query={searchQuery}
|
|
||||||
onChange={handleFilterChange}
|
|
||||||
heatmapView={heatmapView}
|
|
||||||
setHeatmapView={setHeatmapView}
|
|
||||||
/>
|
|
||||||
<MetricsTreemap
|
<MetricsTreemap
|
||||||
data={treeMapData?.payload}
|
data={treeMapData?.payload}
|
||||||
isLoading={isTreeMapLoading || isTreeMapFetching}
|
isLoading={isTreeMapLoading || isTreeMapFetching}
|
||||||
isError={isProportionViewError}
|
isError={isProportionViewError}
|
||||||
viewType={heatmapView}
|
viewType={heatmapView}
|
||||||
openMetricDetails={openMetricDetails}
|
openMetricDetails={openMetricDetails}
|
||||||
|
setHeatmapView={setHeatmapView}
|
||||||
/>
|
/>
|
||||||
<MetricsTable
|
<MetricsTable
|
||||||
isLoading={isMetricsLoading || isMetricsFetching}
|
isLoading={isMetricsLoading || isMetricsFetching}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import * as useGetMetricsListFilterValues from 'hooks/metricsExplorer/useGetMetricsListFilterValues';
|
import * as useGetMetricsListFilterValues from 'hooks/metricsExplorer/useGetMetricsListFilterValues';
|
||||||
|
import * as useQueryBuilderOperationsHooks from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import store from 'store';
|
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', () => {
|
describe('MetricsTable', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest
|
||||||
|
.spyOn(useQueryBuilderOperationsHooks, 'useQueryOperations')
|
||||||
|
.mockReturnValue({
|
||||||
|
handleChangeQueryData: jest.fn(),
|
||||||
|
} as any);
|
||||||
|
});
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(useGetMetricsListFilterValues, 'useGetMetricsListFilterValues')
|
.spyOn(useGetMetricsListFilterValues, 'useGetMetricsListFilterValues')
|
||||||
.mockReturnValue({
|
.mockReturnValue({
|
||||||
|
@ -55,6 +55,7 @@ describe('MetricsTreemap', () => {
|
|||||||
}}
|
}}
|
||||||
openMetricDetails={jest.fn()}
|
openMetricDetails={jest.fn()}
|
||||||
viewType={TreemapViewType.SAMPLES}
|
viewType={TreemapViewType.SAMPLES}
|
||||||
|
setHeatmapView={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
@ -79,6 +80,7 @@ describe('MetricsTreemap', () => {
|
|||||||
}}
|
}}
|
||||||
openMetricDetails={jest.fn()}
|
openMetricDetails={jest.fn()}
|
||||||
viewType={TreemapViewType.SAMPLES}
|
viewType={TreemapViewType.SAMPLES}
|
||||||
|
setHeatmapView={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
@ -105,6 +107,7 @@ describe('MetricsTreemap', () => {
|
|||||||
}}
|
}}
|
||||||
openMetricDetails={jest.fn()}
|
openMetricDetails={jest.fn()}
|
||||||
viewType={TreemapViewType.SAMPLES}
|
viewType={TreemapViewType.SAMPLES}
|
||||||
|
setHeatmapView={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
@ -128,6 +131,7 @@ describe('MetricsTreemap', () => {
|
|||||||
data={null}
|
data={null}
|
||||||
openMetricDetails={jest.fn()}
|
openMetricDetails={jest.fn()}
|
||||||
viewType={TreemapViewType.SAMPLES}
|
viewType={TreemapViewType.SAMPLES}
|
||||||
|
setHeatmapView={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</MemoryRouter>,
|
</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.SUMMARY]: 'Summary',
|
||||||
[MetricType.EXPONENTIAL_HISTOGRAM]: 'ExponentialHistogram',
|
[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 {
|
export interface MetricsSearchProps {
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
onChange: (value: TagFilter) => void;
|
onChange: (value: TagFilter) => void;
|
||||||
heatmapView: TreemapViewType;
|
|
||||||
setHeatmapView: (value: TreemapViewType) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetricsTreemapProps {
|
export interface MetricsTreemapProps {
|
||||||
@ -30,6 +28,7 @@ export interface MetricsTreemapProps {
|
|||||||
isError: boolean;
|
isError: boolean;
|
||||||
viewType: TreemapViewType;
|
viewType: TreemapViewType;
|
||||||
openMetricDetails: (metricName: string) => void;
|
openMetricDetails: (metricName: string) => void;
|
||||||
|
setHeatmapView: (value: TreemapViewType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderByPayload {
|
export interface OrderByPayload {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user