Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UI] EVEREST-1647 Restrict selectable hours for PSMDB db with monthly schedule #1009

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4c2f4ac
fix: restrict selectable hours for PSMDB database for first day month…
dianabirs Jan 15, 2025
173efd0
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 15, 2025
6354d2b
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 16, 2025
43b6f86
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 17, 2025
00ced55
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
fabio-silva Jan 19, 2025
9f15fa8
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 20, 2025
cb3e98c
fix: typo
dianabirs Jan 20, 2025
c29db36
fix: typo
dianabirs Jan 20, 2025
19f676f
fix: set selectable minutes when timezone is UTC+X:30
dianabirs Jan 21, 2025
9542b66
test: unit test for TimeSelection
dianabirs Jan 21, 2025
2282cbf
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 21, 2025
a72a3e3
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 22, 2025
f4ed087
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
dianabirs Jan 23, 2025
ffad493
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
c84a310
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
34fef9a
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
6c2880a
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
9879e5a
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
78c06b9
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
9eee5d1
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
f1877b2
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
1068d52
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 23, 2025
2caa998
Merge branch 'main' into EVEREST-1647-ui-monthly-schedule-creates-an-…
percona-robot Jan 24, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
useEffect(() => {
// This allowed us to get an error from zod .superRefine to avoid duplication of checking the schedule with the same time
trigger();
}, [amPm, hour, minute, onDay, weekDay, selectedTime]);

Check warning on line 62 in ui/apps/everest/src/components/schedule-form-dialog/schedule-form-wrapper/schedule-form-wrapper.tsx

View workflow job for this annotation

GitHub Actions / CI_checks (lint)

React Hook useEffect has a missing dependency: 'trigger'. Either include it or remove the dependency array

