mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
uses pf date and time picker to schedule form
This commit is contained in:
parent
ac5b53b13c
commit
16c6e2d716
@ -35,6 +35,8 @@
|
||||
{
|
||||
"markupOnly": true,
|
||||
"ignoreAttribute": [
|
||||
"dateFieldName",
|
||||
"timeFieldName",
|
||||
"to",
|
||||
"streamType",
|
||||
"path",
|
||||
@ -85,7 +87,7 @@
|
||||
"data-cy",
|
||||
"fieldName"
|
||||
],
|
||||
"ignore": ["Ansible", "Tower", "JSON", "YAML", "lg"],
|
||||
"ignore": ["Ansible", "Tower", "JSON", "YAML", "lg", "hh:mm AM/PM"],
|
||||
"ignoreComponent": [
|
||||
"AboutModal",
|
||||
"code",
|
||||
|
||||
@ -41,7 +41,6 @@ function ScheduleAdd({
|
||||
end,
|
||||
frequency,
|
||||
interval,
|
||||
startDateTime,
|
||||
timezone,
|
||||
occurrences,
|
||||
runOn,
|
||||
@ -49,7 +48,6 @@ function ScheduleAdd({
|
||||
runOnTheMonth,
|
||||
runOnDayMonth,
|
||||
runOnDayNumber,
|
||||
endDateTime,
|
||||
runOnTheOccurrence,
|
||||
credentials,
|
||||
daysOfWeek,
|
||||
@ -100,6 +98,10 @@ function ScheduleAdd({
|
||||
});
|
||||
}
|
||||
}
|
||||
delete requestData.startDate;
|
||||
delete requestData.startTime;
|
||||
delete requestData.endDate;
|
||||
delete requestData.endTime;
|
||||
|
||||
const {
|
||||
data: { id: scheduleId },
|
||||
|
||||
@ -82,7 +82,8 @@ describe('<ScheduleAdd />', () => {
|
||||
frequency: 'none',
|
||||
interval: 1,
|
||||
name: 'Run once schedule',
|
||||
startDateTime: '2020-03-25T10:00:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:00:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -103,7 +104,8 @@ describe('<ScheduleAdd />', () => {
|
||||
interval: 10,
|
||||
name: 'Run every 10 minutes 10 times',
|
||||
occurrences: 10,
|
||||
startDateTime: '2020-03-25T10:30:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:30:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -120,11 +122,13 @@ describe('<ScheduleAdd />', () => {
|
||||
wrapper.find('Formik').invoke('onSubmit')({
|
||||
description: 'test description',
|
||||
end: 'onDate',
|
||||
endDateTime: '2020-03-26T10:45:00',
|
||||
endDate: '2020-03-26',
|
||||
endTime: '10:45:00',
|
||||
frequency: 'hour',
|
||||
interval: 1,
|
||||
name: 'Run every hour until date',
|
||||
startDateTime: '2020-03-25T10:45:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:45:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -144,7 +148,8 @@ describe('<ScheduleAdd />', () => {
|
||||
frequency: 'day',
|
||||
interval: 1,
|
||||
name: 'Run daily',
|
||||
startDateTime: '2020-03-25T10:45:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:45:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -166,7 +171,8 @@ describe('<ScheduleAdd />', () => {
|
||||
interval: 1,
|
||||
name: 'Run weekly on mon/wed/fri',
|
||||
occurrences: 1,
|
||||
startDateTime: '2020-03-25T10:45:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:45:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -188,7 +194,8 @@ describe('<ScheduleAdd />', () => {
|
||||
occurrences: 1,
|
||||
runOn: 'day',
|
||||
runOnDayNumber: 1,
|
||||
startDateTime: '2020-04-01T10:45',
|
||||
startTime: '10:45',
|
||||
startDate: '2020-04-01',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -205,7 +212,8 @@ describe('<ScheduleAdd />', () => {
|
||||
wrapper.find('Formik').invoke('onSubmit')({
|
||||
description: 'test description',
|
||||
end: 'never',
|
||||
endDateTime: '2020-03-26T11:00:00',
|
||||
endDate: '2020-03-26',
|
||||
endTime: '11:00:00',
|
||||
frequency: 'month',
|
||||
interval: 1,
|
||||
name: 'Run monthly on the last Tuesday',
|
||||
@ -213,7 +221,8 @@ describe('<ScheduleAdd />', () => {
|
||||
runOn: 'the',
|
||||
runOnTheDay: 'tuesday',
|
||||
runOnTheOccurrence: -1,
|
||||
startDateTime: '2020-03-31T11:00',
|
||||
startDate: '2020-03-31',
|
||||
startTime: '11:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -237,7 +246,8 @@ describe('<ScheduleAdd />', () => {
|
||||
runOn: 'day',
|
||||
runOnDayMonth: 3,
|
||||
runOnDayNumber: 1,
|
||||
startDateTime: '2020-03-01T00:00',
|
||||
startDate: '2020-03-01',
|
||||
startTime: '00:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -262,7 +272,8 @@ describe('<ScheduleAdd />', () => {
|
||||
runOnTheOccurrence: 2,
|
||||
runOnTheDay: 'friday',
|
||||
runOnTheMonth: 4,
|
||||
startDateTime: '2020-04-10T11:15',
|
||||
startDate: '2020-04-10',
|
||||
startTime: '11:15',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -287,7 +298,8 @@ describe('<ScheduleAdd />', () => {
|
||||
runOnTheOccurrence: 1,
|
||||
runOnTheDay: 'weekday',
|
||||
runOnTheMonth: 10,
|
||||
startDateTime: '2020-04-10T11:15',
|
||||
startDate: '2020-04-10',
|
||||
startTime: '11:15',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -371,7 +383,8 @@ describe('<ScheduleAdd />', () => {
|
||||
wrapper.find('Formik').invoke('onSubmit')({
|
||||
name: 'Schedule',
|
||||
end: 'never',
|
||||
endDateTime: '2021-01-29T14:15:00',
|
||||
endDate: '2021-01-29',
|
||||
endTime: '14:15:00',
|
||||
frequency: 'none',
|
||||
occurrences: 1,
|
||||
runOn: 'day',
|
||||
@ -386,7 +399,8 @@ describe('<ScheduleAdd />', () => {
|
||||
{ name: 'cred 1', id: 10 },
|
||||
{ name: 'cred 2', id: 20 },
|
||||
],
|
||||
startDateTime: '2021-01-28T14:15:00',
|
||||
startDate: '2021-01-28',
|
||||
startTime: '14:15:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -457,7 +471,8 @@ describe('<ScheduleAdd />', () => {
|
||||
frequency: 'none',
|
||||
interval: 1,
|
||||
name: 'Run once schedule',
|
||||
startDateTime: '2020-03-25T10:00:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:00:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
|
||||
@ -41,7 +41,6 @@ function ScheduleEdit({
|
||||
end,
|
||||
frequency,
|
||||
interval,
|
||||
startDateTime,
|
||||
timezone,
|
||||
occurences,
|
||||
runOn,
|
||||
@ -49,7 +48,6 @@ function ScheduleEdit({
|
||||
runOnTheMonth,
|
||||
runOnDayMonth,
|
||||
runOnDayNumber,
|
||||
endDateTime,
|
||||
runOnTheOccurence,
|
||||
daysOfWeek,
|
||||
...submitValues
|
||||
@ -98,6 +96,10 @@ function ScheduleEdit({
|
||||
...submitValues,
|
||||
rrule: rule.toString().replace(/\n/g, ' '),
|
||||
};
|
||||
delete requestData.startDate;
|
||||
delete requestData.startTime;
|
||||
delete requestData.endDate;
|
||||
delete requestData.endTime;
|
||||
|
||||
if (Object.keys(values).includes('daysToKeep')) {
|
||||
if (!requestData.extra_data) {
|
||||
|
||||
@ -16,7 +16,10 @@ import ScheduleEdit from './ScheduleEdit';
|
||||
jest.mock('../../../api');
|
||||
|
||||
let wrapper;
|
||||
|
||||
const now = new Date();
|
||||
const closestQuarterHour = new Date(Math.ceil(now.getTime() / 900000) * 900000);
|
||||
const tomorrow = new Date(closestQuarterHour);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const mockSchedule = {
|
||||
rrule:
|
||||
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
|
||||
@ -202,7 +205,8 @@ describe('<ScheduleEdit />', () => {
|
||||
frequency: 'none',
|
||||
interval: 1,
|
||||
name: 'Run once schedule',
|
||||
startDateTime: '2020-03-25T10:00:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:00:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -223,7 +227,8 @@ describe('<ScheduleEdit />', () => {
|
||||
interval: 10,
|
||||
name: 'Run every 10 minutes 10 times',
|
||||
occurrences: 10,
|
||||
startDateTime: '2020-03-25T10:30:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:30:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -241,11 +246,13 @@ describe('<ScheduleEdit />', () => {
|
||||
wrapper.find('Formik').invoke('onSubmit')({
|
||||
description: 'test description',
|
||||
end: 'onDate',
|
||||
endDateTime: '2020-03-26T10:45:00',
|
||||
endDate: '2020-03-26',
|
||||
endTime: '10:45:00',
|
||||
frequency: 'hour',
|
||||
interval: 1,
|
||||
name: 'Run every hour until date',
|
||||
startDateTime: '2020-03-25T10:45:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:45:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -265,7 +272,8 @@ describe('<ScheduleEdit />', () => {
|
||||
frequency: 'day',
|
||||
interval: 1,
|
||||
name: 'Run daily',
|
||||
startDateTime: '2020-03-25T10:45:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:45:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -287,7 +295,8 @@ describe('<ScheduleEdit />', () => {
|
||||
interval: 1,
|
||||
name: 'Run weekly on mon/wed/fri',
|
||||
occurrences: 1,
|
||||
startDateTime: '2020-03-25T10:45:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:45:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -310,7 +319,8 @@ describe('<ScheduleEdit />', () => {
|
||||
occurrences: 1,
|
||||
runOn: 'day',
|
||||
runOnDayNumber: 1,
|
||||
startDateTime: '2020-04-01T10:45',
|
||||
startDate: '2020-04-01',
|
||||
startTime: '10:45',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -336,12 +346,14 @@ describe('<ScheduleEdit />', () => {
|
||||
runOn: 'the',
|
||||
runOnTheDay: 'tuesday',
|
||||
runOnTheOccurrence: -1,
|
||||
startDateTime: '2020-03-31T11:00',
|
||||
startDate: '2020-03-31',
|
||||
startTime: '11:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||
description: 'test description',
|
||||
endDateTime: '2020-03-26T11:00:00',
|
||||
name: 'Run monthly on the last Tuesday',
|
||||
extra_data: {},
|
||||
occurrences: 1,
|
||||
@ -362,7 +374,8 @@ describe('<ScheduleEdit />', () => {
|
||||
runOn: 'day',
|
||||
runOnDayMonth: 3,
|
||||
runOnDayNumber: 1,
|
||||
startDateTime: '2020-03-01T00:00',
|
||||
startTime: '00:00',
|
||||
startDate: '2020-03-01',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -388,7 +401,8 @@ describe('<ScheduleEdit />', () => {
|
||||
runOnTheOccurrence: 2,
|
||||
runOnTheDay: 'friday',
|
||||
runOnTheMonth: 4,
|
||||
startDateTime: '2020-04-10T11:15',
|
||||
startTime: '11:15',
|
||||
startDate: '2020-04-10',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -415,7 +429,8 @@ describe('<ScheduleEdit />', () => {
|
||||
runOnTheOccurrence: 1,
|
||||
runOnTheDay: 'weekday',
|
||||
runOnTheMonth: 10,
|
||||
startDateTime: '2020-04-10T11:15',
|
||||
startTime: '11:15',
|
||||
startDate: '2020-04-10',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
@ -526,7 +541,8 @@ describe('<ScheduleEdit />', () => {
|
||||
wrapper.find('Formik').invoke('onSubmit')({
|
||||
name: mockSchedule.name,
|
||||
end: 'never',
|
||||
endDateTime: '2021-01-29T14:15:00',
|
||||
endDate: '2021-01-29',
|
||||
endTime: '14:15:00',
|
||||
frequency: 'none',
|
||||
occurrences: 1,
|
||||
runOn: 'day',
|
||||
@ -536,7 +552,8 @@ describe('<ScheduleEdit />', () => {
|
||||
runOnTheMonth: 1,
|
||||
runOnTheOccurrence: 1,
|
||||
skip_tags: '',
|
||||
startDateTime: '2021-01-28T14:15:00',
|
||||
startDate: '2021-01-28',
|
||||
startTime: '14:15:00',
|
||||
timezone: 'America/New_York',
|
||||
credentials: [
|
||||
{ id: 3, name: 'Credential 3', kind: 'ssh', url: '' },
|
||||
@ -622,7 +639,10 @@ describe('<ScheduleEdit />', () => {
|
||||
await act(async () =>
|
||||
wrapper.find('Button[aria-label="Save"]').prop('onClick')()
|
||||
);
|
||||
|
||||
expect(SchedulesAPI.update).toBeCalledWith(27, {
|
||||
endDateTime: undefined,
|
||||
startDateTime: undefined,
|
||||
description: '',
|
||||
extra_data: {},
|
||||
occurrences: 1,
|
||||
@ -630,7 +650,7 @@ describe('<ScheduleEdit />', () => {
|
||||
name: 'foo',
|
||||
inventory: 702,
|
||||
rrule:
|
||||
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
|
||||
'DTSTART;TZID=America/New_York:20200402T184500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
|
||||
});
|
||||
});
|
||||
test('should submit survey with default values properly, without opening prompt wizard', async () => {
|
||||
@ -728,7 +748,8 @@ describe('<ScheduleEdit />', () => {
|
||||
frequency: 'none',
|
||||
interval: 1,
|
||||
name: 'Run once schedule',
|
||||
startDateTime: '2020-03-25T10:00:00',
|
||||
startDate: '2020-03-25',
|
||||
startTime: '10:00:00',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import {
|
||||
DatePicker,
|
||||
isValidDate,
|
||||
yyyyMMddFormat,
|
||||
TimePicker,
|
||||
FormGroup,
|
||||
} from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
import { required, validateTime, combine } from '../../../util/validators';
|
||||
|
||||
const DateTimeGroup = styled.span`
|
||||
display: flex;
|
||||
`;
|
||||
function DateTimePicker({ dateFieldName, timeFieldName, label }) {
|
||||
const [dateField, dateMeta, dateHelpers] = useField({
|
||||
name: `${dateFieldName}`,
|
||||
validate: combine([required(null), isValidDate]),
|
||||
});
|
||||
const [timeField, timeMeta, timeHelpers] = useField({
|
||||
name: `${timeFieldName}`,
|
||||
validate: combine([required(null), validateTime()]),
|
||||
});
|
||||
|
||||
const onDateChange = (inputDate, newDate) => {
|
||||
dateHelpers.setTouched();
|
||||
if (isValidDate(newDate) && inputDate === yyyyMMddFormat(newDate)) {
|
||||
dateHelpers.setValue(new Date(newDate).toISOString().split('T')[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId={`schedule-${label}-datetime`}
|
||||
helperTextInvalid={dateMeta.error || timeMeta.error}
|
||||
isRequired
|
||||
validated={
|
||||
(!dateMeta.touched || !dateMeta.error) &&
|
||||
(!timeMeta.touched || !timeMeta.error)
|
||||
? 'default'
|
||||
: 'error'
|
||||
}
|
||||
label={`${label} date/time`}
|
||||
>
|
||||
<DateTimeGroup>
|
||||
<DatePicker
|
||||
aria-label={t`${label} date`}
|
||||
{...dateField}
|
||||
value={dateField.value.split('T')[0]}
|
||||
onChange={onDateChange}
|
||||
/>
|
||||
<TimePicker
|
||||
placeholder="hh:mm AM/PM"
|
||||
stepMinutes={15}
|
||||
aria-label={t`${label} time`}
|
||||
time={timeField.value}
|
||||
{...timeField}
|
||||
onChange={time => timeHelpers.setValue(time)}
|
||||
/>
|
||||
</DateTimeGroup>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export default DateTimePicker;
|
||||
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import DateTimePicker from './DateTimePicker';
|
||||
|
||||
describe('<DateTimePicker/>', () => {
|
||||
let wrapper;
|
||||
test('should render properly', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik
|
||||
initialValues={{ startDate: '2021-05-26', startTime: '2:15 PM' }}
|
||||
>
|
||||
<DateTimePicker
|
||||
dateFieldName="startDate"
|
||||
timeFieldName="startTime"
|
||||
label="Start"
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
expect(wrapper.find('DatePicker')).toHaveLength(1);
|
||||
expect(wrapper.find('DatePicker').prop('value')).toBe('2021-05-26');
|
||||
expect(wrapper.find('TimePicker')).toHaveLength(1);
|
||||
expect(wrapper.find('TimePicker').prop('value')).toBe('2:15 PM');
|
||||
});
|
||||
test('should update values properly', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Formik
|
||||
initialValues={{ startDate: '2021-05-26', startTime: '2:15 PM' }}
|
||||
>
|
||||
<DateTimePicker
|
||||
dateFieldName="startDate"
|
||||
timeFieldName="startTime"
|
||||
label="Start"
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
wrapper.find('DatePicker').prop('onChange')(
|
||||
'2021-05-29',
|
||||
new Date('Sat May 29 2021 00:00:00 GMT-0400 (Eastern Daylight Time)')
|
||||
);
|
||||
wrapper.find('TimePicker').prop('onChange')('7:15 PM');
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('DatePicker').prop('value')).toBe('2021-05-29');
|
||||
expect(wrapper.find('TimePicker').prop('value')).toBe('7:15 PM');
|
||||
});
|
||||
});
|
||||
@ -14,6 +14,7 @@ import {
|
||||
import AnsibleSelect from '../../AnsibleSelect';
|
||||
import FormField from '../../FormField';
|
||||
import { required } from '../../../util/validators';
|
||||
import DateTimePicker from './DateTimePicker';
|
||||
|
||||
const RunOnRadio = styled(Radio)`
|
||||
label {
|
||||
@ -74,9 +75,8 @@ const FrequencyDetailSubform = () => {
|
||||
const [runOnTheMonth] = useField({
|
||||
name: 'runOnTheMonth',
|
||||
});
|
||||
const [startDateTime] = useField({
|
||||
name: 'startDateTime',
|
||||
});
|
||||
const [startDate] = useField('startDate');
|
||||
|
||||
const [daysOfWeek, daysOfWeekMeta, daysOfWeekHelpers] = useField({
|
||||
name: 'daysOfWeek',
|
||||
validate: required(t`Select a value for this field`),
|
||||
@ -93,10 +93,6 @@ const FrequencyDetailSubform = () => {
|
||||
name: 'runOn',
|
||||
validate: required(t`Select a value for this field`),
|
||||
});
|
||||
const [endDateTime, endDateTimeMeta] = useField({
|
||||
name: 'endDateTime',
|
||||
validate: required(t`Select a value for this field`),
|
||||
});
|
||||
const [frequency] = useField({
|
||||
name: 'frequency',
|
||||
});
|
||||
@ -317,7 +313,7 @@ const FrequencyDetailSubform = () => {
|
||||
</FormGroup>
|
||||
)}
|
||||
{(frequency?.value === 'month' || frequency?.value === 'year') &&
|
||||
!isNaN(new Date(startDateTime.value)) && (
|
||||
!isNaN(new Date(startDate.value)) && (
|
||||
<FormGroup
|
||||
name="runOn"
|
||||
fieldId="schedule-run-on"
|
||||
@ -538,29 +534,14 @@ const FrequencyDetailSubform = () => {
|
||||
/>
|
||||
)}
|
||||
{end?.value === 'onDate' && (
|
||||
<FormGroup
|
||||
fieldId="schedule-end-datetime"
|
||||
helperTextInvalid={endDateTimeMeta.error}
|
||||
isRequired
|
||||
validated={
|
||||
!endDateTimeMeta.touched || !endDateTimeMeta.error
|
||||
? 'default'
|
||||
: 'error'
|
||||
}
|
||||
label={t`End date/time`}
|
||||
>
|
||||
<input
|
||||
className="pf-c-form-control"
|
||||
type="datetime-local"
|
||||
id="schedule-end-datetime"
|
||||
step="1"
|
||||
{...endDateTime}
|
||||
/>
|
||||
</FormGroup>
|
||||
<DateTimePicker
|
||||
dateFieldName="endDate"
|
||||
timeFieldName="endTime"
|
||||
label={t`End`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
/* eslint-enable no-restricted-globals */
|
||||
};
|
||||
|
||||
export default FrequencyDetailSubform;
|
||||
|
||||
@ -22,12 +22,13 @@ import {
|
||||
SubFormLayout,
|
||||
FormFullWidthLayout,
|
||||
} from '../../FormLayout';
|
||||
import { dateToInputDateTime, formatDateStringUTC } from '../../../util/dates';
|
||||
import { dateToInputDateTime } from '../../../util/dates';
|
||||
import useRequest from '../../../util/useRequest';
|
||||
import { required } from '../../../util/validators';
|
||||
import { parseVariableField } from '../../../util/yaml';
|
||||
import FrequencyDetailSubform from './FrequencyDetailSubform';
|
||||
import SchedulePromptableFields from './SchedulePromptableFields';
|
||||
import DateTimePicker from './DateTimePicker';
|
||||
|
||||
const generateRunOnTheDay = (days = []) => {
|
||||
if (
|
||||
@ -79,10 +80,6 @@ const generateRunOnTheDay = (days = []) => {
|
||||
};
|
||||
|
||||
function ScheduleFormFields({ hasDaysToKeepField, zoneOptions }) {
|
||||
const [startDateTime, startDateTimeMeta] = useField({
|
||||
name: 'startDateTime',
|
||||
validate: required(t`Select a valid date and time for this field`),
|
||||
});
|
||||
const [timezone, timezoneMeta] = useField({
|
||||
name: 'timezone',
|
||||
validate: required(t`Select a value for this field`),
|
||||
@ -108,25 +105,11 @@ function ScheduleFormFields({ hasDaysToKeepField, zoneOptions }) {
|
||||
name="description"
|
||||
type="text"
|
||||
/>
|
||||
<FormGroup
|
||||
fieldId="schedule-start-datetime"
|
||||
helperTextInvalid={startDateTimeMeta.error}
|
||||
isRequired
|
||||
validated={
|
||||
!startDateTimeMeta.touched || !startDateTimeMeta.error
|
||||
? 'default'
|
||||
: 'error'
|
||||
}
|
||||
label={t`Start date/time`}
|
||||
>
|
||||
<input
|
||||
className="pf-c-form-control"
|
||||
type="datetime-local"
|
||||
id="schedule-start-datetime"
|
||||
step="1"
|
||||
{...startDateTime}
|
||||
/>
|
||||
</FormGroup>
|
||||
<DateTimePicker
|
||||
dateFieldName="startDate"
|
||||
timeFieldName="startTime"
|
||||
label={t`Start`}
|
||||
/>
|
||||
<FormGroup
|
||||
name="timezone"
|
||||
fieldId="schedule-timezone"
|
||||
@ -394,12 +377,15 @@ function ScheduleForm({
|
||||
) {
|
||||
showPromptButton = true;
|
||||
}
|
||||
const [currentDate, time] = dateToInputDateTime(closestQuarterHour);
|
||||
|
||||
const [tomorrowDate] = dateToInputDateTime(tomorrow);
|
||||
const initialValues = {
|
||||
daysOfWeek: [],
|
||||
description: schedule.description || '',
|
||||
end: 'never',
|
||||
endDateTime: dateToInputDateTime(tomorrow),
|
||||
endDate: tomorrowDate,
|
||||
endTime: time,
|
||||
frequency: 'none',
|
||||
interval: 1,
|
||||
name: schedule.name || '',
|
||||
@ -410,7 +396,8 @@ function ScheduleForm({
|
||||
runOnTheDay: 'sunday',
|
||||
runOnTheMonth: 1,
|
||||
runOnTheOccurrence: 1,
|
||||
startDateTime: dateToInputDateTime(closestQuarterHour),
|
||||
startDate: currentDate,
|
||||
startTime: time,
|
||||
timezone: schedule.timezone || 'America/New_York',
|
||||
};
|
||||
const submitSchedule = (
|
||||
@ -462,14 +449,19 @@ function ScheduleForm({
|
||||
} = RRule.fromString(schedule.rrule.replace(' ', '\n'));
|
||||
|
||||
if (dtstart) {
|
||||
overriddenValues.startDateTime = dateToInputDateTime(
|
||||
new Date(formatDateStringUTC(dtstart))
|
||||
);
|
||||
const [startDate, startTime] = dateToInputDateTime(schedule.dtstart);
|
||||
|
||||
overriddenValues.startDate = startDate;
|
||||
overriddenValues.startTime = startTime;
|
||||
}
|
||||
|
||||
if (schedule.until) {
|
||||
overriddenValues.end = 'onDate';
|
||||
overriddenValues.endDateTime = schedule.until;
|
||||
|
||||
const [endDate, endTime] = dateToInputDateTime(schedule.until);
|
||||
|
||||
overriddenValues.endDate = endDate;
|
||||
overriddenValues.endTime = endTime;
|
||||
} else if (count) {
|
||||
overriddenValues.end = 'after';
|
||||
overriddenValues.occurrences = count;
|
||||
@ -553,18 +545,18 @@ function ScheduleForm({
|
||||
const errors = {};
|
||||
const {
|
||||
end,
|
||||
endDateTime,
|
||||
endDate,
|
||||
frequency,
|
||||
runOn,
|
||||
runOnDayNumber,
|
||||
startDateTime,
|
||||
startDate,
|
||||
} = values;
|
||||
|
||||
if (
|
||||
end === 'onDate' &&
|
||||
new Date(startDateTime) > new Date(endDateTime)
|
||||
new Date(startDate) >= new Date(endDate)
|
||||
) {
|
||||
errors.endDateTime = t`Please select an end date/time that comes after the start date/time.`;
|
||||
errors.endDate = t`Please select an end date/time that comes after the start date/time.`;
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
} from '../../../../testUtils/enzymeHelpers';
|
||||
import { dateToInputDateTime } from '../../../util/dates';
|
||||
import { SchedulesAPI, JobTemplatesAPI, InventoriesAPI } from '../../../api';
|
||||
import ScheduleForm from './ScheduleForm';
|
||||
|
||||
@ -99,8 +100,12 @@ const nonRRuleValuesMatch = () => {
|
||||
expect(wrapper.find('input#schedule-description').prop('value')).toBe(
|
||||
'test description'
|
||||
);
|
||||
expect(wrapper.find('input#schedule-start-datetime').prop('value')).toBe(
|
||||
'2020-04-02T14:45:00'
|
||||
|
||||
expect(
|
||||
wrapper.find('DatePicker[aria-label="Start date"]').prop('value')
|
||||
).toBe('2020-04-02');
|
||||
expect(wrapper.find('TimePicker[aria-label="Start time"]').prop('time')).toBe(
|
||||
'6:45 PM'
|
||||
);
|
||||
expect(wrapper.find('select#schedule-timezone').prop('value')).toBe(
|
||||
'America/New_York'
|
||||
@ -472,6 +477,11 @@ describe('<ScheduleForm />', () => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
test('initially renders expected fields and values', () => {
|
||||
const now = new Date();
|
||||
const closestQuarterHour = new Date(
|
||||
Math.ceil(now.getTime() / 900000) * 900000
|
||||
);
|
||||
const [date, time] = dateToInputDateTime(closestQuarterHour);
|
||||
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||
defaultFieldsVisible();
|
||||
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(0);
|
||||
@ -483,9 +493,9 @@ describe('<ScheduleForm />', () => {
|
||||
|
||||
expect(wrapper.find('input#schedule-name').prop('value')).toBe('');
|
||||
expect(wrapper.find('input#schedule-description').prop('value')).toBe('');
|
||||
expect(
|
||||
wrapper.find('input#schedule-start-datetime').prop('value')
|
||||
).toMatch(/\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d/);
|
||||
|
||||
expect(wrapper.find('DatePicker').prop('value')).toMatch(`${date}`);
|
||||
expect(wrapper.find('TimePicker').prop('time')).toMatch(`${time}`);
|
||||
expect(wrapper.find('select#schedule-timezone').prop('value')).toBe(
|
||||
'America/New_York'
|
||||
);
|
||||
@ -703,18 +713,18 @@ describe('<ScheduleForm />', () => {
|
||||
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(true);
|
||||
expect(wrapper.find('#schedule-end-datetime-helper').length).toBe(0);
|
||||
await act(async () => {
|
||||
wrapper.find('input#schedule-end-datetime').simulate('change', {
|
||||
target: { name: 'endDateTime', value: '2020-03-14T01:45:00' },
|
||||
});
|
||||
wrapper.find('DatePicker[aria-label="End date"]').prop('onChange')(
|
||||
'2020-03-14',
|
||||
new Date('2020-03-14')
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
await act(async () => {
|
||||
wrapper.find('input#schedule-end-datetime').simulate('blur');
|
||||
wrapper.find('DatePicker[aria-label="End date"]').simulate('blur');
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('#schedule-end-datetime-helper').text()).toBe(
|
||||
wrapper.update();
|
||||
expect(wrapper.find('#schedule-End-datetime-helper').text()).toBe(
|
||||
'Please select an end date/time that comes after the start date/time.'
|
||||
);
|
||||
});
|
||||
@ -1041,7 +1051,7 @@ describe('<ScheduleForm />', () => {
|
||||
rrule:
|
||||
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20210101T050000Z',
|
||||
dtend: '2020-10-30T18:45:00Z',
|
||||
until: '2021-01-01T00:00:00',
|
||||
until: '2021-01-01T01:00:00',
|
||||
})}
|
||||
resource={{
|
||||
id: 23,
|
||||
@ -1090,9 +1100,12 @@ describe('<ScheduleForm />', () => {
|
||||
expect(
|
||||
wrapper.find('input#schedule-days-of-week-sat').prop('checked')
|
||||
).toBe(false);
|
||||
expect(wrapper.find('input#schedule-end-datetime').prop('value')).toBe(
|
||||
'2021-01-01T00:00:00'
|
||||
);
|
||||
expect(
|
||||
wrapper.find('DatePicker[aria-label="End date"]').prop('value')
|
||||
).toBe('2021-01-01');
|
||||
expect(
|
||||
wrapper.find('TimePicker[aria-label="End time"]').prop('value')
|
||||
).toBe('1:00 AM');
|
||||
});
|
||||
test('initially renders expected fields and values with existing schedule that runs every month on the last weekday', async () => {
|
||||
await act(async () => {
|
||||
|
||||
@ -2,15 +2,20 @@ import { t } from '@lingui/macro';
|
||||
import { RRule } from 'rrule';
|
||||
import { getRRuleDayConstants } from '../../../util/dates';
|
||||
|
||||
const parseTime = time => {
|
||||
const [hour, minute, ampm] = time.split(/[: ]/);
|
||||
const timeHour =
|
||||
ampm === 'PM' && hour !== '12' ? `${parseInt(hour, 10) + 12}` : `${hour}`;
|
||||
|
||||
return [timeHour, minute];
|
||||
};
|
||||
|
||||
export default function buildRuleObj(values) {
|
||||
const [startDate, startTime] = values.startDateTime.split('T');
|
||||
// Dates are formatted like "YYYY-MM-DD"
|
||||
const [startYear, startMonth, startDay] = startDate.split('-');
|
||||
const [startYear, startMonth, startDay] = values.startDate.split('-');
|
||||
// Times are formatted like "HH:MM:SS" or "HH:MM" if no seconds
|
||||
// have been specified
|
||||
const [startHour = 0, startMinute = 0, startSecond = 0] = startTime.split(
|
||||
':'
|
||||
);
|
||||
const [startHour, startMinute] = parseTime(values.startTime);
|
||||
|
||||
const ruleObj = {
|
||||
interval: values.interval,
|
||||
@ -20,8 +25,7 @@ export default function buildRuleObj(values) {
|
||||
parseInt(startMonth, 10) - 1,
|
||||
startDay,
|
||||
startHour,
|
||||
startMinute,
|
||||
startSecond
|
||||
startMinute
|
||||
)
|
||||
),
|
||||
tzid: values.timezone,
|
||||
@ -77,17 +81,16 @@ export default function buildRuleObj(values) {
|
||||
ruleObj.count = values.occurrences;
|
||||
break;
|
||||
case 'onDate': {
|
||||
const [endDate, endTime] = values.endDateTime.split('T');
|
||||
const [endYear, endMonth, endDay] = endDate.split('-');
|
||||
const [endHour = 0, endMinute = 0, endSecond = 0] = endTime.split(':');
|
||||
const [endYear, endMonth, endDay] = values.endDate.split('-');
|
||||
|
||||
const [endHour, endMinute] = parseTime(values.endTime);
|
||||
ruleObj.until = new Date(
|
||||
Date.UTC(
|
||||
endYear,
|
||||
parseInt(endMonth, 10) - 1,
|
||||
endDay,
|
||||
endHour,
|
||||
endMinute,
|
||||
endSecond
|
||||
endMinute
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
@ -44,15 +44,19 @@ export function timeOfDay() {
|
||||
}
|
||||
|
||||
export function dateToInputDateTime(dateObj) {
|
||||
// input type="date-time" expects values to be formatted
|
||||
// like: YYYY-MM-DDTHH-MM-SS
|
||||
const year = dateObj.getFullYear();
|
||||
const month = prependZeros(dateObj.getMonth() + 1);
|
||||
const day = prependZeros(dateObj.getDate());
|
||||
const hour = prependZeros(dateObj.getHours());
|
||||
const minute = prependZeros(dateObj.getMinutes());
|
||||
const second = prependZeros(dateObj.getSeconds());
|
||||
return `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||
let date = dateObj;
|
||||
if (typeof dateObj === 'string') {
|
||||
date = new Date(dateObj);
|
||||
}
|
||||
const year = date.getFullYear();
|
||||
const month = prependZeros(date.getMonth() + 1);
|
||||
const day = prependZeros(date.getDate());
|
||||
const hour =
|
||||
date.getHours() > 12 ? parseInt(date.getHours(), 10) - 12 : date.getHours();
|
||||
const minute = prependZeros(date.getMinutes());
|
||||
const amPmText = date.getHours() > 11 ? 'PM' : 'AM';
|
||||
|
||||
return [`${year}-${month}-${day}`, `${hour}:${minute} ${amPmText}`];
|
||||
}
|
||||
|
||||
export function getRRuleDayConstants(dayString) {
|
||||
|
||||
@ -70,7 +70,7 @@ describe('dateToInputDateTime', () => {
|
||||
test('it returns the expected value', () => {
|
||||
expect(
|
||||
dateToInputDateTime(new Date('2018-01-31T01:14:52.969227Z'))
|
||||
).toEqual('2018-01-31T01:14:52');
|
||||
).toEqual(['2018-01-31', '1:14 AM']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { isValidDate } from '@patternfly/react-core';
|
||||
|
||||
export function required(message) {
|
||||
const errorMessage = message || t`This field must not be blank`;
|
||||
@ -15,6 +16,25 @@ export function required(message) {
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
export function validateTime() {
|
||||
return value => {
|
||||
const timeRegex = new RegExp(
|
||||
`^\\s*(\\d\\d?):([0-5])(\\d)\\s*([AaPp][Mm])?\\s*$`
|
||||
);
|
||||
let message;
|
||||
const timeComponents = value.split(':');
|
||||
|
||||
const date = new Date();
|
||||
date.setHours(parseInt(timeComponents[0], 10));
|
||||
date.setMinutes(parseInt(timeComponents[1], 10));
|
||||
|
||||
if (!isValidDate(date) || !timeRegex.test(value)) {
|
||||
message = t`Invalid time format`;
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
}
|
||||
|
||||
export function maxLength(max) {
|
||||
return value => {
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
combine,
|
||||
regExp,
|
||||
requiredEmail,
|
||||
validateTime,
|
||||
} from './validators';
|
||||
|
||||
describe('validators', () => {
|
||||
@ -168,4 +169,13 @@ describe('validators', () => {
|
||||
test('bob has email', () => {
|
||||
expect(requiredEmail()('bob@localhost')).toBeUndefined();
|
||||
});
|
||||
test('validate time validates properly', () => {
|
||||
expect(validateTime()('12:15 PM')).toBeUndefined();
|
||||
expect(validateTime()('1:15 PM')).toBeUndefined();
|
||||
expect(validateTime()('01:15 PM')).toBeUndefined();
|
||||
expect(validateTime()('12:15')).toBeUndefined();
|
||||
expect(validateTime()('12:15: PM')).toEqual('Invalid time format');
|
||||
expect(validateTime()('12.15 PM')).toEqual('Invalid time format');
|
||||
expect(validateTime()('12;15 PM')).toEqual('Invalid time format');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user