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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +2 -2
  3. data/app/controllers/ui_job_wizard_controller.rb +15 -0
  4. data/app/helpers/remote_execution_helper.rb +1 -1
  5. data/app/models/job_invocation.rb +2 -4
  6. data/app/models/job_invocation_composer.rb +5 -2
  7. data/app/views/templates/script/package_action.erb +8 -3
  8. data/config/routes.rb +3 -1
  9. data/lib/foreman_remote_execution/engine.rb +5 -5
  10. data/lib/foreman_remote_execution/version.rb +1 -1
  11. data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
  12. data/test/helpers/remote_execution_helper_test.rb +4 -0
  13. data/test/unit/job_invocation_report_template_test.rb +1 -1
  14. data/test/unit/job_invocation_test.rb +1 -2
  15. data/webpack/JobWizard/JobWizard.js +154 -20
  16. data/webpack/JobWizard/JobWizard.scss +43 -1
  17. data/webpack/JobWizard/JobWizardConstants.js +11 -1
  18. data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
  19. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
  20. data/webpack/JobWizard/__tests__/fixtures.js +73 -0
  21. data/webpack/JobWizard/__tests__/integration.test.js +17 -3
  22. data/webpack/JobWizard/autofill.js +8 -1
  23. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
  24. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
  25. data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
  26. data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
  27. data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
  28. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
  29. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
  30. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  31. data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
  32. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
  33. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
  34. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
  35. data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
  36. data/webpack/JobWizard/steps/form/Formatter.js +7 -8
  37. data/webpack/JobWizard/submit.js +8 -3
  38. data/webpack/Routes/routes.js +8 -2
  39. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
  40. data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
  41. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
  42. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
  43. data/webpack/react_app/extend/Fills.js +4 -4
  44. metadata +8 -6
  45. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
  46. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
  47. 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
- setDateTime(today.toString());
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.toString());
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.toString());
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.toString());
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="hh:mm"
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, TextInput, TextArea } from '@patternfly/react-core';
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
- <TextInput
151
- aria-label={name}
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
- type="text"
157
- value={value}
158
- onChange={newValue => setValue({ ...values, [name]: newValue })}
155
+ dateTime={value}
156
+ setDateTime={newValue => setValue({ ...values, [name]: newValue })}
157
+ includeSeconds
159
158
  />
160
159
  </FormGroup>
161
160
  );
@@ -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
- return `${minute} ${hour} * * ${repeatData.daysOfWeek}`;
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.length ? new Date(ends).toISOString() : null,
85
+ end_time: ends?.length ? new Date(ends).toISOString() : null,
81
86
  purpose,
82
87
  }
83
88
  : null,
@@ -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: props => <JobWizardPage {...props} />,
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();
@@ -14,7 +14,6 @@ const HostKebabItems = () => {
14
14
  if (!consoleUrl) return null;
15
15
  return (
16
16
  <DropdownItem
17
- ouiaId="web-console-dropdown-item"
18
17
  icon={<CodeIcon />}
19
18
  href={consoleUrl}
20
19
  target="_blank"
@@ -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 { Text, Bullseye } from '@patternfly/react-core';
4
- import { TableComposable, Tr, Tbody, Td } from '@patternfly/react-table';
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
- <SkeletonLoader
29
- skeletonProps={{ count: 3 }}
30
- status={responseStatus || STATUS.PENDING}
31
- emptyState={
32
- <Bullseye>
33
- <Text
34
- ouiaId="no-results-text"
35
- style={{ marginTop: '20px' }}
36
- component="p"
37
- >
38
- {__('No results found')}
39
- </Text>
40
- </Bullseye>
41
- }
42
- >
43
- {!!jobs?.length && (
44
- <TableComposable
45
- aria-label="recent-jobs-table"
46
- variant="compact"
47
- borders="compactBorderless"
48
- >
49
- <Tbody>
50
- {jobs.map(
51
- ({
52
- status: jobStatus,
53
- status_label: label,
54
- id,
55
- start_at: startAt,
56
- description,
57
- }) => (
58
- <Tr key={id}>
59
- <Td modifier="truncate" key={`name-${id}`}>
60
- <a href={foremanUrl(`/job_invocations/${id}`)}>
61
- {description}
62
- </a>
63
- </Td>
64
- <Td modifier="truncate" key={`date-${id}`}>
65
- <RelativeDateTime date={startAt} />
66
- </Td>
67
- <Td modifier="truncate" key={`status-${id}`}>
68
- <JobStatusIcon status={jobStatus}>{label}</JobStatusIcon>
69
- </Td>
70
- </Tr>
71
- )
72
- )}
73
- </Tbody>
74
- </TableComposable>
75
- )}
76
- </SkeletonLoader>
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: 500,
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, name, component: Component, weight, metadata }) =>
44
+ fills.forEach(({ slot, id, component: Component, weight, metadata }) =>
45
45
  addGlobalFill(
46
46
  slot,
47
- `rex-${name}`,
48
- <Component key={`rex-${name}`} />,
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: 7.2.1
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-09-06 00:00:00.000000000 Z
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.3.20
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
- });