foreman_remote_execution 7.2.1 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby_ci.yml +2 -2
- data/app/controllers/ui_job_wizard_controller.rb +15 -0
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/models/job_invocation.rb +2 -4
- data/app/models/job_invocation_composer.rb +5 -2
- data/app/views/templates/script/package_action.erb +8 -3
- data/config/routes.rb +3 -1
- data/lib/foreman_remote_execution/engine.rb +5 -5
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
- data/test/helpers/remote_execution_helper_test.rb +4 -0
- data/test/unit/job_invocation_report_template_test.rb +1 -1
- data/test/unit/job_invocation_test.rb +1 -2
- data/webpack/JobWizard/JobWizard.js +154 -20
- data/webpack/JobWizard/JobWizard.scss +43 -1
- data/webpack/JobWizard/JobWizardConstants.js +11 -1
- data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
- data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
- data/webpack/JobWizard/__tests__/fixtures.js +73 -0
- data/webpack/JobWizard/__tests__/integration.test.js +17 -3
- data/webpack/JobWizard/autofill.js +8 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
- data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
- data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
- data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
- data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
- data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
- data/webpack/JobWizard/steps/form/Formatter.js +7 -8
- data/webpack/JobWizard/submit.js +8 -3
- data/webpack/Routes/routes.js +8 -2
- data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
- data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
- data/webpack/react_app/extend/Fills.js +4 -4
- metadata +8 -6
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
- data/webpack/JobWizard/steps/Schedule/index.js +0 -178
@@ -5,50 +5,89 @@ import {
|
|
5
5
|
Select,
|
6
6
|
SelectOption,
|
7
7
|
SelectVariant,
|
8
|
+
Alert,
|
9
|
+
AlertActionCloseButton,
|
10
|
+
ValidatedOptions,
|
8
11
|
} from '@patternfly/react-core';
|
9
|
-
import { range } from 'lodash';
|
10
12
|
import { translate as __ } from 'foremanReact/common/I18n';
|
13
|
+
import { helpLabel } from '../form/FormHelpers';
|
14
|
+
|
15
|
+
export const RepeatHour = ({ repeatData, setRepeatData }) => {
|
16
|
+
const isValidMinute = newMinute =>
|
17
|
+
Number.isInteger(parseInt(newMinute, 10)) &&
|
18
|
+
newMinute >= 0 &&
|
19
|
+
newMinute < 60;
|
11
20
|
|
12
|
-
export const RepeatHour = ({ repeatData, setRepeatData, setValid }) => {
|
13
21
|
const { minute } = repeatData;
|
14
22
|
useEffect(() => {
|
15
|
-
if (minute) {
|
16
|
-
|
17
|
-
} else {
|
18
|
-
setValid(false);
|
23
|
+
if (!isValidMinute(minute)) {
|
24
|
+
setRepeatData({ minute: 0 });
|
19
25
|
}
|
20
|
-
|
21
|
-
}, [setValid, minute]);
|
26
|
+
}, [minute, setRepeatData]);
|
22
27
|
const [minuteOpen, setMinuteOpen] = useState(false);
|
28
|
+
const [options, setOptions] = useState([0, 15, 30, 45]);
|
29
|
+
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
23
30
|
return (
|
24
|
-
<FormGroup
|
31
|
+
<FormGroup
|
32
|
+
label={__('At minute')}
|
33
|
+
labelIcon={helpLabel(<div>{__('range: 0-59')}</div>)}
|
34
|
+
isRequired
|
35
|
+
>
|
25
36
|
<Select
|
26
37
|
id="repeat-on-hourly"
|
27
38
|
variant={SelectVariant.typeahead}
|
28
39
|
typeAheadAriaLabel="repeat-at-minute-typeahead"
|
29
40
|
onSelect={(event, selection) => {
|
30
|
-
setRepeatData({ minute: selection });
|
41
|
+
setRepeatData({ minute: parseInt(selection, 10) });
|
31
42
|
setMinuteOpen(false);
|
32
43
|
}}
|
33
|
-
selections={minute || ''}
|
44
|
+
selections={`${minute}` || ''}
|
34
45
|
onToggle={toggle => {
|
35
46
|
setMinuteOpen(toggle);
|
36
47
|
}}
|
37
48
|
isOpen={minuteOpen}
|
38
|
-
width={
|
49
|
+
width={125}
|
39
50
|
menuAppendTo={() => document.querySelector('.pf-c-form.schedule-tab')}
|
40
51
|
toggleAriaLabel="select minute toggle"
|
41
|
-
validated={
|
52
|
+
validated={
|
53
|
+
isValidMinute(minute)
|
54
|
+
? ValidatedOptions.noval
|
55
|
+
: ValidatedOptions.error
|
56
|
+
}
|
57
|
+
onCreateOption={newValue => {
|
58
|
+
if (isValidMinute(newValue)) {
|
59
|
+
setOptions(prev => [...prev, parseInt(newValue, 10)].sort());
|
60
|
+
setRepeatData({ minute: parseInt(newValue, 10) });
|
61
|
+
setIsAlertOpen(false);
|
62
|
+
} else {
|
63
|
+
setIsAlertOpen(true);
|
64
|
+
}
|
65
|
+
}}
|
66
|
+
isCreatable
|
67
|
+
createText={__('Create')}
|
42
68
|
>
|
43
|
-
{
|
44
|
-
<SelectOption
|
69
|
+
{options.map(minuteNumber => (
|
70
|
+
<SelectOption
|
71
|
+
key={minuteNumber}
|
72
|
+
value={`${minuteNumber}`}
|
73
|
+
onClick={() => setIsAlertOpen(false)}
|
74
|
+
/>
|
45
75
|
))}
|
46
76
|
</Select>
|
77
|
+
{isAlertOpen && (
|
78
|
+
<Alert
|
79
|
+
variant="danger"
|
80
|
+
isInline
|
81
|
+
title={__('Minute can only be a number between 0-59')}
|
82
|
+
actionClose={
|
83
|
+
<AlertActionCloseButton onClose={() => setIsAlertOpen(false)} />
|
84
|
+
}
|
85
|
+
/>
|
86
|
+
)}
|
47
87
|
</FormGroup>
|
48
88
|
);
|
49
89
|
};
|
50
90
|
RepeatHour.propTypes = {
|
51
91
|
repeatData: PropTypes.object.isRequired,
|
52
92
|
setRepeatData: PropTypes.func.isRequired,
|
53
|
-
setValid: PropTypes.func.isRequired,
|
54
93
|
};
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import React
|
1
|
+
import React from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
|
-
import {
|
3
|
+
import { FormGroup } from '@patternfly/react-core';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { SelectField } from '../form/SelectField';
|
6
6
|
import { repeatTypes } from '../../JobWizardConstants';
|
@@ -13,18 +13,10 @@ import { RepeatWeek } from './RepeatWeek';
|
|
13
13
|
export const RepeatOn = ({
|
14
14
|
repeatType,
|
15
15
|
setRepeatType,
|
16
|
-
repeatAmount,
|
17
|
-
setRepeatAmount,
|
18
16
|
repeatData,
|
19
17
|
setRepeatData,
|
20
18
|
setValid,
|
21
19
|
}) => {
|
22
|
-
const [repeatValidated, setRepeatValidated] = useState('default');
|
23
|
-
const handleRepeatInputChange = newValue => {
|
24
|
-
setRepeatValidated(!newValue || newValue >= 1 ? 'default' : 'error');
|
25
|
-
setRepeatAmount(newValue);
|
26
|
-
};
|
27
|
-
|
28
20
|
const getRepeatComponent = () => {
|
29
21
|
switch (repeatType) {
|
30
22
|
case repeatTypes.cronline:
|
@@ -61,64 +53,35 @@ export const RepeatOn = ({
|
|
61
53
|
);
|
62
54
|
case repeatTypes.hourly:
|
63
55
|
return (
|
64
|
-
<RepeatHour
|
65
|
-
repeatData={repeatData}
|
66
|
-
setRepeatData={setRepeatData}
|
67
|
-
setValid={setValid}
|
68
|
-
/>
|
56
|
+
<RepeatHour repeatData={repeatData} setRepeatData={setRepeatData} />
|
69
57
|
);
|
70
|
-
case repeatTypes.noRepeat:
|
71
58
|
default:
|
72
59
|
return null;
|
73
60
|
}
|
74
61
|
};
|
75
62
|
return (
|
76
|
-
|
77
|
-
<
|
78
|
-
<
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
<GridItem span={1} />
|
93
|
-
<GridItem span={5}>
|
94
|
-
<FormGroup
|
95
|
-
helperTextInvalid={__(
|
96
|
-
'Repeat amount can only be a positive number'
|
97
|
-
)}
|
98
|
-
validated={repeatValidated}
|
99
|
-
>
|
100
|
-
<TextInput
|
101
|
-
isDisabled={repeatType === repeatTypes.noRepeat}
|
102
|
-
id="repeat-amount"
|
103
|
-
value={repeatAmount}
|
104
|
-
type="text"
|
105
|
-
onChange={newValue => handleRepeatInputChange(newValue)}
|
106
|
-
placeholder={__('Repeat N times')}
|
107
|
-
/>
|
108
|
-
</FormGroup>
|
109
|
-
</GridItem>
|
110
|
-
{getRepeatComponent()}
|
111
|
-
</Grid>
|
112
|
-
</FormGroup>
|
63
|
+
<>
|
64
|
+
<FormGroup label={__('Repeats')}>
|
65
|
+
<SelectField
|
66
|
+
isRequired
|
67
|
+
fieldId="repeat-select"
|
68
|
+
options={Object.values(repeatTypes).filter(
|
69
|
+
type => type !== repeatTypes.noRepeat
|
70
|
+
)}
|
71
|
+
setValue={newValue => {
|
72
|
+
setRepeatType(newValue);
|
73
|
+
}}
|
74
|
+
value={repeatType}
|
75
|
+
/>
|
76
|
+
</FormGroup>
|
77
|
+
{getRepeatComponent()}
|
78
|
+
</>
|
113
79
|
);
|
114
80
|
};
|
115
81
|
|
116
82
|
RepeatOn.propTypes = {
|
117
83
|
repeatType: PropTypes.oneOf(Object.values(repeatTypes)).isRequired,
|
118
84
|
setRepeatType: PropTypes.func.isRequired,
|
119
|
-
repeatAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
120
|
-
.isRequired,
|
121
|
-
setRepeatAmount: PropTypes.func.isRequired,
|
122
85
|
repeatData: PropTypes.object.isRequired,
|
123
86
|
setRepeatData: PropTypes.func.isRequired,
|
124
87
|
setValid: PropTypes.func.isRequired,
|
@@ -7,7 +7,7 @@ import { noop } from '../../../helpers';
|
|
7
7
|
|
8
8
|
const getWeekDays = () => {
|
9
9
|
const locale = documentLocale().replace(/-/g, '_');
|
10
|
-
const baseDate = new Date(Date.UTC(2017, 0,
|
10
|
+
const baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday
|
11
11
|
const weekDays = [];
|
12
12
|
for (let i = 0; i < 7; i++) {
|
13
13
|
try {
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import React, { useEffect, useState, useCallback } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import {
|
4
|
+
FormGroup,
|
5
|
+
Form,
|
6
|
+
Button,
|
7
|
+
ValidatedOptions,
|
8
|
+
} from '@patternfly/react-core';
|
9
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
10
|
+
import { DateTimePicker } from '../form/DateTimePicker';
|
11
|
+
import { helpLabel } from '../form/FormHelpers';
|
12
|
+
import { SCHEDULE_TYPES } from '../../JobWizardConstants';
|
13
|
+
import { WizardTitle } from '../form/WizardTitle';
|
14
|
+
|
15
|
+
export const ScheduleFuture = ({
|
16
|
+
scheduleValue: { startsAt, startsBefore },
|
17
|
+
setScheduleValue,
|
18
|
+
setValid,
|
19
|
+
}) => {
|
20
|
+
const [error, setError] = useState(null);
|
21
|
+
|
22
|
+
const wrappedSetValid = useCallback(setValid, []);
|
23
|
+
useEffect(() => {
|
24
|
+
if (!startsBefore?.length && !startsAt?.length) {
|
25
|
+
wrappedSetValid(false);
|
26
|
+
setError(
|
27
|
+
__(
|
28
|
+
"For Future execution a 'Starts at' date or 'Starts before' date must be selected. Immediate execution can be selected in the previous step."
|
29
|
+
)
|
30
|
+
);
|
31
|
+
} else if (!startsBefore?.length) {
|
32
|
+
wrappedSetValid(true);
|
33
|
+
setError(null);
|
34
|
+
} else if (
|
35
|
+
new Date(startsAt).getTime() >= new Date(startsBefore).getTime()
|
36
|
+
) {
|
37
|
+
wrappedSetValid(false);
|
38
|
+
setError(__("'Starts before' date must be after 'Starts at' date"));
|
39
|
+
} else if (new Date().getTime() >= new Date(startsBefore).getTime()) {
|
40
|
+
wrappedSetValid(false);
|
41
|
+
setError(__("'Starts before' date must in the future"));
|
42
|
+
} else {
|
43
|
+
wrappedSetValid(true);
|
44
|
+
setError(null);
|
45
|
+
}
|
46
|
+
}, [wrappedSetValid, startsAt, startsBefore]);
|
47
|
+
|
48
|
+
return (
|
49
|
+
<>
|
50
|
+
<WizardTitle title={SCHEDULE_TYPES.FUTURE} />
|
51
|
+
<Form className="future-schedule-tab">
|
52
|
+
<FormGroup label={__('Starts at')} fieldId="start-at-date">
|
53
|
+
<DateTimePicker
|
54
|
+
ariaLabel="starts at"
|
55
|
+
dateTime={startsAt}
|
56
|
+
setDateTime={newValue =>
|
57
|
+
setScheduleValue(current => ({
|
58
|
+
...current,
|
59
|
+
startsAt: newValue,
|
60
|
+
}))
|
61
|
+
}
|
62
|
+
/>
|
63
|
+
<Button
|
64
|
+
variant="link"
|
65
|
+
isInline
|
66
|
+
className="clear-datetime-button"
|
67
|
+
onClick={() =>
|
68
|
+
setScheduleValue(current => ({
|
69
|
+
...current,
|
70
|
+
startsAt: null,
|
71
|
+
}))
|
72
|
+
}
|
73
|
+
>
|
74
|
+
{__('Clear input')}
|
75
|
+
</Button>
|
76
|
+
</FormGroup>
|
77
|
+
|
78
|
+
<FormGroup
|
79
|
+
label={__('Starts before')}
|
80
|
+
fieldId="start-before-date"
|
81
|
+
labelIcon={helpLabel(
|
82
|
+
__(
|
83
|
+
'Indicates that the action should be cancelled if it cannot be started before this time.'
|
84
|
+
),
|
85
|
+
'start-before-date'
|
86
|
+
)}
|
87
|
+
validated={error ? ValidatedOptions.error : ValidatedOptions.noval}
|
88
|
+
helperTextInvalid={error}
|
89
|
+
>
|
90
|
+
<DateTimePicker
|
91
|
+
ariaLabel="starts before"
|
92
|
+
dateTime={startsBefore}
|
93
|
+
setDateTime={newValue =>
|
94
|
+
setScheduleValue(current => ({
|
95
|
+
...current,
|
96
|
+
startsBefore: newValue,
|
97
|
+
}))
|
98
|
+
}
|
99
|
+
/>
|
100
|
+
<Button
|
101
|
+
variant="link"
|
102
|
+
isInline
|
103
|
+
className="clear-datetime-button"
|
104
|
+
onClick={() =>
|
105
|
+
setScheduleValue(current => ({
|
106
|
+
...current,
|
107
|
+
startsBefore: null,
|
108
|
+
}))
|
109
|
+
}
|
110
|
+
>
|
111
|
+
{__('Clear input')}
|
112
|
+
</Button>
|
113
|
+
</FormGroup>
|
114
|
+
</Form>
|
115
|
+
</>
|
116
|
+
);
|
117
|
+
};
|
118
|
+
|
119
|
+
ScheduleFuture.propTypes = {
|
120
|
+
scheduleValue: PropTypes.shape({
|
121
|
+
startsAt: PropTypes.string,
|
122
|
+
startsBefore: PropTypes.string,
|
123
|
+
}).isRequired,
|
124
|
+
setScheduleValue: PropTypes.func.isRequired,
|
125
|
+
setValid: PropTypes.func.isRequired,
|
126
|
+
};
|
@@ -0,0 +1,287 @@
|
|
1
|
+
import React, { useEffect, useState, useCallback } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import {
|
4
|
+
Form,
|
5
|
+
FormGroup,
|
6
|
+
Radio,
|
7
|
+
TextInput,
|
8
|
+
ValidatedOptions,
|
9
|
+
Divider,
|
10
|
+
} from '@patternfly/react-core';
|
11
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
12
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
13
|
+
import { RepeatOn } from './RepeatOn';
|
14
|
+
import { SCHEDULE_TYPES } from '../../JobWizardConstants';
|
15
|
+
import { PurposeField } from './PurposeField';
|
16
|
+
import { DateTimePicker } from '../form/DateTimePicker';
|
17
|
+
import { WizardTitle } from '../form/WizardTitle';
|
18
|
+
|
19
|
+
export const ScheduleRecurring = ({
|
20
|
+
scheduleValue,
|
21
|
+
setScheduleValue,
|
22
|
+
setValid,
|
23
|
+
}) => {
|
24
|
+
const {
|
25
|
+
repeatType,
|
26
|
+
repeatAmount,
|
27
|
+
repeatData,
|
28
|
+
startsAt,
|
29
|
+
startsBefore,
|
30
|
+
ends,
|
31
|
+
isNeverEnds,
|
32
|
+
isFuture,
|
33
|
+
purpose,
|
34
|
+
} = scheduleValue;
|
35
|
+
const [validEnd, setValidEnd] = useState(true);
|
36
|
+
const [repeatValidated, setRepeatValidated] = useState('default');
|
37
|
+
const handleRepeatInputChange = newValue => {
|
38
|
+
if (!newValue.length) newValue = 0;
|
39
|
+
setRepeatValidated(
|
40
|
+
!newValue || parseInt(newValue, 10) >= 1 ? 'default' : 'error'
|
41
|
+
);
|
42
|
+
setScheduleValue(current => ({
|
43
|
+
...current,
|
44
|
+
repeatAmount: newValue,
|
45
|
+
}));
|
46
|
+
};
|
47
|
+
const [repeatValid, setRepeatValid] = useState(true);
|
48
|
+
|
49
|
+
const wrappedSetValid = useCallback(setValid, []);
|
50
|
+
useEffect(() => {
|
51
|
+
if (isNeverEnds) setValidEnd(true);
|
52
|
+
else if (!ends) setValidEnd(true);
|
53
|
+
else if (
|
54
|
+
!startsAt.length &&
|
55
|
+
new Date().getTime() <= new Date(ends).getTime()
|
56
|
+
)
|
57
|
+
setValidEnd(true);
|
58
|
+
else if (new Date(startsAt).getTime() <= new Date(ends).getTime())
|
59
|
+
setValidEnd(true);
|
60
|
+
else {
|
61
|
+
setValidEnd(false);
|
62
|
+
}
|
63
|
+
|
64
|
+
if (!validEnd || !repeatValid) {
|
65
|
+
wrappedSetValid(false);
|
66
|
+
} else if (isFuture && startsAt.length) {
|
67
|
+
wrappedSetValid(true);
|
68
|
+
} else if (!isFuture) {
|
69
|
+
wrappedSetValid(true);
|
70
|
+
} else {
|
71
|
+
wrappedSetValid(false);
|
72
|
+
}
|
73
|
+
}, [
|
74
|
+
wrappedSetValid,
|
75
|
+
isNeverEnds,
|
76
|
+
startsAt,
|
77
|
+
startsBefore,
|
78
|
+
isFuture,
|
79
|
+
validEnd,
|
80
|
+
repeatValid,
|
81
|
+
ends,
|
82
|
+
]);
|
83
|
+
|
84
|
+
return (
|
85
|
+
<>
|
86
|
+
<WizardTitle title={SCHEDULE_TYPES.RECURRING} />
|
87
|
+
<Form className="schedule-tab">
|
88
|
+
<FormGroup label={__('Starts')} fieldId="schedule-starts">
|
89
|
+
<div className="pf-c-form">
|
90
|
+
<FormGroup fieldId="schedule-starts-now">
|
91
|
+
<Radio
|
92
|
+
isChecked={!isFuture}
|
93
|
+
onChange={() =>
|
94
|
+
setScheduleValue(current => ({
|
95
|
+
...current,
|
96
|
+
startsAt: '',
|
97
|
+
startsBefore: '',
|
98
|
+
isFuture: false,
|
99
|
+
}))
|
100
|
+
}
|
101
|
+
name="start-now"
|
102
|
+
id="start-now"
|
103
|
+
label={__('Now')}
|
104
|
+
/>
|
105
|
+
</FormGroup>
|
106
|
+
<FormGroup fieldId="start-at-date">
|
107
|
+
<Radio
|
108
|
+
isChecked={isFuture}
|
109
|
+
onChange={() =>
|
110
|
+
setScheduleValue(current => ({
|
111
|
+
...current,
|
112
|
+
startsAt: new Date().toISOString(),
|
113
|
+
isFuture: true,
|
114
|
+
}))
|
115
|
+
}
|
116
|
+
name="start-at"
|
117
|
+
id="start-at"
|
118
|
+
className="schedule-radio"
|
119
|
+
label={
|
120
|
+
<div className="scheudle-radio-wrapper">
|
121
|
+
<div className="schedule-radio-title">{__('At')}</div>
|
122
|
+
<DateTimePicker
|
123
|
+
ariaLabel="starts at"
|
124
|
+
dateTime={startsAt}
|
125
|
+
setDateTime={newValue =>
|
126
|
+
setScheduleValue(current => ({
|
127
|
+
...current,
|
128
|
+
startsAt: newValue,
|
129
|
+
}))
|
130
|
+
}
|
131
|
+
isDisabled={!isFuture}
|
132
|
+
/>
|
133
|
+
</div>
|
134
|
+
}
|
135
|
+
/>
|
136
|
+
</FormGroup>
|
137
|
+
</div>
|
138
|
+
</FormGroup>
|
139
|
+
|
140
|
+
<Divider component="div" />
|
141
|
+
<RepeatOn
|
142
|
+
repeatType={repeatType}
|
143
|
+
repeatData={repeatData}
|
144
|
+
setRepeatType={newValue => {
|
145
|
+
setScheduleValue(current => ({
|
146
|
+
...current,
|
147
|
+
repeatType: newValue,
|
148
|
+
startsBefore: '',
|
149
|
+
}));
|
150
|
+
}}
|
151
|
+
setRepeatData={newValue => {
|
152
|
+
setScheduleValue(current => ({
|
153
|
+
...current,
|
154
|
+
repeatData: newValue,
|
155
|
+
}));
|
156
|
+
}}
|
157
|
+
setValid={setRepeatValid}
|
158
|
+
/>
|
159
|
+
<Divider component="div" />
|
160
|
+
<FormGroup label={__('Ends')} fieldId="schedule-ends">
|
161
|
+
<div className="pf-c-form">
|
162
|
+
<FormGroup fieldId="schedule-ends-never">
|
163
|
+
<Radio
|
164
|
+
isChecked={isNeverEnds}
|
165
|
+
onChange={() =>
|
166
|
+
setScheduleValue(current => ({
|
167
|
+
...current,
|
168
|
+
isNeverEnds: true,
|
169
|
+
ends: null,
|
170
|
+
repeatAmount: null,
|
171
|
+
}))
|
172
|
+
}
|
173
|
+
name="never-ends"
|
174
|
+
id="never-ends"
|
175
|
+
label={__('Never')}
|
176
|
+
/>
|
177
|
+
</FormGroup>
|
178
|
+
<FormGroup
|
179
|
+
fieldId="ends-on-date"
|
180
|
+
validated={
|
181
|
+
validEnd ? ValidatedOptions.noval : ValidatedOptions.error
|
182
|
+
}
|
183
|
+
helperTextInvalid={__('End time needs to be after start time')}
|
184
|
+
helperTextInvalidIcon={<ExclamationCircleIcon />}
|
185
|
+
>
|
186
|
+
<Radio
|
187
|
+
isChecked={!!ends}
|
188
|
+
onChange={() =>
|
189
|
+
setScheduleValue(current => ({
|
190
|
+
...current,
|
191
|
+
ends: new Date().toISOString(),
|
192
|
+
isNeverEnds: false,
|
193
|
+
repeatAmount: null,
|
194
|
+
}))
|
195
|
+
}
|
196
|
+
name="ends-on"
|
197
|
+
id="ends-on"
|
198
|
+
className="schedule-radio"
|
199
|
+
label={
|
200
|
+
<div className="scheudle-radio-wrapper">
|
201
|
+
<div className="schedule-radio-title">{__('On')}</div>
|
202
|
+
<DateTimePicker
|
203
|
+
ariaLabel="ends on"
|
204
|
+
dateTime={ends}
|
205
|
+
isDisabled={!ends}
|
206
|
+
setDateTime={newValue => {
|
207
|
+
setScheduleValue(current => ({
|
208
|
+
...current,
|
209
|
+
ends: newValue,
|
210
|
+
}));
|
211
|
+
}}
|
212
|
+
/>
|
213
|
+
</div>
|
214
|
+
}
|
215
|
+
/>
|
216
|
+
</FormGroup>
|
217
|
+
<FormGroup fieldId="ends-after">
|
218
|
+
<Radio
|
219
|
+
isChecked={repeatAmount === 0 || !!repeatAmount}
|
220
|
+
onChange={() =>
|
221
|
+
setScheduleValue(current => ({
|
222
|
+
...current,
|
223
|
+
ends: null,
|
224
|
+
isNeverEnds: false,
|
225
|
+
repeatAmount: 1,
|
226
|
+
}))
|
227
|
+
}
|
228
|
+
name="ends-after"
|
229
|
+
id="ends-after"
|
230
|
+
className="schedule-radio"
|
231
|
+
label={
|
232
|
+
<div className="scheudle-radio-wrapper">
|
233
|
+
<div className="schedule-radio-title">{__('After')}</div>
|
234
|
+
<FormGroup
|
235
|
+
helperTextInvalid={__(
|
236
|
+
'Repeat amount can only be a positive number'
|
237
|
+
)}
|
238
|
+
validated={repeatValidated}
|
239
|
+
className="schedule-radio-repeat-text"
|
240
|
+
>
|
241
|
+
<TextInput
|
242
|
+
id="repeat-amount"
|
243
|
+
value={repeatAmount || ''}
|
244
|
+
type="number"
|
245
|
+
onChange={handleRepeatInputChange}
|
246
|
+
isDisabled={!(repeatAmount === 0 || !!repeatAmount)}
|
247
|
+
/>
|
248
|
+
</FormGroup>
|
249
|
+
<div className="schedule-radio-occurences">
|
250
|
+
{__('occurences')}
|
251
|
+
</div>
|
252
|
+
</div>
|
253
|
+
}
|
254
|
+
/>
|
255
|
+
</FormGroup>
|
256
|
+
</div>
|
257
|
+
</FormGroup>
|
258
|
+
<Divider component="div" />
|
259
|
+
<PurposeField
|
260
|
+
purpose={purpose}
|
261
|
+
setPurpose={newValue => {
|
262
|
+
setScheduleValue(current => ({
|
263
|
+
...current,
|
264
|
+
purpose: newValue,
|
265
|
+
}));
|
266
|
+
}}
|
267
|
+
/>
|
268
|
+
</Form>
|
269
|
+
</>
|
270
|
+
);
|
271
|
+
};
|
272
|
+
|
273
|
+
ScheduleRecurring.propTypes = {
|
274
|
+
scheduleValue: PropTypes.shape({
|
275
|
+
repeatType: PropTypes.string.isRequired,
|
276
|
+
repeatAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
277
|
+
repeatData: PropTypes.object,
|
278
|
+
startsAt: PropTypes.string,
|
279
|
+
startsBefore: PropTypes.string,
|
280
|
+
ends: PropTypes.string,
|
281
|
+
isFuture: PropTypes.bool,
|
282
|
+
isNeverEnds: PropTypes.bool,
|
283
|
+
purpose: PropTypes.string,
|
284
|
+
}).isRequired,
|
285
|
+
setScheduleValue: PropTypes.func.isRequired,
|
286
|
+
setValid: PropTypes.func.isRequired,
|
287
|
+
};
|