foreman_remote_execution 16.6.3 → 16.6.5
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/lib/foreman_remote_execution/version.rb +1 -1
- data/test/integration/job_wizard_category_js_test.rb +234 -0
- data/webpack/JobInvocationDetail/JobInvocationActions.js +7 -0
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +5 -0
- data/webpack/JobWizard/JobWizard.js +7 -0
- data/webpack/JobWizard/JobWizardConstants.js +5 -0
- data/webpack/JobWizard/JobWizardSelectors.js +8 -0
- data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +53 -9
- data/webpack/JobWizard/__tests__/fixtures.js +24 -0
- data/webpack/JobWizard/autofill.js +57 -4
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +7 -2
- data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +31 -7
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +95 -1
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +5 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +2 -0
- data/webpack/JobWizard/submit.js +15 -2
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +5 -1
- metadata +3 -4
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -90
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 803af8d6f1617172c150d68158d14d6fa9df2e5cc431226c1c0522f679420c7b
|
|
4
|
+
data.tar.gz: 43b21d05688e374f58999c890d0ed3fedcf8e98d978be053bc52f83701e89292
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fed722946268e04461455cffe63c27add5a23a5a0e2bbdd698a2d7bd7c94017deb0342650ecaadf92426d64db8e40682f8871ab4a43a7e60e24c1096c230bb0
|
|
7
|
+
data.tar.gz: c11bfa8d9b1e72d2bb35d11829509923b6fb7babffd98a7782d5c8718adc9628def8739d388be1f5de07c239ba8b852c9de907c8e1b77383c573f460813ccff7
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path('../test_plugin_helper', __dir__)
|
|
4
|
+
require 'integration_test_helper'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
class JobWizardCategoryJsTest < IntegrationTestWithJavascript
|
|
8
|
+
WIZARD_INPUT_NAME_BASE = 'wizard_integration_input'
|
|
9
|
+
WIZARD_FEATURE_INPUT_NAME_BASE = 'wizard_integration_feature_input'
|
|
10
|
+
|
|
11
|
+
setup do
|
|
12
|
+
unique_suffix = SecureRandom.hex(6)
|
|
13
|
+
@wizard_input_name = "#{WIZARD_INPUT_NAME_BASE}_#{unique_suffix}"
|
|
14
|
+
@wizard_feature_input_name = "#{WIZARD_FEATURE_INPUT_NAME_BASE}_#{unique_suffix}"
|
|
15
|
+
default_template_name = "Integration Default REX Form Template #{unique_suffix}"
|
|
16
|
+
feature_template_name = "Integration Feature REX Template #{unique_suffix}"
|
|
17
|
+
|
|
18
|
+
# Prevent real Dynflow proxy calls in CI while planning jobs.
|
|
19
|
+
ProxyAPI::ForemanDynflow::DynflowProxy.any_instance.stubs(:tasks_count).returns(0)
|
|
20
|
+
|
|
21
|
+
@original_rex_form_template = Setting[:remote_execution_form_job_template]
|
|
22
|
+
as_admin do
|
|
23
|
+
@default_job_template = FactoryBot.create(
|
|
24
|
+
:job_template,
|
|
25
|
+
:name => default_template_name,
|
|
26
|
+
:job_category => 'Default Form Category'
|
|
27
|
+
)
|
|
28
|
+
@feature_job_template = FactoryBot.create(
|
|
29
|
+
:job_template,
|
|
30
|
+
:name => feature_template_name,
|
|
31
|
+
:job_category => 'Feature Linked Category'
|
|
32
|
+
)
|
|
33
|
+
add_plain_template_input!(@default_job_template, :name => @wizard_input_name)
|
|
34
|
+
add_plain_template_input!(@feature_job_template, :name => @wizard_feature_input_name)
|
|
35
|
+
Setting[:remote_execution_form_job_template] = @default_job_template.name
|
|
36
|
+
@remote_execution_feature = FactoryBot.create(
|
|
37
|
+
:remote_execution_feature,
|
|
38
|
+
:label => "integration_rex_category_feature_#{unique_suffix}",
|
|
39
|
+
:name => "Integration REX Category Feature #{unique_suffix}",
|
|
40
|
+
:job_template => @feature_job_template
|
|
41
|
+
)
|
|
42
|
+
@rex_host = FactoryBot.create(:host, :with_execution)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
teardown do
|
|
47
|
+
Setting[:remote_execution_form_job_template] = @original_rex_form_template
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
test 'job wizard shows feature template job category when feature query param is present' do
|
|
51
|
+
visit new_job_invocation_path(:feature => @remote_execution_feature.label)
|
|
52
|
+
assert_text 'Category and template', :wait => 30
|
|
53
|
+
wait_for_ajax
|
|
54
|
+
assert_selector(:ouia_component_id, 'job_category', :text => 'Feature Linked Category', :wait => 30)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
test 'job wizard shows default form category when no feature query param' do
|
|
58
|
+
visit new_job_invocation_path
|
|
59
|
+
assert_text 'Category and template', :wait => 30
|
|
60
|
+
wait_for_ajax
|
|
61
|
+
assert_selector(:ouia_component_id, 'job_category', :text => 'Default Form Category', :wait => 30)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
test 'job wizard prefills template input from inputs query param' do
|
|
65
|
+
visit new_job_invocation_path(
|
|
66
|
+
:inputs => { @wizard_input_name => 'from_query_string' },
|
|
67
|
+
:search => ''
|
|
68
|
+
)
|
|
69
|
+
go_to_target_hosts_and_inputs_step!
|
|
70
|
+
assert_selector "textarea##{@wizard_input_name}", :wait => 30
|
|
71
|
+
assert_equal 'from_query_string', find("textarea##{@wizard_input_name}").value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
test 'job wizard does not prefill template input when inputs query param is omitted' do
|
|
75
|
+
visit new_job_invocation_path(:search => '')
|
|
76
|
+
go_to_target_hosts_and_inputs_step!
|
|
77
|
+
assert_selector "textarea##{@wizard_input_name}", :wait => 30
|
|
78
|
+
assert_equal '', find("textarea##{@wizard_input_name}").value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
test 'job wizard prefills template input when feature and inputs query params are present' do
|
|
82
|
+
visit new_job_invocation_path(
|
|
83
|
+
:feature => @remote_execution_feature.label,
|
|
84
|
+
:inputs => { @wizard_feature_input_name => 'feature_and_inputs' },
|
|
85
|
+
:search => ''
|
|
86
|
+
)
|
|
87
|
+
go_to_target_hosts_and_inputs_step!
|
|
88
|
+
assert_selector "textarea##{@wizard_feature_input_name}", :wait => 30
|
|
89
|
+
assert_equal 'feature_and_inputs', find("textarea##{@wizard_feature_input_name}").value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
test 'job wizard run on selected hosts is enabled on review and posts to job invocations API' do
|
|
93
|
+
posted_to_job_invocations_create = false
|
|
94
|
+
subscriber = ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |_n, _s, _f, _i, payload|
|
|
95
|
+
next unless payload[:controller] == 'Api::V2::JobInvocationsController' && payload[:action].to_s == 'create'
|
|
96
|
+
root = payload[:params]
|
|
97
|
+
root = root.to_unsafe_h if root.respond_to?(:to_unsafe_h)
|
|
98
|
+
job_inv = (root[:job_invocation] || root['job_invocation'])
|
|
99
|
+
next unless job_inv
|
|
100
|
+
job_inv = job_inv.to_unsafe_h if job_inv.respond_to?(:to_unsafe_h)
|
|
101
|
+
next unless job_inv['job_template_id'].to_i == @default_job_template.id
|
|
102
|
+
posted_to_job_invocations_create = true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
begin
|
|
106
|
+
visit new_job_invocation_path(:search => "id = #{@rex_host.id}")
|
|
107
|
+
navigate_job_wizard_to_review_step!
|
|
108
|
+
|
|
109
|
+
assert_selector(:ouia_component_id, 'run-on-selected-hosts-footer', :wait => 30)
|
|
110
|
+
run_on_hosts = find(:ouia_component_id, 'run-on-selected-hosts-footer')
|
|
111
|
+
assert_not_equal 'true', run_on_hosts[:'aria-disabled'], 'expected run-on-selected-hosts button to be enabled'
|
|
112
|
+
assert_text 'Run on selected hosts'
|
|
113
|
+
find(:ouia_component_id, 'run-on-selected-hosts-footer').click
|
|
114
|
+
wait_for_ajax
|
|
115
|
+
assert posted_to_job_invocations_create, 'expected Api::V2::JobInvocationsController#create to run'
|
|
116
|
+
assert_match(%r{\A/job_invocations/\d+\z}, page.current_path)
|
|
117
|
+
ensure
|
|
118
|
+
ActiveSupport::Notifications.unsubscribe(subscriber)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
test 'job wizard create API request sends job_template_id and inputs for default template' do
|
|
123
|
+
input_value = 'api_payload_default_template'
|
|
124
|
+
job_inv_params = capture_api_job_invocation_params_during(expected_template_id: @default_job_template.id) do
|
|
125
|
+
visit new_job_invocation_path(
|
|
126
|
+
:search => "id = #{@rex_host.id}",
|
|
127
|
+
:inputs => { @wizard_input_name => input_value }
|
|
128
|
+
)
|
|
129
|
+
navigate_job_wizard_to_review_step!
|
|
130
|
+
find(:ouia_component_id, 'run-on-selected-hosts-footer').click
|
|
131
|
+
wait_for_ajax
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
assert job_inv_params, 'expected job_invocation key in POST /api/job_invocations'
|
|
135
|
+
assert job_inv_params[:job_template_id].present?, 'expected job_template_id to be present'
|
|
136
|
+
assert_equal @default_job_template.id, job_inv_params[:job_template_id].to_i
|
|
137
|
+
assert job_inv_params[:feature].blank?
|
|
138
|
+
assert_equal input_value, hash_from_params_object(job_inv_params[:inputs])[@wizard_input_name]
|
|
139
|
+
job_invocation_id = page.current_path.split('/').last.to_i
|
|
140
|
+
assert_equal @default_job_template.job_category, JobInvocation.find(job_invocation_id).job_category, 'expected job category to be the default template category'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
test 'job wizard create API request sends feature label and inputs without job_template_id' do
|
|
144
|
+
input_value = 'api_payload_feature_template'
|
|
145
|
+
job_inv_params = capture_api_job_invocation_params_during(expected_feature: @remote_execution_feature.label) do
|
|
146
|
+
visit new_job_invocation_path(
|
|
147
|
+
:feature => @remote_execution_feature.label,
|
|
148
|
+
:search => "id = #{@rex_host.id}",
|
|
149
|
+
:inputs => { @wizard_feature_input_name => input_value }
|
|
150
|
+
)
|
|
151
|
+
navigate_job_wizard_to_review_step!
|
|
152
|
+
find(:ouia_component_id, 'run-on-selected-hosts-footer').click
|
|
153
|
+
wait_for_ajax
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
assert job_inv_params, 'expected job_invocation key in POST /api/job_invocations'
|
|
157
|
+
assert_not job_inv_params[:job_template_id].present?
|
|
158
|
+
assert_equal @remote_execution_feature.label, job_inv_params[:feature]
|
|
159
|
+
assert_equal input_value, hash_from_params_object(job_inv_params[:inputs])[@wizard_feature_input_name]
|
|
160
|
+
job_invocation_id = page.current_path.split('/').last.to_i
|
|
161
|
+
assert_equal @feature_job_template.job_category, JobInvocation.find(job_invocation_id).job_category, 'expected job category to be the feature template category'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def capture_api_job_invocation_params_during(expected_template_id: nil, expected_feature: nil)
|
|
167
|
+
captured = nil
|
|
168
|
+
subscriber = ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |_n, _s, _f, _i, payload|
|
|
169
|
+
next unless payload[:controller] == 'Api::V2::JobInvocationsController' && payload[:action].to_s == 'create'
|
|
170
|
+
root = payload[:params]
|
|
171
|
+
root = root.to_unsafe_h if root.respond_to?(:to_unsafe_h)
|
|
172
|
+
job_inv_params = root[:job_invocation] || root['job_invocation']
|
|
173
|
+
next unless job_inv_params
|
|
174
|
+
job_inv_params = job_inv_params.to_unsafe_h if job_inv_params.respond_to?(:to_unsafe_h)
|
|
175
|
+
job_inv_params = job_inv_params.with_indifferent_access
|
|
176
|
+
next if expected_template_id && job_inv_params[:job_template_id].to_i != expected_template_id
|
|
177
|
+
next if expected_feature && job_inv_params[:feature] != expected_feature
|
|
178
|
+
captured = job_inv_params
|
|
179
|
+
end
|
|
180
|
+
begin
|
|
181
|
+
yield
|
|
182
|
+
ensure
|
|
183
|
+
ActiveSupport::Notifications.unsubscribe(subscriber)
|
|
184
|
+
end
|
|
185
|
+
captured
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def hash_from_params_object(obj)
|
|
189
|
+
return {}.with_indifferent_access if obj.blank?
|
|
190
|
+
h = obj.respond_to?(:to_unsafe_h) ? obj.to_unsafe_h : obj.to_h
|
|
191
|
+
h.with_indifferent_access
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def add_plain_template_input!(job_template, name:)
|
|
195
|
+
job_template.template_inputs << FactoryBot.build(
|
|
196
|
+
:template_input,
|
|
197
|
+
:name => name,
|
|
198
|
+
:input_type => 'user',
|
|
199
|
+
:value_type => 'plain',
|
|
200
|
+
:required => false
|
|
201
|
+
)
|
|
202
|
+
job_template.save!
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def job_wizard_click_next!
|
|
206
|
+
wait_for_ajax
|
|
207
|
+
button = find(:ouia_component_id, 'next-footer')
|
|
208
|
+
page.execute_script('arguments[0].scrollIntoView({block: "center"})', button.native)
|
|
209
|
+
button.click
|
|
210
|
+
wait_for_ajax
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def go_to_target_hosts_and_inputs_step!
|
|
214
|
+
assert_text 'Category and template', :wait => 30
|
|
215
|
+
wait_for_ajax
|
|
216
|
+
job_wizard_click_next!
|
|
217
|
+
assert_text 'Target hosts and inputs', :wait => 30
|
|
218
|
+
wait_for_ajax
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def navigate_job_wizard_to_review_step!
|
|
222
|
+
assert_text 'Category and template', :wait => 30
|
|
223
|
+
wait_for_ajax
|
|
224
|
+
job_wizard_click_next!
|
|
225
|
+
assert_text 'Target hosts and inputs', :wait => 30
|
|
226
|
+
job_wizard_click_next!
|
|
227
|
+
assert_text 'Advanced fields', :wait => 30
|
|
228
|
+
job_wizard_click_next!
|
|
229
|
+
assert_text 'Immediate execution', :wait => 30
|
|
230
|
+
job_wizard_click_next!
|
|
231
|
+
assert_text 'Review details', :wait => 30
|
|
232
|
+
wait_for_ajax
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -24,6 +24,13 @@ export const getJobInvocation = url => dispatch => {
|
|
|
24
24
|
handleError: () => {
|
|
25
25
|
dispatch(stopInterval(JOB_INVOCATION_KEY));
|
|
26
26
|
},
|
|
27
|
+
errorToast: ({ response }) =>
|
|
28
|
+
// eslint-disable-next-line camelcase
|
|
29
|
+
response?.data?.error?.full_messages?.[0] ||
|
|
30
|
+
// eslint-disable-next-line camelcase
|
|
31
|
+
response?.data?.error?.full_messages ||
|
|
32
|
+
response?.data?.error?.message ||
|
|
33
|
+
'Error',
|
|
27
34
|
}),
|
|
28
35
|
1000
|
|
29
36
|
);
|
|
@@ -62,10 +62,12 @@ export const JobWizard = ({ rerunData }) => {
|
|
|
62
62
|
hostGroups: [],
|
|
63
63
|
});
|
|
64
64
|
const [hostsSearchQuery, setHostsSearchQuery] = useState('');
|
|
65
|
+
const [selectedBookmark, setSelectedBookmark] = useState(null);
|
|
65
66
|
const [fills, setFills] = useState(
|
|
66
67
|
rerunData
|
|
67
68
|
? {
|
|
68
69
|
search: rerunData?.targeting?.search_query,
|
|
70
|
+
bookmark_id: rerunData?.targeting?.bookmark_id,
|
|
69
71
|
...rerunData.inputs,
|
|
70
72
|
...routerSearch,
|
|
71
73
|
}
|
|
@@ -251,6 +253,7 @@ export const JobWizard = ({ rerunData }) => {
|
|
|
251
253
|
setFills,
|
|
252
254
|
setSelectedTargets,
|
|
253
255
|
setHostsSearchQuery,
|
|
256
|
+
setSelectedBookmark,
|
|
254
257
|
setJobTemplateID,
|
|
255
258
|
setTemplateValues,
|
|
256
259
|
setAdvancedValues,
|
|
@@ -298,6 +301,8 @@ export const JobWizard = ({ rerunData }) => {
|
|
|
298
301
|
setSelected={setSelectedTargets}
|
|
299
302
|
hostsSearchQuery={hostsSearchQuery}
|
|
300
303
|
setHostsSearchQuery={setHostsSearchQuery}
|
|
304
|
+
selectedBookmark={selectedBookmark}
|
|
305
|
+
setSelectedBookmark={setSelectedBookmark}
|
|
301
306
|
/>
|
|
302
307
|
),
|
|
303
308
|
canJumpTo: isTemplate,
|
|
@@ -474,6 +479,7 @@ export const JobWizard = ({ rerunData }) => {
|
|
|
474
479
|
dispatch,
|
|
475
480
|
selectedTargets,
|
|
476
481
|
hostsSearchQuery,
|
|
482
|
+
selectedBookmark,
|
|
477
483
|
location,
|
|
478
484
|
organization,
|
|
479
485
|
feature,
|
|
@@ -507,6 +513,7 @@ JobWizard.propTypes = {
|
|
|
507
513
|
job_category: PropTypes.string,
|
|
508
514
|
targeting: PropTypes.shape({
|
|
509
515
|
search_query: PropTypes.string,
|
|
516
|
+
bookmark_id: PropTypes.number,
|
|
510
517
|
targeting_type: PropTypes.string,
|
|
511
518
|
randomized_ordering: PropTypes.bool,
|
|
512
519
|
}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
2
2
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
3
|
+
import { getControllerSearchProps } from 'foremanReact/constants';
|
|
3
4
|
|
|
4
5
|
export const JOB_TEMPLATES = 'JOB_TEMPLATES';
|
|
5
6
|
export const JOB_CATEGORIES = 'JOB_CATEGORIES';
|
|
@@ -61,6 +62,10 @@ export const hostMethods = {
|
|
|
61
62
|
|
|
62
63
|
export const hostQuerySearchID = 'mainHostQuery';
|
|
63
64
|
export const hostsController = 'hosts';
|
|
65
|
+
export const hostsSearchProps = getControllerSearchProps(
|
|
66
|
+
hostsController,
|
|
67
|
+
hostQuerySearchID
|
|
68
|
+
);
|
|
64
69
|
|
|
65
70
|
export const dataName = {
|
|
66
71
|
[HOSTS]: 'hosts',
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
} from 'foremanReact/redux/API/APISelectors';
|
|
10
10
|
import { STATUS } from 'foremanReact/constants';
|
|
11
11
|
import { selectRouterLocation } from 'foremanReact/routes/RouterSelector';
|
|
12
|
+
import { BOOKMARKS } from 'foremanReact/components/PF4/Bookmarks/BookmarksConstants';
|
|
13
|
+
import { selectBookmarksResults } from 'foremanReact/components/PF4/Bookmarks/BookmarksSelectors';
|
|
12
14
|
|
|
13
15
|
import {
|
|
14
16
|
JOB_TEMPLATES,
|
|
@@ -134,3 +136,9 @@ export const selectRouterSearch = state => {
|
|
|
134
136
|
const { search } = selectRouterLocation(state) || {};
|
|
135
137
|
return URI.parseQuery(search);
|
|
136
138
|
};
|
|
139
|
+
|
|
140
|
+
const HOSTS_CONTROLLER = 'hosts';
|
|
141
|
+
const BOOKMARKS_HOSTS_KEY = `${BOOKMARKS}_${HOSTS_CONTROLLER.toUpperCase()}`;
|
|
142
|
+
|
|
143
|
+
export const selectHostBookmarks = state =>
|
|
144
|
+
selectBookmarksResults(state, BOOKMARKS_HOSTS_KEY, HOSTS_CONTROLLER);
|
|
@@ -3,21 +3,19 @@ import { Provider } from 'react-redux';
|
|
|
3
3
|
import { render, fireEvent, screen, act } from '@testing-library/react';
|
|
4
4
|
import { MockedProvider } from '@apollo/client/testing';
|
|
5
5
|
|
|
6
|
-
import * as APIHooks from 'foremanReact/common/hooks/API/APIHooks';
|
|
7
6
|
import * as api from 'foremanReact/redux/API';
|
|
8
7
|
import JobWizardPageRerun from '../JobWizardPageRerun';
|
|
9
8
|
import * as selectors from '../JobWizardSelectors';
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
testSetup,
|
|
11
|
+
mockApi,
|
|
12
|
+
gqlMock,
|
|
13
|
+
jobInvocation,
|
|
14
|
+
bookmarksList,
|
|
15
|
+
} from './fixtures';
|
|
11
16
|
|
|
12
17
|
const store = testSetup(selectors, api);
|
|
13
18
|
mockApi(api);
|
|
14
|
-
jest.spyOn(APIHooks, 'useAPI');
|
|
15
|
-
APIHooks.useAPI.mockImplementation((action, url) => {
|
|
16
|
-
if (url === '/ui_job_wizard/job_invocation?id=57') {
|
|
17
|
-
return { response: jobInvocation, status: 'RESOLVED' };
|
|
18
|
-
}
|
|
19
|
-
return {};
|
|
20
|
-
});
|
|
21
19
|
|
|
22
20
|
describe('Job wizard fill', () => {
|
|
23
21
|
it('fill defaults into fields', async () => {
|
|
@@ -76,4 +74,50 @@ describe('Job wizard fill', () => {
|
|
|
76
74
|
}).value
|
|
77
75
|
).toBe('6');
|
|
78
76
|
});
|
|
77
|
+
|
|
78
|
+
it('fills bookmark on rerun when job used a bookmark', async () => {
|
|
79
|
+
const bookmark = bookmarksList[0];
|
|
80
|
+
const jobWithBookmark = {
|
|
81
|
+
...jobInvocation,
|
|
82
|
+
job: {
|
|
83
|
+
...jobInvocation.job,
|
|
84
|
+
targeting: {
|
|
85
|
+
...jobInvocation.job.targeting,
|
|
86
|
+
bookmark_id: bookmark.id,
|
|
87
|
+
search_query: null,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
selectors.selectRerunJobInvocationResponse.mockImplementation(
|
|
93
|
+
() => jobWithBookmark
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
render(
|
|
97
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
|
98
|
+
<Provider store={store}>
|
|
99
|
+
<JobWizardPageRerun
|
|
100
|
+
match={{
|
|
101
|
+
params: { id: '99' },
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
</Provider>
|
|
105
|
+
</MockedProvider>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
await act(async () => {
|
|
109
|
+
fireEvent.click(screen.getByText('Target hosts and inputs'));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const hostMethodSelect = screen.getByRole('button', {
|
|
113
|
+
name: 'host method',
|
|
114
|
+
});
|
|
115
|
+
expect(hostMethodSelect.textContent).toContain('Search query');
|
|
116
|
+
|
|
117
|
+
expect(screen.queryAllByText(bookmark.query)).toHaveLength(1);
|
|
118
|
+
|
|
119
|
+
selectors.selectRerunJobInvocationResponse.mockImplementation(
|
|
120
|
+
() => jobInvocation
|
|
121
|
+
);
|
|
122
|
+
});
|
|
79
123
|
});
|
|
@@ -116,6 +116,22 @@ export const jobTemplateResponse = {
|
|
|
116
116
|
],
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
+
export const bookmarksList = [
|
|
120
|
+
{ id: 19, name: 'my hosts', query: 'name ~ myhost', controller: 'hosts' },
|
|
121
|
+
{
|
|
122
|
+
id: 23,
|
|
123
|
+
name: 'active hosts',
|
|
124
|
+
query: 'last_report > "1 hour ago"',
|
|
125
|
+
controller: 'hosts',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 31,
|
|
129
|
+
name: 'dashboard default',
|
|
130
|
+
query: 'os = centos',
|
|
131
|
+
controller: 'dashboard',
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
119
135
|
export const jobCategories = ['Services', 'Ansible Commands', 'Puppet'];
|
|
120
136
|
|
|
121
137
|
export const testSetup = (selectors, api) => {
|
|
@@ -176,6 +192,14 @@ export const testSetup = (selectors, api) => {
|
|
|
176
192
|
subtotal: 3,
|
|
177
193
|
},
|
|
178
194
|
},
|
|
195
|
+
bookmarksPF4: {
|
|
196
|
+
hosts: {
|
|
197
|
+
results: bookmarksList,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
API: {
|
|
201
|
+
BOOKMARKS_HOSTS: { status: 'RESOLVED', results: [] },
|
|
202
|
+
},
|
|
179
203
|
});
|
|
180
204
|
return store;
|
|
181
205
|
};
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { useDispatch } from 'react-redux';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
3
|
import { get } from 'foremanReact/redux/API';
|
|
4
|
-
import {
|
|
4
|
+
import { getBookmarks } from 'foremanReact/components/PF4/Bookmarks/BookmarksActions';
|
|
5
|
+
import {
|
|
6
|
+
HOST_IDS,
|
|
7
|
+
REX_FEATURE,
|
|
8
|
+
hostsController,
|
|
9
|
+
hostsSearchProps,
|
|
10
|
+
} from './JobWizardConstants';
|
|
11
|
+
import { selectHostBookmarks } from './JobWizardSelectors';
|
|
5
12
|
import './JobWizard.scss';
|
|
6
13
|
|
|
7
14
|
export const useAutoFill = ({
|
|
@@ -9,11 +16,28 @@ export const useAutoFill = ({
|
|
|
9
16
|
setFills,
|
|
10
17
|
setSelectedTargets,
|
|
11
18
|
setHostsSearchQuery,
|
|
19
|
+
setSelectedBookmark,
|
|
12
20
|
setJobTemplateID,
|
|
13
21
|
setTemplateValues,
|
|
14
22
|
setAdvancedValues,
|
|
15
23
|
}) => {
|
|
16
24
|
const dispatch = useDispatch();
|
|
25
|
+
const bookmarks = useSelector(selectHostBookmarks);
|
|
26
|
+
const [pendingBookmarkId, setPendingBookmarkId] = useState(null);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (pendingBookmarkId === null || bookmarks.length === 0) return;
|
|
30
|
+
const bookmark = bookmarks.find(bm => bm.id === pendingBookmarkId);
|
|
31
|
+
if (bookmark) {
|
|
32
|
+
setSelectedBookmark({
|
|
33
|
+
id: bookmark.id,
|
|
34
|
+
name: bookmark.name,
|
|
35
|
+
query: bookmark.query,
|
|
36
|
+
});
|
|
37
|
+
setHostsSearchQuery(bookmark.query);
|
|
38
|
+
}
|
|
39
|
+
setPendingBookmarkId(null);
|
|
40
|
+
}, [bookmarks, pendingBookmarkId, setSelectedBookmark, setHostsSearchQuery]);
|
|
17
41
|
|
|
18
42
|
useEffect(() => {
|
|
19
43
|
if (Object.keys(fills).length) {
|
|
@@ -22,10 +46,12 @@ export const useAutoFill = ({
|
|
|
22
46
|
search,
|
|
23
47
|
feature,
|
|
24
48
|
template_id: templateID,
|
|
49
|
+
bookmark_id: bookmarkId,
|
|
25
50
|
...rest
|
|
26
51
|
} = { ...fills };
|
|
27
52
|
setFills({});
|
|
28
53
|
if (hostIds) {
|
|
54
|
+
setSelectedBookmark(null);
|
|
29
55
|
const hostSearch = Array.isArray(hostIds)
|
|
30
56
|
? `id = ${hostIds.join(' or id = ')}`
|
|
31
57
|
: `id = ${hostIds}`;
|
|
@@ -52,9 +78,34 @@ export const useAutoFill = ({
|
|
|
52
78
|
})
|
|
53
79
|
);
|
|
54
80
|
}
|
|
55
|
-
if (
|
|
81
|
+
if (bookmarkId) {
|
|
82
|
+
setSelectedTargets({
|
|
83
|
+
hosts: [],
|
|
84
|
+
hostCollections: [],
|
|
85
|
+
hostGroups: [],
|
|
86
|
+
});
|
|
87
|
+
const numericId = Number(bookmarkId);
|
|
88
|
+
if (bookmarks.length > 0) {
|
|
89
|
+
const bookmark = bookmarks.find(bm => bm.id === numericId);
|
|
90
|
+
if (bookmark) {
|
|
91
|
+
setSelectedBookmark({
|
|
92
|
+
id: bookmark.id,
|
|
93
|
+
name: bookmark.name,
|
|
94
|
+
query: bookmark.query,
|
|
95
|
+
});
|
|
96
|
+
setHostsSearchQuery(bookmark.query);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
setPendingBookmarkId(numericId);
|
|
100
|
+
dispatch(
|
|
101
|
+
getBookmarks(hostsSearchProps.bookmarks.url, hostsController)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
} else if ((search || search === '') && !hostIds?.length) {
|
|
56
105
|
// replace an empty string search with a dummy search query to match all hosts
|
|
57
106
|
// but only if search query was entered (based on presence of :search parameter)
|
|
107
|
+
|
|
108
|
+
setSelectedBookmark(null);
|
|
58
109
|
const hostSearch = search === '' ? "name != ''" : search;
|
|
59
110
|
setHostsSearchQuery(hostSearch);
|
|
60
111
|
}
|
|
@@ -100,9 +151,11 @@ export const useAutoFill = ({
|
|
|
100
151
|
setFills,
|
|
101
152
|
setSelectedTargets,
|
|
102
153
|
setHostsSearchQuery,
|
|
154
|
+
setSelectedBookmark,
|
|
103
155
|
setJobTemplateID,
|
|
104
156
|
setTemplateValues,
|
|
105
157
|
setAdvancedValues,
|
|
106
158
|
dispatch,
|
|
159
|
+
bookmarks,
|
|
107
160
|
]);
|
|
108
161
|
};
|
|
@@ -125,7 +125,7 @@ describe('AdvancedFields', () => {
|
|
|
125
125
|
const resourceSelectField = screen.getByLabelText(
|
|
126
126
|
'adv resource select toggle'
|
|
127
127
|
);
|
|
128
|
-
const searchField = screen.getByPlaceholderText('
|
|
128
|
+
const searchField = screen.getByPlaceholderText('Search');
|
|
129
129
|
const dateField = screen.getByLabelText('adv date datepicker');
|
|
130
130
|
const timeField = screen.getByLabelText('adv date timepicker');
|
|
131
131
|
|
|
@@ -403,6 +403,11 @@ describe('AdvancedFields', () => {
|
|
|
403
403
|
|
|
404
404
|
jest.advanceTimersByTime(10000);
|
|
405
405
|
});
|
|
406
|
-
|
|
406
|
+
const actions = newStore.getActions();
|
|
407
|
+
const resourceSearchAction = actions.filter(
|
|
408
|
+
action => action.key === 'ForemanTasksTask'
|
|
409
|
+
);
|
|
410
|
+
expect(resourceSearchAction).toHaveLength(2);
|
|
411
|
+
expect(String(resourceSearchAction[1].url)).toContain('name=some+search');
|
|
407
412
|
});
|
|
408
413
|
});
|
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
3
4
|
import SearchBar from 'foremanReact/components/SearchBar';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { hostQuerySearchID, hostsSearchProps } from '../../JobWizardConstants';
|
|
6
|
+
import { selectHostBookmarks } from '../../JobWizardSelectors';
|
|
7
|
+
|
|
8
|
+
export const HostSearch = ({ value, setValue, onBookmarkMatch }) => {
|
|
9
|
+
const bookmarks = useSelector(selectHostBookmarks);
|
|
10
|
+
|
|
11
|
+
const handleSearchChange = search => {
|
|
12
|
+
setValue(search);
|
|
13
|
+
onBookmarkMatch(null);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const handleBookmarkSearch = query => {
|
|
17
|
+
const matched = bookmarks.find(
|
|
18
|
+
bookmark => bookmark.query && bookmark.query.trim() === query.trim()
|
|
19
|
+
);
|
|
20
|
+
if (matched) {
|
|
21
|
+
onBookmarkMatch({
|
|
22
|
+
id: matched.id,
|
|
23
|
+
name: matched.name,
|
|
24
|
+
query: matched.query,
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
onBookmarkMatch(null);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
6
30
|
|
|
7
|
-
export const HostSearch = ({ value, setValue }) => {
|
|
8
|
-
const props = getControllerSearchProps(hostsController, hostQuerySearchID);
|
|
9
31
|
return (
|
|
10
32
|
<div className="foreman-search-field">
|
|
11
33
|
<SearchBar
|
|
12
34
|
data={{
|
|
13
|
-
...
|
|
35
|
+
...hostsSearchProps,
|
|
14
36
|
autocomplete: {
|
|
15
37
|
id: hostQuerySearchID,
|
|
16
38
|
url: '/hosts/auto_complete_search',
|
|
17
39
|
searchQuery: value,
|
|
18
40
|
},
|
|
19
41
|
}}
|
|
20
|
-
onSearch={
|
|
21
|
-
onSearchChange={
|
|
42
|
+
onSearch={handleBookmarkSearch}
|
|
43
|
+
onSearchChange={handleSearchChange}
|
|
44
|
+
bookmarksPosition="right"
|
|
22
45
|
/>
|
|
23
46
|
</div>
|
|
24
47
|
);
|
|
@@ -27,4 +50,5 @@ export const HostSearch = ({ value, setValue }) => {
|
|
|
27
50
|
HostSearch.propTypes = {
|
|
28
51
|
value: PropTypes.string.isRequired,
|
|
29
52
|
setValue: PropTypes.func.isRequired,
|
|
53
|
+
onBookmarkMatch: PropTypes.func.isRequired,
|
|
30
54
|
};
|
|
@@ -6,7 +6,12 @@ import * as api from 'foremanReact/redux/API';
|
|
|
6
6
|
import * as routerSelectors from 'foremanReact/routes/RouterSelector';
|
|
7
7
|
import { JobWizard } from '../../../JobWizard';
|
|
8
8
|
import * as selectors from '../../../JobWizardSelectors';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
testSetup,
|
|
11
|
+
mockApi,
|
|
12
|
+
gqlMock,
|
|
13
|
+
bookmarksList,
|
|
14
|
+
} from '../../../__tests__/fixtures';
|
|
10
15
|
|
|
11
16
|
const store = testSetup(selectors, api);
|
|
12
17
|
mockApi(api);
|
|
@@ -183,6 +188,95 @@ describe('Hosts', () => {
|
|
|
183
188
|
expect(screen.queryAllByText('os=gnome')).toHaveLength(1);
|
|
184
189
|
});
|
|
185
190
|
|
|
191
|
+
it('submits bookmark_id when search matches a bookmark', async () => {
|
|
192
|
+
const bookmark = bookmarksList[0];
|
|
193
|
+
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
|
194
|
+
search: '',
|
|
195
|
+
}));
|
|
196
|
+
const bookmarkStore = testSetup(selectors, api);
|
|
197
|
+
mockApi(api);
|
|
198
|
+
|
|
199
|
+
render(
|
|
200
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
|
201
|
+
<Provider store={bookmarkStore}>
|
|
202
|
+
<JobWizard />
|
|
203
|
+
</Provider>
|
|
204
|
+
</MockedProvider>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await act(async () => {
|
|
208
|
+
fireEvent.click(screen.getByText('Target hosts and inputs'));
|
|
209
|
+
});
|
|
210
|
+
await act(async () => {
|
|
211
|
+
fireEvent.click(screen.getByRole('button', { name: 'host method' }));
|
|
212
|
+
});
|
|
213
|
+
await act(async () => {
|
|
214
|
+
fireEvent.click(screen.getByText('Search query'));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await act(async () => {
|
|
218
|
+
fireEvent.click(
|
|
219
|
+
screen.getByRole('button', { name: 'bookmarks dropdown toggle' })
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
await act(async () => {
|
|
223
|
+
fireEvent.click(screen.getByText(bookmark.name));
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await act(async () => {
|
|
227
|
+
fireEvent.click(screen.getByText('Review details'));
|
|
228
|
+
});
|
|
229
|
+
await act(async () => {
|
|
230
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const submitAction = bookmarkStore
|
|
234
|
+
.getActions()
|
|
235
|
+
.find(action => action?.key === 'JOB_INVOCATION');
|
|
236
|
+
expect(submitAction).toBeDefined();
|
|
237
|
+
const { job_invocation: invocation } = submitAction.params;
|
|
238
|
+
expect(invocation.bookmark_id).toBe(bookmark.id);
|
|
239
|
+
expect(invocation.search_query).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('does not submit bookmark_id when search does not match a bookmark', async () => {
|
|
243
|
+
const customQuery = 'some custom query';
|
|
244
|
+
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
|
245
|
+
search: `search=${encodeURIComponent(customQuery)}`,
|
|
246
|
+
}));
|
|
247
|
+
const customStore = testSetup(selectors, api);
|
|
248
|
+
mockApi(api);
|
|
249
|
+
|
|
250
|
+
render(
|
|
251
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
|
252
|
+
<Provider store={customStore}>
|
|
253
|
+
<JobWizard />
|
|
254
|
+
</Provider>
|
|
255
|
+
</MockedProvider>
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
await act(async () => {
|
|
259
|
+
fireEvent.click(screen.getByText('Target hosts and inputs'));
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(screen.queryAllByText(customQuery)).toHaveLength(1);
|
|
263
|
+
|
|
264
|
+
await act(async () => {
|
|
265
|
+
fireEvent.click(screen.getByText('Review details'));
|
|
266
|
+
});
|
|
267
|
+
await act(async () => {
|
|
268
|
+
fireEvent.click(screen.getByText('Submit'));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const submitAction = customStore
|
|
272
|
+
.getActions()
|
|
273
|
+
.find(action => action?.key === 'JOB_INVOCATION');
|
|
274
|
+
expect(submitAction).toBeDefined();
|
|
275
|
+
const { job_invocation: invocation } = submitAction.params;
|
|
276
|
+
expect(invocation.bookmark_id).toBeNull();
|
|
277
|
+
expect(invocation.search_query).toBe(customQuery);
|
|
278
|
+
});
|
|
279
|
+
|
|
186
280
|
it('input fill from url', async () => {
|
|
187
281
|
const inputText = 'test text';
|
|
188
282
|
const advancedInputText = 'test adv text';
|
|
@@ -51,6 +51,7 @@ const HostsAndInputs = ({
|
|
|
51
51
|
setSelected,
|
|
52
52
|
hostsSearchQuery,
|
|
53
53
|
setHostsSearchQuery,
|
|
54
|
+
setSelectedBookmark,
|
|
54
55
|
}) => {
|
|
55
56
|
const defaultHostMethod = hostsSearchQuery.length
|
|
56
57
|
? hostMethods.searchQuery
|
|
@@ -132,6 +133,7 @@ const HostsAndInputs = ({
|
|
|
132
133
|
|
|
133
134
|
const clearSearch = () => {
|
|
134
135
|
setHostsSearchQuery('');
|
|
136
|
+
setSelectedBookmark(null);
|
|
135
137
|
};
|
|
136
138
|
const [errorText, setErrorText] = useState(
|
|
137
139
|
__('Please select at least one host')
|
|
@@ -156,6 +158,7 @@ const HostsAndInputs = ({
|
|
|
156
158
|
className="target-method-select"
|
|
157
159
|
toggleIcon={<FilterIcon />}
|
|
158
160
|
fieldId="host_methods"
|
|
161
|
+
toggleAriaLabel={__('host method')}
|
|
159
162
|
options={Object.values(hostMethods).filter(method => {
|
|
160
163
|
if (method === hostMethods.hostCollections && !withKatello) {
|
|
161
164
|
return false;
|
|
@@ -186,6 +189,7 @@ const HostsAndInputs = ({
|
|
|
186
189
|
<HostSearch
|
|
187
190
|
setValue={setHostsSearchQuery}
|
|
188
191
|
value={hostsSearchQuery}
|
|
192
|
+
onBookmarkMatch={setSelectedBookmark}
|
|
189
193
|
/>
|
|
190
194
|
)}
|
|
191
195
|
{hostMethod === hostMethods.hosts && (
|
|
@@ -286,6 +290,7 @@ HostsAndInputs.propTypes = {
|
|
|
286
290
|
setSelected: PropTypes.func.isRequired,
|
|
287
291
|
hostsSearchQuery: PropTypes.string.isRequired,
|
|
288
292
|
setHostsSearchQuery: PropTypes.func.isRequired,
|
|
293
|
+
setSelectedBookmark: PropTypes.func.isRequired,
|
|
289
294
|
};
|
|
290
295
|
|
|
291
296
|
export default HostsAndInputs;
|
data/webpack/JobWizard/submit.js
CHANGED
|
@@ -2,6 +2,14 @@ import { post } from 'foremanReact/redux/API';
|
|
|
2
2
|
import { repeatTypes, JOB_INVOCATION } from './JobWizardConstants';
|
|
3
3
|
import { buildHostQuery } from './steps/HostsAndInputs/buildHostQuery';
|
|
4
4
|
|
|
5
|
+
const hasExplicitTargets = selectedTargets =>
|
|
6
|
+
selectedTargets.hosts.length > 0 ||
|
|
7
|
+
selectedTargets.hostCollections.length > 0 ||
|
|
8
|
+
selectedTargets.hostGroups.length > 0;
|
|
9
|
+
|
|
10
|
+
const shouldSendBookmark = (selectedBookmark, selectedTargets) =>
|
|
11
|
+
selectedBookmark && !hasExplicitTargets(selectedTargets);
|
|
12
|
+
|
|
5
13
|
export const submit = ({
|
|
6
14
|
jobTemplateID,
|
|
7
15
|
templateValues,
|
|
@@ -9,6 +17,7 @@ export const submit = ({
|
|
|
9
17
|
scheduleValue,
|
|
10
18
|
selectedTargets,
|
|
11
19
|
hostsSearchQuery,
|
|
20
|
+
selectedBookmark,
|
|
12
21
|
location,
|
|
13
22
|
organization,
|
|
14
23
|
feature,
|
|
@@ -110,8 +119,12 @@ export const submit = ({
|
|
|
110
119
|
concurrency_control: {
|
|
111
120
|
concurrency_level: concurrencyLevel,
|
|
112
121
|
},
|
|
113
|
-
bookmark_id:
|
|
114
|
-
|
|
122
|
+
bookmark_id: shouldSendBookmark(selectedBookmark, selectedTargets)
|
|
123
|
+
? selectedBookmark.id
|
|
124
|
+
: null,
|
|
125
|
+
search_query: shouldSendBookmark(selectedBookmark, selectedTargets)
|
|
126
|
+
? null
|
|
127
|
+
: buildHostQuery(selectedTargets, hostsSearchQuery),
|
|
115
128
|
description_format: description,
|
|
116
129
|
execution_timeout_interval: timeoutToKill,
|
|
117
130
|
feature,
|
|
@@ -14,6 +14,7 @@ exports[`TargetingHostsPage renders 1`] = `
|
|
|
14
14
|
md={6}
|
|
15
15
|
>
|
|
16
16
|
<SearchBar
|
|
17
|
+
bookmarksPosition="left"
|
|
17
18
|
data={
|
|
18
19
|
Object {
|
|
19
20
|
"autocomplete": Object {
|
|
@@ -31,9 +32,12 @@ exports[`TargetingHostsPage renders 1`] = `
|
|
|
31
32
|
"controller": "hosts",
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
initialQuery=""
|
|
36
|
+
name={null}
|
|
34
37
|
onBookmarkClick={[Function]}
|
|
35
|
-
onChange={[Function]}
|
|
36
38
|
onSearch={[Function]}
|
|
39
|
+
onSearchChange={[Function]}
|
|
40
|
+
restrictedSearchQuery={[Function]}
|
|
37
41
|
/>
|
|
38
42
|
</Col>
|
|
39
43
|
</Row>
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_remote_execution
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.6.
|
|
4
|
+
version: 16.6.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foreman Remote Execution team
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-06-23 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: deface
|
|
@@ -354,6 +354,7 @@ files:
|
|
|
354
354
|
- test/graphql/queries/job_invocation_query_test.rb
|
|
355
355
|
- test/graphql/queries/job_invocations_query_test.rb
|
|
356
356
|
- test/helpers/remote_execution_helper_test.rb
|
|
357
|
+
- test/integration/job_wizard_category_js_test.rb
|
|
357
358
|
- test/support/remote_execution_helper.rb
|
|
358
359
|
- test/test_plugin_helper.rb
|
|
359
360
|
- test/unit/actions/run_host_job_test.rb
|
|
@@ -420,7 +421,6 @@ files:
|
|
|
420
421
|
- webpack/JobWizard/steps/AdvancedFields/DescriptionField.js
|
|
421
422
|
- webpack/JobWizard/steps/AdvancedFields/Fields.js
|
|
422
423
|
- webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js
|
|
423
|
-
- webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap
|
|
424
424
|
- webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js
|
|
425
425
|
- webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js
|
|
426
426
|
- webpack/JobWizard/steps/CategoryAndTemplate/index.js
|
|
@@ -475,7 +475,6 @@ files:
|
|
|
475
475
|
- webpack/__mocks__/foremanReact/components/Head/index.js
|
|
476
476
|
- webpack/__mocks__/foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState.js
|
|
477
477
|
- webpack/__mocks__/foremanReact/components/Pagination.js
|
|
478
|
-
- webpack/__mocks__/foremanReact/components/SearchBar.js
|
|
479
478
|
- webpack/__mocks__/foremanReact/components/ToastsList/index.js
|
|
480
479
|
- webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js
|
|
481
480
|
- webpack/__mocks__/foremanReact/constants.js
|
data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`AdvancedFields search resources action: resource search 1`] = `
|
|
4
|
-
Array [
|
|
5
|
-
Object {
|
|
6
|
-
"key": "JOB_CATEGORIES",
|
|
7
|
-
"type": "get",
|
|
8
|
-
"url": "/ui_job_wizard/categories",
|
|
9
|
-
},
|
|
10
|
-
Object {
|
|
11
|
-
"key": "HOST_IDS",
|
|
12
|
-
"params": Object {
|
|
13
|
-
"search": "id = 105 or id = 37",
|
|
14
|
-
},
|
|
15
|
-
"type": "get",
|
|
16
|
-
"url": "/api/hosts",
|
|
17
|
-
},
|
|
18
|
-
Object {
|
|
19
|
-
"key": "JOB_TEMPLATES",
|
|
20
|
-
"type": "get",
|
|
21
|
-
"url": URI {
|
|
22
|
-
"_deferred_build": true,
|
|
23
|
-
"_parts": Object {
|
|
24
|
-
"duplicateQueryParameters": false,
|
|
25
|
-
"escapeQuerySpace": true,
|
|
26
|
-
"fragment": null,
|
|
27
|
-
"hostname": null,
|
|
28
|
-
"password": null,
|
|
29
|
-
"path": "foreman/api/v2/job_templates",
|
|
30
|
-
"port": null,
|
|
31
|
-
"preventInvalidHostname": false,
|
|
32
|
-
"protocol": null,
|
|
33
|
-
"query": "search=job_category%3D%22Ansible+Commands%22&per_page=all",
|
|
34
|
-
"urn": null,
|
|
35
|
-
"username": null,
|
|
36
|
-
},
|
|
37
|
-
"_string": "",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
Object {
|
|
41
|
-
"key": "JOB_TEMPLATE",
|
|
42
|
-
"type": "get",
|
|
43
|
-
"url": "/ui_job_wizard/template/178",
|
|
44
|
-
},
|
|
45
|
-
Object {
|
|
46
|
-
"key": "ForemanTasksTask",
|
|
47
|
-
"type": "get",
|
|
48
|
-
"url": URI {
|
|
49
|
-
"_deferred_build": true,
|
|
50
|
-
"_parts": Object {
|
|
51
|
-
"duplicateQueryParameters": false,
|
|
52
|
-
"escapeQuerySpace": true,
|
|
53
|
-
"fragment": null,
|
|
54
|
-
"hostname": null,
|
|
55
|
-
"password": null,
|
|
56
|
-
"path": "/ui_job_wizard/resources",
|
|
57
|
-
"port": null,
|
|
58
|
-
"preventInvalidHostname": false,
|
|
59
|
-
"protocol": null,
|
|
60
|
-
"query": "resource=ForemanTasks%3A%3ATask",
|
|
61
|
-
"urn": null,
|
|
62
|
-
"username": null,
|
|
63
|
-
},
|
|
64
|
-
"_string": "",
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
Object {
|
|
68
|
-
"key": "ForemanTasksTask",
|
|
69
|
-
"type": "get",
|
|
70
|
-
"url": URI {
|
|
71
|
-
"_deferred_build": true,
|
|
72
|
-
"_parts": Object {
|
|
73
|
-
"duplicateQueryParameters": false,
|
|
74
|
-
"escapeQuerySpace": true,
|
|
75
|
-
"fragment": null,
|
|
76
|
-
"hostname": null,
|
|
77
|
-
"password": null,
|
|
78
|
-
"path": "/ui_job_wizard/resources",
|
|
79
|
-
"port": null,
|
|
80
|
-
"preventInvalidHostname": false,
|
|
81
|
-
"protocol": null,
|
|
82
|
-
"query": "resource=ForemanTasks%3A%3ATask&name=some+search",
|
|
83
|
-
"urn": null,
|
|
84
|
-
"username": null,
|
|
85
|
-
},
|
|
86
|
-
"_string": "",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
`;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
|
|
4
|
-
const SearchBar = ({ onChange }) => (
|
|
5
|
-
<input
|
|
6
|
-
className="foreman-search"
|
|
7
|
-
onChange={onChange}
|
|
8
|
-
placeholder="Filter..."
|
|
9
|
-
/>
|
|
10
|
-
);
|
|
11
|
-
export default SearchBar;
|
|
12
|
-
|
|
13
|
-
SearchBar.propTypes = {
|
|
14
|
-
onChange: PropTypes.func,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
SearchBar.defaultProps = {
|
|
18
|
-
onChange: () => null,
|
|
19
|
-
};
|