foreman_remote_execution 4.8.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -4,10 +4,6 @@ class JobTemplateEffectiveUserTest < ActiveSupport::TestCase
4
4
  let(:job_template) { FactoryBot.build(:job_template, :job_category => '') }
5
5
  let(:effective_user) { job_template.effective_user }
6
6
 
7
- before do
8
- Setting::RemoteExecution.load_defaults
9
- end
10
-
11
7
  describe 'by default' do
12
8
  it 'is overridable' do
13
9
  assert effective_user.overridable?
@@ -66,10 +66,6 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
66
66
  before { User.current = FactoryBot.build(:user, :admin) }
67
67
  after { User.current = nil }
68
68
 
69
- before do
70
- Setting::RemoteExecution.load_defaults
71
- end
72
-
73
69
  let(:job_invocation) { FactoryBot.create(:job_invocation, :with_template) }
74
70
  let(:template_invocation) { job_invocation.pattern_template_invocations.first }
75
71
  let(:host) { FactoryBot.create(:host) }
@@ -77,24 +77,73 @@ class TargetingTest < ActiveSupport::TestCase
77
77
  it { _(targeting.reload.hosts).must_be_empty }
78
78
  end
79
79
 
80
+ describe '#resolve_hosts!' do
81
+ let(:second_host) { FactoryBot.create(:host) }
82
+ let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
83
+ let(:targeting) { FactoryBot.build(:targeting) }
84
+
85
+ before do
86
+ host
87
+ second_host
88
+ infra_host
89
+ end
90
+
91
+ context 'with infrastructure host permission' do
92
+ before do
93
+ setup_user('view', 'hosts')
94
+ setup_user('execute_jobs_on', 'infrastructure_hosts')
95
+ end
96
+
97
+ it 'resolves all hosts' do
98
+ hosts = [host, second_host, infra_host]
99
+ targeting.search_query = "name ^ (#{hosts.map(&:name).join(',')})"
100
+ targeting.user = User.current
101
+ targeting.resolve_hosts!
102
+
103
+ targeting.hosts.must_include host
104
+ targeting.hosts.must_include second_host
105
+ targeting.hosts.must_include infra_host
106
+ end
107
+ end
108
+
109
+ context 'without infrastructure host permission' do
110
+ before { setup_user('view', 'hosts') }
111
+
112
+ it 'ignores infrastructure hosts' do
113
+ hosts = [host, second_host, infra_host]
114
+ targeting.search_query = "name ^ (#{hosts.map(&:name).join(',')})"
115
+ targeting.user = User.current
116
+ targeting.resolve_hosts!
117
+
118
+ targeting.hosts.must_include host
119
+ targeting.hosts.must_include second_host
120
+ targeting.hosts.wont_include infra_host
121
+ end
122
+ end
123
+ end
124
+
80
125
  describe '#build_query_from_hosts(ids)' do
81
126
  let(:second_host) { FactoryBot.create(:host) }
127
+ let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
82
128
 
83
129
  before do
84
130
  host
85
131
  second_host
132
+ infra_host
86
133
  end
87
134
 
88
135
  context 'for two hosts' do
89
- let(:query) { Targeting.build_query_from_hosts([ host.id, second_host.id ]) }
136
+ let(:query) { Targeting.build_query_from_hosts([ host.id, second_host.id, infra_host.id ]) }
90
137
 
91
138
  it 'builds query using host names joining inside ^' do
92
139
  _(query).must_include host.name
93
140
  _(query).must_include second_host.name
141
+ _(query).must_include infra_host.name
94
142
  _(query).must_include 'name ^'
95
143
 
96
144
  Host.search_for(query).must_include host
97
145
  Host.search_for(query).must_include second_host
146
+ Host.search_for(query).must_include infra_host
98
147
  end
99
148
  end
100
149
 
@@ -105,6 +154,7 @@ class TargetingTest < ActiveSupport::TestCase
105
154
  _(query).must_equal "name ^ (#{host.name})"
