foreman_remote_execution 4.3.0 → 4.5.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/api/v2/job_invocations_controller.rb +27 -22
- data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
- data/app/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/job_templates_controller.rb +4 -4
- data/app/controllers/ui_job_wizard_controller.rb +12 -0
- data/app/helpers/job_invocations_helper.rb +2 -2
- data/app/helpers/remote_execution_helper.rb +35 -8
- data/app/lib/actions/remote_execution/run_host_job.rb +37 -7
- data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
- data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
- data/app/models/host_proxy_invocation.rb +4 -0
- data/app/models/host_status/execution_status.rb +5 -5
- data/app/models/invocation_provider_input_value.rb +12 -0
- data/app/models/job_invocation.rb +35 -12
- data/app/models/job_invocation_composer.rb +74 -19
- data/app/models/remote_execution_provider.rb +18 -3
- data/app/models/setting/remote_execution.rb +11 -1
- data/app/models/ssh_execution_provider.rb +4 -4
- data/app/models/targeting.rb +5 -1
- data/app/models/template_invocation.rb +2 -0
- data/app/overrides/execution_interface.rb +8 -8
- data/app/overrides/subnet_proxies.rb +6 -6
- data/app/services/renderer_methods.rb +12 -0
- data/app/views/job_invocations/_form.html.erb +8 -0
- data/app/views/job_invocations/index.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
- data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
- data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
- data/extra/cockpit/foreman-cockpit-session +6 -6
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +14 -12
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
- data/locale/action_names.rb +1 -0
- data/locale/de/foreman_remote_execution.po +77 -27
- data/locale/en/foreman_remote_execution.po +77 -27
- data/locale/en_GB/foreman_remote_execution.po +77 -27
- data/locale/es/foreman_remote_execution.po +77 -27
- data/locale/foreman_remote_execution.pot +241 -163
- data/locale/fr/foreman_remote_execution.po +77 -27
- data/locale/ja/foreman_remote_execution.po +77 -27
- data/locale/ko/foreman_remote_execution.po +77 -27
- data/locale/pt_BR/foreman_remote_execution.po +77 -27
- data/locale/ru/foreman_remote_execution.po +77 -27
- data/locale/zh_CN/foreman_remote_execution.po +77 -27
- data/locale/zh_TW/foreman_remote_execution.po +77 -27
- data/package.json +4 -2
- data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
- data/test/helpers/remote_execution_helper_test.rb +16 -0
- data/test/unit/job_invocation_composer_test.rb +100 -3
- data/test/unit/job_invocation_report_template_test.rb +57 -0
- data/test/unit/job_invocation_test.rb +1 -1
- data/webpack/JobWizard/JobWizard.js +75 -11
- data/webpack/JobWizard/JobWizard.scss +14 -0
- data/webpack/JobWizard/JobWizardConstants.js +6 -0
- data/webpack/JobWizard/JobWizardSelectors.js +38 -0
- data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
- data/webpack/JobWizard/__tests__/fixtures.js +26 -0
- data/webpack/JobWizard/__tests__/integration.test.js +156 -0
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +52 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
- data/webpack/JobWizard/steps/form/SelectField.js +48 -0
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
- data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
- data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
- data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
- data/webpack/global_index.js +6 -0
- data/webpack/index.js +3 -4
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
- data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
- data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
- data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
- data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
- data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
- data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
- data/webpack/react_app/extend/reducers.js +5 -0
- metadata +49 -8
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
- data/app/views/api/v2/registration/_form.html.erb +0 -12
- data/test/models/orchestration/ssh_test.rb +0 -56
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'test_plugin_helper'
|
|
2
|
+
|
|
3
|
+
class JobReportTemplateTest < ActiveSupport::TestCase
|
|
4
|
+
class FakeTask < OpenStruct
|
|
5
|
+
class Jail < Safemode::Jail
|
|
6
|
+
allow :action_continuous_output, :result, :ended_at
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context 'with valid job invocation report template' do
|
|
11
|
+
let(:job_invocation_template) do
|
|
12
|
+
file_path = File.read(File.expand_path(Rails.root + "app/views/unattended/report_templates/jobs_-_invocation_report_template.erb"))
|
|
13
|
+
template = ReportTemplate.import_without_save("Job Invocation Report Template", file_path)
|
|
14
|
+
template.save!
|
|
15
|
+
template
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe 'template setting' do
|
|
19
|
+
it 'in settings includes only report templates with job_id input' do
|
|
20
|
+
FactoryBot.create(:report_template, name: 'Template 1')
|
|
21
|
+
job_invocation_template
|
|
22
|
+
templates = Setting::RemoteExecution.job_invocation_report_templates_select
|
|
23
|
+
|
|
24
|
+
assert_include templates, 'Job Invocation Report Template'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe 'task reporting' do
|
|
29
|
+
let(:fake_outputs) do
|
|
30
|
+
[
|
|
31
|
+
{ 'output_type' => 'stderr', 'output' => "error", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
|
|
32
|
+
{ 'output_type' => 'stdout', 'output' => "output", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
|
|
33
|
+
{ 'output_type' => 'stdebug', 'output' => "debug", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
|
|
34
|
+
]
|
|
35
|
+
end
|
|
36
|
+
let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs) }
|
|
37
|
+
|
|
38
|
+
it 'should render task outputs' do
|
|
39
|
+
job_invocation = FactoryBot.create(:job_invocation, :with_task)
|
|
40
|
+
JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
|
|
41
|
+
|
|
42
|
+
input = job_invocation_template.template_inputs.first
|
|
43
|
+
composer_params = { template_id: job_invocation_template.id, input_values: { input.id.to_s => { value: job_invocation.id.to_s } } }
|
|
44
|
+
result = ReportComposer.new(composer_params).render
|
|
45
|
+
|
|
46
|
+
# parsing the CSV result
|
|
47
|
+
CSV.parse(result.strip, headers: true).each_with_index do |row, i|
|
|
48
|
+
row_hash = row.to_h
|
|
49
|
+
assert_equal 'success', row_hash['result']
|
|
50
|
+
assert_equal fake_outputs[i]['output_type'], row_hash['type']
|
|
51
|
+
assert_equal fake_outputs[i]['output'], row_hash['message']
|
|
52
|
+
assert_kind_of Time, Time.zone.parse(row_hash['time']), 'Parsing of time column failed'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -10,7 +10,7 @@ class JobInvocationTest < ActiveSupport::TestCase
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
it 'is able to perform search through job invocations' do
|
|
13
|
-
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).
|
|
13
|
+
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).order('job_invocations.id DESC')
|
|
14
14
|
_(found_jobs).must_equal [job_invocation]
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -1,30 +1,94 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
2
3
|
import { Wizard } from '@patternfly/react-core';
|
|
4
|
+
import { get } from 'foremanReact/redux/API';
|
|
3
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
6
|
import history from 'foremanReact/history';
|
|
7
|
+
import CategoryAndTemplate from './steps/CategoryAndTemplate/';
|
|
8
|
+
import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
|
|
9
|
+
import { JOB_TEMPLATE } from './JobWizardConstants';
|
|
10
|
+
import { selectTemplateError } from './JobWizardSelectors';
|
|
11
|
+
import './JobWizard.scss';
|
|
5
12
|
|
|
6
13
|
export const JobWizard = () => {
|
|
14
|
+
const [jobTemplateID, setJobTemplateID] = useState(null);
|
|
15
|
+
const [category, setCategory] = useState('');
|
|
16
|
+
const [advancedValues, setAdvancedValues] = useState({});
|
|
17
|
+
const dispatch = useDispatch();
|
|
18
|
+
|
|
19
|
+
const setDefaults = useCallback(response => {
|
|
20
|
+
const responseJob = response.data;
|
|
21
|
+
setAdvancedValues({
|
|
22
|
+
effectiveUserValue: responseJob.effective_user?.value || '',
|
|
23
|
+
timeoutToKill: responseJob.job_template.execution_timeout_interval || '',
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (jobTemplateID) {
|
|
28
|
+
dispatch(
|
|
29
|
+
get({
|
|
30
|
+
key: JOB_TEMPLATE,
|
|
31
|
+
url: `/ui_job_wizard/template/${jobTemplateID}`,
|
|
32
|
+
handleSuccess: setDefaults,
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}, [jobTemplateID, setDefaults, dispatch]);
|
|
37
|
+
|
|
38
|
+
const templateError = !!useSelector(selectTemplateError);
|
|
39
|
+
const isTemplate = !templateError && !!jobTemplateID;
|
|
7
40
|
const steps = [
|
|
8
41
|
{
|
|
9
|
-
name: __('Category and
|
|
10
|
-
component:
|
|
42
|
+
name: __('Category and Template'),
|
|
43
|
+
component: (
|
|
44
|
+
<CategoryAndTemplate
|
|
45
|
+
jobTemplate={jobTemplateID}
|
|
46
|
+
setJobTemplate={setJobTemplateID}
|
|
47
|
+
category={category}
|
|
48
|
+
setCategory={setCategory}
|
|
49
|
+
/>
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: __('Target Hosts'),
|
|
54
|
+
component: <p>Target Hosts</p>,
|
|
55
|
+
canJumpTo: isTemplate,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: __('Advanced Fields'),
|
|
59
|
+
component: (
|
|
60
|
+
<AdvancedFields
|
|
61
|
+
advancedValues={advancedValues}
|
|
62
|
+
setAdvancedValues={newValues => {
|
|
63
|
+
setAdvancedValues(currentAdvancedValues => ({
|
|
64
|
+
...currentAdvancedValues,
|
|
65
|
+
...newValues,
|
|
66
|
+
}));
|
|
67
|
+
}}
|
|
68
|
+
jobTemplateID={jobTemplateID}
|
|
69
|
+
/>
|
|
70
|
+
),
|
|
71
|
+
canJumpTo: isTemplate,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: __('Schedule'),
|
|
75
|
+
component: <p>Schedule</p>,
|
|
76
|
+
canJumpTo: isTemplate,
|
|
11
77
|
},
|
|
12
|
-
{ name: __('Target hosts'), component: <p>TargetHosts </p> },
|
|
13
|
-
{ name: __('Advanced fields'), component: <p> AdvancedFields </p> },
|
|
14
|
-
{ name: __('Schedule'), component: <p>Schedule</p> },
|
|
15
78
|
{
|
|
16
|
-
name: __('Review
|
|
17
|
-
component: <p>
|
|
79
|
+
name: __('Review Details'),
|
|
80
|
+
component: <p>Review Details</p>,
|
|
18
81
|
nextButtonText: 'Run',
|
|
82
|
+
canJumpTo: isTemplate,
|
|
19
83
|
},
|
|
20
84
|
];
|
|
21
|
-
const title = __('Run Job');
|
|
22
85
|
return (
|
|
23
86
|
<Wizard
|
|
24
87
|
onClose={() => history.goBack()}
|
|
25
|
-
navAriaLabel=
|
|
88
|
+
navAriaLabel="Run Job steps"
|
|
26
89
|
steps={steps}
|
|
27
|
-
height="
|
|
90
|
+
height="100%"
|
|
91
|
+
className="job-wizard"
|
|
28
92
|
/>
|
|
29
93
|
);
|
|
30
94
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.job-wizard {
|
|
2
|
+
.pf-c-wizard__main {
|
|
3
|
+
z-index: calc(
|
|
4
|
+
var(--pf-c-wizard__footer--ZIndex) + 1
|
|
5
|
+
); // So the select box can be shown above the wizard footer
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.pf-c-wizard__main-body {
|
|
9
|
+
max-width: 500px;
|
|
10
|
+
.advanced-fields-title {
|
|
11
|
+
margin-bottom: 10px;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
2
|
+
|
|
3
|
+
export const JOB_TEMPLATES = 'JOB_TEMPLATES';
|
|
4
|
+
export const JOB_CATEGORIES = 'JOB_CATEGORIES';
|
|
5
|
+
export const JOB_TEMPLATE = 'JOB_TEMPLATE';
|
|
6
|
+
export const templatesUrl = foremanUrl('/api/v2/job_templates');
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
selectAPIResponse,
|
|
3
|
+
selectAPIStatus,
|
|
4
|
+
selectAPIErrorMessage,
|
|
5
|
+
} from 'foremanReact/redux/API/APISelectors';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
JOB_TEMPLATES,
|
|
9
|
+
JOB_CATEGORIES,
|
|
10
|
+
JOB_TEMPLATE,
|
|
11
|
+
} from './JobWizardConstants';
|
|
12
|
+
|
|
13
|
+
export const selectJobTemplatesStatus = state =>
|
|
14
|
+
selectAPIStatus(state, JOB_TEMPLATES);
|
|
15
|
+
|
|
16
|
+
export const filterJobTemplates = templates =>
|
|
17
|
+
templates?.filter(template => !template.snippet) || [];
|
|
18
|
+
|
|
19
|
+
export const selectJobTemplates = state =>
|
|
20
|
+
filterJobTemplates(selectAPIResponse(state, JOB_TEMPLATES)?.results);
|
|
21
|
+
|
|
22
|
+
export const selectJobCategories = state =>
|
|
23
|
+
selectAPIResponse(state, JOB_CATEGORIES).job_categories || [];
|
|
24
|
+
|
|
25
|
+
export const selectJobCategoriesStatus = state =>
|
|
26
|
+
selectAPIStatus(state, JOB_CATEGORIES);
|
|
27
|
+
|
|
28
|
+
export const selectCategoryError = state =>
|
|
29
|
+
selectAPIErrorMessage(state, JOB_CATEGORIES);
|
|
30
|
+
|
|
31
|
+
export const selectAllTemplatesError = state =>
|
|
32
|
+
selectAPIErrorMessage(state, JOB_TEMPLATES);
|
|
33
|
+
|
|
34
|
+
export const selectTemplateError = state =>
|
|
35
|
+
selectAPIErrorMessage(state, JOB_TEMPLATE);
|
|
36
|
+
|
|
37
|
+
export const selectJobTemplate = state =>
|
|
38
|
+
selectAPIResponse(state, JOB_TEMPLATE);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as patternfly from '@patternfly/react-core';
|
|
3
|
+
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
|
|
4
|
+
import JobWizardPage from '../index';
|
|
5
|
+
|
|
6
|
+
jest.spyOn(patternfly, 'Wizard');
|
|
7
|
+
patternfly.Wizard.mockImplementation(props => <div>{props.navAriaLabel}</div>);
|
|
8
|
+
|
|
9
|
+
const fixtures = {
|
|
10
|
+
'renders ': {},
|
|
11
|
+
};
|
|
12
|
+
describe('JobWizardPage rendering', () =>
|
|
13
|
+
testComponentSnapshotsWithFixtures(JobWizardPage, fixtures));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`JobWizardPage rendering renders 1`] = `
|
|
4
|
+
<PageLayout
|
|
5
|
+
breadcrumbOptions={
|
|
6
|
+
Object {
|
|
7
|
+
"breadcrumbItems": Array [
|
|
8
|
+
Object {
|
|
9
|
+
"caption": "Jobs",
|
|
10
|
+
"url": "/jobs",
|
|
11
|
+
},
|
|
12
|
+
Object {
|
|
13
|
+
"caption": "Run job",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
header="Run job"
|
|
19
|
+
searchable={false}
|
|
20
|
+
>
|
|
21
|
+
<Title
|
|
22
|
+
headingLevel="h2"
|
|
23
|
+
size="2xl"
|
|
24
|
+
>
|
|
25
|
+
Run job
|
|
26
|
+
</Title>
|
|
27
|
+
<Divider
|
|
28
|
+
component="div"
|
|
29
|
+
/>
|
|
30
|
+
<JobWizard />
|
|
31
|
+
</PageLayout>
|
|
32
|
+
`;
|
|
@@ -0,0 +1,43 @@
|
|
|
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": "JOB_TEMPLATES",
|
|
12
|
+
"type": "get",
|
|
13
|
+
"url": URI {
|
|
14
|
+
"_deferred_build": true,
|
|
15
|
+
"_parts": Object {
|
|
16
|
+
"duplicateQueryParameters": false,
|
|
17
|
+
"escapeQuerySpace": true,
|
|
18
|
+
"fragment": null,
|
|
19
|
+
"hostname": null,
|
|
20
|
+
"password": null,
|
|
21
|
+
"path": "foreman/api/v2/job_templates",
|
|
22
|
+
"port": null,
|
|
23
|
+
"preventInvalidHostname": false,
|
|
24
|
+
"protocol": null,
|
|
25
|
+
"query": "search=job_category%3D%22Ansible+Commands%22&per_page=all",
|
|
26
|
+
"urn": null,
|
|
27
|
+
"username": null,
|
|
28
|
+
},
|
|
29
|
+
"_string": "",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
exports[`Job wizard fill should select template: select template 1`] = `
|
|
36
|
+
Array [
|
|
37
|
+
Object {
|
|
38
|
+
"key": "JOB_TEMPLATE",
|
|
39
|
+
"type": "get",
|
|
40
|
+
"url": "/ui_job_wizard/template/178",
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const jobTemplate = {
|
|
2
|
+
id: 178,
|
|
3
|
+
name: 'Run Command - Ansible Default',
|
|
4
|
+
template:
|
|
5
|
+
"---\n- hosts: all\n tasks:\n - shell:\n cmd: |\n<%= indent(10) { input('command') } %>\n register: out\n - debug: var=out",
|
|
6
|
+
snippet: false,
|
|
7
|
+
default: true,
|
|
8
|
+
job_category: 'Ansible Commands',
|
|
9
|
+
provider_type: 'Ansible',
|
|
10
|
+
description_format: 'Run %{command}',
|
|
11
|
+
execution_timeout_interval: 2,
|
|
12
|
+
description: null,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const jobTemplates = [jobTemplate];
|
|
16
|
+
|
|
17
|
+
export const jobTemplateResponse = {
|
|
18
|
+
job_template: jobTemplate,
|
|
19
|
+
effective_user: {
|
|
20
|
+
id: null,
|
|
21
|
+
job_template_id: 178,
|
|
22
|
+
value: 'default effective user',
|
|
23
|
+
overridable: true,
|
|
24
|
+
current_user: false,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import configureMockStore from 'redux-mock-store';
|
|
4
|
+
import { mount } from '@theforeman/test';
|
|
5
|
+
import { render, fireEvent, screen, act } from '@testing-library/react';
|
|
6
|
+
import * as api from 'foremanReact/redux/API';
|
|
7
|
+
import { JobWizard } from '../JobWizard';
|
|
8
|
+
import * as selectors from '../JobWizardSelectors';
|
|
9
|
+
import { jobTemplates, jobTemplateResponse as jobTemplate } from './fixtures';
|
|
10
|
+
|
|
11
|
+
jest.spyOn(api, 'get');
|
|
12
|
+
jest.spyOn(selectors, 'selectJobTemplate');
|
|
13
|
+
jest.spyOn(selectors, 'selectJobTemplates');
|
|
14
|
+
jest.spyOn(selectors, 'selectJobCategories');
|
|
15
|
+
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
|
16
|
+
|
|
17
|
+
const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
|
|
18
|
+
|
|
19
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
|
20
|
+
if (action.key === 'JOB_CATEGORIES') {
|
|
21
|
+
handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
|
|
22
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
|
23
|
+
handleSuccess &&
|
|
24
|
+
handleSuccess({
|
|
25
|
+
data: jobTemplate,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return { type: 'get', ...action };
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
selectors.selectJobTemplate.mockImplementation(() => null);
|
|
32
|
+
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
|
33
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
|
34
|
+
selectors.selectJobTemplates.mockImplementation(() => jobTemplates);
|
|
35
|
+
|
|
36
|
+
const mockStore = configureMockStore([]);
|
|
37
|
+
const store = mockStore({});
|
|
38
|
+
describe('Job wizard fill', () => {
|
|
39
|
+
it('should select template', async () => {
|
|
40
|
+
const wrapper = mount(
|
|
41
|
+
<Provider store={store}>
|
|
42
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
|
43
|
+
</Provider>
|
|
44
|
+
);
|
|
45
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
|
46
|
+
4
|
|
47
|
+
);
|
|
48
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
|
49
|
+
expect(store.getActions()).toMatchSnapshot('initial');
|
|
50
|
+
|
|
51
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
|
52
|
+
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
|
53
|
+
await act(async () => {
|
|
54
|
+
await wrapper.find('.pf-c-select__menu-item').simulate('click');
|
|
55
|
+
await wrapper.update();
|
|
56
|
+
});
|
|
57
|
+
expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
|
|
58
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
|
59
|
+
0
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should save data between steps for advanced fields', async () => {
|
|
64
|
+
const wrapper = mount(
|
|
65
|
+
<Provider store={store}>
|
|
66
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
|
67
|
+
</Provider>
|
|
68
|
+
);
|
|
69
|
+
// setup
|
|
70
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
|
71
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
|
72
|
+
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
|
73
|
+
wrapper.find('.pf-c-select__menu-item').simulate('click');
|
|
74
|
+
|
|
75
|
+
// test
|
|
76
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
|
77
|
+
0
|
|
78
|
+
);
|
|
79
|
+
wrapper
|
|
80
|
+
.find('.pf-c-wizard__nav-link')
|
|
81
|
+
.at(2)
|
|
82
|
+
.simulate('click'); // Advanced step
|
|
83
|
+
const effectiveUserInput = () => wrapper.find('input#effective-user');
|
|
84
|
+
const effectiveUesrValue = 'effective user new value';
|
|
85
|
+
effectiveUserInput().getDOMNode().value = effectiveUesrValue;
|
|
86
|
+
await act(async () => {
|
|
87
|
+
await effectiveUserInput().simulate('change');
|
|
88
|
+
wrapper.update();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
|
92
|
+
|
|
93
|
+
wrapper
|
|
94
|
+
.find('.pf-c-wizard__nav-link')
|
|
95
|
+
.at(1)
|
|
96
|
+
.simulate('click');
|
|
97
|
+
|
|
98
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-current').text()).toEqual(
|
|
99
|
+
'Target Hosts'
|
|
100
|
+
);
|
|
101
|
+
wrapper
|
|
102
|
+
.find('.pf-c-wizard__nav-link')
|
|
103
|
+
.at(2)
|
|
104
|
+
.simulate('click'); // Advanced step
|
|
105
|
+
|
|
106
|
+
expect(effectiveUserInput().prop('value')).toEqual(effectiveUesrValue);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('have all steps', async () => {
|
|
110
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
|
111
|
+
selectors.selectJobTemplate.mockRestore();
|
|
112
|
+
selectors.selectJobTemplates.mockRestore();
|
|
113
|
+
selectors.selectJobCategories.mockRestore();
|
|
114
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
|
115
|
+
if (action.key === 'JOB_CATEGORIES') {
|
|
116
|
+
handleSuccess &&
|
|
117
|
+
handleSuccess({ data: { job_categories: jobCategories } });
|
|
118
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
|
119
|
+
handleSuccess &&
|
|
120
|
+
handleSuccess({
|
|
121
|
+
data: jobTemplate,
|
|
122
|
+
});
|
|
123
|
+
} else if (action.key === 'JOB_TEMPLATES') {
|
|
124
|
+
handleSuccess &&
|
|
125
|
+
handleSuccess({
|
|
126
|
+
data: { results: [jobTemplate.job_template] },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return { type: 'get', ...action };
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
render(
|
|
133
|
+
<Provider store={store}>
|
|
134
|
+
<JobWizard />
|
|
135
|
+
</Provider>
|
|
136
|
+
);
|
|
137
|
+
const steps = [
|
|
138
|
+
'Target Hosts',
|
|
139
|
+
'Advanced Fields',
|
|
140
|
+
'Schedule',
|
|
141
|
+
'Review Details',
|
|
142
|
+
'Category and Template',
|
|
143
|
+
];
|
|
144
|
+
// eslint-disable-next-line no-unused-vars
|
|
145
|
+
for await (const step of steps) {
|
|
146
|
+
const stepSelector = screen.getByText(step);
|
|
147
|
+
const stepTitle = screen.getAllByText(step);
|
|
148
|
+
expect(stepTitle).toHaveLength(1);
|
|
149
|
+
await act(async () => {
|
|
150
|
+
await fireEvent.click(stepSelector);
|
|
151
|
+
});
|
|
152
|
+
const stepTitles = screen.getAllByText(step);
|
|
153
|
+
expect(stepTitles).toHaveLength(3);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|