foreman_remote_execution 4.8.0 → 5.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +9 -0
  3. data/app/controllers/ui_job_wizard_controller.rb +16 -4
  4. data/app/graphql/mutations/job_invocations/create.rb +43 -0
  5. data/app/graphql/types/job_invocation_input.rb +13 -0
  6. data/app/graphql/types/recurrence_input.rb +8 -0
  7. data/app/graphql/types/scheduling_input.rb +6 -0
  8. data/app/graphql/types/targeting_enum.rb +7 -0
  9. data/app/lib/actions/remote_execution/run_host_job.rb +4 -0
  10. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +8 -0
  11. data/app/models/job_invocation_composer.rb +1 -1
  12. data/app/models/targeting.rb +2 -2
  13. data/app/views/job_invocations/refresh.js.erb +1 -0
  14. data/config/routes.rb +1 -0
  15. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
  16. data/lib/foreman_remote_execution/engine.rb +110 -6
  17. data/lib/foreman_remote_execution/version.rb +1 -1
  18. data/package.json +6 -6
  19. data/test/functional/api/v2/job_invocations_controller_test.rb +10 -0
  20. data/test/functional/cockpit_controller_test.rb +0 -1
  21. data/test/graphql/mutations/job_invocations/create.rb +58 -0
  22. data/test/helpers/remote_execution_helper_test.rb +0 -1
  23. data/test/unit/actions/run_host_job_test.rb +21 -0
  24. data/test/unit/concerns/host_extensions_test.rb +36 -3
  25. data/test/unit/job_invocation_composer_test.rb +3 -5
  26. data/test/unit/job_invocation_report_template_test.rb +1 -1
  27. data/test/unit/job_template_effective_user_test.rb +0 -4
  28. data/test/unit/remote_execution_provider_test.rb +0 -4
  29. data/test/unit/targeting_test.rb +68 -1
  30. data/webpack/JobWizard/JobWizard.js +94 -13
  31. data/webpack/JobWizard/JobWizard.scss +59 -35
  32. data/webpack/JobWizard/JobWizardConstants.js +28 -1
  33. data/webpack/JobWizard/JobWizardSelectors.js +32 -0
  34. data/webpack/JobWizard/__tests__/fixtures.js +81 -6
  35. data/webpack/JobWizard/__tests__/integration.test.js +26 -15
  36. data/webpack/JobWizard/__tests__/validation.test.js +141 -0
  37. data/webpack/JobWizard/autofill.js +38 -0
  38. data/webpack/JobWizard/index.js +7 -0
  39. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +7 -4
  40. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +32 -9
  41. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +216 -12
  42. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +82 -0
  43. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +1 -0
  44. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
  45. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
  46. data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
  47. data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
  48. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
  49. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +82 -7
  50. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
  51. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +7 -4
  52. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
  53. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
  54. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
  55. data/webpack/JobWizard/steps/HostsAndInputs/index.js +182 -34
  56. data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
  57. data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
  58. data/webpack/JobWizard/steps/Schedule/QueryType.js +46 -43
  59. data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
  60. data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
  61. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
  62. data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
  63. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +95 -31
  64. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
  65. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +59 -19
  66. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +258 -11
  67. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +11 -2
  68. data/webpack/JobWizard/steps/Schedule/index.js +97 -21
  69. data/webpack/JobWizard/steps/form/DateTimePicker.js +41 -8
  70. data/webpack/JobWizard/steps/form/FormHelpers.js +4 -0
  71. data/webpack/JobWizard/steps/form/Formatter.js +39 -8
  72. data/webpack/JobWizard/steps/form/NumberInput.js +3 -2
  73. data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
  74. data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
  75. data/webpack/JobWizard/steps/form/SelectField.js +14 -3
  76. data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
  77. data/webpack/JobWizard/submit.js +120 -0
  78. data/webpack/JobWizard/validation.js +53 -0
  79. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
  80. data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
  81. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
  82. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
  83. data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
  84. data/webpack/helpers.js +1 -0
  85. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +1 -1
  86. metadata +38 -6
  87. data/app/models/setting/remote_execution.rb +0 -94
  88. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
  89. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +0 -37
  90. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +0 -76
