mirror of
https://github.com/ansible/awx.git
synced 2026-03-21 10:57:36 -02:30
Adds support for editing proj/jt/wfjt schedule
This commit is contained in:
@@ -8,6 +8,8 @@ function($filter, $state, $stateParams, Wait, $scope, moment,
|
|||||||
WorkflowJobTemplate, SchedulerStrings, scheduleResolve, timezonesResolve, Alert
|
WorkflowJobTemplate, SchedulerStrings, scheduleResolve, timezonesResolve, Alert
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
console.log(scheduleResolve);
|
||||||
|
|
||||||
let schedule, scheduler, scheduleCredentials = [];
|
let schedule, scheduler, scheduleCredentials = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -161,6 +163,7 @@ function($filter, $state, $stateParams, Wait, $scope, moment,
|
|||||||
function setUntil (scheduler) {
|
function setUntil (scheduler) {
|
||||||
let { until } = scheduleResolve;
|
let { until } = scheduleResolve;
|
||||||
if(until !== ''){
|
if(until !== ''){
|
||||||
|
console.log(until);
|
||||||
const date = moment(until);
|
const date = moment(until);
|
||||||
const endDt = moment.parseZone(date).format("MM/DD/YYYY");
|
const endDt = moment.parseZone(date).format("MM/DD/YYYY");
|
||||||
const endHour = date.format('HH');
|
const endHour = date.format('HH');
|
||||||
|
|||||||
@@ -10,18 +10,21 @@ function FormSubmitError({ error }) {
|
|||||||
if (!error) {
|
if (!error) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (error?.response?.data && typeof error.response.data === 'object') {
|
if (
|
||||||
|
error?.response?.data &&
|
||||||
|
typeof error.response.data === 'object' &&
|
||||||
|
Object.keys(error.response.data).length > 0
|
||||||
|
) {
|
||||||
const errorMessages = error.response.data;
|
const errorMessages = error.response.data;
|
||||||
setErrors(errorMessages);
|
setErrors(errorMessages);
|
||||||
if (errorMessages.__all__) {
|
|
||||||
setErrorMessage(errorMessages.__all__);
|
let messages = [];
|
||||||
} else if (errorMessages.detail) {
|
Object.values(error.response.data).forEach(value => {
|
||||||
setErrorMessage(errorMessages.detail);
|
if (Array.isArray(value)) {
|
||||||
} else if (errorMessages.resources_needed_to_start) {
|
messages = messages.concat(value);
|
||||||
setErrorMessage(errorMessages.resources_needed_to_start);
|
}
|
||||||
} else {
|
});
|
||||||
setErrorMessage(null);
|
setErrorMessage(messages.length > 0 ? messages : null);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/* eslint-disable-next-line no-console */
|
/* eslint-disable-next-line no-console */
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import RoutedTabs from '@components/RoutedTabs';
|
|||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import { TabbedCardHeader } from '@components/Card';
|
import { TabbedCardHeader } from '@components/Card';
|
||||||
import { ScheduleDetail } from '@components/Schedule';
|
import { ScheduleDetail, ScheduleEdit } from '@components/Schedule';
|
||||||
import { SchedulesAPI } from '@api';
|
import { SchedulesAPI } from '@api';
|
||||||
|
|
||||||
function Schedule({ i18n, setBreadcrumb, unifiedJobTemplate }) {
|
function Schedule({ i18n, setBreadcrumb, unifiedJobTemplate }) {
|
||||||
@@ -108,6 +108,11 @@ function Schedule({ i18n, setBreadcrumb, unifiedJobTemplate }) {
|
|||||||
exact
|
exact
|
||||||
/>
|
/>
|
||||||
{schedule && [
|
{schedule && [
|
||||||
|
<Route
|
||||||
|
key="edit"
|
||||||
|
path={`${pathRoot}schedules/:id/edit`}
|
||||||
|
render={() => <ScheduleEdit schedule={schedule} />}
|
||||||
|
/>,
|
||||||
<Route
|
<Route
|
||||||
key="details"
|
key="details"
|
||||||
path={`${pathRoot}schedules/:scheduleId/details`}
|
path={`${pathRoot}schedules/:scheduleId/details`}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { func } from 'prop-types';
|
import { func } from 'prop-types';
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { RRule } from 'rrule';
|
import { RRule } from 'rrule';
|
||||||
import { Card } from '@patternfly/react-core';
|
import { Card } from '@patternfly/react-core';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody } from '@components/Card';
|
||||||
import { getRRuleDayConstants } from '@util/dates';
|
import buildRuleObj from '../shared/buildRuleObj';
|
||||||
import ScheduleForm from '../shared/ScheduleForm';
|
import ScheduleForm from '../shared/ScheduleForm';
|
||||||
|
|
||||||
function ScheduleAdd({ i18n, createSchedule }) {
|
function ScheduleAdd({ i18n, createSchedule }) {
|
||||||
@@ -16,105 +15,9 @@ function ScheduleAdd({ i18n, createSchedule }) {
|
|||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
const pathRoot = pathname.substr(0, pathname.indexOf('schedules'));
|
const pathRoot = pathname.substr(0, pathname.indexOf('schedules'));
|
||||||
|
|
||||||
const buildRuleObj = values => {
|
|
||||||
const [startDate, startTime] = values.startDateTime.split('T');
|
|
||||||
// Dates are formatted like "YYYY-MM-DD"
|
|
||||||
const [startYear, startMonth, startDay] = 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 ruleObj = {
|
|
||||||
interval: values.interval,
|
|
||||||
dtstart: new Date(
|
|
||||||
Date.UTC(
|
|
||||||
startYear,
|
|
||||||
parseInt(startMonth, 10) - 1,
|
|
||||||
startDay,
|
|
||||||
startHour,
|
|
||||||
startMinute,
|
|
||||||
startSecond
|
|
||||||
)
|
|
||||||
),
|
|
||||||
tzid: values.timezone,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (values.frequency) {
|
|
||||||
case 'none':
|
|
||||||
ruleObj.count = 1;
|
|
||||||
ruleObj.freq = RRule.MINUTELY;
|
|
||||||
break;
|
|
||||||
case 'minute':
|
|
||||||
ruleObj.freq = RRule.MINUTELY;
|
|
||||||
break;
|
|
||||||
case 'hour':
|
|
||||||
ruleObj.freq = RRule.HOURLY;
|
|
||||||
break;
|
|
||||||
case 'day':
|
|
||||||
ruleObj.freq = RRule.DAILY;
|
|
||||||
break;
|
|
||||||
case 'week':
|
|
||||||
ruleObj.freq = RRule.WEEKLY;
|
|
||||||
ruleObj.byweekday = values.daysOfWeek.map(day => RRule[day]);
|
|
||||||
break;
|
|
||||||
case 'month':
|
|
||||||
ruleObj.freq = RRule.MONTHLY;
|
|
||||||
if (values.runOn === 'day') {
|
|
||||||
ruleObj.bymonthday = values.runOnDayNumber;
|
|
||||||
} else if (values.runOn === 'the') {
|
|
||||||
ruleObj.bysetpos = parseInt(values.runOnTheOccurrence, 10);
|
|
||||||
ruleObj.byweekday = getRRuleDayConstants(values.runOnTheDay, i18n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'year':
|
|
||||||
ruleObj.freq = RRule.YEARLY;
|
|
||||||
if (values.runOn === 'day') {
|
|
||||||
ruleObj.bymonth = parseInt(values.runOnDayMonth, 10);
|
|
||||||
ruleObj.bymonthday = values.runOnDayNumber;
|
|
||||||
} else if (values.runOn === 'the') {
|
|
||||||
ruleObj.bysetpos = parseInt(values.runOnTheOccurrence, 10);
|
|
||||||
ruleObj.byweekday = getRRuleDayConstants(values.runOnTheDay, i18n);
|
|
||||||
ruleObj.bymonth = parseInt(values.runOnTheMonth, 10);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(i18n._(t`Frequency did not match an expected value`));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (values.end) {
|
|
||||||
case 'never':
|
|
||||||
break;
|
|
||||||
case 'after':
|
|
||||||
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(':');
|
|
||||||
ruleObj.until = new Date(
|
|
||||||
Date.UTC(
|
|
||||||
endYear,
|
|
||||||
parseInt(endMonth, 10) - 1,
|
|
||||||
endDay,
|
|
||||||
endHour,
|
|
||||||
endMinute,
|
|
||||||
endSecond
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(i18n._(t`End did not match an expected value`));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ruleObj;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
try {
|
try {
|
||||||
const rule = new RRule(buildRuleObj(values));
|
const rule = new RRule(buildRuleObj(values, i18n));
|
||||||
const {
|
const {
|
||||||
data: { id: scheduleId },
|
data: { id: scheduleId },
|
||||||
} = await createSchedule({
|
} = await createSchedule({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
import { SchedulesAPI } from '@api';
|
import { SchedulesAPI } from '@api';
|
||||||
import ScheduleAdd from './ScheduleAdd';
|
import ScheduleAdd from './ScheduleAdd';
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ describe('<ScheduleAdd />', () => {
|
|||||||
test('Successfully creates a schedule with weekly repeat frequency on mon/wed/fri', async () => {
|
test('Successfully creates a schedule with weekly repeat frequency on mon/wed/fri', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
daysOfWeek: ['MO', 'WE', 'FR'],
|
daysOfWeek: [RRule.MO, RRule.WE, RRule.FR],
|
||||||
description: 'test description',
|
description: 'test description',
|
||||||
end: 'never',
|
end: 'never',
|
||||||
frequency: 'week',
|
frequency: 'week',
|
||||||
@@ -131,8 +132,7 @@ describe('<ScheduleAdd />', () => {
|
|||||||
expect(createSchedule).toHaveBeenCalledWith({
|
expect(createSchedule).toHaveBeenCalledWith({
|
||||||
description: 'test description',
|
description: 'test description',
|
||||||
name: 'Run weekly on mon/wed/fri',
|
name: 'Run weekly on mon/wed/fri',
|
||||||
rrule:
|
rrule: `DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=${RRule.MO},${RRule.WE},${RRule.FR}`,
|
||||||
'DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=MO,WE,FR',
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
test('Successfully creates a schedule with monthly repeat frequency on the first day of the month', async () => {
|
test('Successfully creates a schedule with monthly repeat frequency on the first day of the month', async () => {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
|
import { object } from 'prop-types';
|
||||||
|
import { Card } from '@patternfly/react-core';
|
||||||
|
import { CardBody } from '@components/Card';
|
||||||
|
import { SchedulesAPI } from '@api';
|
||||||
|
import buildRuleObj from '../shared/buildRuleObj';
|
||||||
|
import ScheduleForm from '../shared/ScheduleForm';
|
||||||
|
|
||||||
|
function ScheduleEdit({ i18n, schedule }) {
|
||||||
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
const { pathname } = location;
|
||||||
|
const pathRoot = pathname.substr(0, pathname.indexOf('schedules'));
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
try {
|
||||||
|
const rule = new RRule(buildRuleObj(values, i18n));
|
||||||
|
const {
|
||||||
|
data: { id: scheduleId },
|
||||||
|
} = await SchedulesAPI.update(schedule.id, {
|
||||||
|
name: values.name,
|
||||||
|
description: values.description,
|
||||||
|
rrule: rule.toString().replace(/\n/g, ' '),
|
||||||
|
});
|
||||||
|
|
||||||
|
history.push(`${pathRoot}schedules/${scheduleId}/details`);
|
||||||
|
} catch (err) {
|
||||||
|
setFormSubmitError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ScheduleForm
|
||||||
|
schedule={schedule}
|
||||||
|
handleCancel={() =>
|
||||||
|
history.push(`${pathRoot}schedules/${schedule.id}/details`)
|
||||||
|
}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
submitError={formSubmitError}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleEdit.propTypes = {
|
||||||
|
schedule: object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ScheduleEdit.defaultProps = {};
|
||||||
|
|
||||||
|
export default withI18n()(ScheduleEdit);
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
|
import { SchedulesAPI } from '@api';
|
||||||
|
import ScheduleEdit from './ScheduleEdit';
|
||||||
|
|
||||||
|
jest.mock('@api/models/Schedules');
|
||||||
|
|
||||||
|
SchedulesAPI.readZoneInfo.mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'America/New_York',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
SchedulesAPI.update.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 27,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const mockSchedule = {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
|
||||||
|
id: 27,
|
||||||
|
type: 'schedule',
|
||||||
|
url: '/api/v2/schedules/27/',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-04-02T18:43:12.664142Z',
|
||||||
|
modified: '2020-04-02T18:43:12.664185Z',
|
||||||
|
name: 'mock schedule',
|
||||||
|
description: '',
|
||||||
|
extra_data: {},
|
||||||
|
inventory: null,
|
||||||
|
scm_branch: null,
|
||||||
|
job_type: null,
|
||||||
|
job_tags: null,
|
||||||
|
skip_tags: null,
|
||||||
|
limit: null,
|
||||||
|
diff_mode: null,
|
||||||
|
verbosity: null,
|
||||||
|
unified_job_template: 11,
|
||||||
|
enabled: true,
|
||||||
|
dtstart: '2020-04-02T18:45:00Z',
|
||||||
|
dtend: '2020-04-02T18:45:00Z',
|
||||||
|
next_run: '2020-04-02T18:45:00Z',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
until: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<ScheduleEdit />', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<ScheduleEdit schedule={mockSchedule} />);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with repeat frequency: None (run once)', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'none',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Run once schedule',
|
||||||
|
startDateTime: '2020-03-25T10:00:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run once schedule',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200325T100000 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with 10 minute repeat frequency after 10 occurrences', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'after',
|
||||||
|
frequency: 'minute',
|
||||||
|
interval: 10,
|
||||||
|
name: 'Run every 10 minutes 10 times',
|
||||||
|
occurrences: 10,
|
||||||
|
startDateTime: '2020-03-25T10:30:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run every 10 minutes 10 times',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200325T103000 RRULE:INTERVAL=10;FREQ=MINUTELY;COUNT=10',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with hourly repeat frequency ending on a specific date/time', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'onDate',
|
||||||
|
endDateTime: '2020-03-26T10:45:00',
|
||||||
|
frequency: 'hour',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Run every hour until date',
|
||||||
|
startDateTime: '2020-03-25T10:45:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run every hour until date',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=HOURLY;UNTIL=20200326T104500',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with daily repeat frequency', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'day',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Run daily',
|
||||||
|
startDateTime: '2020-03-25T10:45:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run daily',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=DAILY',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with weekly repeat frequency on mon/wed/fri', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
daysOfWeek: [RRule.MO, RRule.WE, RRule.FR],
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'week',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Run weekly on mon/wed/fri',
|
||||||
|
occurrences: 1,
|
||||||
|
startDateTime: '2020-03-25T10:45:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run weekly on mon/wed/fri',
|
||||||
|
rrule: `DTSTART;TZID=America/New_York:20200325T104500 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=${RRule.MO},${RRule.WE},${RRule.FR}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with monthly repeat frequency on the first day of the month', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'month',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Run on the first day of the month',
|
||||||
|
occurrences: 1,
|
||||||
|
runOn: 'day',
|
||||||
|
runOnDayNumber: 1,
|
||||||
|
startDateTime: '2020-04-01T10:45',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run on the first day of the month',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200401T104500 RRULE:INTERVAL=1;FREQ=MONTHLY;BYMONTHDAY=1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with monthly repeat frequency on the last tuesday of the month', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
endDateTime: '2020-03-26T11:00:00',
|
||||||
|
frequency: 'month',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Run monthly on the last Tuesday',
|
||||||
|
occurrences: 1,
|
||||||
|
runOn: 'the',
|
||||||
|
runOnTheDay: 'tuesday',
|
||||||
|
runOnTheOccurrence: -1,
|
||||||
|
startDateTime: '2020-03-31T11:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Run monthly on the last Tuesday',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200331T110000 RRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=-1;BYDAY=TU',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with yearly repeat frequency on the first day of March', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'year',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Yearly on the first day of March',
|
||||||
|
occurrences: 1,
|
||||||
|
runOn: 'day',
|
||||||
|
runOnDayMonth: 3,
|
||||||
|
runOnDayNumber: 1,
|
||||||
|
startDateTime: '2020-03-01T00:00',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Yearly on the first day of March',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200301T000000 RRULE:INTERVAL=1;FREQ=YEARLY;BYMONTH=3;BYMONTHDAY=1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with yearly repeat frequency on the second Friday in April', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'year',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Yearly on the second Friday in April',
|
||||||
|
occurrences: 1,
|
||||||
|
runOn: 'the',
|
||||||
|
runOnTheOccurrence: 2,
|
||||||
|
runOnTheDay: 'friday',
|
||||||
|
runOnTheMonth: 4,
|
||||||
|
startDateTime: '2020-04-10T11:15',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Yearly on the second Friday in April',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200410T111500 RRULE:INTERVAL=1;FREQ=YEARLY;BYSETPOS=2;BYDAY=FR;BYMONTH=4',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Successfully creates a schedule with yearly repeat frequency on the first weekday in October', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ScheduleForm').invoke('handleSubmit')({
|
||||||
|
description: 'test description',
|
||||||
|
end: 'never',
|
||||||
|
frequency: 'year',
|
||||||
|
interval: 1,
|
||||||
|
name: 'Yearly on the first weekday in October',
|
||||||
|
occurrences: 1,
|
||||||
|
runOn: 'the',
|
||||||
|
runOnTheOccurrence: 1,
|
||||||
|
runOnTheDay: 'weekday',
|
||||||
|
runOnTheMonth: 10,
|
||||||
|
startDateTime: '2020-04-10T11:15',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(SchedulesAPI.update).toHaveBeenCalledWith(27, {
|
||||||
|
description: 'test description',
|
||||||
|
name: 'Yearly on the first weekday in October',
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200410T111500 RRULE:INTERVAL=1;FREQ=YEARLY;BYSETPOS=1;BYDAY=MO,TU,WE,TH,FR;BYMONTH=10',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './ScheduleEdit';
|
||||||
@@ -5,3 +5,4 @@ export { default as ScheduleOccurrences } from './ScheduleOccurrences';
|
|||||||
export { default as ScheduleToggle } from './ScheduleToggle';
|
export { default as ScheduleToggle } from './ScheduleToggle';
|
||||||
export { default as ScheduleDetail } from './ScheduleDetail';
|
export { default as ScheduleDetail } from './ScheduleDetail';
|
||||||
export { default as ScheduleAdd } from './ScheduleAdd';
|
export { default as ScheduleAdd } from './ScheduleAdd';
|
||||||
|
export { default as ScheduleEdit } from './ScheduleEdit';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import styled from 'styled-components';
|
|||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
import {
|
import {
|
||||||
Checkbox as _Checkbox,
|
Checkbox as _Checkbox,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -255,9 +256,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
<div css="display: flex">
|
<div css="display: flex">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Sun`)}
|
label={i18n._(t`Sun`)}
|
||||||
isChecked={daysOfWeek.value.includes('SU')}
|
isChecked={daysOfWeek.value.includes(RRule.SU)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('SU', checked);
|
updateDaysOfWeek(RRule.SU, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Sunday`)}
|
aria-label={i18n._(t`Sunday`)}
|
||||||
id="schedule-days-of-week-sun"
|
id="schedule-days-of-week-sun"
|
||||||
@@ -265,9 +266,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Mon`)}
|
label={i18n._(t`Mon`)}
|
||||||
isChecked={daysOfWeek.value.includes('MO')}
|
isChecked={daysOfWeek.value.includes(RRule.MO)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('MO', checked);
|
updateDaysOfWeek(RRule.MO, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Monday`)}
|
aria-label={i18n._(t`Monday`)}
|
||||||
id="schedule-days-of-week-mon"
|
id="schedule-days-of-week-mon"
|
||||||
@@ -275,9 +276,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Tue`)}
|
label={i18n._(t`Tue`)}
|
||||||
isChecked={daysOfWeek.value.includes('TU')}
|
isChecked={daysOfWeek.value.includes(RRule.TU)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('TU', checked);
|
updateDaysOfWeek(RRule.TU, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Tuesday`)}
|
aria-label={i18n._(t`Tuesday`)}
|
||||||
id="schedule-days-of-week-tue"
|
id="schedule-days-of-week-tue"
|
||||||
@@ -285,9 +286,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Wed`)}
|
label={i18n._(t`Wed`)}
|
||||||
isChecked={daysOfWeek.value.includes('WE')}
|
isChecked={daysOfWeek.value.includes(RRule.WE)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('WE', checked);
|
updateDaysOfWeek(RRule.WE, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Wednesday`)}
|
aria-label={i18n._(t`Wednesday`)}
|
||||||
id="schedule-days-of-week-wed"
|
id="schedule-days-of-week-wed"
|
||||||
@@ -295,9 +296,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Thu`)}
|
label={i18n._(t`Thu`)}
|
||||||
isChecked={daysOfWeek.value.includes('TH')}
|
isChecked={daysOfWeek.value.includes(RRule.TH)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('TH', checked);
|
updateDaysOfWeek(RRule.TH, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Thursday`)}
|
aria-label={i18n._(t`Thursday`)}
|
||||||
id="schedule-days-of-week-thu"
|
id="schedule-days-of-week-thu"
|
||||||
@@ -305,9 +306,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Fri`)}
|
label={i18n._(t`Fri`)}
|
||||||
isChecked={daysOfWeek.value.includes('FR')}
|
isChecked={daysOfWeek.value.includes(RRule.FR)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('FR', checked);
|
updateDaysOfWeek(RRule.FR, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Friday`)}
|
aria-label={i18n._(t`Friday`)}
|
||||||
id="schedule-days-of-week-fri"
|
id="schedule-days-of-week-fri"
|
||||||
@@ -315,9 +316,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
|
|||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={i18n._(t`Sat`)}
|
label={i18n._(t`Sat`)}
|
||||||
isChecked={daysOfWeek.value.includes('SA')}
|
isChecked={daysOfWeek.value.includes(RRule.SA)}
|
||||||
onChange={checked => {
|
onChange={checked => {
|
||||||
updateDaysOfWeek('SA', checked);
|
updateDaysOfWeek(RRule.SA, checked);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Saturday`)}
|
aria-label={i18n._(t`Saturday`)}
|
||||||
id="schedule-days-of-week-sat"
|
id="schedule-days-of-week-sat"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { shape, func } from 'prop-types';
|
|||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Formik, useField } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
import { Config } from '@contexts/Config';
|
import { Config } from '@contexts/Config';
|
||||||
import { Form, FormGroup, Title } from '@patternfly/react-core';
|
import { Form, FormGroup, Title } from '@patternfly/react-core';
|
||||||
import { SchedulesAPI } from '@api';
|
import { SchedulesAPI } from '@api';
|
||||||
@@ -12,11 +13,60 @@ import ContentLoading from '@components/ContentLoading';
|
|||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import FormField, { FormSubmitError } from '@components/FormField';
|
import FormField, { FormSubmitError } from '@components/FormField';
|
||||||
import { FormColumnLayout, SubFormLayout } from '@components/FormLayout';
|
import { FormColumnLayout, SubFormLayout } from '@components/FormLayout';
|
||||||
import { dateToInputDateTime } from '@util/dates';
|
import { dateToInputDateTime, formatDateStringUTC } from '@util/dates';
|
||||||
import useRequest from '@util/useRequest';
|
import useRequest from '@util/useRequest';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
import FrequencyDetailSubform from './FrequencyDetailSubform';
|
import FrequencyDetailSubform from './FrequencyDetailSubform';
|
||||||
|
|
||||||
|
const generateRunOnTheDay = (days = []) => {
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
RRule.MO,
|
||||||
|
RRule.TU,
|
||||||
|
RRule.WE,
|
||||||
|
RRule.TH,
|
||||||
|
RRule.FR,
|
||||||
|
RRule.SA,
|
||||||
|
RRule.SU,
|
||||||
|
].every(element => days.indexOf(element) > -1)
|
||||||
|
) {
|
||||||
|
return 'day';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR].every(
|
||||||
|
element => days.indexOf(element) > -1
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'weekday';
|
||||||
|
}
|
||||||
|
if ([RRule.SA, RRule.SU].every(element => days.indexOf(element) > -1)) {
|
||||||
|
return 'weekendDay';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.MO) > -1) {
|
||||||
|
return 'monday';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.TU) > -1) {
|
||||||
|
return 'tuesday';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.WE) > -1) {
|
||||||
|
return 'wednesday';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.TH) > -1) {
|
||||||
|
return 'thursday';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.FR) > -1) {
|
||||||
|
return 'friday';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.SA) > -1) {
|
||||||
|
return 'saturday';
|
||||||
|
}
|
||||||
|
if (days.indexOf(RRule.SU) > -1) {
|
||||||
|
return 'sunday';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
function ScheduleFormFields({ i18n, zoneOptions }) {
|
function ScheduleFormFields({ i18n, zoneOptions }) {
|
||||||
const [startDateTime, startDateTimeMeta] = useField({
|
const [startDateTime, startDateTimeMeta] = useField({
|
||||||
name: 'startDateTime',
|
name: 'startDateTime',
|
||||||
@@ -121,6 +171,7 @@ function ScheduleForm({
|
|||||||
submitError,
|
submitError,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
|
let rruleError;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const closestQuarterHour = new Date(
|
const closestQuarterHour = new Date(
|
||||||
Math.ceil(now.getTime() / 900000) * 900000
|
Math.ceil(now.getTime() / 900000) * 900000
|
||||||
@@ -128,6 +179,114 @@ function ScheduleForm({
|
|||||||
const tomorrow = new Date(closestQuarterHour);
|
const tomorrow = new Date(closestQuarterHour);
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
daysOfWeek: [],
|
||||||
|
description: schedule.description || '',
|
||||||
|
end: 'never',
|
||||||
|
endDateTime: dateToInputDateTime(tomorrow),
|
||||||
|
frequency: 'none',
|
||||||
|
interval: 1,
|
||||||
|
name: schedule.name || '',
|
||||||
|
occurrences: 1,
|
||||||
|
runOn: 'day',
|
||||||
|
runOnDayMonth: 1,
|
||||||
|
runOnDayNumber: 1,
|
||||||
|
runOnTheDay: 'sunday',
|
||||||
|
runOnTheMonth: 1,
|
||||||
|
runOnTheOccurrence: 1,
|
||||||
|
startDateTime: dateToInputDateTime(closestQuarterHour),
|
||||||
|
timezone: schedule.timezone || 'America/New_York',
|
||||||
|
};
|
||||||
|
|
||||||
|
const overriddenValues = {};
|
||||||
|
|
||||||
|
if (Object.keys(schedule).length > 0) {
|
||||||
|
if (schedule.rrule) {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
origOptions: {
|
||||||
|
bymonth,
|
||||||
|
bymonthday,
|
||||||
|
bysetpos,
|
||||||
|
byweekday,
|
||||||
|
count,
|
||||||
|
dtstart,
|
||||||
|
freq,
|
||||||
|
interval,
|
||||||
|
},
|
||||||
|
} = RRule.fromString(schedule.rrule.replace(' ', '\n'));
|
||||||
|
|
||||||
|
if (dtstart) {
|
||||||
|
overriddenValues.startDateTime = dateToInputDateTime(
|
||||||
|
new Date(formatDateStringUTC(dtstart))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.until) {
|
||||||
|
overriddenValues.end = 'onDate';
|
||||||
|
overriddenValues.endDateTime = schedule.until;
|
||||||
|
} else if (count) {
|
||||||
|
overriddenValues.end = 'after';
|
||||||
|
overriddenValues.occurrences = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interval) {
|
||||||
|
overriddenValues.interval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof freq === 'number') {
|
||||||
|
switch (freq) {
|
||||||
|
case RRule.MINUTELY:
|
||||||
|
if (schedule.dtstart !== schedule.dtend) {
|
||||||
|
overriddenValues.frequency = 'minute';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RRule.HOURLY:
|
||||||
|
overriddenValues.frequency = 'hour';
|
||||||
|
break;
|
||||||
|
case RRule.DAILY:
|
||||||
|
overriddenValues.frequency = 'day';
|
||||||
|
break;
|
||||||
|
case RRule.WEEKLY:
|
||||||
|
overriddenValues.frequency = 'week';
|
||||||
|
if (byweekday) {
|
||||||
|
overriddenValues.daysOfWeek = byweekday;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RRule.MONTHLY:
|
||||||
|
overriddenValues.frequency = 'month';
|
||||||
|
if (bymonthday) {
|
||||||
|
overriddenValues.runOnDayNumber = bymonthday;
|
||||||
|
} else if (bysetpos) {
|
||||||
|
overriddenValues.runOn = 'the';
|
||||||
|
overriddenValues.runOnTheOccurrence = bysetpos;
|
||||||
|
overriddenValues.runOnTheDay = generateRunOnTheDay(byweekday);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RRule.YEARLY:
|
||||||
|
overriddenValues.frequency = 'year';
|
||||||
|
if (bymonthday) {
|
||||||
|
overriddenValues.runOnDayNumber = bymonthday;
|
||||||
|
overriddenValues.runOnDayMonth = bymonth;
|
||||||
|
} else if (bysetpos) {
|
||||||
|
overriddenValues.runOn = 'the';
|
||||||
|
overriddenValues.runOnTheOccurrence = bysetpos;
|
||||||
|
overriddenValues.runOnTheDay = generateRunOnTheDay(byweekday);
|
||||||
|
overriddenValues.runOnTheMonth = bymonth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
rruleError = error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rruleError = new Error(i18n._(t`Schedule is missing rrule`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
request: loadZoneInfo,
|
request: loadZoneInfo,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
@@ -150,8 +309,8 @@ function ScheduleForm({
|
|||||||
loadZoneInfo();
|
loadZoneInfo();
|
||||||
}, [loadZoneInfo]);
|
}, [loadZoneInfo]);
|
||||||
|
|
||||||
if (contentError) {
|
if (contentError || rruleError) {
|
||||||
return <ContentError error={contentError} />;
|
return <ContentError error={contentError || rruleError} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentLoading) {
|
if (contentLoading) {
|
||||||
@@ -163,24 +322,7 @@ function ScheduleForm({
|
|||||||
{() => {
|
{() => {
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={Object.assign(initialValues, overriddenValues)}
|
||||||
daysOfWeek: [],
|
|
||||||
description: schedule.description || '',
|
|
||||||
end: 'never',
|
|
||||||
endDateTime: dateToInputDateTime(tomorrow),
|
|
||||||
frequency: 'none',
|
|
||||||
interval: 1,
|
|
||||||
name: schedule.name || '',
|
|
||||||
occurrences: 1,
|
|
||||||
runOn: 'day',
|
|
||||||
runOnDayMonth: 1,
|
|
||||||
runOnDayNumber: 1,
|
|
||||||
runOnTheDay: 'sunday',
|
|
||||||
runOnTheMonth: 1,
|
|
||||||
runOnTheOccurrence: 1,
|
|
||||||
startDateTime: dateToInputDateTime(closestQuarterHour),
|
|
||||||
timezone: schedule.timezone || 'America/New_York',
|
|
||||||
}}
|
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
validate={values => {
|
validate={values => {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
@@ -208,7 +350,7 @@ function ScheduleForm({
|
|||||||
(runOnDayNumber < 1 || runOnDayNumber > 31)
|
(runOnDayNumber < 1 || runOnDayNumber > 31)
|
||||||
) {
|
) {
|
||||||
errors.runOn = i18n._(
|
errors.runOn = i18n._(
|
||||||
t`Please select a day number between 1 and 31`
|
t`Please select a day number between 1 and 31.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,40 @@ import ScheduleForm from './ScheduleForm';
|
|||||||
|
|
||||||
jest.mock('@api/models/Schedules');
|
jest.mock('@api/models/Schedules');
|
||||||
|
|
||||||
|
const mockSchedule = {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
|
||||||
|
id: 27,
|
||||||
|
type: 'schedule',
|
||||||
|
url: '/api/v2/schedules/27/',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-04-02T18:43:12.664142Z',
|
||||||
|
modified: '2020-04-02T18:43:12.664185Z',
|
||||||
|
name: 'mock schedule',
|
||||||
|
description: 'test description',
|
||||||
|
extra_data: {},
|
||||||
|
inventory: null,
|
||||||
|
scm_branch: null,
|
||||||
|
job_type: null,
|
||||||
|
job_tags: null,
|
||||||
|
skip_tags: null,
|
||||||
|
limit: null,
|
||||||
|
diff_mode: null,
|
||||||
|
verbosity: null,
|
||||||
|
unified_job_template: 11,
|
||||||
|
enabled: true,
|
||||||
|
dtstart: '2020-04-02T18:45:00Z',
|
||||||
|
dtend: '2020-04-02T18:45:00Z',
|
||||||
|
next_run: '2020-04-02T18:45:00Z',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
until: '',
|
||||||
|
};
|
||||||
|
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const defaultFieldsVisible = () => {
|
const defaultFieldsVisible = () => {
|
||||||
@@ -16,6 +50,21 @@ const defaultFieldsVisible = () => {
|
|||||||
expect(wrapper.find('FormGroup[label="Run frequency"]').length).toBe(1);
|
expect(wrapper.find('FormGroup[label="Run frequency"]').length).toBe(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nonRRuleValuesMatch = () => {
|
||||||
|
expect(wrapper.find('input#schedule-name').prop('value')).toBe(
|
||||||
|
'mock schedule'
|
||||||
|
);
|
||||||
|
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('select#schedule-timezone').prop('value')).toBe(
|
||||||
|
'America/New_York'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
describe('<ScheduleForm />', () => {
|
describe('<ScheduleForm />', () => {
|
||||||
describe('Error', () => {
|
describe('Error', () => {
|
||||||
test('should display error when error occurs while loading', async () => {
|
test('should display error when error occurs while loading', async () => {
|
||||||
@@ -296,20 +345,321 @@ describe('<ScheduleForm />', () => {
|
|||||||
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(true);
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(true);
|
||||||
expect(wrapper.find('#schedule-end-datetime-helper').length).toBe(0);
|
expect(wrapper.find('#schedule-end-datetime-helper').length).toBe(0);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('input#schedule-end-datetime').invoke('onChange')(
|
wrapper.find('input#schedule-end-datetime').simulate('change', {
|
||||||
'2020-03-14T01:45:00',
|
target: { name: 'endDateTime', value: '2020-03-14T01:45:00' },
|
||||||
{
|
});
|
||||||
target: { name: 'endDateTime' },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
setTimeout(() => {
|
await act(async () => {
|
||||||
expect(wrapper.find('#schedule-end-datetime-helper').text()).toBe(
|
wrapper.find('input#schedule-end-datetime').simulate('blur');
|
||||||
'Please select an end date/time that comes after the start date/time.'
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(wrapper.find('#schedule-end-datetime-helper').text()).toBe(
|
||||||
|
'Please select an end date/time that comes after the start date/time.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('error shown when on day number is not between 1 and 31', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('input#schedule-run-on-day-number').simulate('change', {
|
||||||
|
target: { value: 32, name: 'runOnDayNumber' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(wrapper.find('#schedule-run-on-helper').text()).toBe(
|
||||||
|
'Please select a day number between 1 and 31.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Edit', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
SchedulesAPI.readZoneInfo.mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'America/New_York',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs once', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={mockSchedule}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'none'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs every 10 minutes', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={Object.assign(mockSchedule, {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=10;FREQ=MINUTELY',
|
||||||
|
dtend: null,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'minute'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#schedule-run-every').prop('value')).toBe(10);
|
||||||
|
expect(wrapper.find('input#end-never').prop('checked')).toBe(true);
|
||||||
|
expect(wrapper.find('input#end-after').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(false);
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs every hour 10 times', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={Object.assign(mockSchedule, {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;FREQ=HOURLY;COUNT=10',
|
||||||
|
dtend: '2020-04-03T03:45:00Z',
|
||||||
|
until: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'hour'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#schedule-run-every').prop('value')).toBe(1);
|
||||||
|
expect(wrapper.find('input#end-never').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-after').prop('checked')).toBe(true);
|
||||||
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#schedule-occurrences').prop('value')).toBe(10);
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs every day', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={Object.assign(mockSchedule, {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;FREQ=DAILY',
|
||||||
|
dtend: null,
|
||||||
|
until: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'day'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#end-never').prop('checked')).toBe(true);
|
||||||
|
expect(wrapper.find('input#end-after').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#schedule-run-every').prop('value')).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs every week on m/w/f until Jan 1, 2020', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={Object.assign(mockSchedule, {
|
||||||
|
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',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'week'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#schedule-run-every').prop('value')).toBe(1);
|
||||||
|
expect(wrapper.find('input#end-never').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-after').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(true);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-days-of-week-sun').prop('checked')
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-days-of-week-mon').prop('checked')
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-days-of-week-tue').prop('checked')
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-days-of-week-wed').prop('checked')
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-days-of-week-thu').prop('checked')
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-days-of-week-fri').prop('checked')
|
||||||
|
).toBe(true);
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs every month on the last weekday', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={Object.assign(mockSchedule, {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=-1;BYDAY=MO,TU,WE,TH,FR',
|
||||||
|
dtend: null,
|
||||||
|
until: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'month'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#end-never').prop('checked')).toBe(true);
|
||||||
|
expect(wrapper.find('input#end-after').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#schedule-run-every').prop('value')).toBe(1);
|
||||||
|
expect(wrapper.find('input#schedule-run-on-day').prop('checked')).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#schedule-run-on-the').prop('checked')).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
wrapper.find('select#schedule-run-on-the-occurrence').prop('value')
|
||||||
|
).toBe(-1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('select#schedule-run-on-the-day').prop('value')
|
||||||
|
).toBe('weekday');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('initially renders expected fields and values with existing schedule that runs every year on the May 6', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ScheduleForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
schedule={Object.assign(mockSchedule, {
|
||||||
|
rrule:
|
||||||
|
'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;FREQ=YEARLY;BYMONTH=5;BYMONTHDAY=6',
|
||||||
|
dtend: null,
|
||||||
|
until: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('ScheduleForm').length).toBe(1);
|
||||||
|
defaultFieldsVisible();
|
||||||
|
expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
|
||||||
|
expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
|
||||||
|
|
||||||
|
nonRRuleValuesMatch();
|
||||||
|
expect(wrapper.find('select#schedule-frequency').prop('value')).toBe(
|
||||||
|
'year'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#end-never').prop('checked')).toBe(true);
|
||||||
|
expect(wrapper.find('input#end-after').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#end-on-date').prop('checked')).toBe(false);
|
||||||
|
expect(wrapper.find('input#schedule-run-every').prop('value')).toBe(1);
|
||||||
|
expect(wrapper.find('input#schedule-run-on-day').prop('checked')).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(wrapper.find('input#schedule-run-on-the').prop('checked')).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
wrapper.find('select#schedule-run-on-day-month').prop('value')
|
||||||
|
).toBe(5);
|
||||||
|
expect(
|
||||||
|
wrapper.find('input#schedule-run-on-day-number').prop('value')
|
||||||
|
).toBe(6);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
101
awx/ui_next/src/components/Schedule/shared/buildRuleObj.js
Normal file
101
awx/ui_next/src/components/Schedule/shared/buildRuleObj.js
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
|
import { getRRuleDayConstants } from '@util/dates';
|
||||||
|
|
||||||
|
export default function buildRuleObj(values, i18n) {
|
||||||
|
const [startDate, startTime] = values.startDateTime.split('T');
|
||||||
|
// Dates are formatted like "YYYY-MM-DD"
|
||||||
|
const [startYear, startMonth, startDay] = 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 ruleObj = {
|
||||||
|
interval: values.interval,
|
||||||
|
dtstart: new Date(
|
||||||
|
Date.UTC(
|
||||||
|
startYear,
|
||||||
|
parseInt(startMonth, 10) - 1,
|
||||||
|
startDay,
|
||||||
|
startHour,
|
||||||
|
startMinute,
|
||||||
|
startSecond
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tzid: values.timezone,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (values.frequency) {
|
||||||
|
case 'none':
|
||||||
|
ruleObj.count = 1;
|
||||||
|
ruleObj.freq = RRule.MINUTELY;
|
||||||
|
break;
|
||||||
|
case 'minute':
|
||||||
|
ruleObj.freq = RRule.MINUTELY;
|
||||||
|
break;
|
||||||
|
case 'hour':
|
||||||
|
ruleObj.freq = RRule.HOURLY;
|
||||||
|
break;
|
||||||
|
case 'day':
|
||||||
|
ruleObj.freq = RRule.DAILY;
|
||||||
|
break;
|
||||||
|
case 'week':
|
||||||
|
ruleObj.freq = RRule.WEEKLY;
|
||||||
|
ruleObj.byweekday = values.daysOfWeek;
|
||||||
|
break;
|
||||||
|
case 'month':
|
||||||
|
ruleObj.freq = RRule.MONTHLY;
|
||||||
|
if (values.runOn === 'day') {
|
||||||
|
ruleObj.bymonthday = values.runOnDayNumber;
|
||||||
|
} else if (values.runOn === 'the') {
|
||||||
|
ruleObj.bysetpos = parseInt(values.runOnTheOccurrence, 10);
|
||||||
|
ruleObj.byweekday = getRRuleDayConstants(values.runOnTheDay, i18n);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'year':
|
||||||
|
ruleObj.freq = RRule.YEARLY;
|
||||||
|
if (values.runOn === 'day') {
|
||||||
|
ruleObj.bymonth = parseInt(values.runOnDayMonth, 10);
|
||||||
|
ruleObj.bymonthday = values.runOnDayNumber;
|
||||||
|
} else if (values.runOn === 'the') {
|
||||||
|
ruleObj.bysetpos = parseInt(values.runOnTheOccurrence, 10);
|
||||||
|
ruleObj.byweekday = getRRuleDayConstants(values.runOnTheDay, i18n);
|
||||||
|
ruleObj.bymonth = parseInt(values.runOnTheMonth, 10);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(i18n._(t`Frequency did not match an expected value`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.frequency !== 'none') {
|
||||||
|
switch (values.end) {
|
||||||
|
case 'never':
|
||||||
|
break;
|
||||||
|
case 'after':
|
||||||
|
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(':');
|
||||||
|
ruleObj.until = new Date(
|
||||||
|
Date.UTC(
|
||||||
|
endYear,
|
||||||
|
parseInt(endMonth, 10) - 1,
|
||||||
|
endDay,
|
||||||
|
endHour,
|
||||||
|
endMinute,
|
||||||
|
endSecond
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(i18n._(t`End did not match an expected value`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleObj;
|
||||||
|
}
|
||||||
@@ -47,8 +47,9 @@ class Projects extends Component {
|
|||||||
[`${projectSchedulesPath}/add`]: i18n._(t`Create New Schedule`),
|
[`${projectSchedulesPath}/add`]: i18n._(t`Create New Schedule`),
|
||||||
[`${projectSchedulesPath}/${nested?.id}`]: `${nested?.name}`,
|
[`${projectSchedulesPath}/${nested?.id}`]: `${nested?.name}`,
|
||||||
[`${projectSchedulesPath}/${nested?.id}/details`]: i18n._(
|
[`${projectSchedulesPath}/${nested?.id}/details`]: i18n._(
|
||||||
t`Edit Details`
|
t`Schedule Details`
|
||||||
),
|
),
|
||||||
|
[`${projectSchedulesPath}/${nested?.id}/edit`]: i18n._(t`Edit Details`),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({ breadcrumbConfig });
|
this.setState({ breadcrumbConfig });
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class Templates extends Component {
|
|||||||
schedule.id}`]: `${schedule && schedule.name}`,
|
schedule.id}`]: `${schedule && schedule.name}`,
|
||||||
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
|
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
|
||||||
schedule.id}/details`]: i18n._(t`Schedule Details`),
|
schedule.id}/details`]: i18n._(t`Schedule Details`),
|
||||||
|
[`/templates/${template.type}/${template.id}/schedules/${schedule &&
|
||||||
|
schedule.id}/edit`]: i18n._(t`Edit Details`),
|
||||||
};
|
};
|
||||||
this.setState({ breadcrumbConfig });
|
this.setState({ breadcrumbConfig });
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user