foreman_remote_execution 7.2.1 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -8,12 +8,20 @@ import {
|
|
8
8
|
import { debounce } from 'lodash';
|
9
9
|
import { translate as __, documentLocale } from 'foremanReact/common/I18n';
|
10
10
|
|
11
|
+
const formatDateTime = d =>
|
12
|
+
`${d.getFullYear()}-${`0${d.getMonth() + 1}`.slice(
|
13
|
+
-2
|
14
|
+
)}-${`0${d.getDate()}`.slice(-2)} ${`0${d.getHours()}`.slice(
|
15
|
+
-2
|
16
|
+
)}:${`0${d.getMinutes()}`.slice(-2)}:${`0${d.getSeconds()}`.slice(-2)}`;
|
17
|
+
|
11
18
|
export const DateTimePicker = ({
|
12
19
|
dateTime,
|
13
20
|
setDateTime,
|
14
21
|
isDisabled,
|
15
22
|
ariaLabel,
|
16
23
|
allowEmpty,
|
24
|
+
includeSeconds,
|
17
25
|
}) => {
|
18
26
|
const [validated, setValidated] = useState();
|
19
27
|
const dateFormat = date =>
|
@@ -35,12 +43,18 @@ export const DateTimePicker = ({
|
|
35
43
|
if (!time) return false;
|
36
44
|
const split = time.split(':');
|
37
45
|
if (!(split[0].length === 2 && split[1].length === 2)) return false;
|
46
|
+
if (includeSeconds) {
|
47
|
+
if (split[2]?.length !== 2) return false;
|
48
|
+
}
|
38
49
|
if (isValidDate(new Date(`${formattedDate} ${time}`))) return true;
|
39
50
|
if (!formattedDate.length && isValidDate(new Date(`01/01/2020 ${time}`))) {
|
40
51
|
const today = new Date();
|
41
52
|
today.setHours(split[0]);
|
42
53
|
today.setMinutes(split[1]);
|
43
|
-
|
54
|
+
if (includeSeconds) {
|
55
|
+
today.setSeconds(split[2]);
|
56
|
+
}
|
57
|
+
setDateTime(formatDateTime(today));
|
44
58
|
}
|
45
59
|
return false;
|
46
60
|
};
|
@@ -53,7 +67,7 @@ export const DateTimePicker = ({
|
|
53
67
|
} else if (isValidDate(parsedNewDate)) {
|
54
68
|
parsedNewDate.setHours(dateObject.getHours());
|
55
69
|
parsedNewDate.setMinutes(dateObject.getMinutes());
|
56
|
-
setDateTime(parsedNewDate
|
70
|
+
setDateTime(formatDateTime(parsedNewDate));
|
57
71
|
setValidated(ValidatedOptions.noval);
|
58
72
|
} else {
|
59
73
|
setValidated(ValidatedOptions.error);
|
@@ -63,11 +77,11 @@ export const DateTimePicker = ({
|
|
63
77
|
const onTimeChange = newTime => {
|
64
78
|
if (!newTime.length && allowEmpty) {
|
65
79
|
const parsedNewTime = new Date(`${formattedDate} 00:00`);
|
66
|
-
setDateTime(parsedNewTime
|
80
|
+
setDateTime(formatDateTime(parsedNewTime));
|
67
81
|
setValidated(ValidatedOptions.noval);
|
68
82
|
} else if (isValidTime(newTime)) {
|
69
83
|
const parsedNewTime = new Date(`${formattedDate} ${newTime}`);
|
70
|
-
setDateTime(parsedNewTime
|
84
|
+
setDateTime(formatDateTime(parsedNewTime));
|
71
85
|
setValidated(ValidatedOptions.noval);
|
72
86
|
} else {
|
73
87
|
setValidated(ValidatedOptions.error);
|
@@ -97,15 +111,16 @@ export const DateTimePicker = ({
|
|
97
111
|
className="time-picker"
|
98
112
|
time={dateTime ? dateObject.toString() : ''}
|
99
113
|
inputProps={dateTime ? {} : { value: '' }}
|
100
|
-
placeholder=
|
114
|
+
placeholder={includeSeconds ? 'hh:mm:ss' : 'hh:mm'}
|
101
115
|
onChange={debounce(onTimeChange, 1000, {
|
102
116
|
leading: false,
|
103
117
|
trailing: true,
|
104
118
|
})}
|
105
119
|
is24Hour
|
106
|
-
isDisabled={isDisabled}
|
120
|
+
isDisabled={isDisabled || formattedDate.length === 0}
|
107
121
|
invalidFormatErrorMessage={__('Invalid time format')}
|
108
122
|
menuAppendTo={() => document.body}
|
123
|
+
includeSeconds={includeSeconds}
|
109
124
|
/>
|
110
125
|
</>
|
111
126
|
);
|
@@ -117,10 +132,12 @@ DateTimePicker.propTypes = {
|
|
117
132
|
isDisabled: PropTypes.bool,
|
118
133
|
ariaLabel: PropTypes.string,
|
119
134
|
allowEmpty: PropTypes.bool,
|
135
|
+
includeSeconds: PropTypes.bool,
|
120
136
|
};
|
121
137
|
DateTimePicker.defaultProps = {
|
122
138
|
dateTime: null,
|
123
139
|
isDisabled: false,
|
124
140
|
ariaLabel: '',
|
125
141
|
allowEmpty: true,
|
142
|
+
includeSeconds: false,
|
126
143
|
};
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React, { useEffect } from 'react';
|
2
2
|
import { useSelector, useDispatch } from 'react-redux';
|
3
|
-
import { FormGroup,
|
3
|
+
import { FormGroup, TextArea } from '@patternfly/react-core';
|
4
4
|
import PropTypes from 'prop-types';
|
5
5
|
import SearchBar from 'foremanReact/components/SearchBar';
|
6
6
|
import { getControllerSearchProps } from 'foremanReact/constants';
|
@@ -9,6 +9,7 @@ import { getResults } from 'foremanReact/components/AutoComplete/AutoCompleteAct
|
|
9
9
|
import { helpLabel } from './FormHelpers';
|
10
10
|
import { SelectField } from './SelectField';
|
11
11
|
import { ResourceSelectAPI } from './ResourceSelect';
|
12
|
+
import { DateTimePicker } from '../form/DateTimePicker';
|
12
13
|
import { noop } from '../../../helpers';
|
13
14
|
|
14
15
|
const TemplateSearchField = ({
|
@@ -147,15 +148,13 @@ export const formatter = (input, values, setValue) => {
|
|
147
148
|
fieldId={id}
|
148
149
|
isRequired={required}
|
149
150
|
>
|
150
|
-
<
|
151
|
-
|
152
|
-
placeholder="YYYY-mm-dd HH:MM"
|
151
|
+
<DateTimePicker
|
152
|
+
ariaLabel={name}
|
153
153
|
className={hidden ? 'masked-input' : null}
|
154
|
-
required={required}
|
155
154
|
id={id}
|
156
|
-
|
157
|
-
|
158
|
-
|
155
|
+
dateTime={value}
|
156
|
+
setDateTime={newValue => setValue({ ...values, [name]: newValue })}
|
157
|
+
includeSeconds
|
159
158
|
/>
|
160
159
|
</FormGroup>
|
161
160
|
);
|
data/webpack/JobWizard/submit.js
CHANGED
@@ -44,8 +44,13 @@ export const submit = ({
|
|
44
44
|
return repeatData.cronline;
|
45
45
|
case repeatTypes.monthly:
|
46
46
|
return `${minute} ${hour} ${repeatData.days} * *`;
|
47
|
-
case repeatTypes.weekly:
|
48
|
-
|
47
|
+
case repeatTypes.weekly: {
|
48
|
+
const daysKeys = Object.keys(repeatData.daysOfWeek).filter(
|
49
|
+
k => repeatData.daysOfWeek[k]
|
50
|
+
);
|
51
|
+
const days = daysKeys.join(',');
|
52
|
+
return `${minute} ${hour} * * ${days}`;
|
53
|
+
}
|
49
54
|
case repeatTypes.daily:
|
50
55
|
return `${minute} ${hour} * * *`;
|
51
56
|
case repeatTypes.hourly:
|
@@ -77,7 +82,7 @@ export const submit = ({
|
|
77
82
|
? {
|
78
83
|
cron_line: getCronLine(),
|
79
84
|
max_iteration: repeatAmount,
|
80
|
-
end_time: ends
|
85
|
+
end_time: ends?.length ? new Date(ends).toISOString() : null,
|
81
86
|
purpose,
|
82
87
|
}
|
83
88
|
: null,
|
data/webpack/Routes/routes.js
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import JobWizardPage from '../JobWizard';
|
3
|
+
import JobWizardPageRerun from '../JobWizard/JobWizardPageRerun';
|
3
4
|
|
4
5
|
const ForemanREXRoutes = [
|
5
6
|
{
|
6
|
-
path: '/experimental/job_wizard',
|
7
|
+
path: '/experimental/job_wizard/new',
|
7
8
|
exact: true,
|
8
|
-
render:
|
9
|
+
render: () => <JobWizardPage />,
|
10
|
+
},
|
11
|
+
{
|
12
|
+
path: '/experimental/job_wizard/:id/rerun',
|
13
|
+
exact: true,
|
14
|
+
render: props => <JobWizardPageRerun {...props} />,
|
9
15
|
},
|
10
16
|
];
|
11
17
|
|
@@ -0,0 +1 @@
|
|
1
|
+
export const useAPI = jest.fn();
|
@@ -27,7 +27,6 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
|
|
27
27
|
<DropdownItem
|
28
28
|
href={foremanUrl(`${JOB_BASE_URL}${name}`)}
|
29
29
|
key="link-to-all"
|
30
|
-
ouiaId="link-to-all-dropdown-item"
|
31
30
|
>
|
32
31
|
{__('View all jobs')}
|
33
32
|
</DropdownItem>,
|
@@ -36,28 +35,24 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
|
|
36
35
|
`${JOB_BASE_URL}${name}+and+status+%3D+failed+or+status%3D+succeeded`
|
37
36
|
)}
|
38
37
|
key="link-to-finished"
|
39
|
-
ouiaId="link-to-finished-dropdown-item"
|
40
38
|
>
|
41
39
|
{__('View finished jobs')}
|
42
40
|
</DropdownItem>,
|
43
41
|
<DropdownItem
|
44
42
|
href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+running`)}
|
45
43
|
key="link-to-running"
|
46
|
-
ouiaId="link-to-running-dropdown-item"
|
47
44
|
>
|
48
45
|
{__('View running jobs')}
|
49
46
|
</DropdownItem>,
|
50
47
|
<DropdownItem
|
51
48
|
href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+queued`)}
|
52
49
|
key="link-to-scheduled"
|
53
|
-
ouiaId="link-to-scheduled-dropdown-item"
|
54
50
|
>
|
55
51
|
{__('View scheduled jobs')}
|
56
52
|
</DropdownItem>,
|
57
53
|
]}
|
58
54
|
>
|
59
55
|
<Tabs
|
60
|
-
ouiaId="tabs"
|
61
56
|
mountOnEnter
|
62
57
|
unmountOnExit
|
63
58
|
activeKey={activeTab}
|
@@ -1,7 +1,15 @@
|
|
1
1
|
import PropTypes from 'prop-types';
|
2
2
|
import React from 'react';
|
3
|
-
import {
|
4
|
-
|
3
|
+
import {
|
4
|
+
DataList,
|
5
|
+
DataListItem,
|
6
|
+
DataListItemRow,
|
7
|
+
DataListItemCells,
|
8
|
+
DataListCell,
|
9
|
+
DataListWrapModifier,
|
10
|
+
Text,
|
11
|
+
Bullseye,
|
12
|
+
} from '@patternfly/react-core';
|
5
13
|
import { STATUS } from 'foremanReact/constants';
|
6
14
|
|
7
15
|
import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
|
@@ -25,55 +33,55 @@ const RecentJobsTable = ({ status, hostId }) => {
|
|
25
33
|
} = useAPI('get', jobsUrl, RECENT_JOBS_KEY);
|
26
34
|
|
27
35
|
return (
|
28
|
-
<
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
<
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
>
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
{
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
</
|
36
|
+
<DataList aria-label="recent-jobs-table" isCompact>
|
37
|
+
<SkeletonLoader
|
38
|
+
skeletonProps={{ count: 3 }}
|
39
|
+
status={responseStatus || STATUS.PENDING}
|
40
|
+
emptyState={
|
41
|
+
<Bullseye>
|
42
|
+
<Text style={{ marginTop: '20px' }} component="p">
|
43
|
+
{__('No results found')}
|
44
|
+
</Text>
|
45
|
+
</Bullseye>
|
46
|
+
}
|
47
|
+
>
|
48
|
+
{jobs?.length &&
|
49
|
+
jobs.map(
|
50
|
+
({
|
51
|
+
status: jobStatus,
|
52
|
+
status_label: label,
|
53
|
+
id,
|
54
|
+
start_at: startAt,
|
55
|
+
description,
|
56
|
+
}) => (
|
57
|
+
<DataListItem key={id}>
|
58
|
+
<DataListItemRow>
|
59
|
+
<DataListItemCells
|
60
|
+
dataListCells={[
|
61
|
+
<DataListCell
|
62
|
+
wrapModifier={DataListWrapModifier.truncate}
|
63
|
+
key={`name-${id}`}
|
64
|
+
>
|
65
|
+
<a href={foremanUrl(`/job_invocations/${id}`)}>
|
66
|
+
{description}
|
67
|
+
</a>
|
68
|
+
</DataListCell>,
|
69
|
+
<DataListCell key={`date-${id}`}>
|
70
|
+
<RelativeDateTime date={startAt} />
|
71
|
+
</DataListCell>,
|
72
|
+
<DataListCell key={`status-${id}`}>
|
73
|
+
<JobStatusIcon status={jobStatus}>
|
74
|
+
{label}
|
75
|
+
</JobStatusIcon>
|
76
|
+
</DataListCell>,
|
77
|
+
]}
|
78
|
+
/>
|
79
|
+
</DataListItemRow>
|
80
|
+
</DataListItem>
|
81
|
+
)
|
82
|
+
)}
|
83
|
+
</SkeletonLoader>
|
84
|
+
</DataList>
|
77
85
|
);
|
78
86
|
};
|
79
87
|
|
@@ -18,7 +18,7 @@ const fills = [
|
|
18
18
|
slot: 'host-overview-cards',
|
19
19
|
name: 'latest-jobs',
|
20
20
|
component: props => <RecentJobsCard {...props} />,
|
21
|
-
weight:
|
21
|
+
weight: 3200,
|
22
22
|
},
|
23
23
|
{
|
24
24
|
slot: 'registrationAdvanced',
|
@@ -41,11 +41,11 @@ const fills = [
|
|
41
41
|
];
|
42
42
|
|
43
43
|
const registerFills = () => {
|
44
|
-
fills.forEach(({ slot,
|
44
|
+
fills.forEach(({ slot, id, component: Component, weight, metadata }) =>
|
45
45
|
addGlobalFill(
|
46
46
|
slot,
|
47
|
-
|
48
|
-
<Component key={`rex-${
|
47
|
+
id,
|
48
|
+
<Component key={`rex-fill-${id}`} />,
|
49
49
|
weight,
|
50
50
|
metadata
|
51
51
|
)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 8.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deface
|
@@ -408,7 +408,9 @@ files:
|
|
408
408
|
- webpack/JobWizard/JobWizard.js
|
409
409
|
- webpack/JobWizard/JobWizard.scss
|
410
410
|
- webpack/JobWizard/JobWizardConstants.js
|
411
|
+
- webpack/JobWizard/JobWizardPageRerun.js
|
411
412
|
- webpack/JobWizard/JobWizardSelectors.js
|
413
|
+
- webpack/JobWizard/__tests__/JobWizardPageRerun.test.js
|
412
414
|
- webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap
|
413
415
|
- webpack/JobWizard/__tests__/fixtures.js
|
414
416
|
- webpack/JobWizard/__tests__/integration.test.js
|
@@ -444,11 +446,10 @@ files:
|
|
444
446
|
- webpack/JobWizard/steps/Schedule/RepeatMonth.js
|
445
447
|
- webpack/JobWizard/steps/Schedule/RepeatOn.js
|
446
448
|
- webpack/JobWizard/steps/Schedule/RepeatWeek.js
|
449
|
+
- webpack/JobWizard/steps/Schedule/ScheduleFuture.js
|
450
|
+
- webpack/JobWizard/steps/Schedule/ScheduleRecurring.js
|
447
451
|
- webpack/JobWizard/steps/Schedule/ScheduleType.js
|
448
|
-
- webpack/JobWizard/steps/Schedule/StartEndDates.js
|
449
452
|
- webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js
|
450
|
-
- webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js
|
451
|
-
- webpack/JobWizard/steps/Schedule/index.js
|
452
453
|
- webpack/JobWizard/steps/form/DateTimePicker.js
|
453
454
|
- webpack/JobWizard/steps/form/FormHelpers.js
|
454
455
|
- webpack/JobWizard/steps/form/Formatter.js
|
@@ -466,6 +467,7 @@ files:
|
|
466
467
|
- webpack/__mocks__/foremanReact/common/I18n.js
|
467
468
|
- webpack/__mocks__/foremanReact/common/globalIdHelpers.js
|
468
469
|
- webpack/__mocks__/foremanReact/common/helpers.js
|
470
|
+
- webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js
|
469
471
|
- webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js
|
470
472
|
- webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js
|
471
473
|
- webpack/__mocks__/foremanReact/components/Pagination.js
|
@@ -546,7 +548,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
546
548
|
- !ruby/object:Gem::Version
|
547
549
|
version: '0'
|
548
550
|
requirements: []
|
549
|
-
rubygems_version: 3.
|
551
|
+
rubygems_version: 3.2.26
|
550
552
|
signing_key:
|
551
553
|
specification_version: 4
|
552
554
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
@@ -1,106 +0,0 @@
|
|
1
|
-
import React, { useEffect } from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import { FormGroup, Checkbox } from '@patternfly/react-core';
|
4
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
-
import { DateTimePicker } from '../form/DateTimePicker';
|
6
|
-
import { helpLabel } from '../form/FormHelpers';
|
7
|
-
|
8
|
-
export const StartEndDates = ({
|
9
|
-
startsAt,
|
10
|
-
setStartsAt,
|
11
|
-
startsBefore,
|
12
|
-
setStartsBefore,
|
13
|
-
ends,
|
14
|
-
setEnds,
|
15
|
-
isNeverEnds,
|
16
|
-
setIsNeverEnds,
|
17
|
-
validEnd,
|
18
|
-
setValidEnd,
|
19
|
-
isFuture,
|
20
|
-
isStartBeforeDisabled,
|
21
|
-
isEndDisabled,
|
22
|
-
}) => {
|
23
|
-
const toggleIsNeverEnds = (checked, event) => {
|
24
|
-
const value = event?.target?.checked;
|
25
|
-
setIsNeverEnds(value);
|
26
|
-
};
|
27
|
-
useEffect(() => {
|
28
|
-
if (isNeverEnds) setValidEnd(true);
|
29
|
-
else if ((!startsAt.length && !startsBefore.length) || !ends)
|
30
|
-
setValidEnd(true);
|
31
|
-
else if (
|
32
|
-
startsAt.length
|
33
|
-
? new Date(startsAt).getTime() <= new Date(ends).getTime()
|
34
|
-
: new Date(startsBefore).getTime() <= new Date(ends).getTime()
|
35
|
-
)
|
36
|
-
setValidEnd(true);
|
37
|
-
else setValidEnd(false);
|
38
|
-
}, [startsAt, startsBefore, ends, isNeverEnds, setValidEnd]);
|
39
|
-
return (
|
40
|
-
<>
|
41
|
-
<FormGroup label={__('Starts at')} fieldId="start-at-date">
|
42
|
-
<DateTimePicker
|
43
|
-
allowEmpty={!isFuture}
|
44
|
-
ariaLabel="starts at"
|
45
|
-
dateTime={startsAt}
|
46
|
-
setDateTime={setStartsAt}
|
47
|
-
/>
|
48
|
-
</FormGroup>
|
49
|
-
|
50
|
-
<FormGroup
|
51
|
-
label={__('Starts before')}
|
52
|
-
fieldId="start-before-date"
|
53
|
-
labelIcon={helpLabel(
|
54
|
-
__(
|
55
|
-
'Indicates that the action should be cancelled if it cannot be started before this time.'
|
56
|
-
),
|
57
|
-
'start-before-date'
|
58
|
-
)}
|
59
|
-
>
|
60
|
-
<DateTimePicker
|
61
|
-
isDisabled={isStartBeforeDisabled}
|
62
|
-
allowEmpty={!isFuture}
|
63
|
-
ariaLabel="starts before"
|
64
|
-
dateTime={startsBefore}
|
65
|
-
setDateTime={setStartsBefore}
|
66
|
-
/>
|
67
|
-
</FormGroup>
|
68
|
-
<FormGroup
|
69
|
-
label={__('Ends')}
|
70
|
-
fieldId="end-date"
|
71
|
-
helperTextInvalid={__('End time needs to be after start time')}
|
72
|
-
validated={validEnd ? 'success' : 'error'}
|
73
|
-
>
|
74
|
-
<DateTimePicker
|
75
|
-
ariaLabel="ends"
|
76
|
-
dateTime={ends}
|
77
|
-
setDateTime={setEnds}
|
78
|
-
isDisabled={isNeverEnds || isEndDisabled}
|
79
|
-
/>
|
80
|
-
<Checkbox
|
81
|
-
label={__('Never ends')}
|
82
|
-
isChecked={isNeverEnds}
|
83
|
-
onChange={toggleIsNeverEnds}
|
84
|
-
id="never-ends"
|
85
|
-
name="never-ends"
|
86
|
-
/>
|
87
|
-
</FormGroup>
|
88
|
-
</>
|
89
|
-
);
|
90
|
-
};
|
91
|
-
|
92
|
-
StartEndDates.propTypes = {
|
93
|
-
startsAt: PropTypes.string.isRequired,
|
94
|
-
setStartsAt: PropTypes.func.isRequired,
|
95
|
-
startsBefore: PropTypes.string.isRequired,
|
96
|
-
setStartsBefore: PropTypes.func.isRequired,
|
97
|
-
ends: PropTypes.string.isRequired,
|
98
|
-
setEnds: PropTypes.func.isRequired,
|
99
|
-
isNeverEnds: PropTypes.bool.isRequired,
|
100
|
-
setIsNeverEnds: PropTypes.func.isRequired,
|
101
|
-
validEnd: PropTypes.bool.isRequired,
|
102
|
-
setValidEnd: PropTypes.func.isRequired,
|
103
|
-
isFuture: PropTypes.bool.isRequired,
|
104
|
-
isStartBeforeDisabled: PropTypes.bool.isRequired,
|
105
|
-
isEndDisabled: PropTypes.bool.isRequired,
|
106
|
-
};
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { render, fireEvent, screen, act } from '@testing-library/react';
|
3
|
-
import { StartEndDates } from '../StartEndDates';
|
4
|
-
|
5
|
-
const setEnds = jest.fn();
|
6
|
-
const setIsNeverEnds = jest.fn();
|
7
|
-
const setValid = jest.fn();
|
8
|
-
const props = {
|
9
|
-
startsAt: '',
|
10
|
-
startsBefore: '',
|
11
|
-
setStartsAt: jest.fn(),
|
12
|
-
setStartsBefore: jest.fn(),
|
13
|
-
ends: 'some-end-date',
|
14
|
-
setEnds,
|
15
|
-
setIsNeverEnds,
|
16
|
-
isNeverEnds: false,
|
17
|
-
validEnd: true,
|
18
|
-
setValidEnd: setValid,
|
19
|
-
isFuture: false,
|
20
|
-
isStartBeforeDisabled: false,
|
21
|
-
isEndDisabled: false,
|
22
|
-
};
|
23
|
-
|
24
|
-
describe('StartEndDates', () => {
|
25
|
-
it('never ends', async () => {
|
26
|
-
await act(async () => render(<StartEndDates {...props} />));
|
27
|
-
const neverEnds = screen.getByRole('checkbox', { name: 'Never ends' });
|
28
|
-
await act(async () => fireEvent.click(neverEnds));
|
29
|
-
expect(setIsNeverEnds).toBeCalledWith(true);
|
30
|
-
expect(setValid).toBeCalledWith(true);
|
31
|
-
});
|
32
|
-
});
|