@@ -0,0 +1,54 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ FormGroup,
5
+ Select,
6
+ SelectOption,
7
+ SelectVariant,
8
+ } from '@patternfly/react-core';
9
+ import { range } from 'lodash';
10
+ import { translate as __ } from 'foremanReact/common/I18n';
11
+
12
+ export const RepeatHour = ({ repeatData, setRepeatData, setValid }) => {
13
+ const { minute } = repeatData;
14
+ useEffect(() => {
15
+ if (minute) {
16
+ setValid(true);
17
+ } else {
18
+ setValid(false);
19
+ }
20
+ return () => setValid(true);
21
+ }, [setValid, minute]);
22
+ const [minuteOpen, setMinuteOpen] = useState(false);
23
+ return (
24
+ <FormGroup label={__('At minute')} isRequired>
25
+ <Select
26
+ id="repeat-on-hourly"
27
+ variant={SelectVariant.typeahead}
28
+ typeAheadAriaLabel="repeat-at-minute-typeahead"
29
+ onSelect={(event, selection) => {
30
+ setRepeatData({ minute: selection });
31
+ setMinuteOpen(false);
32
+ }}
33
+ selections={minute || ''}
34
+ onToggle={toggle => {
35
+ setMinuteOpen(toggle);
36
+ }}
37
+ isOpen={minuteOpen}
38
+ width={85}
39
+ menuAppendTo={() => document.querySelector('.pf-c-form.schedule-tab')}
40
+ toggleAriaLabel="select minute toggle"
41
+ validated={minute ? 'success' : 'error'}
42
+ >
43
+ {range(60).map(minuteNumber => (
44
+ <SelectOption key={minuteNumber} value={`${minuteNumber}`} />
45
+ ))}
46
+ </Select>
47
+ </FormGroup>
48
+ );
49
+ };
50
+ RepeatHour.propTypes = {
51
+ repeatData: PropTypes.object.isRequired,
52
+ setRepeatData: PropTypes.func.isRequired,
53
+ setValid: PropTypes.func.isRequired,
54
+ };
@@ -0,0 +1,46 @@
1
+ import React, { useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { TextInput, FormGroup, ValidatedOptions } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { RepeatDaily } from './RepeatDaily';
6
+ import { noop } from '../../../helpers';
7
+
8
+ export const RepeatMonth = ({ repeatData, setRepeatData, setValid }) => {
9
+ const { days, at } = repeatData;
10
+ useEffect(() => {
11
+ if (days && at) {
12
+ setValid(true);
13
+ } else {
14
+ setValid(false);
15
+ }
16
+ return () => setValid(true);
17
+ }, [setValid, days, at]);
18
+ return (
19
+ <>
20
+ <FormGroup label={__('Days')} isRequired>
21
+ <TextInput
22
+ isRequired
23
+ validated={days ? ValidatedOptions.noval : ValidatedOptions.error}
24
+ aria-label="days"
25
+ placeholder="1,2..."
26
+ type="text"
27
+ value={repeatData.days || ''}
28
+ onChange={newTime => {
29
+ setRepeatData({ ...repeatData, days: newTime });
30
+ }}
31
+ />
32
+ </FormGroup>
33
+ <RepeatDaily
34
+ repeatData={repeatData}
35
+ setRepeatData={setRepeatData}
36
+ setValid={noop}
37
+ />
38
+ </>
39
+ );
40
+ };
41
+
42
+ RepeatMonth.propTypes = {
43
+ repeatData: PropTypes.object.isRequired,
44
+ setRepeatData: PropTypes.func.isRequired,
45
+ setValid: PropTypes.func.isRequired,
46
+ };
@@ -4,51 +4,112 @@ import { TextInput, Grid, GridItem, 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';
7
+ import { RepeatCron } from './RepeatCron';
8
+ import { RepeatHour } from './RepeatHour';
9
+ import { RepeatMonth } from './RepeatMonth';
10
+ import { RepeatDaily } from './RepeatDaily';
11
+ import { RepeatWeek } from './RepeatWeek';
7
12
 
8
13
  export const RepeatOn = ({
9
14
  repeatType,
10
15
  setRepeatType,
11
16
  repeatAmount,
12
17
  setRepeatAmount,
18
+ repeatData,
19
+ setRepeatData,
20
+ setValid,
13
21
  }) => {
14
22
  const [repeatValidated, setRepeatValidated] = useState('default');
15
23
  const handleRepeatInputChange = newValue => {
16
24
  setRepeatValidated(newValue >= 1 ? 'default' : 'error');
17
25
  setRepeatAmount(newValue);
18
26
  };
27
+
28
+ const getRepeatComponent = () => {
29
+ switch (repeatType) {
30
+ case repeatTypes.cronline:
31
+ return (
32
+ <RepeatCron
33
+ repeatData={repeatData}
34
+ setRepeatData={setRepeatData}
35
+ setValid={setValid}
36
+ />
37
+ );
38
+ case repeatTypes.monthly:
39
+ return (
40
+ <RepeatMonth
41
+ repeatData={repeatData}
42
+ setRepeatData={setRepeatData}
43
+ setValid={setValid}
44
+ />
45
+ );
46
+ case repeatTypes.weekly:
47
+ return (
48
+ <RepeatWeek
49
+ repeatData={repeatData}
50
+ setRepeatData={setRepeatData}
51
+ setValid={setValid}
52
+ />
53
+ );
54
+ case repeatTypes.daily:
55
+ return (
56
+ <RepeatDaily
57
+ repeatData={repeatData}
58
+ setRepeatData={setRepeatData}
59
+ setValid={setValid}
60
+ />
61
+ );
62
+ case repeatTypes.hourly:
63
+ return (
64
+ <RepeatHour
65
+ repeatData={repeatData}
66
+ setRepeatData={setRepeatData}
67
+ setValid={setValid}
68
+ />
69
+ );
70
+ case repeatTypes.noRepeat:
71
+ default:
72
+ return null;
73
+ }
74
+ };
19
75
  return (
20
- <Grid>
21
- <GridItem span={6}>
22
- <SelectField
23
- fieldId="repeat-select"
24
- options={Object.values(repeatTypes)}
25
- setValue={newValue => {
26
- setRepeatType(newValue);
27
- if (newValue === repeatTypes.noRepeat) {
28
- setRepeatValidated('default');
29
- }
30
- }}
31
- value={repeatType}
32
- />
33
- </GridItem>
34
- <GridItem span={1} />
35
- <GridItem span={5}>
36
- <FormGroup
37
- isInline
38
- helperTextInvalid={__('Repeat amount can only be a positive number')}
39
- validated={repeatValidated}
40
- >
41
- <TextInput
42
- isDisabled={repeatType === repeatTypes.noRepeat}
43
- id="repeat-amount"
44
- value={repeatAmount}
45
- type="text"
46
- onChange={newValue => handleRepeatInputChange(newValue)}
47
- placeholder={__('Repeat N times')}
76
+ <FormGroup label={__('Repeat On')}>
77
+ <Grid>
78
+ <GridItem span={6}>
79
+ <SelectField
80
+ isRequired
81
+ fieldId="repeat-select"
82
+ options={Object.values(repeatTypes)}
83
+ setValue={newValue => {
84
+ setRepeatType(newValue);
85
+ if (newValue === repeatTypes.noRepeat) {
86
+ setRepeatValidated('default');
87
+ }
88
+ }}
89
+ value={repeatType}
48
90
  />
49
- </FormGroup>
50
- </GridItem>
51
- </Grid>
91
+ </GridItem>
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>
52
113
  );
53
114
  };
54
115
 
@@ -58,4 +119,7 @@ RepeatOn.propTypes = {
58
119
  repeatAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
59
120
  .isRequired,
60
121
  setRepeatAmount: PropTypes.func.isRequired,
122
+ repeatData: PropTypes.object.isRequired,
123
+ setRepeatData: PropTypes.func.isRequired,
124
+ setValid: PropTypes.func.isRequired,
61
125
  };
@@ -0,0 +1,70 @@
1
+ import React, { useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormGroup, Checkbox } from '@patternfly/react-core';
4
+ import { translate as __, documentLocale } from 'foremanReact/common/I18n';
5
+ import { RepeatDaily } from './RepeatDaily';
6
+ import { noop } from '../../../helpers';
7
+
8
+ const getWeekDays = () => {
9
+ const locale = documentLocale().replace(/-/g, '_');
10
+ const baseDate = new Date(Date.UTC(2017, 0, 2)); // just a Monday
11
+ const weekDays = [];
12
+ for (let i = 0; i < 7; i++) {
13
+ try {
14
+ weekDays.push(baseDate.toLocaleDateString(locale, { weekday: 'short' }));
15
+ } catch {
16
+ weekDays.push(baseDate.toLocaleDateString('en', { weekday: 'short' }));
17
+ }
18
+ baseDate.setDate(baseDate.getDate() + 1);
19
+ }
20
+ return weekDays;
21
+ };
22
+
23
+ export const RepeatWeek = ({ repeatData, setRepeatData, setValid }) => {
24
+ const { daysOfWeek, at } = repeatData;
25
+ useEffect(() => {
26
+ if (daysOfWeek && Object.values(daysOfWeek).includes(true) && at) {
27
+ setValid(true);
28
+ } else {
29
+ setValid(false);
30
+ }
31
+ return () => setValid(true);
32
+ }, [setValid, daysOfWeek, at]);
33
+ const days = getWeekDays();
34
+ const handleChangeDays = (checked, { target: { name } }) => {
35
+ setRepeatData({
36
+ ...repeatData,
37
+ daysOfWeek: { ...repeatData.daysOfWeek, [name]: checked },
38
+ });
39
+ };
40
+ return (
41
+ <>
42
+ <FormGroup label={__('Days of week')} isRequired>
43
+ <div id="repeat-on-weekly">
44
+ {days.map((day, index) => (
45
+ <Checkbox
46
+ aria-label={`${day} checkbox`}
47
+ key={index}
48
+ isChecked={daysOfWeek?.[index]}
49
+ name={index}
50
+ id={`repeat-on-day-${index}`}
51
+ onChange={handleChangeDays}
52
+ label={day}
53
+ />
54
+ ))}
55
+ </div>
56
+ </FormGroup>
57
+
58
+ <RepeatDaily
59
+ repeatData={repeatData}
60
+ setRepeatData={setRepeatData}
61
+ setValid={noop}
62
+ />
63
+ </>
64
+ );
65
+ };
66
+ RepeatWeek.propTypes = {
67
+ repeatData: PropTypes.object.isRequired,
68
+ setRepeatData: PropTypes.func.isRequired,
69
+ setValid: PropTypes.func.isRequired,
70
+ };
@@ -1,48 +1,81 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { FormGroup, Checkbox } from '@patternfly/react-core';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
5
  import { DateTimePicker } from '../form/DateTimePicker';
6
+ import { helpLabel } from '../form/FormHelpers';
6
7
 
7
8
  export const StartEndDates = ({
8
- starts,
9
- setStarts,
9
+ startsAt,
10
+ setStartsAt,
11
+ startsBefore,
12
+ setStartsBefore,
10
13
  ends,
11
14
  setEnds,
12
15
  isNeverEnds,
13
16
  setIsNeverEnds,
17
+ validEnd,
18
+ setValidEnd,
19
+ isFuture,
20
+ isStartBeforeDisabled,
21
+ isEndDisabled,
14
22
  }) => {
15
23
  const toggleIsNeverEnds = (checked, event) => {
16
24
  const value = event?.target?.checked;
17
25
  setIsNeverEnds(value);
18
26
  };
19
- const validateEndDate = () => {
20
- if (isNeverEnds) return 'success';
21
- if (!starts || !ends) return 'success';
22
- if (new Date(starts).getTime() <= new Date(ends).getTime())
23
- return 'success';
24
- return 'error';
25
- };
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]);
26
39
  return (
27
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
+
28
50
  <FormGroup
29
- className="start-date"
30
- label={__('Starts')}
31
- fieldId="start-date"
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
+ )}
32
59
  >