106
155
  Host.search_for(query).must_include host
107
156
  Host.search_for(query).wont_include second_host
157
+ Host.search_for(query).wont_include infra_host
108
158
  end
109
159
  end
110
160
 
@@ -114,6 +164,23 @@ class TargetingTest < ActiveSupport::TestCase
114
164
  it 'builds query to find all hosts' do
115
165
  Host.search_for(query).must_include host
116
166
  Host.search_for(query).must_include second_host
167
+ Host.search_for(query).must_include infra_host
168
+ end
169
+ end
170
+
171
+ context 'without infrastructure host permission' do
172
+ before { User.current = nil }
173
+
174
+ it 'ignores the infrastructure host' do
175
+ query = Targeting.build_query_from_hosts([host.id, second_host.id, infra_host.id])
176
+ _(query).must_include host.name
177
+ _(query).must_include second_host.name
178
+ _(query).wont_include infra_host.name
179
+ _(query).must_include 'name ^'
180
+
181
+ Host.search_for(query).must_include host
182
+ Host.search_for(query).must_include second_host
183
+ Host.search_for(query).wont_include infra_host
117
184
  end
118
185
  end
119
186
  end
@@ -4,6 +4,11 @@ import { useDispatch, useSelector } from 'react-redux';
4
4
  import { Wizard } from '@patternfly/react-core';
5
5
  import { get } from 'foremanReact/redux/API';
6
6
  import history from 'foremanReact/history';
7
+
8
+ import {
9
+ useForemanOrganization,
10
+ useForemanLocation,
11
+ } from 'foremanReact/Root/Context/ForemanContext';
7
12
  import CategoryAndTemplate from './steps/CategoryAndTemplate/';
8
13
  import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
9
14
  import {
@@ -11,18 +16,31 @@ import {
11
16
  WIZARD_TITLES,
12
17
  initialScheduleState,
13
18
  } from './JobWizardConstants';
14
- import { selectTemplateError } from './JobWizardSelectors';
19
+ import {
20
+ selectTemplateError,
21
+ selectJobTemplate,
22
+ selectIsSubmitting,
23
+ } from './JobWizardSelectors';
15
24
  import Schedule from './steps/Schedule/';
16
25
  import HostsAndInputs from './steps/HostsAndInputs/';
26
+ import ReviewDetails from './steps/ReviewDetails/';
27
+ import { useValidation } from './validation';
28
+ import { useAutoFill } from './autofill';
29
+ import { submit } from './submit';
17
30
  import './JobWizard.scss';
18
31
 
19
32
  export const JobWizard = () => {
20
33
  const [jobTemplateID, setJobTemplateID] = useState(null);
21
34
  const [category, setCategory] = useState('');
22
- const [advancedValues, setAdvancedValues] = useState({});
35
+ const [advancedValues, setAdvancedValues] = useState({ templateValues: {} });
23
36
  const [templateValues, setTemplateValues] = useState({}); // TODO use templateValues in advanced fields - description https://github.com/theforeman/foreman_remote_execution/pull/605
24
- const [selectedHosts, setSelectedHosts] = useState(['host1', 'host2']);
25
37
  const [scheduleValue, setScheduleValue] = useState(initialScheduleState);
38
+ const [selectedTargets, setSelectedTargets] = useState({
39
+ hosts: [],
40
+ hostCollections: [],
41
+ hostGroups: [],
42
+ });
43
+ const [hostsSearchQuery, setHostsSearchQuery] = useState('');
26
44
  const dispatch = useDispatch();
27
45
 
28
46
  const setDefaults = useCallback(
@@ -31,7 +49,7 @@ export const JobWizard = () => {
31
49
  template_inputs,
32
50
  advanced_template_inputs,
33
51
  effective_user,
34
- job_template: { execution_timeout_interval, description_format },
52
+ job_template: { name, execution_timeout_interval, description_format },
35
53
  },
36
54
  }) => {
37
55
  const advancedTemplateValues = {};
@@ -52,12 +70,21 @@ export const JobWizard = () => {
52
70
  advancedTemplateValues[input.name] = input?.default || '';
53
71
  });
54
72
  }
