foreman_remote_execution 9.0.1 → 9.1.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 +10 -0
- data/app/controllers/ui_job_wizard_controller.rb +6 -1
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +28 -2
- data/app/models/remote_execution_feature.rb +11 -8
- data/app/views/api/v2/job_invocations/base.json.rabl +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/db/migrate/20220426145007_add_unique_feature_label_index.rb +14 -0
- data/lib/foreman_remote_execution/engine.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/action_names.rb +1 -1
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +15 -12
- data/locale/en/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en/foreman_remote_execution.po +4 -1
- data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en_GB/foreman_remote_execution.po +6 -3
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +16 -13
- data/locale/foreman_remote_execution.pot +25 -17
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +51 -48
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +17 -14
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +12 -9
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +13 -10
- data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ru/foreman_remote_execution.po +12 -9
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +16 -13
- data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_TW/foreman_remote_execution.po +12 -9
- data/package.json +6 -6
- data/webpack/JobWizard/JobWizard.js +97 -32
- data/webpack/JobWizard/StartsBeforeErrorAlert.js +17 -0
- 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 +29 -10
- 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 +38 -1
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +16 -10
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +51 -3
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +33 -13
- data/webpack/JobWizard/steps/form/DateTimePicker.js +1 -1
- data/webpack/JobWizard/submit.js +14 -3
- metadata +4 -2
@@ -36,6 +36,7 @@ import { useValidation } from './validation';
|
|
36
36
|
import { useAutoFill } from './autofill';
|
37
37
|
import { submit } from './submit';
|
38
38
|
import { generateDefaultDescription } from './JobWizardHelpers';
|
39
|
+
import { StartsBeforeErrorAlert } from './StartsBeforeErrorAlert';
|
39
40
|
import './JobWizard.scss';
|
40
41
|
|
41
42
|
export const JobWizard = ({ rerunData }) => {
|
@@ -185,6 +186,24 @@ export const JobWizard = ({ rerunData }) => {
|
|
185
186
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
186
187
|
}, [rerunData, jobTemplateID, dispatch]);
|
187
188
|
|
189
|
+
const [isStartsBeforeError, setIsStartsBeforeError] = useState(false);
|
190
|
+
useEffect(() => {
|
191
|
+
const updateStartsBeforeError = () => {
|
192
|
+
setIsStartsBeforeError(
|
193
|
+
scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE &&
|
194
|
+
new Date().getTime() >= new Date(scheduleValue.startsBefore).getTime()
|
195
|
+
);
|
196
|
+
};
|
197
|
+
let interval;
|
198
|
+
if (scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE) {
|
199
|
+
updateStartsBeforeError();
|
200
|
+
interval = setInterval(updateStartsBeforeError, 5000);
|
201
|
+
}
|
202
|
+
return () => {
|
203
|
+
interval && clearInterval(interval);
|
204
|
+
};
|
205
|
+
}, [scheduleValue.scheduleType, scheduleValue.startsBefore]);
|
206
|
+
|
188
207
|
const [valid, setValid] = useValidation({
|
189
208
|
advancedValues,
|
190
209
|
templateValues,
|
@@ -209,6 +228,11 @@ export const JobWizard = ({ rerunData }) => {
|
|
209
228
|
!templateError &&
|
210
229
|
!!jobTemplateID &&
|
211
230
|
templateResponse.job_template;
|
231
|
+
const areHostsSelected =
|
232
|
+
selectedTargets.hosts.length > 0 ||
|
233
|
+
selectedTargets.hostCollections.length > 0 ||
|
234
|
+
selectedTargets.hostGroups.length > 0 ||
|
235
|
+
hostsSearchQuery.length > 0;
|
212
236
|
const steps = [
|
213
237
|
{
|
214
238
|
name: WIZARD_TITLES.categoryAndTemplate,
|
@@ -238,7 +262,7 @@ export const JobWizard = ({ rerunData }) => {
|
|
238
262
|
/>
|
239
263
|
),
|
240
264
|
canJumpTo: isTemplate,
|
241
|
-
enableNext: isTemplate && valid.hostsAndInputs,
|
265
|
+
enableNext: isTemplate && valid.hostsAndInputs && areHostsSelected,
|
242
266
|
},
|
243
267
|
{
|
244
268
|
name: WIZARD_TITLES.advanced,
|
@@ -254,14 +278,26 @@ export const JobWizard = ({ rerunData }) => {
|
|
254
278
|
templateValues={templateValues}
|
255
279
|
/>
|
256
280
|
),
|
257
|
-
canJumpTo: isTemplate && valid.hostsAndInputs,
|
258
|
-
enableNext:
|
281
|
+
canJumpTo: isTemplate && valid.hostsAndInputs && areHostsSelected,
|
282
|
+
enableNext:
|
283
|
+
isTemplate &&
|
284
|
+
valid.hostsAndInputs &&
|
285
|
+
areHostsSelected &&
|
286
|
+
valid.advanced,
|
259
287
|
},
|
260
288
|
{
|
261
289
|
name: WIZARD_TITLES.schedule,
|
262
|
-
canJumpTo:
|
290
|
+
canJumpTo:
|
291
|
+
isTemplate &&
|
292
|
+
valid.hostsAndInputs &&
|
293
|
+
areHostsSelected &&
|
294
|
+
valid.advanced,
|
263
295
|
enableNext:
|
264
|
-
isTemplate &&
|
296
|
+
isTemplate &&
|
297
|
+
valid.hostsAndInputs &&
|
298
|
+
areHostsSelected &&
|
299
|
+
valid.advanced &&
|
300
|
+
valid.schedule,
|
265
301
|
steps: [
|
266
302
|
{
|
267
303
|
name: WIZARD_TITLES.typeOfExecution,
|
@@ -278,9 +314,17 @@ export const JobWizard = ({ rerunData }) => {
|
|
278
314
|
}}
|
279
315
|
/>
|
280
316
|
),
|
281
|
-
canJumpTo:
|
317
|
+
canJumpTo:
|
318
|
+
isTemplate &&
|
319
|
+
valid.hostsAndInputs &&
|
320
|
+
areHostsSelected &&
|
321
|
+
valid.advanced,
|
282
322
|
|
283
|
-
enableNext:
|
323
|
+
enableNext:
|
324
|
+
isTemplate &&
|
325
|
+
valid.hostsAndInputs &&
|
326
|
+
areHostsSelected &&
|
327
|
+
valid.advanced,
|
284
328
|
},
|
285
329
|
...(scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE
|
286
330
|
? [
|
@@ -298,10 +342,15 @@ export const JobWizard = ({ rerunData }) => {
|
|
298
342
|
}}
|
299
343
|
/>
|
300
344
|
),
|
301
|
-
canJumpTo:
|
345
|
+
canJumpTo:
|
346
|
+
isTemplate &&
|
347
|
+
valid.hostsAndInputs &&
|
348
|
+
areHostsSelected &&
|
349
|
+
valid.advanced,
|
302
350
|
enableNext:
|
303
351
|
isTemplate &&
|
304
352
|
valid.hostsAndInputs &&
|
353
|
+
areHostsSelected &&
|
305
354
|
valid.advanced &&
|
306
355
|
valid.schedule,
|
307
356
|
},
|
@@ -323,10 +372,15 @@ export const JobWizard = ({ rerunData }) => {
|
|
323
372
|
}}
|
324
373
|
/>
|
325
374
|
),
|
326
|
-
canJumpTo:
|
375
|
+
canJumpTo:
|
376
|
+
isTemplate &&
|
377
|
+
valid.hostsAndInputs &&
|
378
|
+
areHostsSelected &&
|
379
|
+
valid.advanced,
|
327
380
|
enableNext:
|
328
381
|
isTemplate &&
|
329
382
|
valid.hostsAndInputs &&
|
383
|
+
areHostsSelected &&
|
330
384
|
valid.advanced &&
|
331
385
|
valid.schedule,
|
332
386
|
},
|
@@ -349,39 +403,50 @@ export const JobWizard = ({ rerunData }) => {
|
|
349
403
|
),
|
350
404
|
nextButtonText: 'Run',
|
351
405
|
canJumpTo:
|
352
|
-
isTemplate &&
|
406
|
+
isTemplate &&
|
407
|
+
valid.advanced &&
|
408
|
+
valid.hostsAndInputs &&
|
409
|
+
areHostsSelected &&
|
410
|
+
valid.schedule,
|
353
411
|
enableNext:
|
354
412
|
isTemplate &&
|
355
413
|
valid.hostsAndInputs &&
|
414
|
+
areHostsSelected &&
|
356
415
|
valid.advanced &&
|
357
416
|
valid.schedule &&
|
358
|
-
!isSubmitting
|
417
|
+
!isSubmitting &&
|
418
|
+
!isStartsBeforeError,
|
359
419
|
},
|
360
420
|
];
|
361
421
|
const location = useForemanLocation();
|
362
422
|
const organization = useForemanOrganization();
|
363
423
|
return (
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
424
|
+
<>
|
425
|
+
{isStartsBeforeError && <StartsBeforeErrorAlert />}
|
426
|
+
<Wizard
|
427
|
+
onClose={() => history.goBack()}
|
428
|
+
navAriaLabel="Run Job steps"
|
429
|
+
steps={steps}
|
430
|
+
height="100%"
|
431
|
+
className="job-wizard"
|
432
|
+
onSave={() => {
|
433
|
+
submit({
|
434
|
+
jobTemplateID,
|
435
|
+
templateValues,
|
436
|
+
advancedValues,
|
437
|
+
scheduleValue,
|
438
|
+
dispatch,
|
439
|
+
selectedTargets,
|
440
|
+
hostsSearchQuery,
|
441
|
+
location,
|
442
|
+
organization,
|
443
|
+
feature: routerSearch?.feature,
|
444
|
+
provider: templateResponse.provider_name,
|
445
|
+
advancedInputs: templateResponse.advanced_template_inputs,
|
446
|
+
});
|
447
|
+
}}
|
448
|
+
/>
|
449
|
+
</>
|
385
450
|
);
|
386
451
|
};
|
387
452
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Divider, Alert } from '@patternfly/react-core';
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
|
5
|
+
export const StartsBeforeErrorAlert = () => (
|
6
|
+
<>
|
7
|
+
<Alert
|
8
|
+
variant="danger"
|
9
|
+
title={__("'Starts before' date must in the future")}
|
10
|
+
>
|
11
|
+
{__(
|
12
|
+
'Please go back to "Schedule" - "Future execution" step to fix the error'
|
13
|
+
)}
|
14
|
+
</Alert>
|
15
|
+
<Divider component="div" />
|
16
|
+
</>
|
17
|
+
);
|
@@ -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}>
|
@@ -51,7 +60,7 @@ describe('AdvancedFields', () => {
|
|
51
60
|
.simulate('click'); // Advanced step
|
52
61
|
|
53
62
|
await act(async () => {
|
54
|
-
jest.
|
63
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
55
64
|
});
|
56
65
|
const effectiveUserInput = () => wrapper.find('input#effective-user');
|
57
66
|
const advancedTemplateInput = () =>
|
@@ -83,7 +92,7 @@ describe('AdvancedFields', () => {
|
|
83
92
|
.simulate('click'); // Advanced step
|
84
93
|
|
85
94
|
await act(async () => {
|
86
|
-
jest.
|
95
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
87
96
|
});
|
88
97
|
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
89
98
|
expect(advancedTemplateInput().prop('value')).toEqual(
|
@@ -100,7 +109,7 @@ describe('AdvancedFields', () => {
|
|
100
109
|
);
|
101
110
|
await act(async () => {
|
102
111
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
103
|
-
jest.
|
112
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
104
113
|
});
|
105
114
|
|
106
115
|
const searchValue = 'search test';
|
@@ -137,7 +146,7 @@ describe('AdvancedFields', () => {
|
|
137
146
|
fireEvent.change(timeField, {
|
138
147
|
target: { value: timeValue },
|
139
148
|
});
|
140
|
-
jest.
|
149
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
141
150
|
});
|
142
151
|
expect(
|
143
152
|
screen.getByLabelText('adv plain hidden', {
|
@@ -156,7 +165,7 @@ describe('AdvancedFields', () => {
|
|
156
165
|
|
157
166
|
await act(async () => {
|
158
167
|
await fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
159
|
-
jest.
|
168
|
+
jest.advanceTimersByTime(1000);
|
160
169
|
});
|
161
170
|
expect(textField.value).toBe(textValue);
|
162
171
|
expect(searchField.value).toBe(searchValue);
|
@@ -177,7 +186,7 @@ describe('AdvancedFields', () => {
|
|
177
186
|
);
|
178
187
|
await act(async () => {
|
179
188
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
180
|
-
jest.
|
189
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
181
190
|
});
|
182
191
|
|
183
192
|
expect(
|
@@ -212,7 +221,7 @@ describe('AdvancedFields', () => {
|
|
212
221
|
);
|
213
222
|
await act(async () => {
|
214
223
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
215
|
-
jest.
|
224
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
216
225
|
});
|
217
226
|
|
218
227
|
const textField = screen.getByLabelText('adv plain hidden', {
|
@@ -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
|
});
|
@@ -280,7 +294,7 @@ describe('AdvancedFields', () => {
|
|
280
294
|
);
|
281
295
|
await act(async () => {
|
282
296
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
283
|
-
jest.
|
297
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
284
298
|
});
|
285
299
|
expect(
|
286
300
|
screen.getByLabelText('description preview', {
|
@@ -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
|
});
|
@@ -349,7 +368,7 @@ describe('AdvancedFields', () => {
|
|
349
368
|
);
|
350
369
|
await act(async () => {
|
351
370
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
352
|
-
jest.
|
371
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
353
372
|
});
|
354
373
|
expect(
|
355
374
|
screen.getByLabelText('description preview', {
|
@@ -369,7 +388,7 @@ describe('AdvancedFields', () => {
|
|
369
388
|
);
|
370
389
|
await act(async () => {
|
371
390
|
fireEvent.click(screen.getByText(WIZARD_TITLES.advanced));
|
372
|
-
jest.
|
391
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover
|
373
392
|
});
|
374
393
|
const resourceSelectField = screen.getByLabelText(
|
375
394
|
'adv resource select typeahead input'
|
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'));
|
@@ -107,6 +128,13 @@ describe('Hosts', () => {
|
|
107
128
|
expect(screen.queryAllByText('Host groups')).toHaveLength(1);
|
108
129
|
expect(screen.queryAllByText('Search query')).toHaveLength(1);
|
109
130
|
expect(screen.queryAllByText('Host collections')).toHaveLength(0);
|
131
|
+
|
132
|
+
await act(async () => {
|
133
|
+
fireEvent.click(
|
134
|
+
// Close the select
|
135
|
+
screen.getByText('Hosts', { selector: '.pf-c-select__toggle-text' })
|
136
|
+
);
|
137
|
+
});
|
110
138
|
});
|
111
139
|
it('Host fill list from url', async () => {
|
112
140
|
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
@@ -151,8 +179,9 @@ describe('Hosts', () => {
|
|
151
179
|
|
152
180
|
it('input fill from url', async () => {
|
153
181
|
const inputText = 'test text';
|
182
|
+
const advancedInputText = 'test adv text';
|
154
183
|
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
155
|
-
search: `feature=test_feature&inputs[plain hidden]=${inputText}`,
|
184
|
+
search: `host_ids%5B%5D=host1&host_ids%5B%5D=host3&feature=test_feature&inputs[plain hidden]=${inputText}&inputs[adv plain hidden]=${advancedInputText}`,
|
156
185
|
}));
|
157
186
|
render(
|
158
187
|
<MockedProvider mocks={gqlMock} addTypename={false}>
|
@@ -175,5 +204,13 @@ describe('Hosts', () => {
|
|
175
204
|
selector: 'textarea',
|
176
205
|
});
|
177
206
|
expect(textField.value).toBe(inputText);
|
207
|
+
|
208
|
+
await act(async () => {
|
209
|
+
fireEvent.click(screen.getByText('Advanced fields'));
|
210
|
+
});
|
211
|
+
const advancedTextField = screen.getByLabelText('adv plain hidden', {
|
212
|
+
selector: 'textarea',
|
213
|
+
});
|
214
|
+
expect(advancedTextField.value).toBe(advancedInputText);
|
178
215
|
});
|
179
216
|
});
|
@@ -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
|
};
|
@@ -51,6 +51,30 @@ const HostsAndInputs = ({
|
|
51
51
|
const isLoading = useSelector(selectIsLoadingHosts);
|
52
52
|
const templateInputs = useSelector(selectTemplateInputs);
|
53
53
|
const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
|
54
|
+
const [wasFocus, setWasFocus] = useState(false);
|
55
|
+
const [isError, setIsError] = useState(false);
|
56
|
+
useEffect(() => {
|
57
|
+
if (wasFocus) {
|
58
|
+
if (
|
59
|
+
selected.hosts.length === 0 &&
|
60
|
+
selected.hostCollections.length === 0 &&
|
61
|
+
selected.hostGroups.length === 0 &&
|
62
|
+
hostsSearchQuery.length === 0
|
63
|
+
) {
|
64
|
+
setIsError(true);
|
65
|
+
} else {
|
66
|
+
setIsError(false);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}, [
|
70
|
+
hostMethod,
|
71
|
+
hostsSearchQuery.length,
|
72
|
+
selected,
|
73
|
+
selected.hostCollections.length,
|
74
|
+
selected.hostGroups.length,
|
75
|
+
selected.hosts.length,
|
76
|
+
wasFocus,
|
77
|
+
]);
|
54
78
|
useEffect(() => {
|
55
79
|
debounce(() => {
|
56
80
|
dispatch(
|
@@ -99,6 +123,9 @@ const HostsAndInputs = ({
|
|
99
123
|
const clearSearch = () => {
|
100
124
|
setHostsSearchQuery('');
|
101
125
|
};
|
126
|
+
const [errorText, setErrorText] = useState(
|
127
|
+
__('Please select at least one host')
|
128
|
+
);
|
102
129
|
return (
|
103
130
|
<div className="target-hosts-and-inputs">
|
104
131
|
<WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
|
@@ -110,8 +137,13 @@ const HostsAndInputs = ({
|
|
110
137
|
/>
|
111
138
|
)}
|
112
139
|
<Form>
|
113
|
-
<FormGroup
|
114
|
-
|
140
|
+
<FormGroup
|
141
|
+
fieldId="host_selection"
|
142
|
+
id="host-selection"
|
143
|
+
helperTextInvalid={errorText}
|
144
|
+
validated={isError ? 'error' : 'default'}
|
145
|
+
>
|
146
|
+
<InputGroup onBlur={() => setWasFocus(true)}>
|
115
147
|
<SelectField
|
116
148
|
isRequired
|
117
149
|
className="target-method-select"
|
@@ -123,7 +155,23 @@ const HostsAndInputs = ({
|
|
123
155
|
}
|
124
156
|
return true;
|
125
157
|
})}
|
126
|
-
setValue={
|
158
|
+
setValue={val => {
|
159
|
+
setHostMethod(val);
|
160
|
+
if (val === hostMethods.searchQuery) {
|
161
|
+
setErrorText(__('Please enter a search query'));
|
162
|
+
}
|
163
|
+
if (val === hostMethods.hosts) {
|
164
|
+
setErrorText(__('Please select at least one host'));
|
165
|
+
}
|
166
|
+
if (val === hostMethods.hostCollections) {
|
167
|
+
setErrorText(
|
168
|
+
__('Please select at least one host collection')
|
169
|
+
);
|
170
|
+
}
|
171
|
+
if (val === hostMethods.hostGroups) {
|
172
|
+
setErrorText(__('Please select at least one host group'));
|
173
|
+
}
|
174
|
+
}}
|
127
175
|
value={hostMethod}
|
128
176
|
/>
|
129
177
|
{hostMethod === hostMethods.searchQuery && (
|