Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions galasa-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 28 additions & 6 deletions galasa-ui/src/components/test-runs/timeframe/TimeFrameContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,13 @@ export default function TimeFrameContent({ values, setValues }: TimeFrameContent
const { getResolvedTimeZone } = useDateTimeFormat();

const [notification, setNotification] = useState<Notification | null>(null);
const [selectedFromOption, setSelectedFromOption] = useState<FromSelectionOptions>(
values.isRelativeToNow ? FromSelectionOptions.duration : FromSelectionOptions.specificFromTime
const [selectedFromOption, setSelectedFromOption] = useState<FromSelectionOptions | null>(
FromSelectionOptions.specificFromTime
);
const [selectedToOption, setSelectedToOption] = useState<ToSelectionOptions>(
values.isRelativeToNow ? ToSelectionOptions.now : ToSelectionOptions.specificToTime
const [selectedToOption, setSelectedToOption] = useState<ToSelectionOptions | null>(
ToSelectionOptions.specificToTime
);
const [hasInitialized, setHasInitialized] = useState(false);

const handleValueChange = useCallback(
(field: keyof TimeFrameValues, value: any) => {
Expand Down Expand Up @@ -172,12 +173,12 @@ export default function TimeFrameContent({ values, setValues }: TimeFrameContent
toDate = new Date();
}

const isisRelativeToNow = field === 'isRelativeToNow' ? value : values.isRelativeToNow;
const isRelativeToNow = field === 'isRelativeToNow' ? value : values.isRelativeToNow;
const {
correctedFrom,
correctedTo,
notification: validationNotification,
} = applyTimeFrameRules(fromDate, toDate, isisRelativeToNow, translations);
} = applyTimeFrameRules(fromDate, toDate, isRelativeToNow, translations);

// Set the notification if there is one
setNotification(validationNotification);
Expand All @@ -197,6 +198,27 @@ export default function TimeFrameContent({ values, setValues }: TimeFrameContent
setValues((prevValues) => ({ ...prevValues, isRelativeToNow }));
}, [selectedToOption, setValues]);

// Sync selected options whenever isRelativeToNow changes (from URL or user interaction)
useEffect(() => {
if (values.isRelativeToNow !== undefined) {
const toOption = values.isRelativeToNow
? ToSelectionOptions.now
: ToSelectionOptions.specificToTime;

// Always sync the "To" option with isRelativeToNow
setSelectedToOption(toOption);

// Only set the "From" option on initial load
if (!hasInitialized) {
const fromOption = values.isRelativeToNow
? FromSelectionOptions.duration
: FromSelectionOptions.specificFromTime;
setSelectedFromOption(fromOption);
setHasInitialized(true);
}
}
}, [values.isRelativeToNow, hasInitialized]);

return (
<div className={styles.timeFrameContainer}>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,5 +473,84 @@ describe('applyTimeFrameRules', () => {
expect(toDateInput).toHaveValue(MOCK_NOW.toLocaleDateString('en-US'));
});
});

test('should sync "To" option when isRelativeToNow changes but keep "From" option unchanged after initialization', async () => {
const initialFrom = '2025-08-10T12:00:00.000Z';
const initialTo = '2025-08-13T12:00:00.000Z';

const TestWrapper = () => {
const [values, setValues] = useState<TimeFrameValues>(() => {
const initialFromDate = new Date(initialFrom);
const initialToDate = new Date(initialTo);

return {
...calculateSynchronizedState(initialFromDate, initialToDate, timezone),
isRelativeToNow: false, // Start with specific time (not relative to now)
};
});

return (
<>
<TimeFrameContent values={values} setValues={setValues} />
{/* Button to simulate external isRelativeToNow change}*/}
<button
data-testid="toggle-relative"
onClick={() =>
setValues((prev) => ({ ...prev, isRelativeToNow: !prev.isRelativeToNow }))
}
>
Toggle isRelativeToNow
</button>
</>
);
};

render(<TestWrapper />);

const nowRadio = screen.getByLabelText('Now');
const specificToTimeRadio = screen.getByLabelText('A specific time', {
selector: '#to-specific-time',
});
const specificFromTimeRadio = screen.getByLabelText('A specific time', {
selector: '#from-specific-time',
});

// Initial state: isRelativeToNow is false, so "A specific time" should be checked for both
await waitFor(() => {
expect(specificToTimeRadio).toBeChecked();
expect(specificFromTimeRadio).toBeChecked();
});

// Act: Manually select "Duration" for the "From" option
const durationFromRadio = screen.getByLabelText(/Duration before/i);
fireEvent.click(durationFromRadio);

await waitFor(() => {
expect(durationFromRadio).toBeChecked();
});

// Simulate an external change to isRelativeToNow to true
const toggleButton = screen.getByTestId('toggle-relative');
fireEvent.click(toggleButton);

// "To" option should sync to "Now" since isRelativeToNow is now true
await waitFor(() => {
expect(nowRadio).toBeChecked();
});

// "From" option should remain as "Duration" (user's manual selection preserved)
expect(durationFromRadio).toBeChecked();

// Toggle isRelativeToNow back to false
fireEvent.click(toggleButton);

// "To" option should sync back to "A specific time"
await waitFor(() => {
expect(specificToTimeRadio).toBeChecked();
});

// "From" option should still remain as "Duration" (not reset to specific time)
expect(durationFromRadio).toBeChecked();
});
});
});