73
+ const generateDefaultDescription = () => {
74
+ if (description_format) return description_format;
75
+ const allInputs = [...advancedInputs, ...inputs];
76
+ if (!allInputs.length) return name;
77
+ const inputsString = allInputs
78
+ .map(({ name: inputname }) => `${inputname}="%{${inputname}}"`)
79
+ .join(' ');
80
+ return `${name} with inputs ${inputsString}`;
81
+ };
55
82
  return {
56
83
  ...currentAdvancedValues,
57
84
  effectiveUserValue: effective_user?.value || '',
58
85
  timeoutToKill: execution_timeout_interval || '',
59
86
  templateValues: advancedTemplateValues,
60
- description: description_format || '',
87
+ description: generateDefaultDescription() || '',
61
88
  isRandomizedOrdering: false,
62
89
  };
63
90
  });
@@ -76,8 +103,20 @@ export const JobWizard = () => {
76
103
  }
77
104
  }, [jobTemplateID, setDefaults, dispatch]);
78
105
 
106
+ const [valid, setValid] = useValidation({
107
+ advancedValues,
108
+ templateValues,
109
+ });
110
+ useAutoFill({
111
+ setSelectedTargets,
112
+ setHostsSearchQuery,
113
+ });
79
114
  const templateError = !!useSelector(selectTemplateError);
80
- const isTemplate = !templateError && !!jobTemplateID;
115
+ const templateResponse = useSelector(selectJobTemplate);
116
+ const isSubmitting = useSelector(selectIsSubmitting);
117
+ const isTemplate =
118
+ !templateError && !!jobTemplateID && templateResponse.job_template;
119
+
81
120
  const steps = [
82
121
  {
83
122
  name: WIZARD_TITLES.categoryAndTemplate,
@@ -89,6 +128,7 @@ export const JobWizard = () => {
89
128
  setCategory={setCategory}
90
129
  />
91
130
  ),
131
+ enableNext: isTemplate,
92
132
  },
93
133
  {
94
134
  name: WIZARD_TITLES.hostsAndInputs,
@@ -96,11 +136,14 @@ export const JobWizard = () => {
96
136
  <HostsAndInputs
97
137
  templateValues={templateValues}
98
138
  setTemplateValues={setTemplateValues}
99
- selectedHosts={selectedHosts}
100
- setSelectedHosts={setSelectedHosts}
139
+ selected={selectedTargets}
140
+ setSelected={setSelectedTargets}
141
+ hostsSearchQuery={hostsSearchQuery}
142
+ setHostsSearchQuery={setHostsSearchQuery}
101
143
  />
102
144
  ),
103
145
  canJumpTo: isTemplate,
146
+ enableNext: isTemplate && valid.hostsAndInputs,
104
147
  },
105
148
  {
106
149
  name: WIZARD_TITLES.advanced,
@@ -113,10 +156,11 @@ export const JobWizard = () => {
113
156
  ...newValues,
114
157
  }));
115
158
  }}
116
- jobTemplateID={jobTemplateID}
159
+ templateValues={templateValues}
117
160
  />
118
161
  ),
119
- canJumpTo: isTemplate,
162
+ canJumpTo: isTemplate && valid.hostsAndInputs,
163
+ enableNext: isTemplate && valid.hostsAndInputs && valid.advanced,
120
164
  },
121
165
  {
122
166
  name: WIZARD_TITLES.schedule,
@@ -124,17 +168,41 @@ export const JobWizard = () => {
124
168
  <Schedule
125
169
  scheduleValue={scheduleValue}
126
170
  setScheduleValue={setScheduleValue}
171
+ setValid={newValue => {
172
+ setValid(currentValid => ({ ...currentValid, schedule: newValue }));
173
+ }}
127
174
  />
128
175
  ),
129
- canJumpTo: isTemplate,
176
+ canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
177
+ enableNext:
178
+ isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
130
179
  },
