foreman_remote_execution 16.6.3 → 16.6.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fd2360fc3d4b48170494112c77f2d07079e94e52c8c6c9b5fcbe2402a690a49
4
- data.tar.gz: ef36ba8ac16d062f24ee3fd78954d26c86c51001c3b6c8e85dcc1c9d01667159
3
+ metadata.gz: c9b021c23fae39af103f3ac3bdb0fd3e208b74fb8ad234f66f7770a5e4dfc78f
4
+ data.tar.gz: 4fd6498051640caae5cf6e4580d01106883a0f33d486b672a0d77ed49205e52c
5
5
  SHA512:
6
- metadata.gz: 96dcba56a99ca8340051cd788eb0115dde2dbcd8ff7cfce5dc82ca6f3679300cebe0c98e74d1fb9c61a09d0e2293d48140996b717114eda87c414968272dcd07
7
- data.tar.gz: 5e33d478ff9bbaaab3edfac6570e378407a9bf501698ad83b31aecfd0b9261293c1d8229d5cd0f014587478a238341af0fb83defdeb5f58b193d21cd8669335a
6
+ metadata.gz: bf4e127076278dca8d3ec7d4b86a625a89bb95c4a4c5cd728e9207e8ff3151f983965061498bbfd813c6d9d08f49ec59f9ba2bc06be113f8e721e8f04dfc0285
7
+ data.tar.gz: 0124e5ae5e0f484269adf05e9e9e3aa10e1262c4c6f635707dee035f217a5206565bc231fa65dee356609ce6e9db332d7c1faf96b8d1b7b3760483171997e5fd
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '16.6.3'.freeze
2
+ VERSION = '16.6.4'.freeze
3
3
  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 { testSetup, mockApi, gqlMock, jobInvocation } from './fixtures';
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 { HOST_IDS, REX_FEATURE } from './JobWizardConstants';
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 ((search || search === '') && !hostIds?.length) {
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('Filter...');
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
- expect(newStore.getActions()).toMatchSnapshot('resource search');
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 { getControllerSearchProps } from 'foremanReact/constants';
5
- import { hostsController, hostQuerySearchID } from '../../JobWizardConstants';
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
- ...props,
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={null}
21
- onSearchChange={search => setValue(search)}
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 { testSetup, mockApi, gqlMock } from '../../../__tests__/fixtures';
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;
@@ -55,6 +55,8 @@ const store = mockStore({
55
55
  subtotal: 3,
56
56
  },
57
57
  },
58
+ bookmarksPF4: {},
59
+ API: {},
58
60
  });
59
61
  jest.useFakeTimers();
60
62
 
@@ -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: null,
114
- search_query: buildHostQuery(selectedTargets, hostsSearchQuery),
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.3
4
+ version: 16.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-15 00:00:00.000000000 Z
10
+ date: 2026-06-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: deface
@@ -420,7 +420,6 @@ files:
420
420
  - webpack/JobWizard/steps/AdvancedFields/DescriptionField.js
421
421
  - webpack/JobWizard/steps/AdvancedFields/Fields.js
422
422
  - webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js
423
- - webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap
424
423
  - webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js
425
424
  - webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js
426
425
  - webpack/JobWizard/steps/CategoryAndTemplate/index.js
@@ -475,7 +474,6 @@ files:
475
474
  - webpack/__mocks__/foremanReact/components/Head/index.js
476
475
  - webpack/__mocks__/foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState.js
477
476
  - webpack/__mocks__/foremanReact/components/Pagination.js
478
- - webpack/__mocks__/foremanReact/components/SearchBar.js
479
477
  - webpack/__mocks__/foremanReact/components/ToastsList/index.js
480
478
  - webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js
481
479
  - webpack/__mocks__/foremanReact/constants.js
@@ -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
- };