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 +4 -4
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/webpack/JobInvocationDetail/JobInvocationActions.js +7 -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 +2 -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: c9b021c23fae39af103f3ac3bdb0fd3e208b74fb8ad234f66f7770a5e4dfc78f
|
|
4
|
+
data.tar.gz: 4fd6498051640caae5cf6e4580d01106883a0f33d486b672a0d77ed49205e52c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf4e127076278dca8d3ec7d4b86a625a89bb95c4a4c5cd728e9207e8ff3151f983965061498bbfd813c6d9d08f49ec59f9ba2bc06be113f8e721e8f04dfc0285
|
|
7
|
+
data.tar.gz: 0124e5ae5e0f484269adf05e9e9e3aa10e1262c4c6f635707dee035f217a5206565bc231fa65dee356609ce6e9db332d7c1faf96b8d1b7b3760483171997e5fd
|
|
@@ -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.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-
|
|
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
|
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
|
-
};
|