33
- <DateTimePicker dateTime={starts} setDateTime={setStarts} />
60
+ <DateTimePicker
61
+ isDisabled={isStartBeforeDisabled}
62
+ allowEmpty={!isFuture}
63
+ ariaLabel="starts before"
64
+ dateTime={startsBefore}
65
+ setDateTime={setStartsBefore}
66
+ />
34
67
  </FormGroup>
35
68
  <FormGroup
36
- className="end-date"
37
69
  label={__('Ends')}
38
70
  fieldId="end-date"
39
71
  helperTextInvalid={__('End time needs to be after start time')}
40
- validated={validateEndDate()}
72
+ validated={validEnd ? 'success' : 'error'}
41
73
  >
42
74
  <DateTimePicker
75
+ ariaLabel="ends"
43
76
  dateTime={ends}
44
77
  setDateTime={setEnds}
45
- isDisabled={isNeverEnds}
78
+ isDisabled={isNeverEnds || isEndDisabled}
46
79
  />
47
80
  <Checkbox
48
81
  label={__('Never ends')}
@@ -57,10 +90,17 @@ export const StartEndDates = ({
57
90
  };
58
91
 
59
92
  StartEndDates.propTypes = {
60
- starts: PropTypes.string.isRequired,
61
- setStarts: PropTypes.func.isRequired,
93
+ startsAt: PropTypes.string.isRequired,
94
+ setStartsAt: PropTypes.func.isRequired,
95
+ startsBefore: PropTypes.string.isRequired,
96
+ setStartsBefore: PropTypes.func.isRequired,
62
97
  ends: PropTypes.string.isRequired,
63
98
  setEnds: PropTypes.func.isRequired,
64
99
  isNeverEnds: PropTypes.bool.isRequired,
65
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,
66
106
  };