131
180
  {
132
181
  name: WIZARD_TITLES.review,
133
- component: <p>Review Details</p>,
182
+ component: (
183
+ <ReviewDetails
184
+ jobCategory={category}
185
+ jobTemplateID={jobTemplateID}
186
+ advancedValues={advancedValues}
187
+ scheduleValue={scheduleValue}
188
+ templateValues={templateValues}
189
+ selectedTargets={selectedTargets}
190
+ hostsSearchQuery={hostsSearchQuery}
191
+ />
192
+ ),
134
193
  nextButtonText: 'Run',
135
- canJumpTo: isTemplate,
194
+ canJumpTo:
195
+ isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
196
+ enableNext:
197
+ isTemplate &&
198
+ valid.hostsAndInputs &&
199
+ valid.advanced &&
200
+ valid.schedule &&
201
+ !isSubmitting,
136
202
  },
137
203
  ];
204
+ const location = useForemanLocation();
205
+ const organization = useForemanOrganization();
138
206
  return (
139
207
  <Wizard
140
208
  onClose={() => history.goBack()}
@@ -142,6 +210,19 @@ export const JobWizard = () => {
142
210
  steps={steps}
143
211
  height="100%"
144
212
  className="job-wizard"
213
+ onSave={() => {
214
+ submit({
215
+ jobTemplateID,
216
+ templateValues,
217
+ advancedValues,
218
+ scheduleValue,
219
+ dispatch,
220
+ selectedTargets,
221
+ hostsSearchQuery,
222
+ location,
223
+ organization,
224
+ });
225
+ }}
145
226
  />
146
227
  );
147
228
  };
@@ -3,54 +3,54 @@
3
3
  margin-bottom: 25px;
4
4
  }
5
5
 
6
+ .pf-c-wizard__nav.pf-m-expanded {
7
+ z-index: calc(
8
+ var(--pf-c-wizard__footer--ZIndex) + 2
9
+ ); // So the small screen navigation can be shown above the select box
10
+ }
11
+
6
12
  .pf-c-wizard__main {
7
13
  overflow: visible;
8
14
  z-index: calc(
9
15
  var(--pf-c-wizard__footer--ZIndex) + 1
10
16
  ); // So the select box can be shown above the wizard footer
11
17
  }
12
-
18
+ .pf-c-wizard__nav {
19
+ z-index: calc(
20
+ var(--pf-c-wizard__footer--ZIndex) + 2
21
+ ); // So the navigation box can be shown above the wizard body
22
+ }
13
23
  .pf-c-wizard__main-body {
14
24
  max-width: 500px;
15
25
  .advanced-fields-title {
16
26
  margin-bottom: 10px;
17
27
  }
18
- #advanced-fields-job-template {
19
- .foreman-search-field {
20
- .rbt-input-hint input{
21
- display: none;
22
- }
23
- // Giving pf3 search bar a pf4 look
24
- .search-bar {
25
- display: block;
26
- }
27
- .input-group-btn {
28
- display: none;
29
- }
30
- li {
31
- font-size: 16px;
32
- }
33
- input {
34
- font-size: 16px;
35
- height: 36px;
36
- }
37
- .foreman-autocomplete .autocomplete-focus-shortcut {
38
- top: 8px;
39
- font-size: 16px;
40
- }
41
- .foreman-autocomplete .autocomplete-aux {
42
- top: 8px;
43
- font-size: 16px;
44
- .autocomplete-clear-button {
45
- font-size: 16px;
46
- }
47
- }
48
- }
49
- }
28
+
29
+ }
30
+
31
+ .gray-text {
32
+ color: var(--pf-global--Color--dark-200);
50
33
  }
