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.
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
- });