Compare commits

...

2 Commits

Author SHA1 Message Date
Ashwin Bhatkal
d93974490b refactor(dashboards): dedup public auto-refresh tick, use nano constant, userEvent
Route the auto-refresh interval through handleTimeChange instead of

duplicating the GetMinMax/setSelectedTimeRange logic, replace the 1e9

magic number with NANO_SECOND_MULTIPLIER, and switch the AutoRefresh

test to userEvent.
2026-07-02 15:06:31 +05:30
Ashwin Bhatkal
035577af45 feat(dashboards): add auto-refresh to public dashboards
Add a self-contained auto-refresh interval selector to the public dashboard
header, shown alongside the time picker when the publisher enabled the time
range. On each tick it recomputes the rolling window from the selected
relative range and updates local state, so panels refetch with an advanced
window. Paused/disabled for fixed 'custom' ranges.

Kept independent of Redux global time (which the public viewer doesn't use);
reuses the shared refreshIntervalOptions.
2026-07-02 09:57:45 +05:30
4 changed files with 108 additions and 2 deletions

View File

@@ -0,0 +1,13 @@
.autoRefresh {
display: flex;
align-items: center;
gap: 6px;
}
.icon {
color: var(--l2-foreground);
}
.select {
min-width: 96px;
}

View File

@@ -0,0 +1,41 @@
import { RefreshCw } from '@signozhq/icons';
import { SelectSimple } from '@signozhq/ui/select';
import { refreshIntervalOptions } from 'container/TopNav/AutoRefreshV2/constants';
import styles from './AutoRefresh.module.scss';
const REFRESH_ITEMS = refreshIntervalOptions.map((option) => ({
value: option.key,
label: option.key === 'off' ? 'Off' : option.label,
}));
interface AutoRefreshProps {
value: string;
disabled?: boolean;
onChange: (value: string) => void;
}
// Interval selector for the public dashboard. Self-contained (no Redux global
// time); the container advances its own time window on each tick.
function AutoRefresh({
value,
disabled = false,
onChange,
}: AutoRefreshProps): JSX.Element {
return (
<div className={styles.autoRefresh}>
<RefreshCw size={14} className={styles.icon} />
<SelectSimple
className={styles.select}
testId="public-dashboard-auto-refresh"
items={REFRESH_ITEMS}
value={value}
disabled={disabled}
withPortal={false}
onChange={(next): void => onChange(next as string)}
/>
</div>
);
}
export default AutoRefresh;

View File

@@ -1,10 +1,12 @@
import { useMemo, useState } from 'react';
import RGL, { WidthProvider } from 'react-grid-layout';
import { useInterval } from 'react-use';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { Card, CardContainer } from 'container/GridCardLayout/styles';
import { refreshIntervalOptions } from 'container/TopNav/AutoRefreshV2/constants';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import {
@@ -14,12 +16,14 @@ import {
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import GetMinMax from 'lib/getMinMax';
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
import { SuccessResponseV2 } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { PublicDashboardDataProps } from 'types/api/dashboard/public/get';
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
import AutoRefresh from './AutoRefresh';
import Panel from './Panel';
import './PublicDashboardContainer.styles.scss';
@@ -121,14 +125,34 @@ function PublicDashboardContainer({
const { maxTime, minTime } = GetMinMax(interval);
setSelectedTimeRange({
startTime: Math.floor(minTime / 1000000000),
endTime: Math.floor(maxTime / 1000000000),
startTime: Math.floor(minTime / NANO_SECOND_MULTIPLIER / 1000),
endTime: Math.floor(maxTime / NANO_SECOND_MULTIPLIER / 1000),
});
}
setSelectedTimeRangeLabel(interval as string);
};
const [refreshIntervalKey, setRefreshIntervalKey] = useState<string>('off');
// Auto-refresh only makes sense for a rolling relative range, not a fixed
// custom window — pause it (and disable the control) when 'custom' is picked.
const isAutoRefreshPaused = selectedTimeRangeLabel === 'custom';
const refreshIntervalMs = useMemo(
() =>
refreshIntervalOptions.find((option) => option.key === refreshIntervalKey)
?.value || 0,
[refreshIntervalKey],
);
// Re-run the existing time-change handler with the current relative range so
// the rolling window advances — no need to duplicate the GetMinMax logic.
useInterval(
() => handleTimeChange(selectedTimeRangeLabel as Time),
isAutoRefreshPaused || refreshIntervalMs === 0 ? null : refreshIntervalMs,
);
return (
<div className="public-dashboard-container">
<div className="public-dashboard-header">
@@ -148,6 +172,11 @@ function PublicDashboardContainer({
{isTimeRangeEnabled && (
<div className="public-dashboard-header-right">
<AutoRefresh
value={refreshIntervalKey}
disabled={isAutoRefreshPaused}
onChange={setRefreshIntervalKey}
/>
<div className="datetime-section">
<DateTimeSelectionV2
showAutoRefresh={false}

View File

@@ -0,0 +1,23 @@
import userEvent from '@testing-library/user-event';
import { render, screen } from 'tests/test-utils';
import AutoRefresh from '../AutoRefresh';
describe('Public dashboard AutoRefresh', () => {
it('renders the interval selector', () => {
render(<AutoRefresh value="off" onChange={jest.fn()} />);
expect(
screen.getByTestId('public-dashboard-auto-refresh'),
).toBeInTheDocument();
});
it('lets the viewer pick a refresh interval', async () => {
const onChange = jest.fn();
render(<AutoRefresh value="off" onChange={onChange} />);
await userEvent.click(screen.getByTestId('public-dashboard-auto-refresh'));
await userEvent.click(screen.getByText('30 seconds'));
expect(onChange).toHaveBeenCalledWith('30s');
});
});