mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting/Chore: Fix TimeRangeInput not working across multiple months (#93622)
This commit is contained in:
parent
fc93d6f795
commit
af82dfa95d
@ -0,0 +1,55 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import { dateTime } from '@grafana/data';
|
||||||
|
|
||||||
|
import { TimeRangeInput } from './TimeRangeInput';
|
||||||
|
|
||||||
|
describe('TimeRangeInput', () => {
|
||||||
|
// TODO: This test is evergreen - the check that we haven't accidentally closed
|
||||||
|
// the picker still passes without the appropriate fix
|
||||||
|
// Seems to be related to jest-dom and how it handles clicking outside the node etc.
|
||||||
|
it('handles selecting dates over multiple months', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const from = dateTime('2024-01-01T00:00:00Z');
|
||||||
|
const to = dateTime('2024-01-01T00:00:00Z');
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TimeRangeInput
|
||||||
|
timeZone="utc"
|
||||||
|
onChange={(payload) => {
|
||||||
|
const { from, to } = payload;
|
||||||
|
onChange({ from: from.toString(), to: to.toString() });
|
||||||
|
}}
|
||||||
|
value={{
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
raw: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// TimeRangeInput renders as a button that looks like an input -
|
||||||
|
// the only one we can see at the start is the button to open the picker
|
||||||
|
await user.click(screen.getByRole('button'));
|
||||||
|
|
||||||
|
const [firstOpenCalendarButton] = await screen.findAllByRole('button', { name: /open calendar/i });
|
||||||
|
await user.click(firstOpenCalendarButton);
|
||||||
|
|
||||||
|
// Select two dates that are on different "screens" of the calendar picker - this is where the bug would occur
|
||||||
|
await user.click(await screen.findByLabelText(/january 1, 2024/i));
|
||||||
|
await user.click(await screen.findByLabelText(/next month/i));
|
||||||
|
await user.click(await screen.findByLabelText(/february 28, 2024/i));
|
||||||
|
|
||||||
|
await user.click(await screen.findByText(/apply time range/i));
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith({
|
||||||
|
from: 'Mon Jan 01 2024 00:00:00 GMT+0000',
|
||||||
|
to: 'Wed Feb 28 2024 23:59:59 GMT+0000',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,11 +1,13 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { FormEvent, MouseEvent, useState } from 'react';
|
import { useDialog } from '@react-aria/dialog';
|
||||||
|
import { FocusScope } from '@react-aria/focus';
|
||||||
|
import { useOverlay } from '@react-aria/overlays';
|
||||||
|
import { createRef, FormEvent, MouseEvent, useState } from 'react';
|
||||||
|
|
||||||
import { dateTime, getDefaultTimeRange, GrafanaTheme2, TimeRange, TimeZone } from '@grafana/data';
|
import { dateTime, getDefaultTimeRange, GrafanaTheme2, TimeRange, TimeZone } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { useStyles2 } from '../../themes/ThemeContext';
|
import { useStyles2 } from '../../themes/ThemeContext';
|
||||||
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
|
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { getInputStyles } from '../Input/Input';
|
import { getInputStyles } from '../Input/Input';
|
||||||
|
|
||||||
@ -77,6 +79,22 @@ export const TimeRangeInput = ({
|
|||||||
onChange({ from, to, raw: { from, to } });
|
onChange({ from, to, raw: { from, to } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const overlayRef = createRef<HTMLElement>();
|
||||||
|
const buttonRef = createRef<HTMLButtonElement>();
|
||||||
|
|
||||||
|
const { dialogProps } = useDialog({}, overlayRef);
|
||||||
|
|
||||||
|
const { overlayProps } = useOverlay(
|
||||||
|
{
|
||||||
|
onClose,
|
||||||
|
isDismissable: true,
|
||||||
|
isOpen,
|
||||||
|
shouldCloseOnInteractOutside: (element) => {
|
||||||
|
return !buttonRef.current?.contains(element);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overlayRef
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<button
|
<button
|
||||||
@ -84,6 +102,7 @@ export const TimeRangeInput = ({
|
|||||||
className={styles.pickerInput}
|
className={styles.pickerInput}
|
||||||
data-testid={selectors.components.TimePicker.openButton}
|
data-testid={selectors.components.TimePicker.openButton}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
|
ref={buttonRef}
|
||||||
>
|
>
|
||||||
{showIcon && <Icon name="clock-nine" size={'sm'} className={styles.icon} />}
|
{showIcon && <Icon name="clock-nine" size={'sm'} className={styles.icon} />}
|
||||||
|
|
||||||
@ -99,7 +118,8 @@ export const TimeRangeInput = ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<ClickOutsideWrapper includeButtonPress={false} onClick={onClose}>
|
<FocusScope contain autoFocus restoreFocus>
|
||||||
|
<section className={styles.content} ref={overlayRef} {...overlayProps} {...dialogProps}>
|
||||||
<TimePickerContent
|
<TimePickerContent
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
value={isValidTimeRange(value) ? value : getDefaultTimeRange()}
|
value={isValidTimeRange(value) ? value : getDefaultTimeRange()}
|
||||||
@ -112,7 +132,8 @@ export const TimeRangeInput = ({
|
|||||||
hideQuickRanges={hideQuickRanges}
|
hideQuickRanges={hideQuickRanges}
|
||||||
weekStart={weekStart}
|
weekStart={weekStart}
|
||||||
/>
|
/>
|
||||||
</ClickOutsideWrapper>
|
</section>
|
||||||
|
</FocusScope>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user