useEffect(() => {
if (mode === 'edit' && setSelectedScheduleName) {
Expand All @@ -84,6 +84,7 @@
autoFillLocation={mode === 'new'}
disableNameEdit={mode === 'edit'}
schedules={schedules}
editMode={mode === 'edit'}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const ScheduleForm = ({
schedules,
showTypeRadio,
disableNameEdit = false,
editMode,
}: ScheduleFormProps) => {
const {
formState: { errors },
Expand Down Expand Up @@ -111,7 +112,12 @@ export const ScheduleForm = ({
isRequired
/>
<LabeledContent label={Messages.repeats}>
<TimeSelection showInfoAlert errorInfoAlert={errorInfoAlert} />
<TimeSelection
showInfoAlert
errorInfoAlert={errorInfoAlert}
shouldRestrictSelectableHours={dbEngine === DbEngineType.PSMDB}
editMode={editMode}
/>
</LabeledContent>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ScheduleFormProps = {
schedules: Schedule[];
showTypeRadio: boolean;
disableNameEdit?: boolean;
editMode: boolean;
};

export const ScheduleFormFields = { ...ScheduleForm, ...TimeSelectionFields };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { Messages } from '../time-selection.messages';
import { AmPM, TimeSelectionFields } from '../time-selection.types';
import { addZeroToSingleDigit } from '../time-selection.utils';

export const TimeFields = () => {
export const TimeFields = ({
selectableHours = HOURS_AM_PM,
selectableMinutes = MINUTES,
}: {
selectableHours?: number[];
selectableMinutes?: number[];
}) => {
const { control } = useFormContext();

return (
Expand All @@ -29,7 +35,11 @@ export const TimeFields = () => {
}}
>
{HOURS_AM_PM.map((value) => (
<MenuItem key={value} value={value}>
<MenuItem
key={value}
value={value}
sx={{ display: selectableHours.includes(value) ? 'block' : 'none' }}
>
{value}
</MenuItem>
))}
Expand All @@ -42,7 +52,13 @@ export const TimeFields = () => {
}}
>
{MINUTES.map((value) => (
<MenuItem key={value} value={value}>
<MenuItem
key={value}
value={value}
sx={{
display: selectableMinutes.includes(value) ? 'block' : 'none',
}}
>
{addZeroToSingleDigit(value)}
</MenuItem>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,112 @@ describe('TimeSelection', () => {
screen.queryByTestId('select-input-week-day')
).not.toBeInTheDocument();
});

it('should render correctly for monthly values for UTX+X:30 timezone', () => {
vi.stubEnv('TZ', 'Asia/Calcutta');
render(
<TestWrapper>
<FormProviderWrapper>
<TimeSelection shouldRestrictSelectableHours />
</FormProviderWrapper>
</TestWrapper>
);

const selectTimeValue = screen.getByTestId('select-input-selected-time');
expect(selectTimeValue).toBeInTheDocument();

fireEvent.change(selectTimeValue, { target: { value: 'month' } });

expect(selectTimeValue.getAttribute('value')).toBe('month');

expect(screen.getByTestId('select-input-hour')).toHaveAttribute(
'value',
'5'
);
expect(screen.getByTestId('select-input-minute')).toHaveAttribute(
'value',
'30'
);
});

it('should render correctly for UTX+X timezone when monthly is selected', () => {
vi.stubEnv('TZ', 'Europe/Amsterdam');
render(
<TestWrapper>
<FormProviderWrapper>
<TimeSelection shouldRestrictSelectableHours />
</FormProviderWrapper>
</TestWrapper>
);

const selectTimeValue = screen.getByTestId('select-input-selected-time');
expect(selectTimeValue).toBeInTheDocument();

fireEvent.change(selectTimeValue, { target: { value: 'month' } });

expect(selectTimeValue.getAttribute('value')).toBe('month');

expect(screen.getByTestId('select-input-hour')).toHaveAttribute(
'value',
'1'
);
expect(screen.getByTestId('select-input-minute')).toHaveAttribute(
'value',
'0'
);
});

it('should render correctly for UTX-X timezone if monthly is selected ', () => {
vi.stubEnv('TZ', 'America/Los_Angeles');
render(
<TestWrapper>
<FormProviderWrapper>
<TimeSelection shouldRestrictSelectableHours />
</FormProviderWrapper>
</TestWrapper>
);

const selectTimeValue = screen.getByTestId('select-input-selected-time');
expect(selectTimeValue).toBeInTheDocument();

fireEvent.change(selectTimeValue, { target: { value: 'month' } });

expect(selectTimeValue.getAttribute('value')).toBe('month');

expect(screen.getByTestId('select-input-hour')).toHaveAttribute(
'value',
'12'
);
expect(screen.getByTestId('select-input-minute')).toHaveAttribute(
'value',
'0'
);
});

it('should render normally if monthly is selected and selectable hours are not restricted', () => {
vi.stubEnv('TZ', 'Asia/Calcutta');
render(
<TestWrapper>
<FormProviderWrapper>
<TimeSelection shouldRestrictSelectableHours={false} />
</FormProviderWrapper>
</TestWrapper>
);

const selectTimeValue = screen.getByTestId('select-input-selected-time');
expect(selectTimeValue).toBeInTheDocument();

fireEvent.change(selectTimeValue, { target: { value: 'month' } });

expect(selectTimeValue.getAttribute('value')).toBe('month');

expect(screen.getByTestId('select-input-hour')).toHaveAttribute(
'value',
'12'
);
expect(screen.getByTestId('select-input-minute')).toHaveAttribute(
'value',
'0'
);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Alert, Box, MenuItem } from '@mui/material';
import { SelectInput } from '@percona/ui-lib';
import { useMemo } from 'react';
import { useMemo, useEffect } from 'react';
import { useFormContext } from 'react-hook-form';
import { HoursField } from './fields/hours-field';
import { MonthsField } from './fields/months-field';
Expand All @@ -14,21 +14,84 @@ import {
timeValueHumanized,
} from './time-selection.types';
import { getTimeText } from './time-selection.utils';
import { HOURS_AM_PM, MINUTES } from './time-selection.constants';

export const TimeSelection = ({
errorInfoAlert,
showInfoAlert,
sx,
sxTimeFields,
shouldRestrictSelectableHours = false,
editMode = false,
}: TimeSelectionProps) => {
const { watch } = useFormContext();
const { watch, setValue, getFieldState, resetField } = useFormContext();

const selectedTime: TimeValue = watch(TimeSelectionFields.selectedTime);
const minute: number = watch(TimeSelectionFields.minute);
const hour: number = watch(TimeSelectionFields.hour);
const amPm: string = watch(TimeSelectionFields.amPm);
const weekDay: string = watch(TimeSelectionFields.weekDay);
const onDay: number = watch(TimeSelectionFields.onDay);

const OFFSET = -1 * (new Date().getTimezoneOffset() / 60);

const TIMEZONE_OFFSET_HOURS = Math.floor(OFFSET);

const TIMEZONE_OFFSET_MINUTES = (OFFSET - TIMEZONE_OFFSET_HOURS) * 60;

const isFirstDayOfTheMonthAndPositiveOffset =
selectedTime === TimeValue.months &&
onDay === 1 &&
TIMEZONE_OFFSET_HOURS > 0;

const changeSelectableTime =
shouldRestrictSelectableHours && isFirstDayOfTheMonthAndPositiveOffset;

const selectableHours = changeSelectableTime
? Array.from(
{ length: 12 - TIMEZONE_OFFSET_HOURS },
(_, i) => i + Math.floor(TIMEZONE_OFFSET_HOURS)
)
: HOURS_AM_PM;

const selectableMinutes =
TIMEZONE_OFFSET_MINUTES > 0 && changeSelectableTime
? Array.from({ length: 30 }, (_, i) => i + TIMEZONE_OFFSET_MINUTES)
: MINUTES;

useEffect(() => {
const { isDirty: isHourFieldDirty } = getFieldState(
TimeSelectionFields.hour
);
const { isDirty: isMinuteFieldDirty } = getFieldState(
TimeSelectionFields.minute
);
if (changeSelectableTime && !isHourFieldDirty && !editMode) {
setValue(TimeSelectionFields.hour, Math.floor(TIMEZONE_OFFSET_HOURS));
}
if (
changeSelectableTime &&
TIMEZONE_OFFSET_MINUTES > 0 &&
!isMinuteFieldDirty &&
!editMode
) {
setValue(TimeSelectionFields.minute, TIMEZONE_OFFSET_MINUTES);
}
}, [
selectedTime,
onDay,
hour,
setValue,
getFieldState,
isFirstDayOfTheMonthAndPositiveOffset,
shouldRestrictSelectableHours,
resetField,
editMode,
changeSelectableTime,
TIMEZONE_OFFSET_MINUTES,
TIMEZONE_OFFSET_HOURS,
]);

const timeInfoText = useMemo(
() =>
showInfoAlert
Expand Down Expand Up @@ -84,7 +147,12 @@ export const TimeSelection = ({
{selectedTime === TimeValue.months && <MonthsField />}
{(selectedTime === TimeValue.days ||
selectedTime === TimeValue.weeks ||
selectedTime === TimeValue.months) && <TimeFields />}
selectedTime === TimeValue.months) && (
<TimeFields
selectableHours={selectableHours}
selectableMinutes={selectableMinutes}
/>
)}
</Box>
</Box>
{errorInfoAlert}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export interface TimeSelectionProps {
errorInfoAlert?: ReactNode;
sx?: SxProps<Theme>;
sxTimeFields?: SxProps<Theme>;
shouldRestrictSelectableHours?: boolean;
editMode?: boolean;
}

export type TimeProps = {
Expand Down
Loading