mirror of
https://github.com/ansible/awx.git
synced 2026-02-04 19:18:13 -03:30
Compare commits
1 Commits
devel
...
13089-Sche
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26a947ed31 |
@@ -55,7 +55,6 @@ function DateTimePicker({ dateFieldName, timeFieldName, label }) {
|
|||||||
onChange={onDateChange}
|
onChange={onDateChange}
|
||||||
/>
|
/>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
placeholder="hh:mm AM/PM"
|
|
||||||
stepMinutes={15}
|
stepMinutes={15}
|
||||||
aria-label={
|
aria-label={
|
||||||
timeFieldName.startsWith('start') ? t`Start time` : t`End time`
|
timeFieldName.startsWith('start') ? t`Start time` : t`End time`
|
||||||
|
|||||||
93
awx/ui/src/components/Schedule/shared/FrequenciesList.js
Normal file
93
awx/ui/src/components/Schedule/shared/FrequenciesList.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Switch,
|
||||||
|
Toolbar,
|
||||||
|
ToolbarContent,
|
||||||
|
ToolbarItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { PencilAltIcon } from '@patternfly/react-icons';
|
||||||
|
import {
|
||||||
|
TableComposable,
|
||||||
|
Tbody,
|
||||||
|
Thead,
|
||||||
|
Th,
|
||||||
|
Tr,
|
||||||
|
Td,
|
||||||
|
} from '@patternfly/react-table';
|
||||||
|
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import ContentEmpty from 'components/ContentEmpty';
|
||||||
|
|
||||||
|
function FrequenciesList({ openWizard }) {
|
||||||
|
const [isShowingRules, setIsShowingRules] = useState(true);
|
||||||
|
const [frequencies] = useField('frequencies');
|
||||||
|
const list = (freq) => (
|
||||||
|
<Tr key={freq.rrule}>
|
||||||
|
<Td>{freq.frequency}</Td>
|
||||||
|
<Td>{freq.rrule}</Td>
|
||||||
|
<Td>{t`End`}</Td>
|
||||||
|
<Td>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
aria-label={t`Click to toggle default value`}
|
||||||
|
ouiaId={freq ? `${freq}-button` : 'new-freq-button'}
|
||||||
|
onClick={() => {
|
||||||
|
openWizard(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PencilAltIcon />
|
||||||
|
</Button>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarContent>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
openWizard(true);
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
{isShowingRules ? t`Add RRules` : t`Add Exception`}
|
||||||
|
</Button>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Switch
|
||||||
|
label={t`Occurances`}
|
||||||
|
labelOff={t`Exceptions`}
|
||||||
|
isChecked={isShowingRules}
|
||||||
|
onChange={(isChecked) => {
|
||||||
|
setIsShowingRules(isChecked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ToolbarItem>
|
||||||
|
</ToolbarContent>
|
||||||
|
</Toolbar>
|
||||||
|
<div css="overflow: auto">
|
||||||
|
{frequencies.value[0].frequency === '' &&
|
||||||
|
frequencies.value.length < 2 ? (
|
||||||
|
<ContentEmpty title={t`RRules`} message={t`Add RRules`} />
|
||||||
|
) : (
|
||||||
|
<TableComposable aria-label={t`RRules`} ouiaId="rrules-list">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{t`Frequency`}</Th>
|
||||||
|
<Th>{t`RRule`}</Th>
|
||||||
|
<Th>{t`Ending`}</Th>
|
||||||
|
<Th>{t`Actions`}</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>{frequencies.value.map((freq, i) => list(freq, i))}</Tbody>
|
||||||
|
</TableComposable>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FrequenciesList;
|
||||||
@@ -1,568 +0,0 @@
|
|||||||
import 'styled-components/macro';
|
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { useField } from 'formik';
|
|
||||||
|
|
||||||
import { t, Trans, Plural } from '@lingui/macro';
|
|
||||||
import { RRule } from 'rrule';
|
|
||||||
import {
|
|
||||||
Checkbox as _Checkbox,
|
|
||||||
FormGroup,
|
|
||||||
Radio,
|
|
||||||
TextInput,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
import { required, requiredPositiveInteger } from 'util/validators';
|
|
||||||
import AnsibleSelect from '../../AnsibleSelect';
|
|
||||||
import FormField from '../../FormField';
|
|
||||||
import DateTimePicker from './DateTimePicker';
|
|
||||||
|
|
||||||
const RunOnRadio = styled(Radio)`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(:last-of-type) {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
select:not(:first-of-type) {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RunEveryLabel = styled.p`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Checkbox = styled(_Checkbox)`
|
|
||||||
:not(:last-of-type) {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FrequencyDetailSubform = ({ frequency, prefix, isException }) => {
|
|
||||||
const id = prefix.replace('.', '-');
|
|
||||||
const [runOnDayMonth] = useField({
|
|
||||||
name: `${prefix}.runOnDayMonth`,
|
|
||||||
});
|
|
||||||
const [runOnDayNumber] = useField({
|
|
||||||
name: `${prefix}.runOnDayNumber`,
|
|
||||||
});
|
|
||||||
const [runOnTheOccurrence] = useField({
|
|
||||||
name: `${prefix}.runOnTheOccurrence`,
|
|
||||||
});
|
|
||||||
const [runOnTheDay] = useField({
|
|
||||||
name: `${prefix}.runOnTheDay`,
|
|
||||||
});
|
|
||||||
const [runOnTheMonth] = useField({
|
|
||||||
name: `${prefix}.runOnTheMonth`,
|
|
||||||
});
|
|
||||||
const [startDate] = useField(`${prefix}.startDate`);
|
|
||||||
|
|
||||||
const [daysOfWeek, daysOfWeekMeta, daysOfWeekHelpers] = useField({
|
|
||||||
name: `${prefix}.daysOfWeek`,
|
|
||||||
validate: (val) => {
|
|
||||||
if (frequency === 'week') {
|
|
||||||
return required(t`Select a value for this field`)(val?.length > 0);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [end, endMeta] = useField({
|
|
||||||
name: `${prefix}.end`,
|
|
||||||
validate: required(t`Select a value for this field`),
|
|
||||||
});
|
|
||||||
const [interval, intervalMeta] = useField({
|
|
||||||
name: `${prefix}.interval`,
|
|
||||||
validate: requiredPositiveInteger(),
|
|
||||||
});
|
|
||||||
const [runOn, runOnMeta] = useField({
|
|
||||||
name: `${prefix}.runOn`,
|
|
||||||
validate: (val) => {
|
|
||||||
if (frequency === 'month' || frequency === 'year') {
|
|
||||||
return required(t`Select a value for this field`)(val);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const monthOptions = [
|
|
||||||
{
|
|
||||||
key: 'january',
|
|
||||||
value: 1,
|
|
||||||
label: t`January`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'february',
|
|
||||||
value: 2,
|
|
||||||
label: t`February`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'march',
|
|
||||||
value: 3,
|
|
||||||
label: t`March`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'april',
|
|
||||||
value: 4,
|
|
||||||
label: t`April`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'may',
|
|
||||||
value: 5,
|
|
||||||
label: t`May`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'june',
|
|
||||||
value: 6,
|
|
||||||
label: t`June`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'july',
|
|
||||||
value: 7,
|
|
||||||
label: t`July`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'august',
|
|
||||||
value: 8,
|
|
||||||
label: t`August`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'september',
|
|
||||||
value: 9,
|
|
||||||
label: t`September`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'october',
|
|
||||||
value: 10,
|
|
||||||
label: t`October`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'november',
|
|
||||||
value: 11,
|
|
||||||
label: t`November`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'december',
|
|
||||||
value: 12,
|
|
||||||
label: t`December`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const updateDaysOfWeek = (day, checked) => {
|
|
||||||
const newDaysOfWeek = daysOfWeek.value ? [...daysOfWeek.value] : [];
|
|
||||||
daysOfWeekHelpers.setTouched(true);
|
|
||||||
if (checked) {
|
|
||||||
newDaysOfWeek.push(day);
|
|
||||||
daysOfWeekHelpers.setValue(newDaysOfWeek);
|
|
||||||
} else {
|
|
||||||
daysOfWeekHelpers.setValue(
|
|
||||||
newDaysOfWeek.filter((selectedDay) => selectedDay !== day)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPeriodLabel = () => {
|
|
||||||
switch (frequency) {
|
|
||||||
case 'minute':
|
|
||||||
return t`Minute`;
|
|
||||||
case 'hour':
|
|
||||||
return t`Hour`;
|
|
||||||
case 'day':
|
|
||||||
return t`Day`;
|
|
||||||
case 'week':
|
|
||||||
return t`Week`;
|
|
||||||
case 'month':
|
|
||||||
return t`Month`;
|
|
||||||
case 'year':
|
|
||||||
return t`Year`;
|
|
||||||
default:
|
|
||||||
throw new Error(t`Frequency did not match an expected value`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRunEveryLabel = () => {
|
|
||||||
const intervalValue = interval.value;
|
|
||||||
|
|
||||||
switch (frequency) {
|
|
||||||
case 'minute':
|
|
||||||
return <Plural value={intervalValue} one="minute" other="minutes" />;
|
|
||||||
case 'hour':
|
|
||||||
return <Plural value={intervalValue} one="hour" other="hours" />;
|
|
||||||
case 'day':
|
|
||||||
return <Plural value={intervalValue} one="day" other="days" />;
|
|
||||||
case 'week':
|
|
||||||
return <Plural value={intervalValue} one="week" other="weeks" />;
|
|
||||||
case 'month':
|
|
||||||
return <Plural value={intervalValue} one="month" other="months" />;
|
|
||||||
case 'year':
|
|
||||||
return <Plural value={intervalValue} one="year" other="years" />;
|
|
||||||
default:
|
|
||||||
throw new Error(t`Frequency did not match an expected value`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p css="grid-column: 1/-1">
|
|
||||||
<b>{getPeriodLabel()}</b>
|
|
||||||
</p>
|
|
||||||
<FormGroup
|
|
||||||
name={`${prefix}.interval`}
|
|
||||||
fieldId={`schedule-run-every-${id}`}
|
|
||||||
helperTextInvalid={intervalMeta.error}
|
|
||||||
isRequired
|
|
||||||
validated={
|
|
||||||
!intervalMeta.touched || !intervalMeta.error ? 'default' : 'error'
|
|
||||||
}
|
|
||||||
label={isException ? t`Skip every` : t`Run every`}
|
|
||||||
>
|
|
||||||
<div css="display: flex">
|
|
||||||
<TextInput
|
|
||||||
css="margin-right: 10px;"
|
|
||||||
id={`schedule-run-every-${id}`}
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
{...interval}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
interval.onChange(event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RunEveryLabel>{getRunEveryLabel()}</RunEveryLabel>
|
|
||||||
</div>
|
|
||||||
</FormGroup>
|
|
||||||
{frequency === 'week' && (
|
|
||||||
<FormGroup
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
fieldId={`schedule-days-of-week-${id}`}
|
|
||||||
helperTextInvalid={daysOfWeekMeta.error}
|
|
||||||
isRequired
|
|
||||||
validated={
|
|
||||||
!daysOfWeekMeta.touched || !daysOfWeekMeta.error
|
|
||||||
? 'default'
|
|
||||||
: 'error'
|
|
||||||
}
|
|
||||||
label={t`On days`}
|
|
||||||
>
|
|
||||||
<div css="display: flex">
|
|
||||||
<Checkbox
|
|
||||||
label={t`Sun`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.SU)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.SU, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Sunday`}
|
|
||||||
id={`schedule-days-of-week-sun-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-sun-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={t`Mon`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.MO)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.MO, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Monday`}
|
|
||||||
id={`schedule-days-of-week-mon-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-mon-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={t`Tue`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.TU)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.TU, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Tuesday`}
|
|
||||||
id={`schedule-days-of-week-tue-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-tue-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={t`Wed`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.WE)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.WE, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Wednesday`}
|
|
||||||
id={`schedule-days-of-week-wed-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-wed-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={t`Thu`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.TH)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.TH, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Thursday`}
|
|
||||||
id={`schedule-days-of-week-thu-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-thu-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={t`Fri`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.FR)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.FR, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Friday`}
|
|
||||||
id={`schedule-days-of-week-fri-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-fri-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={t`Sat`}
|
|
||||||
isChecked={daysOfWeek.value?.includes(RRule.SA)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
updateDaysOfWeek(RRule.SA, checked);
|
|
||||||
}}
|
|
||||||
aria-label={t`Saturday`}
|
|
||||||
id={`schedule-days-of-week-sat-${id}`}
|
|
||||||
ouiaId={`schedule-days-of-week-sat-${id}`}
|
|
||||||
name={`${prefix}.daysOfWeek`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
{(frequency === 'month' || frequency === 'year') &&
|
|
||||||
!Number.isNaN(new Date(startDate.value)) && (
|
|
||||||
<FormGroup
|
|
||||||
name={`${prefix}.runOn`}
|
|
||||||
fieldId={`schedule-run-on-${id}`}
|
|
||||||
helperTextInvalid={runOnMeta.error}
|
|
||||||
isRequired
|
|
||||||
validated={
|
|
||||||
!runOnMeta.touched || !runOnMeta.error ? 'default' : 'error'
|
|
||||||
}
|
|
||||||
label={t`Run on`}
|
|
||||||
>
|
|
||||||
<RunOnRadio
|
|
||||||
id={`schedule-run-on-day-${id}`}
|
|
||||||
name={`${prefix}.runOn`}
|
|
||||||
label={
|
|
||||||
<div css="display: flex;align-items: center;">
|
|
||||||
{frequency === 'month' && (
|
|
||||||
<span
|
|
||||||
id="radio-schedule-run-on-day"
|
|
||||||
css="margin-right: 10px;"
|
|
||||||
>
|
|
||||||
<Trans>Day</Trans>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{frequency === 'year' && (
|
|
||||||
<AnsibleSelect
|
|
||||||
id={`schedule-run-on-day-month-${id}`}
|
|
||||||
css="margin-right: 10px"
|
|
||||||
isDisabled={runOn.value !== 'day'}
|
|
||||||
data={monthOptions}
|
|
||||||
{...runOnDayMonth}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<TextInput
|
|
||||||
id={`schedule-run-on-day-number-${id}`}
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="31"
|
|
||||||
step="1"
|
|
||||||
isDisabled={runOn.value !== 'day'}
|
|
||||||
{...runOnDayNumber}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
runOnDayNumber.onChange(event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
value="day"
|
|
||||||
isChecked={runOn.value === 'day'}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
event.target.value = 'day';
|
|
||||||
runOn.onChange(event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<RunOnRadio
|
|
||||||
id={`schedule-run-on-the-${id}`}
|
|
||||||
name={`${prefix}.runOn`}
|
|
||||||
label={
|
|
||||||
<div css="display: flex;align-items: center;">
|
|
||||||
<span
|
|
||||||
id={`radio-schedule-run-on-the-${id}`}
|
|
||||||
css="margin-right: 10px;"
|
|
||||||
>
|
|
||||||
<Trans>The</Trans>
|
|
||||||
</span>
|
|
||||||
<AnsibleSelect
|
|
||||||
id={`schedule-run-on-the-occurrence-${id}`}
|
|
||||||
isDisabled={runOn.value !== 'the'}
|
|
||||||
data={[
|
|
||||||
{ value: 1, key: 'first', label: t`First` },
|
|
||||||
{
|
|
||||||
value: 2,
|
|
||||||
key: 'second',
|
|
||||||
label: t`Second`,
|
|
||||||
},
|
|
||||||
{ value: 3, key: 'third', label: t`Third` },
|
|
||||||
{
|
|
||||||
value: 4,
|
|
||||||
key: 'fourth',
|
|
||||||
label: t`Fourth`,
|
|
||||||
},
|
|
||||||
{ value: 5, key: 'fifth', label: t`Fifth` },
|
|
||||||
{ value: -1, key: 'last', label: t`Last` },
|
|
||||||
]}
|
|
||||||
{...runOnTheOccurrence}
|
|
||||||
/>
|
|
||||||
<AnsibleSelect
|
|
||||||
id={`schedule-run-on-the-day-${id}`}
|
|
||||||
isDisabled={runOn.value !== 'the'}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
value: 'sunday',
|
|
||||||
key: 'sunday',
|
|
||||||
label: t`Sunday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'monday',
|
|
||||||
key: 'monday',
|
|
||||||
label: t`Monday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'tuesday',
|
|
||||||
key: 'tuesday',
|
|
||||||
label: t`Tuesday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'wednesday',
|
|
||||||
key: 'wednesday',
|
|
||||||
label: t`Wednesday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'thursday',
|
|
||||||
key: 'thursday',
|
|
||||||
label: t`Thursday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'friday',
|
|
||||||
key: 'friday',
|
|
||||||
label: t`Friday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'saturday',
|
|
||||||
key: 'saturday',
|
|
||||||
label: t`Saturday`,
|
|
||||||
},
|
|
||||||
{ value: 'day', key: 'day', label: t`Day` },
|
|
||||||
{
|
|
||||||
value: 'weekday',
|
|
||||||
key: 'weekday',
|
|
||||||
label: t`Weekday`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'weekendDay',
|
|
||||||
key: 'weekendDay',
|
|
||||||
label: t`Weekend day`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
{...runOnTheDay}
|
|
||||||
/>
|
|
||||||
{frequency === 'year' && (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
id={`of-schedule-run-on-the-month-${id}`}
|
|
||||||
css="margin-left: 10px;"
|
|
||||||
>
|
|
||||||
<Trans>of</Trans>
|
|
||||||
</span>
|
|
||||||
<AnsibleSelect
|
|
||||||
id={`schedule-run-on-the-month-${id}`}
|
|
||||||
isDisabled={runOn.value !== 'the'}
|
|
||||||
data={monthOptions}
|
|
||||||
{...runOnTheMonth}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
value="the"
|
|
||||||
isChecked={runOn.value === 'the'}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
event.target.value = 'the';
|
|
||||||
runOn.onChange(event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
<FormGroup
|
|
||||||
name={`${prefix}.end`}
|
|
||||||
fieldId={`schedule-end-${id}`}
|
|
||||||
helperTextInvalid={endMeta.error}
|
|
||||||
isRequired
|
|
||||||
validated={!endMeta.touched || !endMeta.error ? 'default' : 'error'}
|
|
||||||
label={t`End`}
|
|
||||||
>
|
|
||||||
<Radio
|
|
||||||
id={`end-never-${id}`}
|
|
||||||
name={`${prefix}.end`}
|
|
||||||
label={t`Never`}
|
|
||||||
value="never"
|
|
||||||
isChecked={end.value === 'never'}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
event.target.value = 'never';
|
|
||||||
end.onChange(event);
|
|
||||||
}}
|
|
||||||
ouiaId={`end-never-radio-button-${id}`}
|
|
||||||
/>
|
|
||||||
<Radio
|
|
||||||
id={`end-after-${id}`}
|
|
||||||
name={`${prefix}.end`}
|
|
||||||
label={t`After number of occurrences`}
|
|
||||||
value="after"
|
|
||||||
isChecked={end.value === 'after'}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
event.target.value = 'after';
|
|
||||||
end.onChange(event);
|
|
||||||
}}
|
|
||||||
ouiaId={`end-after-radio-button-${id}`}
|
|
||||||
/>
|
|
||||||
<Radio
|
|
||||||
id={`end-on-date-${id}`}
|
|
||||||
name={`${prefix}.end`}
|
|
||||||
label={t`On date`}
|
|
||||||
value="onDate"
|
|
||||||
isChecked={end.value === 'onDate'}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
event.target.value = 'onDate';
|
|
||||||
end.onChange(event);
|
|
||||||
}}
|
|
||||||
ouiaId={`end-on-radio-button-${id}`}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
{end?.value === 'after' && (
|
|
||||||
<FormField
|
|
||||||
id={`schedule-occurrences-${id}`}
|
|
||||||
label={t`Occurrences`}
|
|
||||||
name={`${prefix}.occurrences`}
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{end?.value === 'onDate' && (
|
|
||||||
<DateTimePicker
|
|
||||||
dateFieldName={`${prefix}.endDate`}
|
|
||||||
timeFieldName={`${prefix}.endTime`}
|
|
||||||
label={t`End date/time`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FrequencyDetailSubform;
|
|
||||||
@@ -1,30 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { arrayOf, string } from 'prop-types';
|
import { t } from '@lingui/macro';
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
||||||
|
|
||||||
export default function FrequencySelect({
|
export default function FrequencySelect({ id, onBlur, placeholderText }) {
|
||||||
id,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onBlur,
|
|
||||||
placeholderText,
|
|
||||||
children,
|
|
||||||
}) {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [frequency, , frequencyHelpers] = useField('freq');
|
||||||
const onSelect = (event, selectedValue) => {
|
|
||||||
if (selectedValue === 'none') {
|
|
||||||
onChange([]);
|
|
||||||
setIsOpen(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const index = value.indexOf(selectedValue);
|
|
||||||
if (index === -1) {
|
|
||||||
onChange(value.concat(selectedValue));
|
|
||||||
} else {
|
|
||||||
onChange(value.slice(0, index).concat(value.slice(index + 1)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onToggle = (val) => {
|
const onToggle = (val) => {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
@@ -35,21 +17,26 @@ export default function FrequencySelect({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
variant={SelectVariant.checkbox}
|
onSelect={(e, v) => {
|
||||||
onSelect={onSelect}
|
frequencyHelpers.setValue(v);
|
||||||
selections={value}
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
selections={frequency.value}
|
||||||
placeholderText={placeholderText}
|
placeholderText={placeholderText}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
|
value={frequency.value}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
ouiaId={`frequency-select-${id}`}
|
ouiaId={`frequency-select-${id}`}
|
||||||
|
onBlur={() => frequencyHelpers.setTouched(true)}
|
||||||
>
|
>
|
||||||
{children}
|
<SelectOption value={RRule.MINUTELY}>{t`Minute`}</SelectOption>
|
||||||
|
<SelectOption value={RRule.HOURLY}>{t`Hour`}</SelectOption>
|
||||||
|
<SelectOption value={RRule.DAILY}>{t`Day`}</SelectOption>
|
||||||
|
<SelectOption value={RRule.WEEKLY}>{t`Week`}</SelectOption>
|
||||||
|
<SelectOption value={RRule.MONTHLY}>{t`Month`}</SelectOption>
|
||||||
|
<SelectOption value={RRule.YEARLY}>{t`Year`}</SelectOption>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
FrequencySelect.propTypes = {
|
|
||||||
value: arrayOf(string).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { SelectOption, SelectVariant };
|
export { SelectOption, SelectVariant };
|
||||||
|
|||||||
77
awx/ui/src/components/Schedule/shared/MonthandYearForm.js
Normal file
77
awx/ui/src/components/Schedule/shared/MonthandYearForm.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import AnsibleSelect from 'components/AnsibleSelect';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
FormGroup,
|
||||||
|
Checkbox as _Checkbox,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import { bysetposOptions, monthOptions } from './scheduleFormHelpers';
|
||||||
|
|
||||||
|
const GroupWrapper = styled(FormGroup)`
|
||||||
|
&& .pf-c-form__group-control {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
&& .pf-c-form__group-label {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const Checkbox = styled(_Checkbox)`
|
||||||
|
:not(:last-of-type) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
function MonthandYearForm({ id }) {
|
||||||
|
const [bySetPos, , bySetPosHelpers] = useField('bysetpos');
|
||||||
|
const [byMonth, , byMonthHelpers] = useField('bymonth');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GroupWrapper
|
||||||
|
fieldId={`schedule-run-on-${id}`}
|
||||||
|
label={<b>{t`Run on a specific month`}</b>}
|
||||||
|
>
|
||||||
|
<Grid hasGutter>
|
||||||
|
{monthOptions.map((month) => (
|
||||||
|
<GridItem key={month.label} span={2} rowSpan={2}>
|
||||||
|
<Checkbox
|
||||||
|
label={month.label}
|
||||||
|
isChecked={byMonth.value?.includes(month.value)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
byMonthHelpers.setValue([...byMonth.value, month.value]);
|
||||||
|
} else {
|
||||||
|
const removed = byMonth.value.filter(
|
||||||
|
(i) => i !== month.value
|
||||||
|
);
|
||||||
|
byMonthHelpers.setValue(removed);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
id={`bymonth-${month.label}`}
|
||||||
|
ouiaId={`bymonth-${month.label}`}
|
||||||
|
name="bymonth"
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</GroupWrapper>
|
||||||
|
<GroupWrapper
|
||||||
|
label={<b>{t`Run on a specific week day at monthly intervals`}</b>}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
id={`schedule-run-on-the-occurrence-${id}`}
|
||||||
|
data={bysetposOptions}
|
||||||
|
{...bySetPos}
|
||||||
|
onChange={(e, v) => {
|
||||||
|
bySetPosHelpers.setValue(v);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GroupWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default MonthandYearForm;
|
||||||
45
awx/ui/src/components/Schedule/shared/OrdinalDayForm.js
Normal file
45
awx/ui/src/components/Schedule/shared/OrdinalDayForm.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import { FormGroup, TextInput } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const GroupWrapper = styled(FormGroup)`
|
||||||
|
&& .pf-c-form__group-control {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
&& .pf-c-form__group-label {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function OrdinalDayForm() {
|
||||||
|
const [byMonthDay] = useField('bymonthday');
|
||||||
|
const [byYearDay] = useField('byyearday');
|
||||||
|
return (
|
||||||
|
<GroupWrapper
|
||||||
|
label={<b>{t`On a specific number day`}</b>}
|
||||||
|
name="ordinalDay"
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
placeholder={t`Run on a day of month`}
|
||||||
|
aria-label={t`Type a numbered day`}
|
||||||
|
type="number"
|
||||||
|
onChange={(value, event) => {
|
||||||
|
byMonthDay.onChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder={t`Run on a day of year`}
|
||||||
|
aria-label={t`Type a numbered day`}
|
||||||
|
type="number"
|
||||||
|
onChange={(value, event) => {
|
||||||
|
byYearDay.onChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GroupWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrdinalDayForm;
|
||||||
67
awx/ui/src/components/Schedule/shared/ScheduleEndForm.js
Normal file
67
awx/ui/src/components/Schedule/shared/ScheduleEndForm.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { FormGroup, Radio } from '@patternfly/react-core';
|
||||||
|
import FormField from 'components/FormField';
|
||||||
|
import DateTimePicker from './DateTimePicker';
|
||||||
|
|
||||||
|
function ScheduleEndForm() {
|
||||||
|
const [endType, , { setValue }] = useField('endingType');
|
||||||
|
const [count] = useField('count');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormGroup name="end" label={t`End`}>
|
||||||
|
<Radio
|
||||||
|
id="endNever"
|
||||||
|
name={t`Never End`}
|
||||||
|
label={t`Never`}
|
||||||
|
value="never"
|
||||||
|
isChecked={endType.value === 'never'}
|
||||||
|
onChange={() => {
|
||||||
|
setValue('never');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
name="Count"
|
||||||
|
id="after"
|
||||||
|
label={t`After number of occurrences`}
|
||||||
|
value="after"
|
||||||
|
isChecked={endType.value === 'after'}
|
||||||
|
onChange={() => {
|
||||||
|
setValue('after');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
name="End Date"
|
||||||
|
label={t`On date`}
|
||||||
|
value="onDate"
|
||||||
|
id="endDate"
|
||||||
|
isChecked={endType.value === 'onDate'}
|
||||||
|
onChange={() => {
|
||||||
|
setValue('onDate');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{endType.value === 'after' && (
|
||||||
|
<FormField
|
||||||
|
label={t`Occurrences`}
|
||||||
|
name="count"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
step="1"
|
||||||
|
isRequired
|
||||||
|
{...count}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{endType.value === 'onDate' && (
|
||||||
|
<DateTimePicker
|
||||||
|
dateFieldName="endDate"
|
||||||
|
timeFieldName="endTime"
|
||||||
|
label={t`End date/time`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScheduleEndForm;
|
||||||
@@ -18,14 +18,9 @@ import SchedulePromptableFields from './SchedulePromptableFields';
|
|||||||
import ScheduleFormFields from './ScheduleFormFields';
|
import ScheduleFormFields from './ScheduleFormFields';
|
||||||
import UnsupportedScheduleForm from './UnsupportedScheduleForm';
|
import UnsupportedScheduleForm from './UnsupportedScheduleForm';
|
||||||
import parseRuleObj, { UnsupportedRRuleError } from './parseRuleObj';
|
import parseRuleObj, { UnsupportedRRuleError } from './parseRuleObj';
|
||||||
import buildRuleObj from './buildRuleObj';
|
import ScheduleFormWizard from './ScheduleFormWizard';
|
||||||
import buildRuleSet from './buildRuleSet';
|
import FrequenciesList from './FrequenciesList';
|
||||||
|
// import { validateSchedule } from './scheduleFormHelpers';
|
||||||
const NUM_DAYS_PER_FREQUENCY = {
|
|
||||||
week: 7,
|
|
||||||
month: 31,
|
|
||||||
year: 365,
|
|
||||||
};
|
|
||||||
|
|
||||||
function ScheduleForm({
|
function ScheduleForm({
|
||||||
hasDaysToKeepField,
|
hasDaysToKeepField,
|
||||||
@@ -40,15 +35,16 @@ function ScheduleForm({
|
|||||||
}) {
|
}) {
|
||||||
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
||||||
const [isSaveDisabled, setIsSaveDisabled] = useState(false);
|
const [isSaveDisabled, setIsSaveDisabled] = useState(false);
|
||||||
|
const [isScheduleWizardOpen, setIsScheduleWizardOpen] = useState(false);
|
||||||
const originalLabels = useRef([]);
|
const originalLabels = useRef([]);
|
||||||
const originalInstanceGroups = useRef([]);
|
const originalInstanceGroups = useRef([]);
|
||||||
|
|
||||||
let rruleError;
|
let rruleError;
|
||||||
const now = DateTime.now();
|
const now = DateTime.now();
|
||||||
|
|
||||||
const closestQuarterHour = DateTime.fromMillis(
|
const closestQuarterHour = DateTime.fromMillis(
|
||||||
Math.ceil(now.ts / 900000) * 900000
|
Math.ceil(now.ts / 900000) * 900000
|
||||||
);
|
);
|
||||||
const tomorrow = closestQuarterHour.plus({ days: 1 });
|
|
||||||
const isTemplate =
|
const isTemplate =
|
||||||
resource.type === 'workflow_job_template' ||
|
resource.type === 'workflow_job_template' ||
|
||||||
resource.type === 'job_template';
|
resource.type === 'job_template';
|
||||||
@@ -283,69 +279,10 @@ function ScheduleForm({
|
|||||||
}
|
}
|
||||||
const [currentDate, time] = dateToInputDateTime(closestQuarterHour.toISO());
|
const [currentDate, time] = dateToInputDateTime(closestQuarterHour.toISO());
|
||||||
|
|
||||||
const [tomorrowDate] = dateToInputDateTime(tomorrow.toISO());
|
|
||||||
const initialFrequencyOptions = {
|
|
||||||
minute: {
|
|
||||||
interval: 1,
|
|
||||||
end: 'never',
|
|
||||||
occurrences: 1,
|
|
||||||
endDate: tomorrowDate,
|
|
||||||
endTime: time,
|
|
||||||
},
|
|
||||||
hour: {
|
|
||||||
interval: 1,
|
|
||||||
end: 'never',
|
|
||||||
occurrences: 1,
|
|
||||||
endDate: tomorrowDate,
|
|
||||||
endTime: time,
|
|
||||||
},
|
|
||||||
day: {
|
|
||||||
interval: 1,
|
|
||||||
end: 'never',
|
|
||||||
occurrences: 1,
|
|
||||||
endDate: tomorrowDate,
|
|
||||||
endTime: time,
|
|
||||||
},
|
|
||||||
week: {
|
|
||||||
interval: 1,
|
|
||||||
end: 'never',
|
|
||||||
occurrences: 1,
|
|
||||||
endDate: tomorrowDate,
|
|
||||||
endTime: time,
|
|
||||||
daysOfWeek: [],
|
|
||||||
},
|
|
||||||
month: {
|
|
||||||
interval: 1,
|
|
||||||
end: 'never',
|
|
||||||
occurrences: 1,
|
|
||||||
endDate: tomorrowDate,
|
|
||||||
endTime: time,
|
|
||||||
runOn: 'day',
|
|
||||||
runOnTheOccurrence: 1,
|
|
||||||
runOnTheDay: 'sunday',
|
|
||||||
runOnDayNumber: 1,
|
|
||||||
},
|
|
||||||
year: {
|
|
||||||
interval: 1,
|
|
||||||
end: 'never',
|
|
||||||
occurrences: 1,
|
|
||||||
endDate: tomorrowDate,
|
|
||||||
endTime: time,
|
|
||||||
runOn: 'day',
|
|
||||||
runOnTheOccurrence: 1,
|
|
||||||
runOnTheDay: 'sunday',
|
|
||||||
runOnTheMonth: 1,
|
|
||||||
runOnDayMonth: 1,
|
|
||||||
runOnDayNumber: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
description: schedule.description || '',
|
description: schedule.description || '',
|
||||||
frequency: [],
|
frequencies: [],
|
||||||
exceptionFrequency: [],
|
exceptionFrequency: [],
|
||||||
frequencyOptions: initialFrequencyOptions,
|
|
||||||
exceptionOptions: initialFrequencyOptions,
|
|
||||||
name: schedule.name || '',
|
name: schedule.name || '',
|
||||||
startDate: currentDate,
|
startDate: currentDate,
|
||||||
startTime: time,
|
startTime: time,
|
||||||
@@ -367,11 +304,9 @@ function ScheduleForm({
|
|||||||
}
|
}
|
||||||
initialValues.daysToKeep = initialDaysToKeep;
|
initialValues.daysToKeep = initialDaysToKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
let overriddenValues = {};
|
|
||||||
if (schedule.rrule) {
|
if (schedule.rrule) {
|
||||||
try {
|
try {
|
||||||
overriddenValues = parseRuleObj(schedule);
|
parseRuleObj(schedule);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof UnsupportedRRuleError) {
|
if (error instanceof UnsupportedRRuleError) {
|
||||||
return (
|
return (
|
||||||
@@ -394,89 +329,33 @@ function ScheduleForm({
|
|||||||
if (contentLoading) {
|
if (contentLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
|
const frequencies = [];
|
||||||
const validate = (values) => {
|
frequencies.push(parseRuleObj(schedule));
|
||||||
const errors = {};
|
|
||||||
|
|
||||||
values.frequency.forEach((freq) => {
|
|
||||||
const options = values.frequencyOptions[freq];
|
|
||||||
const freqErrors = {};
|
|
||||||
|
|
||||||
if (
|
|
||||||
(freq === 'month' || freq === 'year') &&
|
|
||||||
options.runOn === 'day' &&
|
|
||||||
(options.runOnDayNumber < 1 || options.runOnDayNumber > 31)
|
|
||||||
) {
|
|
||||||
freqErrors.runOn = t`Please select a day number between 1 and 31.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.end === 'after' && !options.occurrences) {
|
|
||||||
freqErrors.occurrences = t`Please enter a number of occurrences.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.end === 'onDate') {
|
|
||||||
if (
|
|
||||||
DateTime.fromFormat(
|
|
||||||
`${values.startDate} ${values.startTime}`,
|
|
||||||
'yyyy-LL-dd h:mm a'
|
|
||||||
).toMillis() >=
|
|
||||||
DateTime.fromFormat(
|
|
||||||
`${options.endDate} ${options.endTime}`,
|
|
||||||
'yyyy-LL-dd h:mm a'
|
|
||||||
).toMillis()
|
|
||||||
) {
|
|
||||||
freqErrors.endDate = t`Please select an end date/time that comes after the start date/time.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
DateTime.fromISO(options.endDate)
|
|
||||||
.diff(DateTime.fromISO(values.startDate), 'days')
|
|
||||||
.toObject().days < NUM_DAYS_PER_FREQUENCY[freq]
|
|
||||||
) {
|
|
||||||
const rule = new RRule(
|
|
||||||
buildRuleObj({
|
|
||||||
startDate: values.startDate,
|
|
||||||
startTime: values.startTime,
|
|
||||||
frequency: freq,
|
|
||||||
...options,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (rule.all().length === 0) {
|
|
||||||
errors.startDate = t`Selected date range must have at least 1 schedule occurrence.`;
|
|
||||||
freqErrors.endDate = t`Selected date range must have at least 1 schedule occurrence.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Object.keys(freqErrors).length > 0) {
|
|
||||||
if (!errors.frequencyOptions) {
|
|
||||||
errors.frequencyOptions = {};
|
|
||||||
}
|
|
||||||
errors.frequencyOptions[freq] = freqErrors;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (values.exceptionFrequency.length > 0 && !scheduleHasInstances(values)) {
|
|
||||||
errors.exceptionFrequency = t`This schedule has no occurrences due to the selected exceptions.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Config>
|
<Config>
|
||||||
{() => (
|
{() => (
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
...initialValues,
|
name: schedule.name || '',
|
||||||
...overriddenValues,
|
description: schedule.description || '',
|
||||||
frequencyOptions: {
|
frequencies: frequencies || [],
|
||||||
...initialValues.frequencyOptions,
|
freq: RRule.DAILY,
|
||||||
...overriddenValues.frequencyOptions,
|
interval: 1,
|
||||||
},
|
wkst: RRule.SU,
|
||||||
exceptionOptions: {
|
byweekday: [],
|
||||||
...initialValues.exceptionOptions,
|
byweekno: [],
|
||||||
...overriddenValues.exceptionOptions,
|
bymonth: [],
|
||||||
},
|
bymonthday: '',
|
||||||
|
byyearday: '',
|
||||||
|
bysetpos: '',
|
||||||
|
until: schedule.until || null,
|
||||||
|
endDate: currentDate,
|
||||||
|
endTime: time,
|
||||||
|
count: 1,
|
||||||
|
endingType: 'never',
|
||||||
|
timezone: schedule.timezone || now.zoneName,
|
||||||
|
startDate: currentDate,
|
||||||
|
startTime: time,
|
||||||
}}
|
}}
|
||||||
onSubmit={(values) => {
|
onSubmit={(values) => {
|
||||||
submitSchedule(
|
submitSchedule(
|
||||||
@@ -488,73 +367,90 @@ function ScheduleForm({
|
|||||||
credentials
|
credentials
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
validate={validate}
|
validate={() => {}}
|
||||||
>
|
>
|
||||||
{(formik) => (
|
{(formik) => (
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
<>
|
||||||
<FormColumnLayout>
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
<ScheduleFormFields
|
<FormColumnLayout>
|
||||||
hasDaysToKeepField={hasDaysToKeepField}
|
<ScheduleFormFields
|
||||||
zoneOptions={zoneOptions}
|
hasDaysToKeepField={hasDaysToKeepField}
|
||||||
zoneLinks={zoneLinks}
|
zoneOptions={zoneOptions}
|
||||||
/>
|
zoneLinks={zoneLinks}
|
||||||
{isWizardOpen && (
|
|
||||||
<SchedulePromptableFields
|
|
||||||
schedule={schedule}
|
|
||||||
credentials={credentials}
|
|
||||||
surveyConfig={surveyConfig}
|
|
||||||
launchConfig={launchConfig}
|
|
||||||
resource={resource}
|
|
||||||
onCloseWizard={() => {
|
|
||||||
setIsWizardOpen(false);
|
|
||||||
}}
|
|
||||||
onSave={() => {
|
|
||||||
setIsWizardOpen(false);
|
|
||||||
setIsSaveDisabled(false);
|
|
||||||
}}
|
|
||||||
resourceDefaultCredentials={resourceDefaultCredentials}
|
|
||||||
labels={originalLabels.current}
|
|
||||||
instanceGroups={originalInstanceGroups.current}
|
|
||||||
/>
|
/>
|
||||||
)}
|
{isWizardOpen && (
|
||||||
<FormSubmitError error={submitError} />
|
<SchedulePromptableFields
|
||||||
<FormFullWidthLayout>
|
schedule={schedule}
|
||||||
<ActionGroup>
|
credentials={credentials}
|
||||||
<Button
|
surveyConfig={surveyConfig}
|
||||||
ouiaId="schedule-form-save-button"
|
launchConfig={launchConfig}
|
||||||
aria-label={t`Save`}
|
resource={resource}
|
||||||
variant="primary"
|
onCloseWizard={() => {
|
||||||
type="button"
|
setIsWizardOpen(false);
|
||||||
onClick={formik.handleSubmit}
|
}}
|
||||||
isDisabled={isSaveDisabled}
|
onSave={() => {
|
||||||
>
|
setIsWizardOpen(false);
|
||||||
{t`Save`}
|
setIsSaveDisabled(false);
|
||||||
</Button>
|
}}
|
||||||
|
resourceDefaultCredentials={resourceDefaultCredentials}
|
||||||
{isTemplate && showPromptButton && (
|
labels={originalLabels.current}
|
||||||
|
instanceGroups={originalInstanceGroups.current}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<FormFullWidthLayout>
|
||||||
|
<FrequenciesList openWizard={setIsScheduleWizardOpen} />
|
||||||
|
</FormFullWidthLayout>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
|
<FormFullWidthLayout>
|
||||||
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
ouiaId="schedule-form-prompt-button"
|
ouiaId="schedule-form-save-button"
|
||||||
|
aria-label={t`Save`}
|
||||||
|
variant="primary"
|
||||||
|
type="button"
|
||||||
|
onClick={formik.handleSubmit}
|
||||||
|
isDisabled={isSaveDisabled}
|
||||||
|
>
|
||||||
|
{t`Save`}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {}}
|
||||||
|
>{t`Preview occurances`}</Button>
|
||||||
|
|
||||||
|
{isTemplate && showPromptButton && (
|
||||||
|
<Button
|
||||||
|
ouiaId="schedule-form-prompt-button"
|
||||||
|
variant="secondary"
|
||||||
|
type="button"
|
||||||
|
aria-label={t`Prompt`}
|
||||||
|
onClick={() => setIsWizardOpen(true)}
|
||||||
|
>
|
||||||
|
{t`Prompt`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
ouiaId="schedule-form-cancel-button"
|
||||||
|
aria-label={t`Cancel`}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t`Prompt`}
|
onClick={handleCancel}
|
||||||
onClick={() => setIsWizardOpen(true)}
|
|
||||||
>
|
>
|
||||||
{t`Prompt`}
|
{t`Cancel`}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</ActionGroup>
|
||||||
<Button
|
</FormFullWidthLayout>
|
||||||
ouiaId="schedule-form-cancel-button"
|
</FormColumnLayout>
|
||||||
aria-label={t`Cancel`}
|
</Form>
|
||||||
variant="secondary"
|
{isScheduleWizardOpen && (
|
||||||
type="button"
|
<ScheduleFormWizard
|
||||||
onClick={handleCancel}
|
staticFormFormkik={formik}
|
||||||
>
|
isOpen={isScheduleWizardOpen}
|
||||||
{t`Cancel`}
|
handleSave={() => {}}
|
||||||
</Button>
|
setIsOpen={setIsScheduleWizardOpen}
|
||||||
</ActionGroup>
|
/>
|
||||||
</FormFullWidthLayout>
|
)}
|
||||||
</FormColumnLayout>
|
</>
|
||||||
</Form>
|
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
)}
|
)}
|
||||||
@@ -575,24 +471,3 @@ ScheduleForm.defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default ScheduleForm;
|
export default ScheduleForm;
|
||||||
|
|
||||||
function scheduleHasInstances(values) {
|
|
||||||
let rangeToCheck = 1;
|
|
||||||
values.frequency.forEach((freq) => {
|
|
||||||
if (NUM_DAYS_PER_FREQUENCY[freq] > rangeToCheck) {
|
|
||||||
rangeToCheck = NUM_DAYS_PER_FREQUENCY[freq];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ruleSet = buildRuleSet(values, true);
|
|
||||||
const startDate = DateTime.fromISO(values.startDate);
|
|
||||||
const endDate = startDate.plus({ days: rangeToCheck });
|
|
||||||
const instances = ruleSet.between(
|
|
||||||
startDate.toJSDate(),
|
|
||||||
endDate.toJSDate(),
|
|
||||||
true,
|
|
||||||
(date, i) => i === 0
|
|
||||||
);
|
|
||||||
|
|
||||||
return instances.length > 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,41 +1,27 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { FormGroup, Title } from '@patternfly/react-core';
|
import { FormGroup } from '@patternfly/react-core';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
|
||||||
import 'styled-components/macro';
|
|
||||||
import FormField from 'components/FormField';
|
import FormField from 'components/FormField';
|
||||||
import { required } from 'util/validators';
|
import { required } from 'util/validators';
|
||||||
import { useConfig } from 'contexts/Config';
|
import { useConfig } from 'contexts/Config';
|
||||||
import Popover from '../../Popover';
|
import Popover from '../../Popover';
|
||||||
import AnsibleSelect from '../../AnsibleSelect';
|
import AnsibleSelect from '../../AnsibleSelect';
|
||||||
import FrequencySelect, { SelectOption } from './FrequencySelect';
|
|
||||||
import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext';
|
import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext';
|
||||||
import { SubFormLayout, FormColumnLayout } from '../../FormLayout';
|
|
||||||
import FrequencyDetailSubform from './FrequencyDetailSubform';
|
|
||||||
import DateTimePicker from './DateTimePicker';
|
import DateTimePicker from './DateTimePicker';
|
||||||
import sortFrequencies from './sortFrequencies';
|
|
||||||
|
|
||||||
const SelectClearOption = styled(SelectOption)`
|
|
||||||
& > input[type='checkbox'] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function ScheduleFormFields({
|
export default function ScheduleFormFields({
|
||||||
hasDaysToKeepField,
|
hasDaysToKeepField,
|
||||||
zoneOptions,
|
zoneOptions,
|
||||||
zoneLinks,
|
zoneLinks,
|
||||||
|
setTimeZone,
|
||||||
}) {
|
}) {
|
||||||
const helpText = getHelpText();
|
const helpText = getHelpText();
|
||||||
const [timezone, timezoneMeta] = useField({
|
const [timezone, timezoneMeta] = useField({
|
||||||
name: 'timezone',
|
name: 'timezone',
|
||||||
validate: required(t`Select a value for this field`),
|
validate: required(t`Select a value for this field`),
|
||||||
});
|
});
|
||||||
const [frequency, frequencyMeta, frequencyHelper] = useField({
|
|
||||||
name: 'frequency',
|
|
||||||
validate: required(t`Select a value for this field`),
|
|
||||||
});
|
|
||||||
const [timezoneMessage, setTimezoneMessage] = useState('');
|
const [timezoneMessage, setTimezoneMessage] = useState('');
|
||||||
const warnLinkedTZ = (event, selectedValue) => {
|
const warnLinkedTZ = (event, selectedValue) => {
|
||||||
if (zoneLinks[selectedValue]) {
|
if (zoneLinks[selectedValue]) {
|
||||||
@@ -46,6 +32,7 @@ export default function ScheduleFormFields({
|
|||||||
setTimezoneMessage('');
|
setTimezoneMessage('');
|
||||||
}
|
}
|
||||||
timezone.onChange(event, selectedValue);
|
timezone.onChange(event, selectedValue);
|
||||||
|
setTimeZone(zoneLinks(selectedValue));
|
||||||
};
|
};
|
||||||
let timezoneValidatedStatus = 'default';
|
let timezoneValidatedStatus = 'default';
|
||||||
if (timezoneMeta.touched && timezoneMeta.error) {
|
if (timezoneMeta.touched && timezoneMeta.error) {
|
||||||
@@ -55,16 +42,6 @@ export default function ScheduleFormFields({
|
|||||||
}
|
}
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
const [exceptionFrequency, exceptionFrequencyMeta, exceptionFrequencyHelper] =
|
|
||||||
useField({
|
|
||||||
name: 'exceptionFrequency',
|
|
||||||
validate: required(t`Select a value for this field`),
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateFrequency = (setFrequency) => (values) => {
|
|
||||||
setFrequency(values.sort(sortFrequencies));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -103,33 +80,7 @@ export default function ScheduleFormFields({
|
|||||||
onChange={warnLinkedTZ}
|
onChange={warnLinkedTZ}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
|
||||||
name="frequency"
|
|
||||||
fieldId="schedule-frequency"
|
|
||||||
helperTextInvalid={frequencyMeta.error}
|
|
||||||
validated={
|
|
||||||
!frequencyMeta.touched || !frequencyMeta.error ? 'default' : 'error'
|
|
||||||
}
|
|
||||||
label={t`Repeat frequency`}
|
|
||||||
>
|
|
||||||
<FrequencySelect
|
|
||||||
id="schedule-frequency"
|
|
||||||
onChange={updateFrequency(frequencyHelper.setValue)}
|
|
||||||
value={frequency.value}
|
|
||||||
placeholderText={
|
|
||||||
frequency.value.length ? t`Select frequency` : t`None (run once)`
|
|
||||||
}
|
|
||||||
onBlur={frequencyHelper.setTouched}
|
|
||||||
>
|
|
||||||
<SelectClearOption value="none">{t`None (run once)`}</SelectClearOption>
|
|
||||||
<SelectOption value="minute">{t`Minute`}</SelectOption>
|
|
||||||
<SelectOption value="hour">{t`Hour`}</SelectOption>
|
|
||||||
<SelectOption value="day">{t`Day`}</SelectOption>
|
|
||||||
<SelectOption value="week">{t`Week`}</SelectOption>
|
|
||||||
<SelectOption value="month">{t`Month`}</SelectOption>
|
|
||||||
<SelectOption value="year">{t`Year`}</SelectOption>
|
|
||||||
</FrequencySelect>
|
|
||||||
</FormGroup>
|
|
||||||
{hasDaysToKeepField ? (
|
{hasDaysToKeepField ? (
|
||||||
<FormField
|
<FormField
|
||||||
id="schedule-days-to-keep"
|
id="schedule-days-to-keep"
|
||||||
@@ -140,68 +91,6 @@ export default function ScheduleFormFields({
|
|||||||
isRequired
|
isRequired
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{frequency.value.length ? (
|
|
||||||
<SubFormLayout>
|
|
||||||
<Title size="md" headingLevel="h4">
|
|
||||||
{t`Frequency Details`}
|
|
||||||
</Title>
|
|
||||||
{frequency.value.map((val) => (
|
|
||||||
<FormColumnLayout key={val} stacked>
|
|
||||||
<FrequencyDetailSubform
|
|
||||||
frequency={val}
|
|
||||||
prefix={`frequencyOptions.${val}`}
|
|
||||||
/>
|
|
||||||
</FormColumnLayout>
|
|
||||||
))}
|
|
||||||
<Title
|
|
||||||
size="md"
|
|
||||||
headingLevel="h4"
|
|
||||||
css="margin-top: var(--pf-c-card--child--PaddingRight)"
|
|
||||||
>{t`Exceptions`}</Title>
|
|
||||||
<FormColumnLayout stacked>
|
|
||||||
<FormGroup
|
|
||||||
name="exceptions"
|
|
||||||
fieldId="exception-frequency"
|
|
||||||
helperTextInvalid={exceptionFrequencyMeta.error}
|
|
||||||
validated={
|
|
||||||
!exceptionFrequencyMeta.touched || !exceptionFrequencyMeta.error
|
|
||||||
? 'default'
|
|
||||||
: 'error'
|
|
||||||
}
|
|
||||||
label={t`Add exceptions`}
|
|
||||||
>
|
|
||||||
<FrequencySelect
|
|
||||||
id="exception-frequency"
|
|
||||||
onChange={updateFrequency(exceptionFrequencyHelper.setValue)}
|
|
||||||
value={exceptionFrequency.value}
|
|
||||||
placeholderText={
|
|
||||||
exceptionFrequency.value.length
|
|
||||||
? t`Select frequency`
|
|
||||||
: t`None`
|
|
||||||
}
|
|
||||||
onBlur={exceptionFrequencyHelper.setTouched}
|
|
||||||
>
|
|
||||||
<SelectClearOption value="none">{t`None`}</SelectClearOption>
|
|
||||||
<SelectOption value="minute">{t`Minute`}</SelectOption>
|
|
||||||
<SelectOption value="hour">{t`Hour`}</SelectOption>
|
|
||||||
<SelectOption value="day">{t`Day`}</SelectOption>
|
|
||||||
<SelectOption value="week">{t`Week`}</SelectOption>
|
|
||||||
<SelectOption value="month">{t`Month`}</SelectOption>
|
|
||||||
<SelectOption value="year">{t`Year`}</SelectOption>
|
|
||||||
</FrequencySelect>
|
|
||||||
</FormGroup>
|
|
||||||
</FormColumnLayout>
|
|
||||||
{exceptionFrequency.value.map((val) => (
|
|
||||||
<FormColumnLayout key={val} stacked>
|
|
||||||
<FrequencyDetailSubform
|
|
||||||
frequency={val}
|
|
||||||
prefix={`exceptionOptions.${val}`}
|
|
||||||
isException
|
|
||||||
/>
|
|
||||||
</FormColumnLayout>
|
|
||||||
))}
|
|
||||||
</SubFormLayout>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
199
awx/ui/src/components/Schedule/shared/ScheduleFormWizard.js
Normal file
199
awx/ui/src/components/Schedule/shared/ScheduleFormWizard.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Wizard,
|
||||||
|
WizardContextConsumer,
|
||||||
|
WizardFooter,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
|
import { useField, useFormikContext } from 'formik';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { formatDateString } from 'util/dates';
|
||||||
|
import FrequencySelect from './FrequencySelect';
|
||||||
|
import MonthandYearForm from './MonthandYearForm';
|
||||||
|
import OrdinalDayForm from './OrdinalDayForm';
|
||||||
|
import WeekdayForm from './WeekdayForm';
|
||||||
|
import ScheduleEndForm from './ScheduleEndForm';
|
||||||
|
import parseRuleObj from './parseRuleObj';
|
||||||
|
import { buildDtStartObj } from './buildRuleObj';
|
||||||
|
|
||||||
|
const GroupWrapper = styled(FormGroup)`
|
||||||
|
&& .pf-c-form__group-control {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
&& .pf-c-form__group-label {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function ScheduleFormWizard({ isOpen, setIsOpen }) {
|
||||||
|
const { values, resetForm, initialValues } = useFormikContext();
|
||||||
|
const [freq, freqMeta] = useField('freq');
|
||||||
|
const [{ value: frequenciesValue }] = useField('frequencies');
|
||||||
|
const [interval, , intervalHelpers] = useField('interval');
|
||||||
|
|
||||||
|
const handleSubmit = (goToStepById) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
endingType,
|
||||||
|
endTime,
|
||||||
|
endDate,
|
||||||
|
timezone,
|
||||||
|
startDate,
|
||||||
|
startTime,
|
||||||
|
frequencies,
|
||||||
|
...rest
|
||||||
|
} = values;
|
||||||
|
if (endingType === 'onDate') {
|
||||||
|
const dt = DateTime.fromFormat(
|
||||||
|
`${endDate} ${endTime}`,
|
||||||
|
'yyyy-MM-dd h:mm a',
|
||||||
|
{
|
||||||
|
zone: timezone,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
rest.until = formatDateString(dt, timezone);
|
||||||
|
|
||||||
|
delete rest.count;
|
||||||
|
}
|
||||||
|
if (endingType === 'never') delete rest.count;
|
||||||
|
|
||||||
|
const rule = new RRule(rest);
|
||||||
|
|
||||||
|
const start = buildDtStartObj({
|
||||||
|
startDate: values.startDate,
|
||||||
|
startTime: values.startTime,
|
||||||
|
timezone: values.timezone,
|
||||||
|
frequency: values.freq,
|
||||||
|
});
|
||||||
|
const newFrequency = parseRuleObj({
|
||||||
|
timezone,
|
||||||
|
frequency: freq.value,
|
||||||
|
rrule: rule.toString(),
|
||||||
|
dtstart: start,
|
||||||
|
});
|
||||||
|
if (goToStepById) {
|
||||||
|
goToStepById(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm({
|
||||||
|
values: {
|
||||||
|
...initialValues,
|
||||||
|
description: values.description,
|
||||||
|
name: values.name,
|
||||||
|
startDate: values.startDate,
|
||||||
|
startTime: values.startTime,
|
||||||
|
timezone: values.timezone,
|
||||||
|
frequencies: frequenciesValue[0].frequency.length
|
||||||
|
? [...frequenciesValue, newFrequency]
|
||||||
|
: [newFrequency],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const CustomFooter = (
|
||||||
|
<WizardFooter>
|
||||||
|
<WizardContextConsumer>
|
||||||
|
{({ activeStep, onNext, onBack, goToStepById }) => (
|
||||||
|
<>
|
||||||
|
{activeStep.id === 2 ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
handleSubmit(true, goToStepById);
|
||||||
|
}}
|
||||||
|
>{t`Finish and create new`}</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handleSubmit(false);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>{t`Finish and close`}</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button variant="primary" onClick={onNext}>{t`Next`}</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button variant="secondary" onClick={onBack}>{t`Back`}</Button>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
resetForm({
|
||||||
|
values: {
|
||||||
|
...initialValues,
|
||||||
|
description: values.description,
|
||||||
|
name: values.name,
|
||||||
|
startDate: values.startDate,
|
||||||
|
startTime: values.startTime,
|
||||||
|
timezone: values.timezone,
|
||||||
|
frequencies: values.frequencies,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>{t`Cancel`}</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</WizardContextConsumer>
|
||||||
|
</WizardFooter>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wizard
|
||||||
|
onClose={() => setIsOpen(false)}
|
||||||
|
isOpen={isOpen}
|
||||||
|
footer={CustomFooter}
|
||||||
|
steps={[
|
||||||
|
{
|
||||||
|
key: 'frequency',
|
||||||
|
name: 'Frequency',
|
||||||
|
id: 1,
|
||||||
|
component: (
|
||||||
|
<>
|
||||||
|
<Title size="md" headingLevel="h4">{t`Repeat frequency`}</Title>
|
||||||
|
<GroupWrapper
|
||||||
|
name="freq"
|
||||||
|
fieldId="schedule-frequency"
|
||||||
|
isRequired
|
||||||
|
helperTextInvalid={freqMeta.error}
|
||||||
|
validated={
|
||||||
|
!freqMeta.touched || !freqMeta.error ? 'default' : 'error'
|
||||||
|
}
|
||||||
|
label={<b>{t`Frequency`}</b>}
|
||||||
|
>
|
||||||
|
<FrequencySelect />
|
||||||
|
</GroupWrapper>
|
||||||
|
<GroupWrapper isRequired label={<b>{t`Interval`}</b>}>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
value={interval.value}
|
||||||
|
placeholder={t`Choose an interval for the schedule`}
|
||||||
|
aria-label={t`Choose an interval for the schedule`}
|
||||||
|
onChange={(v) => intervalHelpers.setValue(v)}
|
||||||
|
/>
|
||||||
|
</GroupWrapper>
|
||||||
|
<WeekdayForm />
|
||||||
|
<MonthandYearForm />
|
||||||
|
<OrdinalDayForm />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'End',
|
||||||
|
key: 'end',
|
||||||
|
id: 2,
|
||||||
|
component: <ScheduleEndForm />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default ScheduleFormWizard;
|
||||||
164
awx/ui/src/components/Schedule/shared/WeekdayForm.js
Normal file
164
awx/ui/src/components/Schedule/shared/WeekdayForm.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Checkbox as _Checkbox,
|
||||||
|
FormGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { weekdayOptions } from './scheduleFormHelpers';
|
||||||
|
|
||||||
|
const Checkbox = styled(_Checkbox)`
|
||||||
|
:not(:last-of-type) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const GroupWrapper = styled(FormGroup)`
|
||||||
|
&& .pf-c-form__group-control {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
&& .pf-c-form__group-label {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function WeekdayForm({ id }) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [daysOfWeek, daysOfWeekMeta, daysOfWeekHelpers] = useField('byweekday');
|
||||||
|
const [weekStartDay, , weekStartDayHelpers] = useField('wkst');
|
||||||
|
const updateDaysOfWeek = (day, checked) => {
|
||||||
|
const newDaysOfWeek = daysOfWeek.value ? [...daysOfWeek.value] : [];
|
||||||
|
daysOfWeekHelpers.setTouched(true);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
newDaysOfWeek.push(day);
|
||||||
|
daysOfWeekHelpers.setValue(newDaysOfWeek);
|
||||||
|
} else {
|
||||||
|
daysOfWeekHelpers.setValue(
|
||||||
|
newDaysOfWeek.filter((selectedDay) => selectedDay !== day)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GroupWrapper
|
||||||
|
name="wkst"
|
||||||
|
label={<b>{t`Select the first day of the week`}</b>}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
onSelect={(e, value) => {
|
||||||
|
weekStartDayHelpers.setValue(value);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
onBlur={() => setIsOpen(false)}
|
||||||
|
selections={weekStartDay.value}
|
||||||
|
onToggle={(isopen) => setIsOpen(isopen)}
|
||||||
|
isOpen={isOpen}
|
||||||
|
id={`schedule-run-on-the-day-${id}`}
|
||||||
|
onChange={(e, v) => {
|
||||||
|
weekStartDayHelpers.setValue(v);
|
||||||
|
}}
|
||||||
|
{...weekStartDay}
|
||||||
|
>
|
||||||
|
{weekdayOptions.map(({ key, value, label }) => (
|
||||||
|
<SelectOption key={key} value={value}>
|
||||||
|
{label}
|
||||||
|
</SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</GroupWrapper>
|
||||||
|
<GroupWrapper
|
||||||
|
name="byweekday"
|
||||||
|
fieldId={`schedule-days-of-week-${id}`}
|
||||||
|
helperTextInvalid={daysOfWeekMeta.error}
|
||||||
|
validated={
|
||||||
|
!daysOfWeekMeta.touched || !daysOfWeekMeta.error ? 'default' : 'error'
|
||||||
|
}
|
||||||
|
label={<b>{t`On selected day(s) of the week`}</b>}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Sun`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.SU)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.SU, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Sunday`}
|
||||||
|
id={`schedule-days-of-week-sun-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-sun-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Mon`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.MO)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.MO, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Monday`}
|
||||||
|
id={`schedule-days-of-week-mon-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-mon-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Tue`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.TU)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.TU, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Tuesday`}
|
||||||
|
id={`schedule-days-of-week-tue-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-tue-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Wed`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.WE)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.WE, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Wednesday`}
|
||||||
|
id={`schedule-days-of-week-wed-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-wed-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Thu`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.TH)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.TH, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Thursday`}
|
||||||
|
id={`schedule-days-of-week-thu-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-thu-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Fri`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.FR)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.FR, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Friday`}
|
||||||
|
id={`schedule-days-of-week-fri-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-fri-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={t`Sat`}
|
||||||
|
isChecked={daysOfWeek.value?.includes(RRule.SA)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
updateDaysOfWeek(RRule.SA, checked);
|
||||||
|
}}
|
||||||
|
aria-label={t`Saturday`}
|
||||||
|
id={`schedule-days-of-week-sat-${id}`}
|
||||||
|
ouiaId={`schedule-days-of-week-sat-${id}`}
|
||||||
|
name="daysOfWeek"
|
||||||
|
/>
|
||||||
|
</GroupWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default WeekdayForm;
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
|
||||||
import { RRule } from 'rrule';
|
import { RRule } from 'rrule';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { getRRuleDayConstants } from 'util/dates';
|
|
||||||
|
|
||||||
window.RRule = RRule;
|
window.RRule = RRule;
|
||||||
window.DateTime = DateTime;
|
window.DateTime = DateTime;
|
||||||
@@ -22,7 +20,7 @@ export function buildDtStartObj(values) {
|
|||||||
startHour
|
startHour
|
||||||
)}${pad(startMinute)}00`;
|
)}${pad(startMinute)}00`;
|
||||||
const rruleString = values.timezone
|
const rruleString = values.timezone
|
||||||
? `DTSTART;TZID=${values.timezone}:${dateString}`
|
? `DTSTART;TZID=${values.timezone}${dateString}`
|
||||||
: `DTSTART:${dateString}Z`;
|
: `DTSTART:${dateString}Z`;
|
||||||
const rule = RRule.fromString(rruleString);
|
const rule = RRule.fromString(rruleString);
|
||||||
|
|
||||||
@@ -38,7 +36,8 @@ function pad(num) {
|
|||||||
|
|
||||||
export default function buildRuleObj(values, includeStart) {
|
export default function buildRuleObj(values, includeStart) {
|
||||||
const ruleObj = {
|
const ruleObj = {
|
||||||
interval: values.interval,
|
interval: values.interval || 1,
|
||||||
|
freq: values.freq,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (includeStart) {
|
if (includeStart) {
|
||||||
@@ -49,68 +48,6 @@ export default function buildRuleObj(values, includeStart) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
ruleObj.bymonth = parseInt(values.runOnTheMonth, 10);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(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': {
|
|
||||||
ruleObj.until = buildDateTime(
|
|
||||||
values.endDate,
|
|
||||||
values.endTime,
|
|
||||||
values.timezone
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(t`End did not match an expected value (${values.end})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ruleObj;
|
return ruleObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { RRule, RRuleSet } from 'rrule';
|
import { RRule, RRuleSet } from 'rrule';
|
||||||
import buildRuleObj, { buildDtStartObj } from './buildRuleObj';
|
import buildRuleObj, { buildDtStartObj } from './buildRuleObj';
|
||||||
|
import { FREQUENCIESCONSTANTS } from './scheduleFormHelpers';
|
||||||
|
|
||||||
window.RRuleSet = RRuleSet;
|
window.RRuleSet = RRuleSet;
|
||||||
|
|
||||||
@@ -12,42 +13,31 @@ export default function buildRuleSet(values, useUTCStart) {
|
|||||||
startDate: values.startDate,
|
startDate: values.startDate,
|
||||||
startTime: values.startTime,
|
startTime: values.startTime,
|
||||||
timezone: values.timezone,
|
timezone: values.timezone,
|
||||||
|
frequency: values.freq,
|
||||||
});
|
});
|
||||||
set.rrule(startRule);
|
set.rrule(startRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.frequency.length === 0) {
|
values.frequencies.forEach(({ frequency, rrule }) => {
|
||||||
const rule = buildRuleObj(
|
if (!frequencies.includes(frequency)) {
|
||||||
{
|
|
||||||
startDate: values.startDate,
|
|
||||||
startTime: values.startTime,
|
|
||||||
timezone: values.timezone,
|
|
||||||
frequency: 'none',
|
|
||||||
interval: 1,
|
|
||||||
},
|
|
||||||
useUTCStart
|
|
||||||
);
|
|
||||||
set.rrule(new RRule(rule));
|
|
||||||
}
|
|
||||||
|
|
||||||
frequencies.forEach((frequency) => {
|
|
||||||
if (!values.frequency.includes(frequency)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rule = buildRuleObj(
|
const rule = buildRuleObj(
|
||||||
{
|
{
|
||||||
startDate: values.startDate,
|
startDate: values.startDate,
|
||||||
startTime: values.startTime,
|
startTime: values.startTime,
|
||||||
timezone: values.timezone,
|
timezone: values.timezone,
|
||||||
frequency,
|
freq: FREQUENCIESCONSTANTS[frequency],
|
||||||
...values.frequencyOptions[frequency],
|
rrule,
|
||||||
},
|
},
|
||||||
useUTCStart
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
set.rrule(new RRule(rule));
|
set.rrule(new RRule(rule));
|
||||||
});
|
});
|
||||||
|
|
||||||
frequencies.forEach((frequency) => {
|
values.exceptions?.forEach(({ frequency, rrule }) => {
|
||||||
if (!values.exceptionFrequency?.includes(frequency)) {
|
if (!values.exceptionFrequency?.includes(frequency)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -56,8 +46,8 @@ export default function buildRuleSet(values, useUTCStart) {
|
|||||||
startDate: values.startDate,
|
startDate: values.startDate,
|
||||||
startTime: values.startTime,
|
startTime: values.startTime,
|
||||||
timezone: values.timezone,
|
timezone: values.timezone,
|
||||||
frequency,
|
freq: FREQUENCIESCONSTANTS[frequency],
|
||||||
...values.exceptionOptions[frequency],
|
rrule,
|
||||||
},
|
},
|
||||||
useUTCStart
|
useUTCStart
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ export class UnsupportedRRuleError extends Error {
|
|||||||
|
|
||||||
export default function parseRuleObj(schedule) {
|
export default function parseRuleObj(schedule) {
|
||||||
let values = {
|
let values = {
|
||||||
frequency: [],
|
frequency: '',
|
||||||
frequencyOptions: {},
|
rrules: '',
|
||||||
exceptionFrequency: [],
|
|
||||||
exceptionOptions: {},
|
|
||||||
timezone: schedule.timezone,
|
timezone: schedule.timezone,
|
||||||
};
|
};
|
||||||
|
if (Object.values(schedule).length === 0) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
const ruleset = rrulestr(schedule.rrule.replace(' ', '\n'), {
|
const ruleset = rrulestr(schedule.rrule.replace(' ', '\n'), {
|
||||||
forceset: true,
|
forceset: true,
|
||||||
});
|
});
|
||||||
@@ -40,25 +42,9 @@ export default function parseRuleObj(schedule) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSingleOccurrence(values)) {
|
|
||||||
values.frequency = [];
|
|
||||||
values.frequencyOptions = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSingleOccurrence(values) {
|
|
||||||
if (values.frequency.length > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (values.frequency[0] !== 'minute') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const options = values.frequencyOptions.minute;
|
|
||||||
return options.end === 'after' && options.occurrences === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseDtstart(schedule, values) {
|
function parseDtstart(schedule, values) {
|
||||||
// TODO: should this rely on DTSTART in rruleset rather than schedule.dtstart?
|
// TODO: should this rely on DTSTART in rruleset rather than schedule.dtstart?
|
||||||
const [startDate, startTime] = dateToInputDateTime(
|
const [startDate, startTime] = dateToInputDateTime(
|
||||||
@@ -81,27 +67,12 @@ const frequencyTypes = {
|
|||||||
[RRule.YEARLY]: 'year',
|
[RRule.YEARLY]: 'year',
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseRrule(rruleString, schedule, values) {
|
function parseRrule(rruleString, schedule) {
|
||||||
const { frequency, options } = parseRule(
|
const { frequency } = parseRule(rruleString, schedule);
|
||||||
rruleString,
|
|
||||||
schedule,
|
|
||||||
values.exceptionFrequency
|
|
||||||
);
|
|
||||||
|
|
||||||
if (values.frequencyOptions[frequency]) {
|
const freq = { frequency, rrule: rruleString };
|
||||||
throw new UnsupportedRRuleError(
|
|
||||||
'Duplicate exception frequency types not supported'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return freq;
|
||||||
...values,
|
|
||||||
frequency: [...values.frequency, frequency].sort(sortFrequencies),
|
|
||||||
frequencyOptions: {
|
|
||||||
...values.frequencyOptions,
|
|
||||||
[frequency]: options,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseExRule(exruleString, schedule, values) {
|
function parseExRule(exruleString, schedule, values) {
|
||||||
@@ -129,20 +100,10 @@ function parseExRule(exruleString, schedule, values) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRule(ruleString, schedule, frequencies) {
|
function parseRule(ruleString, schedule) {
|
||||||
const {
|
const {
|
||||||
origOptions: {
|
origOptions: { count, freq, interval, until, ...rest },
|
||||||
bymonth,
|
|
||||||
bymonthday,
|
|
||||||
bysetpos,
|
|
||||||
byweekday,
|
|
||||||
count,
|
|
||||||
freq,
|
|
||||||
interval,
|
|
||||||
until,
|
|
||||||
},
|
|
||||||
} = RRule.fromString(ruleString);
|
} = RRule.fromString(ruleString);
|
||||||
|
|
||||||
const now = DateTime.now();
|
const now = DateTime.now();
|
||||||
const closestQuarterHour = DateTime.fromMillis(
|
const closestQuarterHour = DateTime.fromMillis(
|
||||||
Math.ceil(now.ts / 900000) * 900000
|
Math.ceil(now.ts / 900000) * 900000
|
||||||
@@ -156,17 +117,17 @@ function parseRule(ruleString, schedule, frequencies) {
|
|||||||
endTime: time,
|
endTime: time,
|
||||||
occurrences: 1,
|
occurrences: 1,
|
||||||
interval: 1,
|
interval: 1,
|
||||||
end: 'never',
|
endingType: 'never',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (until) {
|
if (until?.length) {
|
||||||
options.end = 'onDate';
|
options.endingType = 'onDate';
|
||||||
const end = DateTime.fromISO(until.toISOString());
|
const end = DateTime.fromISO(until.toISOString());
|
||||||
const [endDate, endTime] = dateToInputDateTime(end, schedule.timezone);
|
const [endDate, endTime] = dateToInputDateTime(end, schedule.timezone);
|
||||||
options.endDate = endDate;
|
options.endDate = endDate;
|
||||||
options.endTime = endTime;
|
options.endTime = endTime;
|
||||||
} else if (count) {
|
} else if (count) {
|
||||||
options.end = 'after';
|
options.endingType = 'after';
|
||||||
options.occurrences = count;
|
options.occurrences = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,101 +139,10 @@ function parseRule(ruleString, schedule, frequencies) {
|
|||||||
throw new Error(`Unexpected rrule frequency: ${freq}`);
|
throw new Error(`Unexpected rrule frequency: ${freq}`);
|
||||||
}
|
}
|
||||||
const frequency = frequencyTypes[freq];
|
const frequency = frequencyTypes[freq];
|
||||||
if (frequencies.includes(frequency)) {
|
|
||||||
throw new Error(`Duplicate frequency types not supported (${frequency})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freq === RRule.WEEKLY && byweekday) {
|
|
||||||
options.daysOfWeek = byweekday;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freq === RRule.MONTHLY) {
|
|
||||||
options.runOn = 'day';
|
|
||||||
options.runOnTheOccurrence = 1;
|
|
||||||
options.runOnTheDay = 'sunday';
|
|
||||||
options.runOnDayNumber = 1;
|
|
||||||
|
|
||||||
if (bymonthday) {
|
|
||||||
options.runOnDayNumber = bymonthday;
|
|
||||||
}
|
|
||||||
if (bysetpos) {
|
|
||||||
options.runOn = 'the';
|
|
||||||
options.runOnTheOccurrence = bysetpos;
|
|
||||||
options.runOnTheDay = generateRunOnTheDay(byweekday);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freq === RRule.YEARLY) {
|
|
||||||
options.runOn = 'day';
|
|
||||||
options.runOnTheOccurrence = 1;
|
|
||||||
options.runOnTheDay = 'sunday';
|
|
||||||
options.runOnTheMonth = 1;
|
|
||||||
options.runOnDayMonth = 1;
|
|
||||||
options.runOnDayNumber = 1;
|
|
||||||
|
|
||||||
if (bymonthday) {
|
|
||||||
options.runOnDayNumber = bymonthday;
|
|
||||||
options.runOnDayMonth = bymonth;
|
|
||||||
}
|
|
||||||
if (bysetpos) {
|
|
||||||
options.runOn = 'the';
|
|
||||||
options.runOnTheOccurrence = bysetpos;
|
|
||||||
options.runOnTheDay = generateRunOnTheDay(byweekday);
|
|
||||||
options.runOnTheMonth = bymonth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
frequency,
|
frequency,
|
||||||
options,
|
...options,
|
||||||
|
...rest,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function 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;
|
|
||||||
}
|
|
||||||
|
|||||||
232
awx/ui/src/components/Schedule/shared/scheduleFormHelpers.js
Normal file
232
awx/ui/src/components/Schedule/shared/scheduleFormHelpers.js
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { RRule } from 'rrule';
|
||||||
|
import buildRuleObj from './buildRuleObj';
|
||||||
|
import buildRuleSet from './buildRuleSet';
|
||||||
|
|
||||||
|
// const NUM_DAYS_PER_FREQUENCY = {
|
||||||
|
// week: 7,
|
||||||
|
// month: 31,
|
||||||
|
// year: 365,
|
||||||
|
// };
|
||||||
|
// const validateSchedule = () =>
|
||||||
|
// const errors = {};
|
||||||
|
|
||||||
|
// values.frequencies.forEach((freq) => {
|
||||||
|
// const options = values.frequencyOptions[freq];
|
||||||
|
// const freqErrors = {};
|
||||||
|
|
||||||
|
// if (
|
||||||
|
// (freq === 'month' || freq === 'year') &&
|
||||||
|
// options.runOn === 'day' &&
|
||||||
|
// (options.runOnDayNumber < 1 || options.runOnDayNumber > 31)
|
||||||
|
// ) {
|
||||||
|
// freqErrors.runOn = t`Please select a day number between 1 and 31.`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (options.end === 'after' && !options.occurrences) {
|
||||||
|
// freqErrors.occurrences = t`Please enter a number of occurrences.`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (options.end === 'onDate') {
|
||||||
|
// if (
|
||||||
|
// DateTime.fromFormat(
|
||||||
|
// `${values.startDate} ${values.startTime}`,
|
||||||
|
// 'yyyy-LL-dd h:mm a'
|
||||||
|
// ).toMillis() >=
|
||||||
|
// DateTime.fromFormat(
|
||||||
|
// `${options.endDate} ${options.endTime}`,
|
||||||
|
// 'yyyy-LL-dd h:mm a'
|
||||||
|
// ).toMillis()
|
||||||
|
// ) {
|
||||||
|
// freqErrors.endDate = t`Please select an end date/time that comes after the start date/time.`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (
|
||||||
|
// DateTime.fromISO(options.endDate)
|
||||||
|
// .diff(DateTime.fromISO(values.startDate), 'days')
|
||||||
|
// .toObject().days < NUM_DAYS_PER_FREQUENCY[freq]
|
||||||
|
// ) {
|
||||||
|
// const rule = new RRule(
|
||||||
|
// buildRuleObj({
|
||||||
|
// startDate: values.startDate,
|
||||||
|
// startTime: values.startTime,
|
||||||
|
// frequencies: freq,
|
||||||
|
// ...options,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// if (rule.all().length === 0) {
|
||||||
|
// errors.startDate = t`Selected date range must have at least 1 schedule occurrence.`;
|
||||||
|
// freqErrors.endDate = t`Selected date range must have at least 1 schedule occurrence.`;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (Object.keys(freqErrors).length > 0) {
|
||||||
|
// if (!errors.frequencyOptions) {
|
||||||
|
// errors.frequencyOptions = {};
|
||||||
|
// }
|
||||||
|
// errors.frequencyOptions[freq] = freqErrors;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (values.exceptionFrequency.length > 0 && !scheduleHasInstances(values)) {
|
||||||
|
// errors.exceptionFrequency = t`This schedule has no occurrences due to the
|
||||||
|
// selected exceptions.`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ({});
|
||||||
|
// function scheduleHasInstances(values) {
|
||||||
|
// let rangeToCheck = 1;
|
||||||
|
// values.frequencies.forEach((freq) => {
|
||||||
|
// if (NUM_DAYS_PER_FREQUENCY[freq] > rangeToCheck) {
|
||||||
|
// rangeToCheck = NUM_DAYS_PER_FREQUENCY[freq];
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const ruleSet = buildRuleSet(values, true);
|
||||||
|
// const startDate = DateTime.fromISO(values.startDate);
|
||||||
|
// const endDate = startDate.plus({ days: rangeToCheck });
|
||||||
|
// const instances = ruleSet.between(
|
||||||
|
// startDate.toJSDate(),
|
||||||
|
// endDate.toJSDate(),
|
||||||
|
// true,
|
||||||
|
// (date, i) => i === 0
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return instances.length > 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const bysetposOptions = [
|
||||||
|
{ value: '', key: 'none', label: 'None' },
|
||||||
|
{ value: 1, key: 'first', label: t`First` },
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
key: 'second',
|
||||||
|
label: t`Second`,
|
||||||
|
},
|
||||||
|
{ value: 3, key: 'third', label: t`Third` },
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
key: 'fourth',
|
||||||
|
label: t`Fourth`,
|
||||||
|
},
|
||||||
|
{ value: 5, key: 'fifth', label: t`Fifth` },
|
||||||
|
{ value: -1, key: 'last', label: t`Last` },
|
||||||
|
];
|
||||||
|
|
||||||
|
const monthOptions = [
|
||||||
|
{
|
||||||
|
key: 'january',
|
||||||
|
value: 1,
|
||||||
|
label: t`January`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'february',
|
||||||
|
value: 2,
|
||||||
|
label: t`February`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'march',
|
||||||
|
value: 3,
|
||||||
|
label: t`March`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'april',
|
||||||
|
value: 4,
|
||||||
|
label: t`April`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'may',
|
||||||
|
value: 5,
|
||||||
|
label: t`May`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'june',
|
||||||
|
value: 6,
|
||||||
|
label: t`June`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'july',
|
||||||
|
value: 7,
|
||||||
|
label: t`July`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'august',
|
||||||
|
value: 8,
|
||||||
|
label: t`August`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'september',
|
||||||
|
value: 9,
|
||||||
|
label: t`September`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'october',
|
||||||
|
value: 10,
|
||||||
|
label: t`October`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'november',
|
||||||
|
value: 11,
|
||||||
|
label: t`November`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'december',
|
||||||
|
value: 12,
|
||||||
|
label: t`December`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const weekdayOptions = [
|
||||||
|
{
|
||||||
|
value: RRule.SU,
|
||||||
|
key: 'sunday',
|
||||||
|
label: t`Sunday`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RRule.MO,
|
||||||
|
key: 'monday',
|
||||||
|
label: t`Monday`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RRule.TU,
|
||||||
|
key: 'tuesday',
|
||||||
|
label: t`Tuesday`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RRule.WE,
|
||||||
|
key: 'wednesday',
|
||||||
|
label: t`Wednesday`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RRule.TH,
|
||||||
|
key: 'thursday',
|
||||||
|
label: t`Thursday`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RRule.FR,
|
||||||
|
key: 'friday',
|
||||||
|
label: t`Friday`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: RRule.SA,
|
||||||
|
key: 'saturday',
|
||||||
|
label: t`Saturday`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const FREQUENCIESCONSTANTS = {
|
||||||
|
minute: RRule.MINUTELY,
|
||||||
|
hour: RRule.HOURLY,
|
||||||
|
day: RRule.DAILY,
|
||||||
|
week: RRule.WEEKLY,
|
||||||
|
month: RRule.MONTHLY,
|
||||||
|
year: RRule.YEARLY,
|
||||||
|
};
|
||||||
|
export {
|
||||||
|
monthOptions,
|
||||||
|
weekdayOptions,
|
||||||
|
bysetposOptions,
|
||||||
|
// validateSchedule,
|
||||||
|
FREQUENCIESCONSTANTS,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user