51
34
 
52
- .hosts-chip-group {
53
- margin-top: 8px;
35
+ .target-hosts-and-inputs {
36
+ .hosts-chip-group {
37
+ margin-top: 8px;
38
+ float: left;
39
+ clear: left;
40
+ display: block;
41
+ }
42
+ .clear-chips {
43
+ margin-top: 8px;
44
+ }
45
+ .pf-c-select__toggle-typeahead {
46
+ border: 0px;
47
+ }
48
+
49
+ .target-method-select {
50
+ .pf-c-select__toggle-wrapper {
51
+ flex-wrap: nowrap;
52
+ }
53
+ }
54
54
  }
55
55
  input[type='radio'],
56
56
  input[type='checkbox'] {
@@ -60,6 +60,17 @@
60
60
  .advanced-scheduling-button {
61
61
  text-align: start;
62
62
  }
63
+ #repeat-on-weekly {
64
+ display: grid;
65
+ grid-template-columns: repeat(7, 1fr);
66
+ }
67
+ .pf-l-grid {
68
+ gap: var(--pf-c-form--GridGap);
69
+ }
70
+ #repeat-on-hourly {
71
+ max-height: 300px;
72
+ overflow: scroll;
73
+ }
63
74
  }
64
75
 
65
76
  .pf-c-date-picker {
@@ -79,4 +90,17 @@
79
90
  min-height: 40px;
80
91
  min-width: 100px;
81
92
  }
93
+ #host-selection {
94
+ width: 500px;
95
+ }
96
+
97
+ .pf-c-modal-box {
98
+ width: auto;
99
+ }
100
+
101
+ .review-details {
102
+ .advanced-fields {
103
+ margin-left: 10px;
104
+ }
105
+ }
82
106
  }
@@ -4,6 +4,7 @@ import { foremanUrl } from 'foremanReact/common/helpers';
4
4
  export const JOB_TEMPLATES = 'JOB_TEMPLATES';
5
5
  export const JOB_CATEGORIES = 'JOB_CATEGORIES';
6
6
  export const JOB_TEMPLATE = 'JOB_TEMPLATE';
7
+ export const JOB_INVOCATION = 'JOB_INVOCATION';
7
8
  export const templatesUrl = foremanUrl('/api/v2/job_templates');
8
9
 
