mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
fix: resolve ui full reload on auto-refresh (#8383)
This commit is contained in:
parent
26d55875f5
commit
06ef9ff384
@ -194,7 +194,7 @@ function HostMetricTraces({
|
|||||||
{!isError && traces.length > 0 && (
|
{!isError && traces.length > 0 && (
|
||||||
<div className="host-metric-traces-table">
|
<div className="host-metric-traces-table">
|
||||||
<TraceExplorerControls
|
<TraceExplorerControls
|
||||||
isLoading={isFetching}
|
isLoading={isFetching && traces.length === 0}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
perPageOptions={PER_PAGE_OPTIONS}
|
perPageOptions={PER_PAGE_OPTIONS}
|
||||||
showSizeChanger={false}
|
showSizeChanger={false}
|
||||||
@ -203,7 +203,7 @@ function HostMetricTraces({
|
|||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={isFetching}
|
loading={isFetching && traces.length === 0}
|
||||||
dataSource={traces}
|
dataSource={traces}
|
||||||
columns={traceListColumns}
|
columns={traceListColumns}
|
||||||
onRow={(): Record<string, unknown> => ({
|
onRow={(): Record<string, unknown> => ({
|
||||||
|
|||||||
@ -37,7 +37,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -86,8 +86,12 @@ function HostMetricsDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedView, setSelectedView] = useState<VIEWS>(
|
const [selectedView, setSelectedView] = useState<VIEWS>(
|
||||||
@ -150,10 +154,11 @@ function HostMetricsDetails({
|
|||||||
}, [initialFilters]);
|
}, [initialFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -181,6 +186,7 @@ function HostMetricsDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -356,6 +362,7 @@ function HostMetricsDetails({
|
|||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSearchParams({});
|
setSearchParams({});
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -15,11 +15,12 @@ import {
|
|||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
import { useResizeObserver } from 'hooks/useDimensions';
|
||||||
|
import { useMultiIntersectionObserver } from 'hooks/useMultiIntersectionObserver';
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
import { QueryFunctionContext, useQueries, UseQueryResult } from 'react-query';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
|
||||||
@ -53,6 +54,11 @@ function Metrics({
|
|||||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||||
?.active || false;
|
?.active || false;
|
||||||
|
|
||||||
|
const {
|
||||||
|
visibilities,
|
||||||
|
setElement,
|
||||||
|
} = useMultiIntersectionObserver(hostWidgetInfo.length, { threshold: 0.1 });
|
||||||
|
|
||||||
const queryPayloads = useMemo(
|
const queryPayloads = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getHostQueryPayload(
|
getHostQueryPayload(
|
||||||
@ -65,11 +71,15 @@ function Metrics({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const queries = useQueries(
|
const queries = useQueries(
|
||||||
queryPayloads.map((payload) => ({
|
queryPayloads.map((payload, index) => ({
|
||||||
queryKey: ['host-metrics', payload, ENTITY_VERSION_V4, 'HOST'],
|
queryKey: ['host-metrics', payload, ENTITY_VERSION_V4, 'HOST'],
|
||||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
queryFn: ({
|
||||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
signal,
|
||||||
enabled: !!payload,
|
}: QueryFunctionContext): Promise<
|
||||||
|
SuccessResponse<MetricRangePayloadProps>
|
||||||
|
> => GetMetricQueryRange(payload, ENTITY_VERSION_V4, signal),
|
||||||
|
enabled: !!payload && visibilities[index],
|
||||||
|
keepPreviousData: true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -143,7 +153,7 @@ function Metrics({
|
|||||||
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
|
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
|
||||||
idx: number,
|
idx: number,
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
if (query.isLoading) {
|
if ((!query.data && query.isLoading) || !visibilities[idx]) {
|
||||||
return <Skeleton />;
|
return <Skeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +191,7 @@ function Metrics({
|
|||||||
</div>
|
</div>
|
||||||
<Row gutter={24} className="host-metrics-container">
|
<Row gutter={24} className="host-metrics-container">
|
||||||
{queries.map((query, idx) => (
|
{queries.map((query, idx) => (
|
||||||
<Col span={12} key={hostWidgetInfo[idx].title}>
|
<Col ref={setElement(idx)} span={12} key={hostWidgetInfo[idx].title}>
|
||||||
<Typography.Text>{hostWidgetInfo[idx].title}</Typography.Text>
|
<Typography.Text>{hostWidgetInfo[idx].title}</Typography.Text>
|
||||||
<Card bordered className="host-metrics-card" ref={graphRef}>
|
<Card bordered className="host-metrics-card" ref={graphRef}>
|
||||||
{renderCardContent(query, idx)}
|
{renderCardContent(query, idx)}
|
||||||
|
|||||||
@ -96,11 +96,41 @@ function HostsList(): JSX.Element {
|
|||||||
};
|
};
|
||||||
}, [pageSize, currentPage, filters, minTime, maxTime, orderBy]);
|
}, [pageSize, currentPage, filters, minTime, maxTime, orderBy]);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedHostName) {
|
||||||
|
return [
|
||||||
|
'hostList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(filters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'hostList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(filters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
filters,
|
||||||
|
orderBy,
|
||||||
|
selectedHostName,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetHostList(
|
const { data, isFetching, isLoading, isError } = useGetHostList(
|
||||||
query as HostListPayload,
|
query as HostListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['hostList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -212,6 +242,7 @@ function HostsList(): JSX.Element {
|
|||||||
<HostsListControls
|
<HostsListControls
|
||||||
filters={filters}
|
filters={filters}
|
||||||
handleFiltersChange={handleFiltersChange}
|
handleFiltersChange={handleFiltersChange}
|
||||||
|
showAutoRefresh={!selectedHostData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HostsListTable
|
<HostsListTable
|
||||||
|
|||||||
@ -11,9 +11,11 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
function HostsListControls({
|
function HostsListControls({
|
||||||
handleFiltersChange,
|
handleFiltersChange,
|
||||||
filters,
|
filters,
|
||||||
|
showAutoRefresh,
|
||||||
}: {
|
}: {
|
||||||
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
||||||
filters: IBuilderQuery['filters'];
|
filters: IBuilderQuery['filters'];
|
||||||
|
showAutoRefresh: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||||
const updatedCurrentQuery = useMemo(
|
const updatedCurrentQuery = useMemo(
|
||||||
@ -58,7 +60,7 @@ function HostsListControls({
|
|||||||
|
|
||||||
<div className="time-selector">
|
<div className="time-selector">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh
|
showAutoRefresh={showAutoRefresh}
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -93,9 +93,13 @@ export default function HostsListTable({
|
|||||||
const showHostsEmptyState =
|
const showHostsEmptyState =
|
||||||
!isFetching &&
|
!isFetching &&
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
|
formattedHostMetricsData.length === 0 &&
|
||||||
(!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics) &&
|
(!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics) &&
|
||||||
!filters.items.length;
|
!filters.items.length;
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isLoading || isFetching) && formattedHostMetricsData.length === 0;
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <Typography>{data?.error || 'Something went wrong'}</Typography>;
|
return <Typography>{data?.error || 'Something went wrong'}</Typography>;
|
||||||
}
|
}
|
||||||
@ -127,7 +131,7 @@ export default function HostsListTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading || isFetching) {
|
if (showTableLoadingState) {
|
||||||
return (
|
return (
|
||||||
<div className="hosts-list-loading-state">
|
<div className="hosts-list-loading-state">
|
||||||
<Skeleton.Input
|
<Skeleton.Input
|
||||||
@ -155,7 +159,7 @@ export default function HostsListTable({
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
className="hosts-list-table"
|
className="hosts-list-table"
|
||||||
dataSource={isLoading || isFetching ? [] : formattedHostMetricsData}
|
dataSource={showTableLoadingState ? [] : formattedHostMetricsData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -170,7 +174,7 @@ export default function HostsListTable({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
|
|||||||
@ -28,6 +28,7 @@ describe('HostsListControls', () => {
|
|||||||
<HostsListControls
|
<HostsListControls
|
||||||
handleFiltersChange={mockHandleFiltersChange}
|
handleFiltersChange={mockHandleFiltersChange}
|
||||||
filters={mockFilters}
|
filters={mockFilters}
|
||||||
|
showAutoRefresh={false}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -59,13 +59,27 @@ describe('HostsListTable', () => {
|
|||||||
setPageSize: mockSetPageSize,
|
setPageSize: mockSetPageSize,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
it('renders loading state if isLoading is true', () => {
|
it('renders loading state if isLoading is true and tableData is empty', () => {
|
||||||
const { container } = render(<HostsListTable {...mockProps} isLoading />);
|
const { container } = render(
|
||||||
|
<HostsListTable
|
||||||
|
{...mockProps}
|
||||||
|
isLoading
|
||||||
|
hostMetricsData={[]}
|
||||||
|
tableData={{ payload: { data: { hosts: [] } } }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
|
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders loading state if isFetching is true', () => {
|
it('renders loading state if isFetching is true and tableData is empty', () => {
|
||||||
const { container } = render(<HostsListTable {...mockProps} isFetching />);
|
const { container } = render(
|
||||||
|
<HostsListTable
|
||||||
|
{...mockProps}
|
||||||
|
isFetching
|
||||||
|
hostMetricsData={[]}
|
||||||
|
tableData={{ payload: { data: { hosts: [] } } }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
|
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,7 +89,17 @@ describe('HostsListTable', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders empty state if no hosts are found', () => {
|
it('renders empty state if no hosts are found', () => {
|
||||||
const { container } = render(<HostsListTable {...mockProps} />);
|
const { container } = render(
|
||||||
|
<HostsListTable
|
||||||
|
{...mockProps}
|
||||||
|
hostMetricsData={[]}
|
||||||
|
tableData={{
|
||||||
|
payload: {
|
||||||
|
data: { hosts: [] },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
|
expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,6 +107,7 @@ describe('HostsListTable', () => {
|
|||||||
const { container } = render(
|
const { container } = render(
|
||||||
<HostsListTable
|
<HostsListTable
|
||||||
{...mockProps}
|
{...mockProps}
|
||||||
|
hostMetricsData={[]}
|
||||||
tableData={{
|
tableData={{
|
||||||
...mockTableData,
|
...mockTableData,
|
||||||
payload: {
|
payload: {
|
||||||
@ -90,6 +115,7 @@ describe('HostsListTable', () => {
|
|||||||
data: {
|
data: {
|
||||||
...mockTableData.payload.data,
|
...mockTableData.payload.data,
|
||||||
sentAnyHostMetricsData: false,
|
sentAnyHostMetricsData: false,
|
||||||
|
hosts: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -102,6 +128,7 @@ describe('HostsListTable', () => {
|
|||||||
const { container } = render(
|
const { container } = render(
|
||||||
<HostsListTable
|
<HostsListTable
|
||||||
{...mockProps}
|
{...mockProps}
|
||||||
|
hostMetricsData={[]}
|
||||||
tableData={{
|
tableData={{
|
||||||
...mockTableData,
|
...mockTableData,
|
||||||
payload: {
|
payload: {
|
||||||
@ -109,6 +136,7 @@ describe('HostsListTable', () => {
|
|||||||
data: {
|
data: {
|
||||||
...mockTableData.payload.data,
|
...mockTableData.payload.data,
|
||||||
isSendingIncorrectK8SAgentMetrics: true,
|
isSendingIncorrectK8SAgentMetrics: true,
|
||||||
|
hosts: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -85,8 +85,12 @@ function ClusterDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -195,10 +199,11 @@ function ClusterDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -226,6 +231,7 @@ function ClusterDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -462,6 +468,7 @@ function ClusterDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -189,6 +189,32 @@ function K8sClustersList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedClusterName) {
|
||||||
|
return [
|
||||||
|
'clusterList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'clusterList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
selectedClusterName,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedRowData,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -198,7 +224,7 @@ function K8sClustersList({
|
|||||||
} = useGetK8sClustersList(
|
} = useGetK8sClustersList(
|
||||||
fetchGroupedByRowDataQuery as K8sClustersListPayload,
|
fetchGroupedByRowDataQuery as K8sClustersListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['clusterList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -254,11 +280,44 @@ function K8sClustersList({
|
|||||||
return groupedByRowData?.payload?.data?.records || [];
|
return groupedByRowData?.payload?.data?.records || [];
|
||||||
}, [groupedByRowData, selectedRowData]);
|
}, [groupedByRowData, selectedRowData]);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedClusterName) {
|
||||||
|
return [
|
||||||
|
'clusterList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'clusterList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedClusterName,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sClustersList(
|
const { data, isFetching, isLoading, isError } = useGetK8sClustersList(
|
||||||
query as K8sClustersListPayload,
|
query as K8sClustersListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['clusterList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -583,6 +642,9 @@ function K8sClustersList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedClustersData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -595,12 +657,13 @@ function K8sClustersList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.NODES}
|
entity={K8sCategory.NODES}
|
||||||
|
showAutoRefresh={!selectedClusterData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
className="k8s-list-table clusters-list-table"
|
className="k8s-list-table clusters-list-table"
|
||||||
dataSource={isFetching || isLoading ? [] : formattedClustersData}
|
dataSource={showTableLoadingState ? [] : formattedClustersData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -612,12 +675,11 @@ function K8sClustersList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -84,8 +84,12 @@ function DaemonSetDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -211,10 +215,11 @@ function DaemonSetDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -242,6 +247,7 @@ function DaemonSetDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -476,6 +482,7 @@ function DaemonSetDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -191,6 +191,32 @@ function K8sDaemonSetsList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedDaemonSetUID) {
|
||||||
|
return [
|
||||||
|
'daemonSetList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'daemonSetList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
selectedDaemonSetUID,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedRowData,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -200,7 +226,7 @@ function K8sDaemonSetsList({
|
|||||||
} = useGetK8sDaemonSetsList(
|
} = useGetK8sDaemonSetsList(
|
||||||
fetchGroupedByRowDataQuery as K8sDaemonSetsListPayload,
|
fetchGroupedByRowDataQuery as K8sDaemonSetsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['daemonSetList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -251,11 +277,44 @@ function K8sDaemonSetsList({
|
|||||||
[groupedByRowData, groupBy],
|
[groupedByRowData, groupBy],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedDaemonSetUID) {
|
||||||
|
return [
|
||||||
|
'daemonSetList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'daemonSetList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedDaemonSetUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sDaemonSetsList(
|
const { data, isFetching, isLoading, isError } = useGetK8sDaemonSetsList(
|
||||||
query as K8sDaemonSetsListPayload,
|
query as K8sDaemonSetsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['daemonSetList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -591,6 +650,9 @@ function K8sDaemonSetsList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedDaemonSetsData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -603,6 +665,7 @@ function K8sDaemonSetsList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.DAEMONSETS}
|
entity={K8sCategory.DAEMONSETS}
|
||||||
|
showAutoRefresh={!selectedDaemonSetData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
@ -610,7 +673,7 @@ function K8sDaemonSetsList({
|
|||||||
className={classNames('k8s-list-table', 'daemonSets-list-table', {
|
className={classNames('k8s-list-table', 'daemonSets-list-table', {
|
||||||
'expanded-daemonsets-list-table': isGroupedByAttribute,
|
'expanded-daemonsets-list-table': isGroupedByAttribute,
|
||||||
})}
|
})}
|
||||||
dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData}
|
dataSource={showTableLoadingState ? [] : formattedDaemonSetsData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -622,12 +685,11 @@ function K8sDaemonSetsList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -88,8 +88,12 @@ function DeploymentDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -215,10 +219,11 @@ function DeploymentDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -246,6 +251,7 @@ function DeploymentDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -487,6 +493,7 @@ function DeploymentDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -192,6 +192,32 @@ function K8sDeploymentsList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedDeploymentUID) {
|
||||||
|
return [
|
||||||
|
'deploymentList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'deploymentList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
selectedDeploymentUID,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedRowData,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -201,7 +227,7 @@ function K8sDeploymentsList({
|
|||||||
} = useGetK8sDeploymentsList(
|
} = useGetK8sDeploymentsList(
|
||||||
fetchGroupedByRowDataQuery as K8sDeploymentsListPayload,
|
fetchGroupedByRowDataQuery as K8sDeploymentsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['deploymentList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -252,11 +278,44 @@ function K8sDeploymentsList({
|
|||||||
[groupedByRowData, groupBy],
|
[groupedByRowData, groupBy],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedDeploymentUID) {
|
||||||
|
return [
|
||||||
|
'deploymentList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'deploymentList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedDeploymentUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList(
|
const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList(
|
||||||
query as K8sDeploymentsListPayload,
|
query as K8sDeploymentsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['deploymentList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -596,6 +655,9 @@ function K8sDeploymentsList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedDeploymentsData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -608,6 +670,7 @@ function K8sDeploymentsList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.NODES}
|
entity={K8sCategory.NODES}
|
||||||
|
showAutoRefresh={!selectedDeploymentData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
@ -615,7 +678,7 @@ function K8sDeploymentsList({
|
|||||||
className={classNames('k8s-list-table', 'deployments-list-table', {
|
className={classNames('k8s-list-table', 'deployments-list-table', {
|
||||||
'expanded-deployments-list-table': isGroupedByAttribute,
|
'expanded-deployments-list-table': isGroupedByAttribute,
|
||||||
})}
|
})}
|
||||||
dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
|
dataSource={showTableLoadingState ? [] : formattedDeploymentsData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -627,12 +690,11 @@ function K8sDeploymentsList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -270,7 +270,7 @@ export default function Events({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading && <LoadingContainer />}
|
{isLoading && formattedEntityEvents.length === 0 && <LoadingContainer />}
|
||||||
|
|
||||||
{!isLoading && !isError && formattedEntityEvents.length === 0 && (
|
{!isLoading && !isError && formattedEntityEvents.length === 0 && (
|
||||||
<EntityDetailsEmptyContainer category={category} view="events" />
|
<EntityDetailsEmptyContainer category={category} view="events" />
|
||||||
|
|||||||
@ -24,12 +24,13 @@ import {
|
|||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
import { QueryFunctionContext, useQueries, UseQueryResult } from 'react-query';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
import { Options } from 'uplot';
|
import { Options } from 'uplot';
|
||||||
|
|
||||||
import { FeatureKeys } from '../../../../constants/features';
|
import { FeatureKeys } from '../../../../constants/features';
|
||||||
|
import { useMultiIntersectionObserver } from '../../../../hooks/useMultiIntersectionObserver';
|
||||||
import { useAppContext } from '../../../../providers/App/App';
|
import { useAppContext } from '../../../../providers/App/App';
|
||||||
|
|
||||||
interface EntityMetricsProps<T> {
|
interface EntityMetricsProps<T> {
|
||||||
@ -73,6 +74,12 @@ function EntityMetrics<T>({
|
|||||||
const dotMetricsEnabled =
|
const dotMetricsEnabled =
|
||||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||||
?.active || false;
|
?.active || false;
|
||||||
|
|
||||||
|
const {
|
||||||
|
visibilities,
|
||||||
|
setElement,
|
||||||
|
} = useMultiIntersectionObserver(entityWidgetInfo.length, { threshold: 0.1 });
|
||||||
|
|
||||||
const queryPayloads = useMemo(
|
const queryPayloads = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getEntityQueryPayload(
|
getEntityQueryPayload(
|
||||||
@ -91,11 +98,15 @@ function EntityMetrics<T>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const queries = useQueries(
|
const queries = useQueries(
|
||||||
queryPayloads.map((payload) => ({
|
queryPayloads.map((payload, index) => ({
|
||||||
queryKey: [queryKey, payload, ENTITY_VERSION_V4, category],
|
queryKey: [queryKey, payload, ENTITY_VERSION_V4, category],
|
||||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
queryFn: ({
|
||||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
signal,
|
||||||
enabled: !!payload,
|
}: QueryFunctionContext): Promise<
|
||||||
|
SuccessResponse<MetricRangePayloadProps>
|
||||||
|
> => GetMetricQueryRange(payload, ENTITY_VERSION_V4, signal),
|
||||||
|
enabled: !!payload && visibilities[index],
|
||||||
|
keepPreviousData: true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -186,7 +197,7 @@ function EntityMetrics<T>({
|
|||||||
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
|
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
|
||||||
idx: number,
|
idx: number,
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
if (query.isLoading) {
|
if ((!query.data && query.isLoading) || !visibilities[idx]) {
|
||||||
return <Skeleton />;
|
return <Skeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +207,7 @@ function EntityMetrics<T>({
|
|||||||
return <div>{errorMessage}</div>;
|
return <div>{errorMessage}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { panelType } = (query.data?.params as any).compositeQuery;
|
const panelType = (query.data?.params as any)?.compositeQuery?.panelType;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -234,7 +245,7 @@ function EntityMetrics<T>({
|
|||||||
</div>
|
</div>
|
||||||
<Row gutter={24} className="entity-metrics-container">
|
<Row gutter={24} className="entity-metrics-container">
|
||||||
{queries.map((query, idx) => (
|
{queries.map((query, idx) => (
|
||||||
<Col span={12} key={entityWidgetInfo[idx].title}>
|
<Col ref={setElement(idx)} span={12} key={entityWidgetInfo[idx].title}>
|
||||||
<Typography.Text>{entityWidgetInfo[idx].title}</Typography.Text>
|
<Typography.Text>{entityWidgetInfo[idx].title}</Typography.Text>
|
||||||
<Card bordered className="entity-metrics-card" ref={graphRef}>
|
<Card bordered className="entity-metrics-card" ref={graphRef}>
|
||||||
{renderCardContent(query, idx)}
|
{renderCardContent(query, idx)}
|
||||||
|
|||||||
@ -203,7 +203,7 @@ function EntityTraces({
|
|||||||
{!isError && traces.length > 0 && (
|
{!isError && traces.length > 0 && (
|
||||||
<div className="entity-traces-table">
|
<div className="entity-traces-table">
|
||||||
<TraceExplorerControls
|
<TraceExplorerControls
|
||||||
isLoading={isFetching}
|
isLoading={isFetching && traces.length === 0}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
perPageOptions={PER_PAGE_OPTIONS}
|
perPageOptions={PER_PAGE_OPTIONS}
|
||||||
showSizeChanger={false}
|
showSizeChanger={false}
|
||||||
@ -212,7 +212,7 @@ function EntityTraces({
|
|||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={isFetching}
|
loading={isFetching && traces.length === 0}
|
||||||
dataSource={traces}
|
dataSource={traces}
|
||||||
columns={traceListColumns}
|
columns={traceListColumns}
|
||||||
onRow={(): Record<string, unknown> => ({
|
onRow={(): Record<string, unknown> => ({
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -81,8 +81,12 @@ function JobDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -204,10 +208,11 @@ function JobDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -235,6 +240,7 @@ function JobDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -469,6 +475,7 @@ function JobDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -186,6 +186,25 @@ function K8sJobsList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedJobUID) {
|
||||||
|
return [
|
||||||
|
'jobList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'jobList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [queryFilters, orderBy, selectedJobUID, minTime, maxTime, selectedRowData]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -195,7 +214,7 @@ function K8sJobsList({
|
|||||||
} = useGetK8sJobsList(
|
} = useGetK8sJobsList(
|
||||||
fetchGroupedByRowDataQuery as K8sJobsListPayload,
|
fetchGroupedByRowDataQuery as K8sJobsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['jobList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -251,11 +270,44 @@ function K8sJobsList({
|
|||||||
return groupedByRowData?.payload?.data?.records || [];
|
return groupedByRowData?.payload?.data?.records || [];
|
||||||
}, [groupedByRowData, selectedRowData]);
|
}, [groupedByRowData, selectedRowData]);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedJobUID) {
|
||||||
|
return [
|
||||||
|
'jobList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'jobList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedJobUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sJobsList(
|
const { data, isFetching, isLoading, isError } = useGetK8sJobsList(
|
||||||
query as K8sJobsListPayload,
|
query as K8sJobsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['jobList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -581,6 +633,7 @@ function K8sJobsList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.JOBS}
|
entity={K8sCategory.JOBS}
|
||||||
|
showAutoRefresh={!selectedJobData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ interface K8sHeaderProps {
|
|||||||
handleFilterVisibilityChange: () => void;
|
handleFilterVisibilityChange: () => void;
|
||||||
isFiltersVisible: boolean;
|
isFiltersVisible: boolean;
|
||||||
entity: K8sCategory;
|
entity: K8sCategory;
|
||||||
|
showAutoRefresh: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function K8sHeader({
|
function K8sHeader({
|
||||||
@ -46,6 +47,7 @@ function K8sHeader({
|
|||||||
handleFilterVisibilityChange,
|
handleFilterVisibilityChange,
|
||||||
isFiltersVisible,
|
isFiltersVisible,
|
||||||
entity,
|
entity,
|
||||||
|
showAutoRefresh,
|
||||||
}: K8sHeaderProps): JSX.Element {
|
}: K8sHeaderProps): JSX.Element {
|
||||||
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
|
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -136,7 +138,7 @@ function K8sHeader({
|
|||||||
|
|
||||||
<div className="k8s-list-controls-right">
|
<div className="k8s-list-controls-right">
|
||||||
<DateTimeSelectionV2
|
<DateTimeSelectionV2
|
||||||
showAutoRefresh
|
showAutoRefresh={showAutoRefresh}
|
||||||
showRefreshText={false}
|
showRefreshText={false}
|
||||||
hideShareModal
|
hideShareModal
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -190,6 +190,32 @@ function K8sNamespacesList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedNamespaceUID) {
|
||||||
|
return [
|
||||||
|
'namespaceList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'namespaceList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
selectedNamespaceUID,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedRowData,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -199,7 +225,7 @@ function K8sNamespacesList({
|
|||||||
} = useGetK8sNamespacesList(
|
} = useGetK8sNamespacesList(
|
||||||
fetchGroupedByRowDataQuery as K8sNamespacesListPayload,
|
fetchGroupedByRowDataQuery as K8sNamespacesListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['namespaceList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -250,11 +276,44 @@ function K8sNamespacesList({
|
|||||||
[groupedByRowData, groupBy],
|
[groupedByRowData, groupBy],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedNamespaceUID) {
|
||||||
|
return [
|
||||||
|
'namespaceList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'namespaceList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedNamespaceUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList(
|
const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList(
|
||||||
query as K8sNamespacesListPayload,
|
query as K8sNamespacesListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['namespaceList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -592,6 +651,9 @@ function K8sNamespacesList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedNamespacesData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -604,12 +666,13 @@ function K8sNamespacesList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.NODES}
|
entity={K8sCategory.NODES}
|
||||||
|
showAutoRefresh={!selectedNamespaceData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
className="k8s-list-table namespaces-list-table"
|
className="k8s-list-table namespaces-list-table"
|
||||||
dataSource={isFetching || isLoading ? [] : formattedNamespacesData}
|
dataSource={showTableLoadingState ? [] : formattedNamespacesData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -621,12 +684,11 @@ function K8sNamespacesList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -35,7 +35,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -85,8 +85,12 @@ function NamespaceDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -195,10 +199,11 @@ function NamespaceDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -226,6 +231,7 @@ function NamespaceDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -461,6 +467,7 @@ function NamespaceDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -184,6 +184,32 @@ function K8sNodesList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedNodeUID) {
|
||||||
|
return [
|
||||||
|
'nodeList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'nodeList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
selectedNodeUID,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedRowData,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -193,7 +219,7 @@ function K8sNodesList({
|
|||||||
} = useGetK8sNodesList(
|
} = useGetK8sNodesList(
|
||||||
fetchGroupedByRowDataQuery as K8sNodesListPayload,
|
fetchGroupedByRowDataQuery as K8sNodesListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['nodeList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -249,11 +275,44 @@ function K8sNodesList({
|
|||||||
[groupedByRowData, groupBy],
|
[groupedByRowData, groupBy],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedNodeUID) {
|
||||||
|
return [
|
||||||
|
'nodeList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'nodeList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedNodeUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sNodesList(
|
const { data, isFetching, isLoading, isError } = useGetK8sNodesList(
|
||||||
query as K8sNodesListPayload,
|
query as K8sNodesListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['nodeList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -571,6 +630,9 @@ function K8sNodesList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedNodesData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -583,12 +645,13 @@ function K8sNodesList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.NODES}
|
entity={K8sCategory.NODES}
|
||||||
|
showAutoRefresh={!selectedNodeData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
className="k8s-list-table nodes-list-table"
|
className="k8s-list-table nodes-list-table"
|
||||||
dataSource={isFetching || isLoading ? [] : formattedNodesData}
|
dataSource={showTableLoadingState ? [] : formattedNodesData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -600,12 +663,11 @@ function K8sNodesList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -85,8 +85,12 @@ function NodeDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -195,10 +199,11 @@ function NodeDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -226,6 +231,7 @@ function NodeDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -464,6 +470,7 @@ function NodeDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -205,11 +205,44 @@ function K8sPodsList({
|
|||||||
return queryPayload;
|
return queryPayload;
|
||||||
}, [pageSize, currentPage, queryFilters, minTime, maxTime, orderBy, groupBy]);
|
}, [pageSize, currentPage, queryFilters, minTime, maxTime, orderBy, groupBy]);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedPodUID) {
|
||||||
|
return [
|
||||||
|
'podList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'podList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedPodUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sPodsList(
|
const { data, isFetching, isLoading, isError } = useGetK8sPodsList(
|
||||||
query as K8sPodsListPayload,
|
query as K8sPodsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['hostList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -261,6 +294,25 @@ function K8sPodsList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData]);
|
}, [minTime, maxTime, orderBy, selectedRowData]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedPodUID) {
|
||||||
|
return [
|
||||||
|
'podList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'podList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [queryFilters, orderBy, selectedPodUID, minTime, maxTime, selectedRowData]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -270,7 +322,7 @@ function K8sPodsList({
|
|||||||
} = useGetK8sPodsList(
|
} = useGetK8sPodsList(
|
||||||
fetchGroupedByRowDataQuery as K8sPodsListPayload,
|
fetchGroupedByRowDataQuery as K8sPodsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['hostList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -629,6 +681,9 @@ function K8sPodsList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedPodsData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -645,6 +700,7 @@ function K8sPodsList({
|
|||||||
onAddColumn={handleAddColumn}
|
onAddColumn={handleAddColumn}
|
||||||
onRemoveColumn={handleRemoveColumn}
|
onRemoveColumn={handleRemoveColumn}
|
||||||
entity={K8sCategory.PODS}
|
entity={K8sCategory.PODS}
|
||||||
|
showAutoRefresh={!selectedPodData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
@ -652,7 +708,7 @@ function K8sPodsList({
|
|||||||
className={classNames('k8s-list-table', {
|
className={classNames('k8s-list-table', {
|
||||||
'expanded-k8s-list-table': isGroupedByAttribute,
|
'expanded-k8s-list-table': isGroupedByAttribute,
|
||||||
})}
|
})}
|
||||||
dataSource={isFetching || isLoading ? [] : formattedPodsData}
|
dataSource={showTableLoadingState ? [] : formattedPodsData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -663,12 +719,11 @@ function K8sPodsList({
|
|||||||
onChange: onPaginationChange,
|
onChange: onPaginationChange,
|
||||||
}}
|
}}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -89,8 +89,12 @@ function PodDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -212,10 +216,11 @@ function PodDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / TimeRangeOffset),
|
startTime: Math.floor(minTime / TimeRangeOffset),
|
||||||
@ -243,6 +248,7 @@ function PodDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -485,6 +491,7 @@ function PodDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -191,6 +191,32 @@ function K8sStatefulSetsList({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const groupedByRowDataQueryKey = useMemo(() => {
|
||||||
|
if (selectedStatefulSetUID) {
|
||||||
|
return [
|
||||||
|
'statefulSetList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'statefulSetList',
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(selectedRowData),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
selectedStatefulSetUID,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedRowData,
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groupedByRowData,
|
data: groupedByRowData,
|
||||||
isFetching: isFetchingGroupedByRowData,
|
isFetching: isFetchingGroupedByRowData,
|
||||||
@ -200,7 +226,7 @@ function K8sStatefulSetsList({
|
|||||||
} = useGetK8sStatefulSetsList(
|
} = useGetK8sStatefulSetsList(
|
||||||
fetchGroupedByRowDataQuery as K8sStatefulSetsListPayload,
|
fetchGroupedByRowDataQuery as K8sStatefulSetsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['statefulSetList', fetchGroupedByRowDataQuery],
|
queryKey: groupedByRowDataQueryKey,
|
||||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -256,11 +282,44 @@ function K8sStatefulSetsList({
|
|||||||
return groupedByRowData?.payload?.data?.records || [];
|
return groupedByRowData?.payload?.data?.records || [];
|
||||||
}, [groupedByRowData, selectedRowData]);
|
}, [groupedByRowData, selectedRowData]);
|
||||||
|
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (selectedStatefulSetUID) {
|
||||||
|
return [
|
||||||
|
'statefulSetList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'statefulSetList',
|
||||||
|
String(pageSize),
|
||||||
|
String(currentPage),
|
||||||
|
JSON.stringify(queryFilters),
|
||||||
|
JSON.stringify(orderBy),
|
||||||
|
JSON.stringify(groupBy),
|
||||||
|
String(minTime),
|
||||||
|
String(maxTime),
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
selectedStatefulSetUID,
|
||||||
|
pageSize,
|
||||||
|
currentPage,
|
||||||
|
queryFilters,
|
||||||
|
orderBy,
|
||||||
|
groupBy,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const { data, isFetching, isLoading, isError } = useGetK8sStatefulSetsList(
|
const { data, isFetching, isLoading, isError } = useGetK8sStatefulSetsList(
|
||||||
query as K8sStatefulSetsListPayload,
|
query as K8sStatefulSetsListPayload,
|
||||||
{
|
{
|
||||||
queryKey: ['statefulSetList', query],
|
queryKey,
|
||||||
enabled: !!query,
|
enabled: !!query,
|
||||||
|
keepPreviousData: true,
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
dotMetricsEnabled,
|
dotMetricsEnabled,
|
||||||
@ -592,6 +651,9 @@ function K8sStatefulSetsList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedStatefulSetsData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -604,6 +666,7 @@ function K8sStatefulSetsList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.STATEFULSETS}
|
entity={K8sCategory.STATEFULSETS}
|
||||||
|
showAutoRefresh={!selectedStatefulSetData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
@ -611,7 +674,7 @@ function K8sStatefulSetsList({
|
|||||||
className={classNames('k8s-list-table', 'statefulSets-list-table', {
|
className={classNames('k8s-list-table', 'statefulSets-list-table', {
|
||||||
'expanded-statefulsets-list-table': isGroupedByAttribute,
|
'expanded-statefulsets-list-table': isGroupedByAttribute,
|
||||||
})}
|
})}
|
||||||
dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData}
|
dataSource={showTableLoadingState ? [] : formattedStatefulSetsData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -623,12 +686,11 @@ function K8sStatefulSetsList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import {
|
|||||||
ScrollText,
|
ScrollText,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, 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';
|
||||||
@ -84,8 +84,12 @@ function StatefulSetDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -211,10 +215,11 @@ function StatefulSetDetails({
|
|||||||
}, [initialFilters, initialEventsFilters]);
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -242,6 +247,7 @@ function StatefulSetDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -477,6 +483,7 @@ function StatefulSetDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
@ -574,6 +574,9 @@ function K8sVolumesList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTableLoadingState =
|
||||||
|
(isFetching || isLoading) && formattedVolumesData.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="k8s-list">
|
<div className="k8s-list">
|
||||||
<K8sHeader
|
<K8sHeader
|
||||||
@ -586,6 +589,7 @@ function K8sVolumesList({
|
|||||||
handleGroupByChange={handleGroupByChange}
|
handleGroupByChange={handleGroupByChange}
|
||||||
selectedGroupBy={groupBy}
|
selectedGroupBy={groupBy}
|
||||||
entity={K8sCategory.NODES}
|
entity={K8sCategory.NODES}
|
||||||
|
showAutoRefresh={!selectedVolumeData}
|
||||||
/>
|
/>
|
||||||
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
@ -593,7 +597,7 @@ function K8sVolumesList({
|
|||||||
className={classNames('k8s-list-table', 'volumes-list-table', {
|
className={classNames('k8s-list-table', 'volumes-list-table', {
|
||||||
'expanded-volumes-list-table': isGroupedByAttribute,
|
'expanded-volumes-list-table': isGroupedByAttribute,
|
||||||
})}
|
})}
|
||||||
dataSource={isFetching || isLoading ? [] : formattedVolumesData}
|
dataSource={showTableLoadingState ? [] : formattedVolumesData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: currentPage,
|
current: currentPage,
|
||||||
@ -605,12 +609,11 @@ function K8sVolumesList({
|
|||||||
}}
|
}}
|
||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isFetching || isLoading,
|
spinning: showTableLoadingState,
|
||||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
}}
|
}}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText:
|
emptyText: showTableLoadingState ? null : (
|
||||||
isFetching || isLoading ? null : (
|
|
||||||
<div className="no-filtered-hosts-message-container">
|
<div className="no-filtered-hosts-message-container">
|
||||||
<div className="no-filtered-hosts-message-content">
|
<div className="no-filtered-hosts-message-content">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import GetMinMax from 'lib/getMinMax';
|
import GetMinMax from 'lib/getMinMax';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
@ -44,8 +44,12 @@ function VolumeDetails({
|
|||||||
endTime: endMs,
|
endTime: endMs,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const lastSelectedInterval = useRef<Time | null>(null);
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
||||||
selectedTime as Time,
|
lastSelectedInterval.current
|
||||||
|
? lastSelectedInterval.current
|
||||||
|
: (selectedTime as Time),
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
@ -62,10 +66,11 @@ function VolumeDetails({
|
|||||||
}, [volume]);
|
}, [volume]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedInterval(selectedTime as Time);
|
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
|
||||||
|
setSelectedInterval(currentSelectedInterval as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (currentSelectedInterval !== 'custom') {
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
|
||||||
|
|
||||||
setModalTimeRange({
|
setModalTimeRange({
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
@ -76,6 +81,7 @@ function VolumeDetails({
|
|||||||
|
|
||||||
const handleTimeChange = useCallback(
|
const handleTimeChange = useCallback(
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
lastSelectedInterval.current = interval as Time;
|
||||||
setSelectedInterval(interval as Time);
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
@ -104,6 +110,7 @@ function VolumeDetails({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
|
lastSelectedInterval.current = null;
|
||||||
setSelectedInterval(selectedTime as Time);
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
if (selectedTime !== 'custom') {
|
||||||
|
|||||||
54
frontend/src/hooks/useMultiIntersectionObserver.ts
Normal file
54
frontend/src/hooks/useMultiIntersectionObserver.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
type SetElement = (el: Element | null) => void;
|
||||||
|
|
||||||
|
// To manage intersection observers for multiple items
|
||||||
|
export function useMultiIntersectionObserver(
|
||||||
|
itemCount: number,
|
||||||
|
options: IntersectionObserverInit = { threshold: 0.1 },
|
||||||
|
): {
|
||||||
|
visibilities: boolean[];
|
||||||
|
setElement: (index: number) => SetElement;
|
||||||
|
} {
|
||||||
|
const elementsRef = useRef<(Element | null)[]>([]);
|
||||||
|
|
||||||
|
const [everVisibles, setEverVisibles] = useState<boolean[]>(
|
||||||
|
new Array(itemCount).fill(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
const setElement = useCallback<(index: number) => SetElement>(
|
||||||
|
(index) => (el): void => {
|
||||||
|
elementsRef.current[index] = el;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!elementsRef.current.length) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
setEverVisibles((prev) => {
|
||||||
|
const newVis = [...prev];
|
||||||
|
let changed = false;
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const idx = elementsRef.current.indexOf(entry.target);
|
||||||
|
if (idx !== -1 && entry.isIntersecting && !newVis[idx]) {
|
||||||
|
newVis[idx] = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return changed ? newVis : prev;
|
||||||
|
});
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
elementsRef.current.forEach((el) => {
|
||||||
|
if (el) observer.observe(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [itemCount, options]);
|
||||||
|
|
||||||
|
return { visibilities: everVisibles, setElement };
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user