foreman_remote_execution 8.2.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
- data/app/controllers/job_invocations_controller.rb +20 -1
- data/app/controllers/ui_job_wizard_controller.rb +3 -1
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
- data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
- data/app/views/job_invocations/_form.html.erb +1 -1
- data/app/views/job_invocations/show.html.erb +1 -1
- data/app/views/job_invocations/welcome.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +1 -1
- data/lib/foreman_remote_execution/engine.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/action_names.rb +2 -2
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +266 -154
- data/locale/en/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en/foreman_remote_execution.po +132 -24
- data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en_GB/foreman_remote_execution.po +149 -41
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +320 -210
- data/locale/foreman_remote_execution.pot +394 -211
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +353 -241
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +368 -261
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +161 -53
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +335 -225
- data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ru/foreman_remote_execution.po +161 -53
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +465 -359
- data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_TW/foreman_remote_execution.po +162 -54
- data/webpack/JobWizard/JobWizard.js +52 -10
- data/webpack/JobWizard/JobWizardConstants.js +1 -1
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +8 -0
- data/webpack/JobWizard/__tests__/fixtures.js +5 -0
- data/webpack/JobWizard/__tests__/integration.test.js +15 -0
- data/webpack/JobWizard/__tests__/validation.test.js +27 -0
- data/webpack/JobWizard/autofill.js +1 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +19 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +8 -0
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +3 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +32 -2
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +16 -10
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +51 -3
- data/webpack/JobWizard/steps/Schedule/QueryType.js +1 -1
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +0 -1
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +25 -5
- data/webpack/JobWizard/steps/form/DateTimePicker.js +0 -1
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +0 -1
- data/webpack/JobWizard/steps/form/SelectField.js +0 -1
- data/webpack/JobWizard/submit.js +13 -2
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
- data/webpack/react_app/components/RecentJobsCard/constants.js +2 -2
- metadata +6 -6
@@ -209,6 +209,11 @@ export const JobWizard = ({ rerunData }) => {
|
|
209
209
|
!templateError &&
|
210
210
|
!!jobTemplateID &&
|
211
211
|
templateResponse.job_template;
|
212
|
+
const areHostsSelected =
|
213
|
+
selectedTargets.hosts.length > 0 ||
|
214
|
+
selectedTargets.hostCollections.length > 0 ||
|
215
|
+
selectedTargets.hostGroups.length > 0 ||
|
216
|
+
hostsSearchQuery.length > 0;
|
212
217
|
const steps = [
|
213
218
|
{
|
214
219
|
name: WIZARD_TITLES.categoryAndTemplate,
|
@@ -238,7 +243,7 @@ export const JobWizard = ({ rerunData }) => {
|
|
238
243
|
/>
|
239
244
|
),
|
240
245
|
canJumpTo: isTemplate,
|
241
|
-
enableNext: isTemplate && valid.hostsAndInputs,
|
246
|
+
enableNext: isTemplate && valid.hostsAndInputs && areHostsSelected,
|
242
247
|
},
|
243
248
|
{
|
244
249
|
name: WIZARD_TITLES.advanced,
|
@@ -254,14 +259,26 @@ export const JobWizard = ({ rerunData }) => {
|
|
254
259
|
templateValues={templateValues}
|
255
260
|
/>
|
256
261
|
),
|
257
|
-
canJumpTo: isTemplate && valid.hostsAndInputs,
|
258
|
-
enableNext:
|
262
|
+
canJumpTo: isTemplate && valid.hostsAndInputs && areHostsSelected,
|
263
|
+
enableNext:
|
264
|
+
isTemplate &&
|
265
|
+
valid.hostsAndInputs &&
|
266
|
+
areHostsSelected &&
|
267
|
+
valid.advanced,
|
259
268
|
},
|
260
269
|
{
|
261
270
|
name: WIZARD_TITLES.schedule,
|
262
|
-
canJumpTo:
|
271
|
+
canJumpTo:
|
272
|
+
isTemplate &&
|
273
|
+
valid.hostsAndInputs &&
|
274
|
+
areHostsSelected &&
|
275
|
+
valid.advanced,
|
263
276
|
enableNext:
|
264
|
-
isTemplate &&
|
277
|
+
isTemplate &&
|
278
|
+
valid.hostsAndInputs &&
|
279
|
+
areHostsSelected &&
|
280
|
+
valid.advanced &&
|
281
|
+
valid.schedule,
|
265
282
|
steps: [
|
266
283
|
{
|
267
284
|
name: WIZARD_TITLES.typeOfExecution,
|
@@ -278,9 +295,17 @@ export const JobWizard = ({ rerunData }) => {
|
|
278
295
|
}}
|
279
296
|
/>
|
280
297
|
),
|
281
|
-
canJumpTo:
|
298
|
+
canJumpTo:
|
299
|
+
isTemplate &&
|
300
|
+
valid.hostsAndInputs &&
|
301
|
+
areHostsSelected &&
|
302
|
+
valid.advanced,
|
282
303
|
|
283
|
-
enableNext:
|
304
|
+
enableNext:
|
305
|
+
isTemplate &&
|
306
|
+
valid.hostsAndInputs &&
|
307
|
+
areHostsSelected &&
|
308
|
+
valid.advanced,
|
284
309
|
},
|
285
310
|
...(scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE
|
286
311
|
? [
|
@@ -298,10 +323,15 @@ export const JobWizard = ({ rerunData }) => {
|
|
298
323
|
}}
|
299
324
|
/>
|
300
325
|
),
|
301
|
-
canJumpTo:
|
326
|
+
canJumpTo:
|
327
|
+
isTemplate &&
|
328
|
+
valid.hostsAndInputs &&
|
329
|
+
areHostsSelected &&
|
330
|
+
valid.advanced,
|
302
331
|
enableNext:
|
303
332
|
isTemplate &&
|
304
333
|
valid.hostsAndInputs &&
|
334
|
+
areHostsSelected &&
|
305
335
|
valid.advanced &&
|
306
336
|
valid.schedule,
|
307
337
|
},
|
@@ -323,10 +353,15 @@ export const JobWizard = ({ rerunData }) => {
|
|
323
353
|
}}
|
324
354
|
/>
|
325
355
|
),
|
326
|
-
canJumpTo:
|
356
|
+
canJumpTo:
|
357
|
+
isTemplate &&
|
358
|
+
valid.hostsAndInputs &&
|
359
|
+
areHostsSelected &&
|
360
|
+
valid.advanced,
|
327
361
|
enableNext:
|
328
362
|
isTemplate &&
|
329
363
|
valid.hostsAndInputs &&
|
364
|
+
areHostsSelected &&
|
330
365
|
valid.advanced &&
|
331
366
|
valid.schedule,
|
332
367
|
},
|
@@ -349,10 +384,15 @@ export const JobWizard = ({ rerunData }) => {
|
|
349
384
|
),
|
350
385
|
nextButtonText: 'Run',
|
351
386
|
canJumpTo:
|
352
|
-
isTemplate &&
|
387
|
+
isTemplate &&
|
388
|
+
valid.advanced &&
|
389
|
+
valid.hostsAndInputs &&
|
390
|
+
areHostsSelected &&
|
391
|
+
valid.schedule,
|
353
392
|
enableNext:
|
354
393
|
isTemplate &&
|
355
394
|
valid.hostsAndInputs &&
|
395
|
+
areHostsSelected &&
|
356
396
|
valid.advanced &&
|
357
397
|
valid.schedule &&
|
358
398
|
!isSubmitting,
|
@@ -379,6 +419,8 @@ export const JobWizard = ({ rerunData }) => {
|
|
379
419
|
location,
|
380
420
|
organization,
|
381
421
|
feature: routerSearch?.feature,
|
422
|
+
provider: templateResponse.provider_name,
|
423
|
+
advancedInputs: templateResponse.advanced_template_inputs,
|
382
424
|
});
|
383
425
|
}}
|
384
426
|
/>
|
@@ -23,7 +23,7 @@ export const SCHEDULE_TYPES = {
|
|
23
23
|
};
|
24
24
|
|
25
25
|
export const WIZARD_TITLES = {
|
26
|
-
categoryAndTemplate: __('Category and
|
26
|
+
categoryAndTemplate: __('Category and template'),
|
27
27
|
hostsAndInputs: __('Target hosts and inputs'),
|
28
28
|
advanced: __('Advanced fields'),
|
29
29
|
schedule: __('Schedule'),
|
@@ -7,6 +7,14 @@ Array [
|
|
7
7
|
"type": "get",
|
8
8
|
"url": "/ui_job_wizard/categories",
|
9
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
|
+
},
|
10
18
|
Object {
|
11
19
|
"key": "JOB_TEMPLATES",
|
12
20
|
"type": "get",
|
@@ -18,6 +18,15 @@ import {
|
|
18
18
|
const store = testSetup(selectors, api);
|
19
19
|
|
20
20
|
describe('Job wizard fill', () => {
|
21
|
+
beforeEach(() => {
|
22
|
+
jest.spyOn(selectors, 'selectRouterSearch');
|
23
|
+
selectors.selectRouterSearch.mockImplementation(() => ({
|
24
|
+
'host_ids[]': ['105', '37'],
|
25
|
+
}));
|
26
|
+
});
|
27
|
+
afterEach(() => {
|
28
|
+
selectors.selectRouterSearch.mockRestore();
|
29
|
+
});
|
21
30
|
it('should select template', async () => {
|
22
31
|
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
23
32
|
if (action.key === 'JOB_CATEGORIES') {
|
@@ -33,7 +42,13 @@ describe('Job wizard fill', () => {
|
|
33
42
|
handleSuccess({
|
34
43
|
data: jobTemplate,
|
35
44
|
});
|
45
|
+
} else if (action.key === 'HOST_IDS') {
|
46
|
+
handleSuccess &&
|
47
|
+
handleSuccess({
|
48
|
+
data: { results: [{ name: 'host1' }, { name: 'host3' }] },
|
49
|
+
});
|
36
50
|
}
|
51
|
+
|
37
52
|
return { type: 'get', ...action };
|
38
53
|
});
|
39
54
|
selectors.selectJobTemplate.mockRestore();
|
@@ -41,11 +41,26 @@ describe('Job wizard validation', () => {
|
|
41
41
|
expect(screen.getByText(WIZARD_TITLES.review)).toBeDisabled();
|
42
42
|
await act(async () => {
|
43
43
|
fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
|
44
|
+
await new Promise(resolve => setTimeout(resolve, 0)); // to resolve gql
|
44
45
|
});
|
46
|
+
const select = name =>
|
47
|
+
screen.getByRole('button', { name: `${name} toggle` });
|
48
|
+
fireEvent.click(select('hosts'));
|
49
|
+
await act(async () => {
|
50
|
+
fireEvent.click(screen.getByText('host1'));
|
51
|
+
});
|
52
|
+
|
53
|
+
expect(screen.getByText(WIZARD_TITLES.advanced)).toBeDisabled();
|
54
|
+
expect(screen.getByText(WIZARD_TITLES.schedule)).toBeDisabled();
|
55
|
+
expect(screen.getByText(WIZARD_TITLES.review)).toBeDisabled();
|
45
56
|
const textField = screen.getByLabelText('plain hidden', {
|
46
57
|
selector: 'textarea',
|
47
58
|
});
|
48
59
|
await act(async () => {
|
60
|
+
fireEvent.click(
|
61
|
+
// Close the select
|
62
|
+
select('hosts')
|
63
|
+
);
|
49
64
|
await fireEvent.change(textField, {
|
50
65
|
target: { value: 'text' },
|
51
66
|
});
|
@@ -85,8 +100,20 @@ describe('Job wizard validation', () => {
|
|
85
100
|
// setup
|
86
101
|
await act(async () => {
|
87
102
|
fireEvent.click(screen.getByText(WIZARD_TITLES.hostsAndInputs));
|
103
|
+
await new Promise(resolve => setTimeout(resolve, 0)); // to resolve gql
|
88
104
|
});
|
105
|
+
|
106
|
+
const select = name =>
|
107
|
+
screen.getByRole('button', { name: `${name} toggle` });
|
108
|
+
fireEvent.click(select('hosts'));
|
89
109
|
await act(async () => {
|
110
|
+
fireEvent.click(screen.getByText('host1'));
|
111
|
+
});
|
112
|
+
await act(async () => {
|
113
|
+
fireEvent.click(
|
114
|
+
// Close the host select
|
115
|
+
select('hosts')
|
116
|
+
);
|
90
117
|
await fireEvent.change(
|
91
118
|
screen.getByLabelText('plain hidden', {
|
92
119
|
selector: 'textarea',
|
@@ -72,6 +72,7 @@ export const useAutoFill = ({
|
|
72
72
|
if (input) {
|
73
73
|
if (typeof rest[key] === 'string') {
|
74
74
|
setTemplateValues(prev => ({ ...prev, [input]: rest[key] }));
|
75
|
+
setAdvancedValues(prev => ({ ...prev, [input]: rest[key] }));
|
75
76
|
} else {
|
76
77
|
const { value, advanced } = rest[key];
|
77
78
|
if (advanced) {
|
@@ -26,6 +26,15 @@ mockApi(api);
|
|
26
26
|
jest.useFakeTimers();
|
27
27
|
|
28
28
|
describe('AdvancedFields', () => {
|
29
|
+
beforeEach(() => {
|
30
|
+
jest.spyOn(selectors, 'selectRouterSearch');
|
31
|
+
selectors.selectRouterSearch.mockImplementation(() => ({
|
32
|
+
'host_ids[]': ['105', '37'],
|
33
|
+
}));
|
34
|
+
});
|
35
|
+
afterEach(() => {
|
36
|
+
selectors.selectRouterSearch.mockRestore();
|
37
|
+
});
|
29
38
|
it('should save data between steps for advanced fields', async () => {
|
30
39
|
const wrapper = mount(
|
31
40
|
<MockedProvider mocks={gqlMock} addTypename={false}>
|
@@ -270,6 +279,11 @@ describe('AdvancedFields', () => {
|
|
270
279
|
handleSuccess({
|
271
280
|
data: { results: [jobTemplate] },
|
272
281
|
});
|
282
|
+
} else if (action.key === 'HOST_IDS') {
|
283
|
+
handleSuccess &&
|
284
|
+
handleSuccess({
|
285
|
+
data: { results: [{ name: 'host1' }, { name: 'host3' }] },
|
286
|
+
});
|
273
287
|
}
|
274
288
|
return { type: 'get', ...action };
|
275
289
|
});
|
@@ -339,6 +353,11 @@ describe('AdvancedFields', () => {
|
|
339
353
|
handleSuccess({
|
340
354
|
data: { results: [jobTemplate] },
|
341
355
|
});
|
356
|
+
} else if (action.key === 'HOST_IDS') {
|
357
|
+
handleSuccess &&
|
358
|
+
handleSuccess({
|
359
|
+
data: { results: [{ name: 'host1' }, { name: 'host3' }] },
|
360
|
+
});
|
342
361
|
}
|
343
362
|
return { type: 'get', ...action };
|
344
363
|
});
|
data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap
CHANGED
@@ -7,6 +7,14 @@ Array [
|
|
7
7
|
"type": "get",
|
8
8
|
"url": "/ui_job_wizard/categories",
|
9
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
|
+
},
|
10
18
|
Object {
|
11
19
|
"key": "JOB_TEMPLATES",
|
12
20
|
"type": "get",
|
@@ -30,6 +30,27 @@ describe('Hosts', () => {
|
|
30
30
|
const select = name =>
|
31
31
|
screen.getByRole('button', { name: `${name} toggle` });
|
32
32
|
fireEvent.click(select('hosts'));
|
33
|
+
await act(async () => {
|
34
|
+
fireEvent.click(screen.getByText('host1'));
|
35
|
+
fireEvent.click(select('hosts'));
|
36
|
+
});
|
37
|
+
expect(
|
38
|
+
screen.queryAllByText('Please select at least one host')
|
39
|
+
).toHaveLength(0);
|
40
|
+
await act(async () => {
|
41
|
+
fireEvent.click(select('hosts'));
|
42
|
+
});
|
43
|
+
await act(async () => {
|
44
|
+
fireEvent.click(
|
45
|
+
screen.getByText('host1', {
|
46
|
+
selector: '.pf-c-select__menu-item',
|
47
|
+
})
|
48
|
+
);
|
49
|
+
fireEvent.blur(select('hosts'));
|
50
|
+
});
|
51
|
+
expect(
|
52
|
+
screen.queryAllByText('Please select at least one host')
|
53
|
+
).toHaveLength(1);
|
33
54
|
await act(async () => {
|
34
55
|
fireEvent.click(screen.getByText('host1'));
|
35
56
|
fireEvent.click(screen.getByText('host2'));
|
@@ -70,7 +91,7 @@ describe('Hosts', () => {
|
|
70
91
|
expect(screen.queryAllByText('host_collection1')).toHaveLength(1);
|
71
92
|
|
72
93
|
await act(async () => {
|
73
|
-
fireEvent.click(screen.getByText('Category and
|
94
|
+
fireEvent.click(screen.getByText('Category and template'));
|
74
95
|
});
|
75
96
|
await act(async () => {
|
76
97
|
fireEvent.click(screen.getByText('Target hosts and inputs'));
|
@@ -151,8 +172,9 @@ describe('Hosts', () => {
|
|
151
172
|
|
152
173
|
it('input fill from url', async () => {
|
153
174
|
const inputText = 'test text';
|
175
|
+
const advancedInputText = 'test adv text';
|
154
176
|
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
155
|
-
search: `feature=test_feature&inputs[plain hidden]=${inputText}`,
|
177
|
+
search: `host_ids%5B%5D=host1&host_ids%5B%5D=host3&feature=test_feature&inputs[plain hidden]=${inputText}&inputs[adv plain hidden]=${advancedInputText}`,
|
156
178
|
}));
|
157
179
|
render(
|
158
180
|
<MockedProvider mocks={gqlMock} addTypename={false}>
|
@@ -175,5 +197,13 @@ describe('Hosts', () => {
|
|
175
197
|
selector: 'textarea',
|
176
198
|
});
|
177
199
|
expect(textField.value).toBe(inputText);
|
200
|
+
|
201
|
+
await act(async () => {
|
202
|
+
fireEvent.click(screen.getByText('Advanced fields'));
|
203
|
+
});
|
204
|
+
const advancedTextField = screen.getByLabelText('adv plain hidden', {
|
205
|
+
selector: 'textarea',
|
206
|
+
});
|
207
|
+
expect(advancedTextField.value).toBe(advancedInputText);
|
178
208
|
});
|
179
209
|
});
|
@@ -1,18 +1,24 @@
|
|
1
1
|
export const buildHostQuery = (selected, search) => {
|
2
2
|
const { hosts, hostCollections, hostGroups } = selected;
|
3
|
-
const hostsSearch = `
|
4
|
-
const hostCollectionsSearch = `
|
3
|
+
const hostsSearch = `id ^ (${hosts.map(({ id }) => id).join(',')})`;
|
4
|
+
const hostCollectionsSearch = `host_collection_id ^ (${hostCollections
|
5
5
|
.map(({ id }) => id)
|
6
|
-
.join(',')})
|
7
|
-
const hostGroupsSearch = `
|
6
|
+
.join(',')})`;
|
7
|
+
const hostGroupsSearch = `hostgroup_id ^ (${hostGroups
|
8
8
|
.map(({ id }) => id)
|
9
|
-
.join(',')})
|
10
|
-
|
9
|
+
.join(',')})`;
|
10
|
+
const queryParts = [
|
11
11
|
hosts.length ? hostsSearch : false,
|
12
12
|
hostCollections.length ? hostCollectionsSearch : false,
|
13
13
|
hostGroups.length ? hostGroupsSearch : false,
|
14
|
-
search.length ?
|
15
|
-
]
|
16
|
-
|
17
|
-
|
14
|
+
search.length ? search : false,
|
15
|
+
].filter(Boolean);
|
16
|
+
|
17
|
+
if (queryParts.length === 0) {
|
18
|
+
return 'name=a AND name=b';
|
19
|
+
}
|
20
|
+
if (queryParts.length === 1) {
|
21
|
+
return queryParts[0] || 'name=a AND name=b';
|
22
|
+
}
|
23
|
+
return queryParts.map(p => `(${p})`).join(' or ') || 'name=a AND name=b';
|
18
24
|
};
|
@@ -54,6 +54,30 @@ const HostsAndInputs = ({
|
|
54
54
|
const isLoading = useSelector(selectIsLoadingHosts);
|
55
55
|
const templateInputs = useSelector(selectTemplateInputs);
|
56
56
|
const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
|
57
|
+
const [wasFocus, setWasFocus] = useState(false);
|
58
|
+
const [isError, setIsError] = useState(false);
|
59
|
+
useEffect(() => {
|
60
|
+
if (wasFocus) {
|
61
|
+
if (
|
62
|
+
selected.hosts.length === 0 &&
|
63
|
+
selected.hostCollections.length === 0 &&
|
64
|
+
selected.hostGroups.length === 0 &&
|
65
|
+
hostsSearchQuery.length === 0
|
66
|
+
) {
|
67
|
+
setIsError(true);
|
68
|
+
} else {
|
69
|
+
setIsError(false);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}, [
|
73
|
+
hostMethod,
|
74
|
+
hostsSearchQuery.length,
|
75
|
+
selected,
|
76
|
+
selected.hostCollections.length,
|
77
|
+
selected.hostGroups.length,
|
78
|
+
selected.hosts.length,
|
79
|
+
wasFocus,
|
80
|
+
]);
|
57
81
|
useEffect(() => {
|
58
82
|
debounce(() => {
|
59
83
|
dispatch(
|
@@ -103,6 +127,9 @@ const HostsAndInputs = ({
|
|
103
127
|
dispatch(resetData(hostsController, hostQuerySearchID));
|
104
128
|
setHostsSearchQuery('');
|
105
129
|
};
|
130
|
+
const [errorText, setErrorText] = useState(
|
131
|
+
__('Please select at least one host')
|
132
|
+
);
|
106
133
|
return (
|
107
134
|
<div className="target-hosts-and-inputs">
|
108
135
|
<WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
|
@@ -114,8 +141,13 @@ const HostsAndInputs = ({
|
|
114
141
|
/>
|
115
142
|
)}
|
116
143
|
<Form>
|
117
|
-
<FormGroup
|
118
|
-
|
144
|
+
<FormGroup
|
145
|
+
fieldId="host_selection"
|
146
|
+
id="host-selection"
|
147
|
+
helperTextInvalid={errorText}
|
148
|
+
validated={isError ? 'error' : 'default'}
|
149
|
+
>
|
150
|
+
<InputGroup onBlur={() => setWasFocus(true)}>
|
119
151
|
<SelectField
|
120
152
|
isRequired
|
121
153
|
className="target-method-select"
|
@@ -127,7 +159,23 @@ const HostsAndInputs = ({
|
|
127
159
|
}
|
128
160
|
return true;
|
129
161
|
})}
|
130
|
-
setValue={
|
162
|
+
setValue={val => {
|
163
|
+
setHostMethod(val);
|
164
|
+
if (val === hostMethods.searchQuery) {
|
165
|
+
setErrorText(__('Please enter a search query'));
|
166
|
+
}
|
167
|
+
if (val === hostMethods.hosts) {
|
168
|
+
setErrorText(__('Please select at least one host'));
|
169
|
+
}
|
170
|
+
if (val === hostMethods.hostCollections) {
|
171
|
+
setErrorText(
|
172
|
+
__('Please select at least one host collection')
|
173
|
+
);
|
174
|
+
}
|
175
|
+
if (val === hostMethods.hostGroups) {
|
176
|
+
setErrorText(__('Please select at least one host group'));
|
177
|
+
}
|
178
|
+
}}
|
131
179
|
value={hostMethod}
|
132
180
|
/>
|
133
181
|
{hostMethod === hostMethods.searchQuery && (
|
@@ -31,7 +31,7 @@ export const QueryType = ({ isTypeStatic, setIsTypeStatic }) => (
|
|
31
31
|
id="query-type-dynamic"
|
32
32
|
label={__('Dynamic query')}
|
33
33
|
body={__(
|
34
|
-
"evaluates just before the execution is started, so if it's
|
34
|
+
"evaluates just before the execution is started, so if it's planned in future, targeted hosts set may change before it"
|
35
35
|
)}
|
36
36
|
/>
|
37
37
|
</FormGroup>
|
@@ -47,7 +47,6 @@ export const RepeatHour = ({ repeatData, setRepeatData }) => {
|
|
47
47
|
}}
|
48
48
|
isOpen={minuteOpen}
|
49
49
|
width={125}
|
50
|
-
menuAppendTo={() => document.querySelector('.pf-c-form.schedule-tab')}
|
51
50
|
toggleAriaLabel="select minute toggle"
|
52
51
|
validated={
|
53
52
|
isValidMinute(minute)
|
@@ -39,15 +39,35 @@ api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
|
39
39
|
handleSuccess({
|
40
40
|
data: { results: [jobTemplateResponse.job_template] },
|
41
41
|
});
|
42
|
+
} else if (action.key === 'HOST_IDS') {
|
43
|
+
handleSuccess &&
|
44
|
+
handleSuccess({
|
45
|
+
data: { results: [{ name: 'host1' }, { name: 'host3' }] },
|
46
|
+
});
|
42
47
|
}
|
43
48
|
return { type: 'get', ...action };
|
44
49
|
});
|
45
50
|
|
46
51
|
const mockStore = configureMockStore([]);
|
47
|
-
const store = mockStore({
|
52
|
+
const store = mockStore({
|
53
|
+
HOSTS_API: {
|
54
|
+
response: {
|
55
|
+
subtotal: 3,
|
56
|
+
},
|
57
|
+
},
|
58
|
+
});
|
48
59
|
jest.useFakeTimers();
|
49
60
|
|
50
61
|
describe('Schedule', () => {
|
62
|
+
beforeEach(() => {
|
63
|
+
jest.spyOn(selectors, 'selectRouterSearch');
|
64
|
+
selectors.selectRouterSearch.mockImplementation(() => ({
|
65
|
+
'host_ids[]': ['105', '37'],
|
66
|
+
}));
|
67
|
+
});
|
68
|
+
afterEach(() => {
|
69
|
+
selectors.selectRouterSearch.mockRestore();
|
70
|
+
});
|
51
71
|
it('sub steps appear', () => {
|
52
72
|
render(
|
53
73
|
<Provider store={store}>
|
@@ -119,7 +139,7 @@ describe('Schedule', () => {
|
|
119
139
|
});
|
120
140
|
|
121
141
|
act(() => {
|
122
|
-
fireEvent.click(screen.getByText('Category and
|
142
|
+
fireEvent.click(screen.getByText('Category and template'));
|
123
143
|
});
|
124
144
|
act(() => {
|
125
145
|
fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
|
@@ -226,7 +246,7 @@ describe('Schedule', () => {
|
|
226
246
|
});
|
227
247
|
|
228
248
|
act(() => {
|
229
|
-
fireEvent.click(screen.getByText('Category and
|
249
|
+
fireEvent.click(screen.getByText('Category and template'));
|
230
250
|
});
|
231
251
|
act(() => {
|
232
252
|
fireEvent.click(
|
@@ -286,9 +306,9 @@ describe('Schedule', () => {
|
|
286
306
|
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
287
307
|
|
288
308
|
await act(async () => {
|
289
|
-
fireEvent.click(screen.getByText('Category and
|
309
|
+
fireEvent.click(screen.getByText('Category and template'));
|
290
310
|
});
|
291
|
-
expect(screen.getAllByText('Category and
|
311
|
+
expect(screen.getAllByText('Category and template')).toHaveLength(3);
|
292
312
|
|
293
313
|
await act(async () => {
|
294
314
|
fireEvent.click(
|
@@ -43,7 +43,6 @@ export const SelectField = ({
|
|
43
43
|
isOpen={isOpen}
|
44
44
|
className="without_select2"
|
45
45
|
maxHeight="45vh"
|
46
|
-
menuAppendTo={() => document.body}
|
47
46
|
placeholderText=" " // To prevent showing first option as selected
|
48
47
|
aria-labelledby={fieldId}
|
49
48
|
toggleAriaLabel={`${label} toggle`}
|
data/webpack/JobWizard/submit.js
CHANGED
@@ -12,6 +12,8 @@ export const submit = ({
|
|
12
12
|
location,
|
13
13
|
organization,
|
14
14
|
feature,
|
15
|
+
provider,
|
16
|
+
advancedInputs,
|
15
17
|
dispatch,
|
16
18
|
}) => {
|
17
19
|
const {
|
@@ -37,6 +39,13 @@ export const submit = ({
|
|
37
39
|
keyPassphrase,
|
38
40
|
timeToPickup,
|
39
41
|
} = advancedValues;
|
42
|
+
const providerInputs = advancedInputs.filter(v => v.provider_input);
|
43
|
+
const providerValues = {};
|
44
|
+
providerInputs.forEach(({ name }) => {
|
45
|
+
providerValues[name] = advancedTemplateValues[name];
|
46
|
+
delete advancedTemplateValues[name];
|
47
|
+
});
|
48
|
+
|
40
49
|
const getCronLine = () => {
|
41
50
|
const [hour, minute] = repeatData.at
|
42
51
|
? repeatData.at.split(':')
|
@@ -104,14 +113,16 @@ export const submit = ({
|
|
104
113
|
concurrency_level: concurrencyLevel,
|
105
114
|
},
|
106
115
|
bookmark_id: null,
|
107
|
-
search_query:
|
108
|
-
buildHostQuery(selectedTargets, hostsSearchQuery) || 'name ~ *',
|
116
|
+
search_query: buildHostQuery(selectedTargets, hostsSearchQuery),
|
109
117
|
description_format: description,
|
110
118
|
execution_timeout_interval: timeoutToKill,
|
111
119
|
feature,
|
112
120
|
time_to_pickup: timeToPickup,
|
113
121
|
},
|
114
122
|
};
|
123
|
+
if (Object.keys(providerValues).length) {
|
124
|
+
api.job_invocation[provider] = providerValues;
|
125
|
+
}
|
115
126
|
|
116
127
|
dispatch(
|
117
128
|
post({
|