9
10
  export const repeatTypes = {
@@ -26,8 +27,34 @@ export const WIZARD_TITLES = {
26
27
  export const initialScheduleState = {
27
28
  repeatType: repeatTypes.noRepeat,
28
29
  repeatAmount: '',
29
- starts: '',
30
+ repeatData: {},
31
+ startsAt: '',
32
+ startsBefore: '',
30
33
  ends: '',
31
34
  isFuture: false,
32
35
  isNeverEnds: false,
36
+ isTypeStatic: true,
37
+ purpose: '',
33
38
  };
39
+ export const HOSTS_API = 'HOSTS_API';
40
+ export const HOSTS = 'HOSTS';
41
+ export const HOST_COLLECTIONS = 'HOST_COLLECTIONS';
42
+ export const HOST_GROUPS = 'HOST_GROUPS';
43
+ export const hostMethods = {
44
+ hosts: __('Hosts'),
45
+ hostCollections: __('Host collections'),
46
+ hostGroups: __('Host groups'),
47
+ searchQuery: __('Search query'),
48
+ };
49
+
50
+ export const hostQuerySearchID = 'searchBar'; // until https://projects.theforeman.org/issues/33737 is used
51
+ export const hostsController = 'hosts';
52
+
53
+ export const dataName = {
54
+ [HOSTS]: 'hosts',
55
+ [HOST_GROUPS]: 'hostgroups',
56
+ };
57
+ export const HOSTS_TO_PREVIEW_AMOUNT = 20;
58
+
59
+ export const DEBOUNCE_HOST_COUNT = 700;
60
+ export const HOST_IDS = 'HOST_IDS';
@@ -1,13 +1,18 @@
1
+ import URI from 'urijs';
1
2
  import {
2
3
  selectAPIResponse,
3
4
  selectAPIStatus,
4
5
  selectAPIErrorMessage,
5
6
  } from 'foremanReact/redux/API/APISelectors';
7
+ import { STATUS } from 'foremanReact/constants';
8
+ import { selectRouterLocation } from 'foremanReact/routes/RouterSelector';
6
9
 
7
10
  import {
8
11
  JOB_TEMPLATES,
9
12
  JOB_CATEGORIES,
10
13
  JOB_TEMPLATE,
14
+ HOSTS_API,
15
+ JOB_INVOCATION,
11
16
  } from './JobWizardConstants';
12
17
 
13
18
  export const selectJobTemplatesStatus = state =>
@@ -22,6 +27,9 @@ export const selectJobTemplates = state =>
22
27
  export const selectJobCategories = state =>
23
28
  selectAPIResponse(state, JOB_CATEGORIES).job_categories || [];
24
29
 
30
+ export const selectWithKatello = state =>
31
+ selectAPIResponse(state, JOB_CATEGORIES).with_katello || false;
32
+
25
33
  export const selectJobCategoriesStatus = state =>
26
34
  selectAPIStatus(state, JOB_CATEGORIES);
27
35
 
@@ -45,3 +53,27 @@ export const selectAdvancedTemplateInputs = state =>
45
53
 
46
54
  export const selectTemplateInputs = state =>
47
55
  selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
56
+
57
+ export const selectHostCount = state =>
58
+ selectAPIResponse(state, HOSTS_API).subtotal || 0;
59
+
60
+ export const selectHosts = state =>
61
+ (selectAPIResponse(state, HOSTS_API).results || []).map(host => host.name);
62
+
63
+ export const selectIsLoadingHosts = state =>
64
+ !selectAPIStatus(state, HOSTS_API) ||
65
+ selectAPIStatus(state, HOSTS_API) === STATUS.PENDING;
66
+
67
+ export const selectResponse = selectAPIResponse;
68
+
69
+ export const selectIsLoading = (state, key) =>
70
+ selectAPIStatus(state, key) === STATUS.PENDING;
71
+
72
+ export const selectIsSubmitting = state =>
73
+ selectAPIStatus(state, JOB_INVOCATION) === STATUS.PENDING ||
74
+ selectAPIStatus(state, JOB_INVOCATION) === STATUS.RESOLVED;
75
+
76
+ export const selectRouterSearch = state => {
77
+ const { search } = selectRouterLocation(state);
78
+ return URI.parseQuery(search);
79
+ };
@@ -1,4 +1,6 @@
1
1
  import configureMockStore from 'redux-mock-store';
2
+ import hostsQuery from '../steps/HostsAndInputs/hosts.gql';
3
+ import hostgroupsQuery from '../steps/HostsAndInputs/hostgroups.gql';
2
4
 
3
5
  export const jobTemplate = {
4
6
  id: 178,
@@ -9,7 +11,6 @@ export const jobTemplate = {
9
11
  default: true,
10
12
  job_category: 'Ansible Commands',
11
13
  provider_type: 'Ansible',
12
- description_format: 'Run %{command}',
13
14
  execution_timeout_interval: 2,
14
15
  description: null,
15
16
  };
@@ -48,6 +49,16 @@ export const jobTemplateResponse = {
48
49
  default: '',
49
50
  hidden_value: false,
50
51
  },
52
+ {
53
+ name: 'adv resource select',
54
+ required: false,
55
+ input_type: 'user',
56
+ value_type: 'resource',
57
+ advanced: true,
58
+ resource_type: 'ForemanTasks::Task',
59
+ default: '',
60
+ hidden_value: false,
61
+ },
51
62
  {
52
63
  name: 'adv search',
53
64
  required: false,
@@ -57,6 +68,7 @@ export const jobTemplateResponse = {
57
68
  resource_type: 'foreman_tasks/tasks',
58
69
  default: '',
59
70
  hidden_value: false,
71
+ url: 'foreman_tasks/tasks',
60
72
  },
61
73
  {
62
74
  name: 'adv date',
@@ -92,9 +104,11 @@ export const testSetup = (selectors, api) => {
92
104
  jest.spyOn(selectors, 'selectJobTemplates');
93
105
  jest.spyOn(selectors, 'selectJobCategories');
94
106
  jest.spyOn(selectors, 'selectJobCategoriesStatus');
107
+ jest.spyOn(selectors, 'selectWithKatello');
95
108
 
96
109
  jest.spyOn(selectors, 'selectTemplateInputs');
97
110
  jest.spyOn(selectors, 'selectAdvancedTemplateInputs');
111
+ selectors.selectWithKatello.mockImplementation(() => true);
98
112
  selectors.selectTemplateInputs.mockImplementation(
99
113
  () => jobTemplateResponse.template_inputs
100
114
  );
@@ -106,15 +120,31 @@ export const testSetup = (selectors, api) => {
106
120
  jobTemplate,
107
121
  { ...jobTemplate, id: 2, name: 'template2' },
108
122
  ]);
123
+ selectors.selectJobTemplate.mockImplementation(() => jobTemplateResponse);
109
124
  const mockStore = configureMockStore([]);
110
- const store = mockStore({});
125
+ const store = mockStore({
126
+ ForemanTasksTask: {
127
+ response: {
128
+ subtotal: 10,
129
+ results: [
130
+ { id: '1', name: 'resource1' },
131
+ { id: '2', name: 'resource2' },
132
+ ],
133
+ },
134
+ },
135
+ HOST_COLLECTIONS: {
136
+ response: {
137
+ subtotal: 3,
138
+ results: [
139
+ { id: '74', name: 'host_collection1' },
140
+ { id: '43', name: 'host_collection2' },
141
+ ],
142
+ },
143
+ },
144
+ });
111
145
  return store;
112
146
  };
113
147
 
114
- export const mockTemplate = selectors => {
115
- selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
116
- selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
117
- };
118
148
  export const mockApi = api => {
119
149
  api.get.mockImplementation(({ handleSuccess, ...action }) => {
120
150
  if (action.key === 'JOB_CATEGORIES') {
@@ -130,7 +160,52 @@ export const mockApi = api => {
130
160
  handleSuccess({
131
161
  data: { results: [jobTemplate] },
132
162
  });
163
+ } else if (action.key === 'HOST_IDS') {
164
+ handleSuccess &&
165
+ handleSuccess({
166
+ data: { results: [{ name: 'host1' }, { name: 'host3' }] },
167
+ });
133
168
  }
134
169
  return { type: 'get', ...action };
135
170
  });
136
171
  };
172
+
173
+ export const gqlMock = [
174
+ {
175
+ request: {
176
+ query: hostsQuery,
177
+ variables: {
178
+ search: 'name~"" and organization_id=1 and location_id=2',
179
+ },
180
+ },
181
+ result: {
182
+ data: {
183
+ hosts: {
184
+ totalCount: 3,
185
+ nodes: [{ name: 'host1' }, { name: 'host2' }, { name: 'host3' }],
186
+ },
187
+ },
188
+ },
189
+ },
190
+
191
+ {
192
+ request: {
193
+ query: hostgroupsQuery,
194
+ variables: {
195
+ search: 'name~"" and organization_id=1 and location_id=2',
196
+ },
197
+ },
198
+ result: {
199
+ data: {
200
+ hostgroups: {
201
+ totalCount: 3,
202
+ nodes: [
203
+ { name: 'host_group1' },
204
+ { name: 'host_group2' },
205
+ { name: 'host_group3' },
206
+ ],
207
+ },
208
+ },
209
+ },
210
+ },
211
+ ];