foreman_remote_execution 17.2.0 → 17.2.1
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/job_invocations_controller.rb +16 -1
- data/config/routes.rb +2 -1
- data/lib/foreman_remote_execution/plugin.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/job_invocations_controller_test.rb +84 -0
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +0 -2
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +6 -78
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +4 -39
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +0 -8
- data/webpack/JobWizard/Footer.js +16 -13
- data/webpack/JobWizard/__tests__/Footer.test.js +64 -0
- data/webpack/JobWizard/__tests__/integration.test.js +98 -53
- data/webpack/react_app/components/TargetingHosts/__tests__/HostItem.test.js +73 -3
- data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +69 -36
- metadata +3 -4
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +0 -51
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostItem.test.js.snap +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2273dcbf9a1bd18e838f9312d802e752f181723049c543895e2ba9bbe47309b
|
|
4
|
+
data.tar.gz: 4be8496d42f9abcd54116d8e0dc09669d0e8c67674341e9672601450dc77a57a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0656e10eb461a75898d1359ab6c03f5e10a6bbac18e03bac8ca804afd06f1050dc6bb6fb8a05ca0ced9a725dc4cd6b6202e164fca9a0ecf74179708aaa05d955
|
|
7
|
+
data.tar.gz: 76bfdccd452647bbdd56866eb6175c1efbb1efde242ef416a23902e429b1be6099e3a1f00ae9ddd1f2e956a21cb28baf099e31934ab501e112532934fdc7eadd
|
|
@@ -2,6 +2,7 @@ class JobInvocationsController < ApplicationController
|
|
|
2
2
|
include ::Foreman::Controller::AutoCompleteSearch
|
|
3
3
|
include ::ForemanTasks::Concerns::Parameters::Triggering
|
|
4
4
|
include ::JobInvocationsChartHelper
|
|
5
|
+
include RemoteExecutionHelper
|
|
5
6
|
|
|
6
7
|
def new
|
|
7
8
|
return @composer = prepare_composer if params[:feature].present?
|
|
@@ -110,6 +111,20 @@ class JobInvocationsController < ApplicationController
|
|
|
110
111
|
render :partial => 'job_invocations/preview_hosts_list'
|
|
111
112
|
end
|
|
112
113
|
|
|
114
|
+
def report
|
|
115
|
+
@job_invocation = resource_base.find(params[:id])
|
|
116
|
+
template = job_report_template
|
|
117
|
+
unless template
|
|
118
|
+
return not_found(_('Report template not found or not configured properly'))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
unless ReportTemplate.authorized(:generate_report_templates).find_by(:id => template.id)
|
|
122
|
+
return render_403(_('Missing permissions to generate report templates'))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
redirect_to generate_report_template_path(template, job_report_template_parameters(@job_invocation, template))
|
|
126
|
+
end
|
|
127
|
+
|
|
113
128
|
def cancel
|
|
114
129
|
@job_invocation = resource_base.find(params[:id])
|
|
115
130
|
result = @job_invocation.cancel(params[:force])
|
|
@@ -159,7 +174,7 @@ class JobInvocationsController < ApplicationController
|
|
|
159
174
|
'create'
|
|
160
175
|
when 'cancel'
|
|
161
176
|
'cancel'
|
|
162
|
-
when 'chart', 'preview_job_invocations_per_host'
|
|
177
|
+
when 'chart', 'preview_job_invocations_per_host', 'report'
|
|
163
178
|
'view'
|
|
164
179
|
else
|
|
165
180
|
super
|
data/config/routes.rb
CHANGED
|
@@ -36,6 +36,7 @@ Rails.application.routes.draw do
|
|
|
36
36
|
end
|
|
37
37
|
member do
|
|
38
38
|
post 'cancel'
|
|
39
|
+
get 'report'
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
|
@@ -70,7 +71,7 @@ Rails.application.routes.draw do
|
|
|
70
71
|
get 'hosts'
|
|
71
72
|
post 'cancel'
|
|
72
73
|
post 'rerun'
|
|
73
|
-
get
|
|
74
|
+
get 'template_invocations', :to => 'template_invocations#template_invocations'
|
|
74
75
|
get 'outputs'
|
|
75
76
|
post 'outputs'
|
|
76
77
|
end
|
|
@@ -143,7 +143,7 @@ Foreman::Plugin.register :foreman_remote_execution do
|
|
|
143
143
|
permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
|
|
144
144
|
permission :create_job_invocations, { :job_invocations => [:new, :create, :legacy_create, :refresh, :rerun, :preview_hosts],
|
|
145
145
|
'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
|
|
146
|
-
permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host], :template_invocations => [:show, :show_template_invocation_by_host],
|
|
146
|
+
permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host, :report], :template_invocations => [:show, :show_template_invocation_by_host],
|
|
147
147
|
'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs, :hosts] }, :resource_type => 'JobInvocation'
|
|
148
148
|
permission :view_template_invocations, { :template_invocations => [:show, :template_invocation_preview, :show_template_invocation_by_host],
|
|
149
149
|
'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation'
|
|
@@ -60,6 +60,90 @@ class JobInvocationsControllerTest < ActionController::TestCase
|
|
|
60
60
|
assert_response :success
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
context '#report' do
|
|
64
|
+
setup do
|
|
65
|
+
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
|
66
|
+
@report_template = FactoryBot.create(:report_template, :name => 'Job - Invocation Report', :template => '<%= "report output" %>')
|
|
67
|
+
@report_template.template_inputs.create!(:name => 'job_id', :input_type => 'user')
|
|
68
|
+
Setting['remote_execution_job_invocation_report_template'] = @report_template.name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
test 'should redirect to report generation page' do
|
|
72
|
+
get :report, params: { :id => @invocation.id }, session: set_session_user
|
|
73
|
+
template_input = @report_template.template_inputs.where(name: 'job_id').first
|
|
74
|
+
expected_params = {
|
|
75
|
+
report_template_report: {
|
|
76
|
+
input_values: {
|
|
77
|
+
"#{template_input.id}": {
|
|
78
|
+
value: @invocation.id,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
assert_redirected_to generate_report_template_path(@report_template, expected_params)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
test 'should redirect to report generation page with custom template name' do
|
|
87
|
+
custom_template = FactoryBot.create(:report_template, :name => 'My Custom Job Report', :template => '<%= "custom report" %>')
|
|
88
|
+
custom_template.template_inputs.create!(:name => 'job_id', :input_type => 'user')
|
|
89
|
+
Setting['remote_execution_job_invocation_report_template'] = custom_template.name
|
|
90
|
+
|
|
91
|
+
get :report, params: { :id => @invocation.id }, session: set_session_user
|
|
92
|
+
template_input = custom_template.template_inputs.where(name: 'job_id').first
|
|
93
|
+
expected_params = {
|
|
94
|
+
report_template_report: {
|
|
95
|
+
input_values: {
|
|
96
|
+
"#{template_input.id}": {
|
|
97
|
+
value: @invocation.id,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
assert_redirected_to generate_report_template_path(custom_template, expected_params)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
test 'should return 404 when report template is not configured' do
|
|
106
|
+
Setting['remote_execution_job_invocation_report_template'] = 'Nonexistent Template'
|
|
107
|
+
get :report, params: { :id => @invocation.id }, session: set_session_user
|
|
108
|
+
assert_response :not_found
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
test 'should deny access when user lacks generate_report_templates permission' do
|
|
112
|
+
user = FactoryBot.create(:user, :admin => false)
|
|
113
|
+
@report_template.organizations = user.organizations
|
|
114
|
+
@report_template.locations = user.locations
|
|
115
|
+
setup_user('view', 'job_invocations', nil, user)
|
|
116
|
+
setup_user('view', 'hosts', nil, user)
|
|
117
|
+
setup_user('view', 'report_templates', nil, user)
|
|
118
|
+
|
|
119
|
+
get :report, params: { :id => @invocation.id }, session: set_session_user(user)
|
|
120
|
+
assert_response :forbidden
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
test 'should redirect when user has generate_report_templates permission' do
|
|
124
|
+
user = FactoryBot.create(:user, :admin => false)
|
|
125
|
+
@report_template.organizations = user.organizations
|
|
126
|
+
@report_template.locations = user.locations
|
|
127
|
+
setup_user('view', 'job_invocations', nil, user)
|
|
128
|
+
setup_user('view', 'hosts', nil, user)
|
|
129
|
+
setup_user('view', 'report_templates', nil, user)
|
|
130
|
+
setup_user('generate', 'report_templates', nil, user)
|
|
131
|
+
|
|
132
|
+
get :report, params: { :id => @invocation.id }, session: set_session_user(user)
|
|
133
|
+
template_input = @report_template.template_inputs.where(name: 'job_id').first
|
|
134
|
+
expected_params = {
|
|
135
|
+
report_template_report: {
|
|
136
|
+
input_values: {
|
|
137
|
+
"#{template_input.id}": {
|
|
138
|
+
value: @invocation.id,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
assert_redirected_to generate_report_template_path(@report_template, expected_params)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
63
147
|
context 'restricted access' do
|
|
64
148
|
setup do
|
|
65
149
|
@admin = users(:admin)
|
|
@@ -10,8 +10,6 @@ export const CANCEL_JOB = 'CANCEL_JOB';
|
|
|
10
10
|
export const GET_TEMPLATE_INVOCATIONS = 'GET_TEMPLATE_INVOCATIONS';
|
|
11
11
|
export const CHANGE_ENABLED_RECURRING_LOGIC = 'CHANGE_ENABLED_RECURRING_LOGIC';
|
|
12
12
|
export const CANCEL_RECURRING_LOGIC = 'CANCEL_RECURRING_LOGIC';
|
|
13
|
-
export const GET_REPORT_TEMPLATES = 'GET_REPORT_TEMPLATES';
|
|
14
|
-
export const GET_REPORT_TEMPLATE_INPUTS = 'GET_REPORT_TEMPLATE_INPUTS';
|
|
15
13
|
export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS';
|
|
16
14
|
export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
|
|
17
15
|
export const DIRECT_OPEN_HOST_LIMIT = 3;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/* eslint-disable max-lines */
|
|
2
1
|
import PropTypes from 'prop-types';
|
|
3
|
-
import React, { useCallback,
|
|
2
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
4
3
|
import { useDispatch, useSelector } from 'react-redux';
|
|
5
4
|
import { Button, Split, SplitItem } from '@patternfly/react-core';
|
|
6
5
|
import { UndoIcon } from '@patternfly/react-icons';
|
|
@@ -14,17 +13,12 @@ import {
|
|
|
14
13
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
15
14
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
16
15
|
import { usePermissions } from 'foremanReact/common/hooks/Permissions/permissionHooks';
|
|
17
|
-
import { get } from 'foremanReact/redux/API';
|
|
18
16
|
import {
|
|
19
17
|
cancelJob,
|
|
20
18
|
cancelRecurringLogic,
|
|
21
19
|
enableRecurringLogic,
|
|
22
20
|
} from './JobInvocationActions';
|
|
23
|
-
import {
|
|
24
|
-
STATUS,
|
|
25
|
-
GET_REPORT_TEMPLATES,
|
|
26
|
-
GET_REPORT_TEMPLATE_INPUTS,
|
|
27
|
-
} from './JobInvocationConstants';
|
|
21
|
+
import { STATUS } from './JobInvocationConstants';
|
|
28
22
|
import { selectTaskCancelable } from './JobInvocationSelectors';
|
|
29
23
|
|
|
30
24
|
const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
@@ -43,26 +37,13 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
43
37
|
'generate_report_templates',
|
|
44
38
|
]);
|
|
45
39
|
const [isActionOpen, setIsActionOpen] = useState(false);
|
|
46
|
-
const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
|
|
47
|
-
const [templateInputId, setTemplateInputId] = useState(undefined);
|
|
48
40
|
const dispatch = useDispatch();
|
|
49
|
-
const reportHref =
|
|
50
|
-
if (reportTemplateJobId === undefined || templateInputId === undefined) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
const queryParams = new URLSearchParams({
|
|
54
|
-
[`report_template_report[input_values][${templateInputId}][value]`]: jobId,
|
|
55
|
-
});
|
|
56
|
-
return foremanUrl(
|
|
57
|
-
`/templates/report_templates/${reportTemplateJobId}/generate?${queryParams.toString()}`
|
|
58
|
-
);
|
|
59
|
-
}, [jobId, reportTemplateJobId, templateInputId]);
|
|
41
|
+
const reportHref = foremanUrl(`/job_invocations/${jobId}/report`);
|
|
60
42
|
|
|
61
43
|
const isCreateReportDisabled =
|
|
62
44
|
!canGenerateReportTemplates ||
|
|
63
45
|
task?.state === STATUS.RUNNING ||
|
|
64
|
-
task?.state === STATUS.PENDING
|
|
65
|
-
reportHref === undefined;
|
|
46
|
+
task?.state === STATUS.PENDING;
|
|
66
47
|
|
|
67
48
|
const onActionFocus = useCallback(() => {
|
|
68
49
|
const element = document.getElementById(
|
|
@@ -78,58 +59,6 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
78
59
|
}, [onActionFocus]);
|
|
79
60
|
const onActionToggle = useCallback((_event, val) => setIsActionOpen(val), []);
|
|
80
61
|
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
let isMounted = true;
|
|
83
|
-
dispatch(
|
|
84
|
-
get({
|
|
85
|
-
key: GET_REPORT_TEMPLATES,
|
|
86
|
-
url: '/api/report_templates',
|
|
87
|
-
handleSuccess: ({ data: { results } }) => {
|
|
88
|
-
if (isMounted) {
|
|
89
|
-
setReportTemplateJobId(
|
|
90
|
-
results.find(result => result.name === 'Job - Invocation Report')
|
|
91
|
-
?.id
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
handleError: () => {
|
|
96
|
-
if (isMounted) {
|
|
97
|
-
setReportTemplateJobId(undefined);
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
})
|
|
101
|
-
);
|
|
102
|
-
return () => {
|
|
103
|
-
isMounted = false;
|
|
104
|
-
};
|
|
105
|
-
}, [dispatch]);
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
let isMounted = true;
|
|
108
|
-
if (reportTemplateJobId !== undefined) {
|
|
109
|
-
dispatch(
|
|
110
|
-
get({
|
|
111
|
-
key: GET_REPORT_TEMPLATE_INPUTS,
|
|
112
|
-
url: `/api/templates/${reportTemplateJobId}/template_inputs`,
|
|
113
|
-
handleSuccess: ({ data: { results } }) => {
|
|
114
|
-
if (isMounted) {
|
|
115
|
-
setTemplateInputId(
|
|
116
|
-
results.find(result => result.name === 'job_id')?.id
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
handleError: () => {
|
|
121
|
-
if (isMounted) {
|
|
122
|
-
setTemplateInputId(undefined);
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
})
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
return () => {
|
|
129
|
-
isMounted = false;
|
|
130
|
-
};
|
|
131
|
-
}, [dispatch, reportTemplateJobId]);
|
|
132
|
-
|
|
133
62
|
const recurrenceDropdownItems = useMemo(
|
|
134
63
|
() =>
|
|
135
64
|
recurrence
|
|
@@ -262,7 +191,7 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
262
191
|
variant="control"
|
|
263
192
|
isDisabled={!canCreateJobInvocations}
|
|
264
193
|
>
|
|
265
|
-
{__(
|
|
194
|
+
{__('Rerun all')}
|
|
266
195
|
</Button>,
|
|
267
196
|
]}
|
|
268
197
|
splitButtonVariant="action"
|
|
@@ -279,12 +208,11 @@ const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
|
|
279
208
|
<Button
|
|
280
209
|
component="a"
|
|
281
210
|
ouiaId="button-create-report"
|
|
282
|
-
className="button-create-report"
|
|
283
211
|
href={reportHref}
|
|
284
212
|
variant="secondary"
|
|
285
213
|
isDisabled={isCreateReportDisabled}
|
|
286
214
|
>
|
|
287
|
-
{__(
|
|
215
|
+
{__('Create report')}
|
|
288
216
|
</Button>
|
|
289
217
|
</SplitItem>
|
|
290
218
|
<SplitItem>
|
|
@@ -15,8 +15,6 @@ import {
|
|
|
15
15
|
jobInvocationData,
|
|
16
16
|
jobInvocationDataScheduled,
|
|
17
17
|
jobInvocationDataRecurring,
|
|
18
|
-
mockReportTemplatesResponse,
|
|
19
|
-
mockReportTemplateInputsResponse,
|
|
20
18
|
} from './fixtures';
|
|
21
19
|
import {
|
|
22
20
|
cancelJob,
|
|
@@ -28,8 +26,6 @@ import {
|
|
|
28
26
|
CANCEL_JOB,
|
|
29
27
|
CANCEL_RECURRING_LOGIC,
|
|
30
28
|
CHANGE_ENABLED_RECURRING_LOGIC,
|
|
31
|
-
GET_REPORT_TEMPLATES,
|
|
32
|
-
GET_REPORT_TEMPLATE_INPUTS,
|
|
33
29
|
JOB_INVOCATION_KEY,
|
|
34
30
|
} from '../JobInvocationConstants';
|
|
35
31
|
|
|
@@ -83,23 +79,9 @@ jest.mock('foremanReact/routes/common/PageLayout/PageLayout', () =>
|
|
|
83
79
|
);
|
|
84
80
|
|
|
85
81
|
const setupApiMocks = () => {
|
|
86
|
-
api.get.mockImplementation(({ handleSuccess, key, ...action }) =>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
handleSuccess({
|
|
90
|
-
data: mockReportTemplatesResponse,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
} else if (key === GET_REPORT_TEMPLATE_INPUTS) {
|
|
94
|
-
if (handleSuccess) {
|
|
95
|
-
handleSuccess({
|
|
96
|
-
data: mockReportTemplateInputsResponse,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return { type: 'get', key, ...action };
|
|
102
|
-
});
|
|
82
|
+
api.get.mockImplementation(({ handleSuccess, key, ...action }) =>
|
|
83
|
+
({ type: 'get', key, ...action })
|
|
84
|
+
);
|
|
103
85
|
APIActions.post.mockImplementation(payload => ({ type: 'post', ...payload }));
|
|
104
86
|
APIActions.put.mockImplementation(payload => ({ type: 'put', ...payload }));
|
|
105
87
|
};
|
|
@@ -131,8 +113,6 @@ jest.mock('../JobInvocationHostTable.js', () => () => (
|
|
|
131
113
|
<div data-testid="mock-table">Mock Table</div>
|
|
132
114
|
));
|
|
133
115
|
|
|
134
|
-
const reportTemplateJobId = mockReportTemplatesResponse.results[0].id;
|
|
135
|
-
|
|
136
116
|
const defaultHistory = { push: jest.fn() };
|
|
137
117
|
|
|
138
118
|
const renderJobInvocationDetailPage = (
|
|
@@ -235,9 +215,7 @@ describe('JobInvocationDetailPage', () => {
|
|
|
235
215
|
|
|
236
216
|
// checks the global actions and if they link to the correct url
|
|
237
217
|
expect(screen.getByText('Create report').getAttribute('href')).toEqual(
|
|
238
|
-
foremanUrl(
|
|
239
|
-
`/templates/report_templates/${mockReportTemplatesResponse.results[0].id}/generate?report_template_report%5Binput_values%5D%5B${mockReportTemplateInputsResponse.results[0].id}%5D%5Bvalue%5D=${jobId}`
|
|
240
|
-
)
|
|
218
|
+
foremanUrl(`/job_invocations/${jobId}/report`)
|
|
241
219
|
);
|
|
242
220
|
expect(screen.getByText('Rerun all').getAttribute('href')).toEqual(
|
|
243
221
|
foremanUrl(`/job_invocations/${jobId}/rerun`)
|
|
@@ -319,19 +297,6 @@ describe('JobInvocationDetailPage', () => {
|
|
|
319
297
|
{ jobId }
|
|
320
298
|
);
|
|
321
299
|
|
|
322
|
-
expect(api.get).toHaveBeenCalledWith(
|
|
323
|
-
expect.objectContaining({
|
|
324
|
-
key: GET_REPORT_TEMPLATES,
|
|
325
|
-
url: '/api/report_templates',
|
|
326
|
-
})
|
|
327
|
-
);
|
|
328
|
-
expect(api.get).toHaveBeenCalledWith(
|
|
329
|
-
expect.objectContaining({
|
|
330
|
-
key: GET_REPORT_TEMPLATE_INPUTS,
|
|
331
|
-
url: `/api/templates/${reportTemplateJobId}/template_inputs`,
|
|
332
|
-
})
|
|
333
|
-
);
|
|
334
|
-
|
|
335
300
|
api.get.mockClear();
|
|
336
301
|
APIActions.post.mockClear();
|
|
337
302
|
APIActions.put.mockClear();
|
|
@@ -140,14 +140,6 @@ export const mockPermissionsData = {
|
|
|
140
140
|
edit_recurring_logics: true,
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
-
export const mockReportTemplatesResponse = {
|
|
144
|
-
results: [{ id: '12', name: 'Job - Invocation Report' }],
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export const mockReportTemplateInputsResponse = {
|
|
148
|
-
results: [{ id: '34', name: 'job_id' }],
|
|
149
|
-
};
|
|
150
|
-
|
|
151
143
|
const templateInvocationID = 157;
|
|
152
144
|
|
|
153
145
|
export const jobInvocationOutput = [
|
data/webpack/JobWizard/Footer.js
CHANGED
|
@@ -22,6 +22,19 @@ export const Footer = ({ canSubmit, onSave }) => {
|
|
|
22
22
|
activeStep && activeStep.enableNext !== undefined
|
|
23
23
|
? activeStep.enableNext
|
|
24
24
|
: true;
|
|
25
|
+
const isReviewStep = activeStep.name === WIZARD_TITLES.review;
|
|
26
|
+
const canSkipToReview = canSubmit && !isReviewStep;
|
|
27
|
+
const skipToReviewTooltip = () => {
|
|
28
|
+
if (canSkipToReview) {
|
|
29
|
+
return __('Skip to review step');
|
|
30
|
+
}
|
|
31
|
+
if (isReviewStep) {
|
|
32
|
+
return __('Already on the review step');
|
|
33
|
+
}
|
|
34
|
+
return __(
|
|
35
|
+
'Fill all required fields in all the steps to start the job'
|
|
36
|
+
);
|
|
37
|
+
};
|
|
25
38
|
|
|
26
39
|
return (
|
|
27
40
|
<>
|
|
@@ -32,9 +45,7 @@ export const Footer = ({ canSubmit, onSave }) => {
|
|
|
32
45
|
onClick={onNext}
|
|
33
46
|
isDisabled={!isValid || isSubmitting}
|
|
34
47
|
>
|
|
35
|
-
{
|
|
36
|
-
? __('Submit')
|
|
37
|
-
: __('Next')}
|
|
48
|
+
{isReviewStep ? __('Submit') : __('Next')}
|
|
38
49
|
</Button>
|
|
39
50
|
<Button
|
|
40
51
|
ouiaId="back-footer"
|
|
@@ -68,22 +79,14 @@ export const Footer = ({ canSubmit, onSave }) => {
|
|
|
68
79
|
</Button>
|
|
69
80
|
</Tooltip>
|
|
70
81
|
<Tooltip
|
|
71
|
-
content={
|
|
72
|
-
<div>
|
|
73
|
-
{canSubmit
|
|
74
|
-
? __('Skip to review step')
|
|
75
|
-
: __(
|
|
76
|
-
'Fill all required fields in all the steps to start the job'
|
|
77
|
-
)}
|
|
78
|
-
</div>
|
|
79
|
-
}
|
|
82
|
+
content={<div>{skipToReviewTooltip()}</div>}
|
|
80
83
|
triggerRef={tooltipSkipTo}
|
|
81
84
|
>
|
|
82
85
|
<Button
|
|
83
86
|
ouiaId="skip-to-review-footer"
|
|
84
87
|
variant="tertiary"
|
|
85
88
|
onClick={() => goToStepByName(WIZARD_TITLES.review)}
|
|
86
|
-
isAriaDisabled={!
|
|
89
|
+
isAriaDisabled={!canSkipToReview}
|
|
87
90
|
isDisabled={isSubmitting}
|
|
88
91
|
ref={tooltipSkipTo}
|
|
89
92
|
>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import { render, fireEvent, screen, act } from '@testing-library/react';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
import { Wizard } from '@patternfly/react-core/deprecated';
|
|
6
|
+
import configureMockStore from 'redux-mock-store';
|
|
7
|
+
import { Footer } from '../Footer';
|
|
8
|
+
import { WIZARD_TITLES } from '../JobWizardConstants';
|
|
9
|
+
|
|
10
|
+
const store = configureMockStore([])({ API: {} });
|
|
11
|
+
|
|
12
|
+
const renderFooterWizard = ({ canSubmit = true, startAtStep = 1 } = {}) =>
|
|
13
|
+
render(
|
|
14
|
+
<Provider store={store}>
|
|
15
|
+
<Wizard
|
|
16
|
+
steps={[
|
|
17
|
+
{
|
|
18
|
+
name: WIZARD_TITLES.categoryAndTemplate,
|
|
19
|
+
component: <div>category step</div>,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: WIZARD_TITLES.review,
|
|
23
|
+
component: <div>review step</div>,
|
|
24
|
+
},
|
|
25
|
+
]}
|
|
26
|
+
startAtStep={startAtStep}
|
|
27
|
+
footer={<Footer canSubmit={canSubmit} onSave={jest.fn()} />}
|
|
28
|
+
/>
|
|
29
|
+
</Provider>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
describe('Job wizard footer', () => {
|
|
33
|
+
it('enables Skip to review when required fields are filled', () => {
|
|
34
|
+
renderFooterWizard({ canSubmit: true });
|
|
35
|
+
|
|
36
|
+
expect(screen.getByText('Skip to review')).not.toHaveAttribute(
|
|
37
|
+
'aria-disabled',
|
|
38
|
+
'true'
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('disables Skip to review when required fields are missing', () => {
|
|
43
|
+
renderFooterWizard({ canSubmit: false });
|
|
44
|
+
|
|
45
|
+
expect(screen.getByText('Skip to review')).toHaveAttribute(
|
|
46
|
+
'aria-disabled',
|
|
47
|
+
'true'
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('disables Skip to review on the review step', async () => {
|
|
52
|
+
renderFooterWizard({ canSubmit: true });
|
|
53
|
+
|
|
54
|
+
await act(async () => {
|
|
55
|
+
fireEvent.click(screen.getByText('Skip to review'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(screen.getByText('Submit')).toBeInTheDocument();
|
|
59
|
+
expect(screen.getByText('Skip to review')).toHaveAttribute(
|
|
60
|
+
'aria-disabled',
|
|
61
|
+
'true'
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Provider } from 'react-redux';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { render, screen, within, waitFor } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import '@testing-library/jest-dom';
|
|
5
6
|
import { MockedProvider } from '@apollo/client/testing';
|
|
6
7
|
import * as api from 'foremanReact/redux/API';
|
|
7
8
|
import { JobWizard } from '../JobWizard';
|
|
@@ -17,17 +18,55 @@ import {
|
|
|
17
18
|
|
|
18
19
|
const store = testSetup(selectors, api);
|
|
19
20
|
|
|
21
|
+
const renderJobWizard = ({ withGql = false } = {}) => {
|
|
22
|
+
const wizard = (
|
|
23
|
+
<Provider store={store}>
|
|
24
|
+
<JobWizard />
|
|
25
|
+
</Provider>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (withGql) {
|
|
29
|
+
return render(
|
|
30
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
|
31
|
+
{wizard}
|
|
32
|
+
</MockedProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return render(wizard);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getWizardNavigation = () =>
|
|
40
|
+
screen.getByRole('navigation', { name: 'Run Job steps' });
|
|
41
|
+
|
|
42
|
+
const getWizardStep = stepName =>
|
|
43
|
+
within(getWizardNavigation()).getByRole('button', { name: stepName });
|
|
44
|
+
|
|
45
|
+
const getDisabledWizardSteps = () =>
|
|
46
|
+
within(getWizardNavigation())
|
|
47
|
+
.getAllByRole('button')
|
|
48
|
+
.filter(button => button.getAttribute('aria-disabled') === 'true');
|
|
49
|
+
|
|
50
|
+
const expectDispatchedGet = expected => {
|
|
51
|
+
expect(store.getActions()).toEqual(
|
|
52
|
+
expect.arrayContaining([expect.objectContaining({ type: 'get', ...expected })])
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
20
56
|
describe('Job wizard fill', () => {
|
|
21
57
|
beforeEach(() => {
|
|
58
|
+
store.clearActions();
|
|
22
59
|
jest.spyOn(selectors, 'selectRouterSearch');
|
|
23
60
|
selectors.selectRouterSearch.mockImplementation(() => ({
|
|
24
61
|
'host_ids[]': ['105', '37'],
|
|
25
62
|
}));
|
|
26
63
|
});
|
|
64
|
+
|
|
27
65
|
afterEach(() => {
|
|
28
66
|
selectors.selectRouterSearch.mockRestore();
|
|
29
67
|
});
|
|
30
|
-
|
|
68
|
+
|
|
69
|
+
it('selects a template and enables wizard steps', async () => {
|
|
31
70
|
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
|
32
71
|
if (action.key === 'JOB_CATEGORIES') {
|
|
33
72
|
handleSuccess &&
|
|
@@ -54,74 +93,80 @@ describe('Job wizard fill', () => {
|
|
|
54
93
|
selectors.selectJobTemplate.mockRestore();
|
|
55
94
|
jest.spyOn(selectors, 'selectJobTemplate');
|
|
56
95
|
selectors.selectJobTemplate.mockImplementation(() => ({}));
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
|
|
97
|
+
renderJobWizard();
|
|
98
|
+
|
|
99
|
+
expect(getDisabledWizardSteps()).toHaveLength(5);
|
|
100
|
+
expect(getWizardStep(WIZARD_TITLES.categoryAndTemplate)).toBeInTheDocument();
|
|
101
|
+
|
|
102
|
+
expectDispatchedGet({
|
|
103
|
+
key: 'JOB_CATEGORIES',
|
|
104
|
+
url: '/ui_job_wizard/categories',
|
|
105
|
+
});
|
|
106
|
+
expectDispatchedGet({
|
|
107
|
+
key: 'HOST_IDS',
|
|
108
|
+
params: { search: 'id = 105 or id = 37' },
|
|
109
|
+
url: '/api/hosts',
|
|
110
|
+
});
|
|
111
|
+
expectDispatchedGet({
|
|
112
|
+
key: 'JOB_TEMPLATES',
|
|
113
|
+
});
|
|
114
|
+
|
|
65
115
|
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
|
66
|
-
expect(store.getActions()).toMatchSnapshot('initial');
|
|
67
116
|
selectors.selectJobTemplate.mockRestore();
|
|
68
117
|
jest.spyOn(selectors, 'selectJobTemplate');
|
|
69
118
|
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
119
|
+
|
|
120
|
+
await userEvent.click(
|
|
121
|
+
screen.getByRole('button', { name: 'Job template toggle' })
|
|
122
|
+
);
|
|
123
|
+
await userEvent.click(screen.getByText(jobTemplate.job_template.name));
|
|
124
|
+
|
|
125
|
+
expect(store.getActions().at(-1)).toEqual(
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
key: 'JOB_TEMPLATE',
|
|
128
|
+
type: 'get',
|
|
129
|
+
url: '/ui_job_wizard/template/178',
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await waitFor(() => {
|
|
134
|
+
expect(getDisabledWizardSteps()).toHaveLength(0);
|
|
78
135
|
});
|
|
79
|
-
expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
|
|
80
|
-
wrapper.update();
|
|
81
|
-
expect(
|
|
82
|
-
wrapper.find('.pf-v5-c-wizard__nav-link.pf-m-disabled')
|
|
83
|
-
).toHaveLength(0);
|
|
84
136
|
});
|
|
85
137
|
|
|
86
|
-
it('
|
|
138
|
+
it('renders all wizard steps and navigates between them', async () => {
|
|
87
139
|
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
|
88
140
|
selectors.selectJobTemplates.mockRestore();
|
|
89
141
|
selectors.selectJobCategories.mockRestore();
|
|
90
142
|
mockApi(api);
|
|
91
143
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<Provider store={store}>
|
|
95
|
-
<JobWizard />
|
|
96
|
-
</Provider>
|
|
97
|
-
</MockedProvider>
|
|
98
|
-
);
|
|
144
|
+
renderJobWizard({ withGql: true });
|
|
145
|
+
|
|
99
146
|
const steps = [
|
|
100
147
|
WIZARD_TITLES.hostsAndInputs,
|
|
101
148
|
WIZARD_TITLES.categoryAndTemplate,
|
|
102
149
|
WIZARD_TITLES.advanced,
|
|
103
150
|
WIZARD_TITLES.review,
|
|
104
151
|
];
|
|
105
|
-
|
|
106
|
-
for
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
const stepTitles = screen.getAllByText(step);
|
|
114
|
-
expect(stepTitles).toHaveLength(3);
|
|
152
|
+
|
|
153
|
+
for (const step of steps) {
|
|
154
|
+
expect(screen.getAllByText(step)).toHaveLength(1);
|
|
155
|
+
|
|
156
|
+
await userEvent.click(getWizardStep(step));
|
|
157
|
+
|
|
158
|
+
expect(screen.getAllByText(step)).toHaveLength(3);
|
|
115
159
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
expect(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
await
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
160
|
+
|
|
161
|
+
expect(screen.getAllByText(WIZARD_TITLES.typeOfExecution)).toHaveLength(1);
|
|
162
|
+
expect(
|
|
163
|
+
screen.queryByText('Select the type of execution')
|
|
164
|
+
).not.toBeInTheDocument();
|
|
165
|
+
|
|
166
|
+
await userEvent.click(getWizardStep(WIZARD_TITLES.typeOfExecution));
|
|
167
|
+
|
|
168
|
+
expect(
|
|
169
|
+
screen.getByText('Select the type of execution')
|
|
170
|
+
).toBeInTheDocument();
|
|
126
171
|
});
|
|
127
172
|
});
|
|
@@ -1,6 +1,76 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
2
5
|
import HostItem from '../components/HostItem';
|
|
3
6
|
import { HostItemFixtures } from './fixtures';
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
jest.unmock('foremanReact/components/common/ActionButtons/ActionButtons');
|
|
9
|
+
|
|
10
|
+
const renderHostItem = props =>
|
|
11
|
+
render(
|
|
12
|
+
<table>
|
|
13
|
+
<tbody>
|
|
14
|
+
<HostItem {...props} />
|
|
15
|
+
</tbody>
|
|
16
|
+
</table>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe('HostItem', () => {
|
|
20
|
+
it('renders the host name as a link with the provided URL', () => {
|
|
21
|
+
renderHostItem(HostItemFixtures.renders);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByRole('link', { name: 'Host1' })).toHaveAttribute(
|
|
24
|
+
'href',
|
|
25
|
+
'/host1'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders the host status', () => {
|
|
30
|
+
renderHostItem(HostItemFixtures.renders);
|
|
31
|
+
|
|
32
|
+
expect(screen.getByText('Succeeded')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders no action buttons when actions are empty', () => {
|
|
36
|
+
renderHostItem(HostItemFixtures.renders);
|
|
37
|
+
|
|
38
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renders a disabled link when link is not provided', () => {
|
|
42
|
+
renderHostItem({
|
|
43
|
+
...HostItemFixtures.renders,
|
|
44
|
+
link: '',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(screen.getByRole('link', { name: 'Host1' })).toHaveAttribute(
|
|
48
|
+
'href',
|
|
49
|
+
'#'
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders action buttons and calls action on click', async () => {
|
|
54
|
+
const onClick = jest.fn();
|
|
55
|
+
|
|
56
|
+
renderHostItem({
|
|
57
|
+
...HostItemFixtures.renders,
|
|
58
|
+
actions: [{ title: 'View output', action: { onClick } }],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await userEvent.click(
|
|
62
|
+
screen.getByRole('button', { name: 'View output' })
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders failed status text for error status', () => {
|
|
69
|
+
renderHostItem({
|
|
70
|
+
...HostItemFixtures.renders,
|
|
71
|
+
status: 'error',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(screen.getByText('Failed')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -1,43 +1,76 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
3
5
|
import AggregateStatus from './index';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
const defaultStatuses = {
|
|
8
|
+
success: 19,
|
|
9
|
+
failed: 20,
|
|
10
|
+
pending: 3,
|
|
11
|
+
cancelled: 31,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const renderAggregateStatus = ({
|
|
15
|
+
statuses = defaultStatuses,
|
|
16
|
+
chartFilter = jest.fn(),
|
|
17
|
+
} = {}) => {
|
|
18
|
+
const view = render(
|
|
19
|
+
<AggregateStatus statuses={statuses} chartFilter={chartFilter} />
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return { chartFilter, ...view };
|
|
23
|
+
};
|
|
6
24
|
|
|
7
25
|
describe('AggregateStatus', () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
it('renders status notifications without counts when statuses are empty', () => {
|
|
27
|
+
const { container } = renderAggregateStatus({ statuses: {} });
|
|
28
|
+
|
|
29
|
+
expect(container.textContent.trim()).toBe('');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders status counts from props', () => {
|
|
33
|
+
renderAggregateStatus();
|
|
34
|
+
|
|
35
|
+
expect(screen.getByText('19')).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByText('20')).toBeInTheDocument();
|
|
37
|
+
expect(screen.getByText('3')).toBeInTheDocument();
|
|
38
|
+
expect(screen.getByText('31')).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('calls chartFilter with success when the success count is clicked', async () => {
|
|
42
|
+
const { chartFilter } = renderAggregateStatus();
|
|
43
|
+
|
|
44
|
+
await userEvent.click(screen.getByText('19'));
|
|
45
|
+
|
|
46
|
+
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
47
|
+
expect(chartFilter).toHaveBeenCalledWith('success');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('calls chartFilter with failed when the failed count is clicked', async () => {
|
|
51
|
+
const { chartFilter } = renderAggregateStatus();
|
|
52
|
+
|
|
53
|
+
await userEvent.click(screen.getByText('20'));
|
|
54
|
+
|
|
55
|
+
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
56
|
+
expect(chartFilter).toHaveBeenCalledWith('failed');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('calls chartFilter with pending when the pending count is clicked', async () => {
|
|
60
|
+
const { chartFilter } = renderAggregateStatus();
|
|
61
|
+
|
|
62
|
+
await userEvent.click(screen.getByText('3'));
|
|
63
|
+
|
|
64
|
+
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(chartFilter).toHaveBeenCalledWith('pending');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('calls chartFilter with cancelled when the cancelled count is clicked', async () => {
|
|
69
|
+
const { chartFilter } = renderAggregateStatus();
|
|
70
|
+
|
|
71
|
+
await userEvent.click(screen.getByText('31'));
|
|
72
|
+
|
|
73
|
+
expect(chartFilter).toHaveBeenCalledTimes(1);
|
|
74
|
+
expect(chartFilter).toHaveBeenCalledWith('cancelled');
|
|
42
75
|
});
|
|
43
76
|
});
|
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: 17.2.
|
|
4
|
+
version: 17.2.1
|
|
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-09-03 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: deface
|
|
@@ -417,8 +417,8 @@ files:
|
|
|
417
417
|
- webpack/JobWizard/JobWizardSelectors.js
|
|
418
418
|
- webpack/JobWizard/PermissionDenied.js
|
|
419
419
|
- webpack/JobWizard/StartsErrorAlert.js
|
|
420
|
+
- webpack/JobWizard/__tests__/Footer.test.js
|
|
420
421
|
- webpack/JobWizard/__tests__/JobWizardPageRerun.test.js
|
|
421
|
-
- webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap
|
|
422
422
|
- webpack/JobWizard/__tests__/fixtures.js
|
|
423
423
|
- webpack/JobWizard/__tests__/integration.test.js
|
|
424
424
|
- webpack/JobWizard/__tests__/validation.test.js
|
|
@@ -525,7 +525,6 @@ files:
|
|
|
525
525
|
- webpack/react_app/components/TargetingHosts/__tests__/TargetingHosts.test.js
|
|
526
526
|
- webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsPage.test.js
|
|
527
527
|
- webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js
|
|
528
|
-
- webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostItem.test.js.snap
|
|
529
528
|
- webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostStatus.test.js.snap
|
|
530
529
|
- webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap
|
|
531
530
|
- webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`Job wizard fill should select template: initial 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
|
-
]
|
|
41
|
-
`;
|
|
42
|
-
|
|
43
|
-
exports[`Job wizard fill should select template: select template 1`] = `
|
|
44
|
-
Array [
|
|
45
|
-
Object {
|
|
46
|
-
"key": "JOB_TEMPLATE",
|
|
47
|
-
"type": "get",
|
|
48
|
-
"url": "/ui_job_wizard/template/178",
|
|
49
|
-
},
|
|
50
|
-
]
|
|
51
|
-
`;
|
data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostItem.test.js.snap
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`HostItem renders 1`] = `
|
|
4
|
-
<tr
|
|
5
|
-
id="targeting-host-Host1"
|
|
6
|
-
>
|
|
7
|
-
<td
|
|
8
|
-
className="host_name"
|
|
9
|
-
>
|
|
10
|
-
<a
|
|
11
|
-
href="/host1"
|
|
12
|
-
>
|
|
13
|
-
Host1
|
|
14
|
-
</a>
|
|
15
|
-
</td>
|
|
16
|
-
<td
|
|
17
|
-
className="host_status"
|
|
18
|
-
>
|
|
19
|
-
<HostStatus
|
|
20
|
-
status="success"
|
|
21
|
-
/>
|
|
22
|
-
</td>
|
|
23
|
-
<td
|
|
24
|
-
className="host_actions"
|
|
25
|
-
>
|
|
26
|
-
<ActionButtons
|
|
27
|
-
buttons={Array []}
|
|
28
|
-
/>
|
|
29
|
-
</td>
|
|
30
|
-
</tr>
|
|
31
|
-
`;
|