foreman_remote_execution 4.2.3 → 4.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/js_ci.yml +29 -0
- data/.github/workflows/{ci.yml → ruby_ci.yml} +22 -50
- data/.prettierrc +4 -0
- data/.rubocop.yml +13 -49
- data/.rubocop_todo.yml +326 -102
- data/Gemfile +1 -4
- data/app/controllers/api/v2/job_invocations_controller.rb +28 -23
- data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
- data/app/controllers/job_templates_controller.rb +4 -4
- data/app/controllers/ui_job_wizard_controller.rb +30 -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 +68 -5
- 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 +5 -5
- data/app/models/host_status/execution_status.rb +12 -5
- data/app/models/invocation_provider_input_value.rb +12 -0
- data/app/models/job_invocation.rb +28 -8
- data/app/models/job_invocation_composer.rb +73 -18
- data/app/models/remote_execution_provider.rb +18 -3
- data/app/models/setting/remote_execution.rb +10 -0
- data/app/models/ssh_execution_provider.rb +4 -4
- 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/config/routes.rb +4 -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/extra/cockpit/foreman-cockpit-session +6 -6
- data/foreman_remote_execution.gemspec +1 -3
- data/lib/foreman_remote_execution/engine.rb +22 -7
- 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 +29 -6
- data/test/functional/api/v2/registration_controller_test.rb +4 -13
- data/test/functional/ui_job_wizard_controller_test.rb +16 -0
- data/test/helpers/remote_execution_helper_test.rb +16 -0
- data/test/unit/job_invocation_composer_test.rb +86 -2
- data/test/unit/job_invocation_report_template_test.rb +57 -0
- data/webpack/JobWizard/JobWizard.js +96 -0
- 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/index.js +32 -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/Routes/routes.js +12 -0
- data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
- data/webpack/__mocks__/foremanReact/history.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 +10 -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/TargetingHostsPage.js +1 -1
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +0 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -1
- 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 +58 -34
- data/app/views/api/v2/registration/_form.html.erb +0 -12
@@ -16,16 +16,34 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
16
16
|
setup_user('create', 'hosts')
|
17
17
|
end
|
18
18
|
|
19
|
+
class AnsibleInputs < RemoteExecutionProvider
|
20
|
+
class << self
|
21
|
+
def provider_input_namespace
|
22
|
+
:ansible
|
23
|
+
end
|
24
|
+
|
25
|
+
def provider_inputs
|
26
|
+
[
|
27
|
+
ForemanRemoteExecution::ProviderInput.new(name: 'tags', label: 'Tags', value: 'fooo', value_type: 'plain'),
|
28
|
+
ForemanRemoteExecution::ProviderInput.new(name: 'tags_flag', label: 'Tags Flag', value: '--tags', options: "--tags\n--skip-tags"),
|
29
|
+
]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
RemoteExecutionProvider.register(:AnsibleInputs, AnsibleInputs)
|
34
|
+
|
19
35
|
let(:trying_job_template_1) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'trying1', :provider_type => 'SSH') }
|
20
36
|
let(:trying_job_template_2) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_2', :name => 'trying2', :provider_type => 'Mcollective') }
|
21
37
|
let(:trying_job_template_3) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'trying3', :provider_type => 'SSH') }
|
22
38
|
let(:unauthorized_job_template_1) { FactoryBot.create(:job_template, :job_category => 'trying_job_template_1', :name => 'unauth1', :provider_type => 'SSH') }
|
23
39
|
let(:unauthorized_job_template_2) { FactoryBot.create(:job_template, :job_category => 'unauthorized_job_template_2', :name => 'unauth2', :provider_type => 'Ansible') }
|
24
40
|
|
41
|
+
let(:provider_inputs_job_template) { FactoryBot.create(:job_template, :job_category => 'trying_test_inputs', :name => 'trying provider inputs', :provider_type => 'AnsibleInputs') }
|
25
42
|
|
26
43
|
let(:input1) { FactoryBot.create(:template_input, :template => trying_job_template_1, :input_type => 'user') }
|
27
44
|
let(:input2) { FactoryBot.create(:template_input, :template => trying_job_template_3, :input_type => 'user') }
|
28
45
|
let(:input3) { FactoryBot.create(:template_input, :template => trying_job_template_1, :input_type => 'user', :required => true) }
|
46
|
+
let(:input4) { FactoryBot.create(:template_input, :template => provider_inputs_job_template, :input_type => 'user') }
|
29
47
|
let(:unauthorized_input1) { FactoryBot.create(:template_input, :template => unauthorized_job_template_1, :input_type => 'user') }
|
30
48
|
|
31
49
|
let(:ansible_params) { { } }
|
@@ -604,8 +622,10 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
604
622
|
end
|
605
623
|
end
|
606
624
|
|
607
|
-
describe '
|
608
|
-
let(:composer)
|
625
|
+
describe '.from_api_params' do
|
626
|
+
let(:composer) do
|
627
|
+
JobInvocationComposer.from_api_params(params)
|
628
|
+
end
|
609
629
|
let(:bookmark) { bookmarks(:one) }
|
610
630
|
|
611
631
|
context 'with targeting from bookmark' do
|
@@ -659,6 +679,26 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
659
679
|
end
|
660
680
|
end
|
661
681
|
|
682
|
+
context 'with provider inputs' do
|
683
|
+
let(:params) do
|
684
|
+
{ :job_category => provider_inputs_job_template.job_category,
|
685
|
+
:job_template_id => provider_inputs_job_template.id,
|
686
|
+
:targeting_type => 'static_query',
|
687
|
+
:search_query => 'some hosts',
|
688
|
+
:inputs => { input4.name => 'some_value' },
|
689
|
+
:ansible => { 'tags' => 'bar', 'tags_flag' => '--skip-tags' } }
|
690
|
+
end
|
691
|
+
|
692
|
+
it 'detects provider inputs' do
|
693
|
+
assert composer.save!
|
694
|
+
scope = composer.job_invocation.pattern_template_invocations.first.provider_input_values
|
695
|
+
tags = scope.find_by :name => 'tags'
|
696
|
+
flags = scope.find_by :name => 'tags_flag'
|
697
|
+
assert_equal 'bar', tags.value
|
698
|
+
assert_equal '--skip-tags', flags.value
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
662
702
|
context 'with effective user' do
|
663
703
|
let(:params) do
|
664
704
|
{ :job_category => trying_job_template_1.job_category,
|
@@ -714,12 +754,23 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
714
754
|
assert composer.save!
|
715
755
|
_(composer.job_invocation.remote_execution_feature).must_equal feature
|
716
756
|
end
|
757
|
+
|
758
|
+
it 'sets the remote execution_feature id based on `feature` param' do
|
759
|
+
params[:remote_execution_feature_id] = nil
|
760
|
+
params[:feature] = feature.label
|
761
|
+
params[:job_template_id] = trying_job_template_1.id
|
762
|
+
refute_equal feature.job_template, trying_job_template_1
|
763
|
+
|
764
|
+
assert composer.save!
|
765
|
+
_(composer.job_invocation.remote_execution_feature).must_equal feature
|
766
|
+
end
|
717
767
|
end
|
718
768
|
|
719
769
|
context 'with invalid targeting' do
|
720
770
|
let(:params) do
|
721
771
|
{ :job_category => trying_job_template_1.job_category,
|
722
772
|
:job_template_id => trying_job_template_1.id,
|
773
|
+
:targeting_type => 'fake',
|
723
774
|
:search_query => 'some hosts',
|
724
775
|
:inputs => {input1.name => 'some_value'}}
|
725
776
|
end
|
@@ -825,6 +876,39 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
825
876
|
end
|
826
877
|
end
|
827
878
|
|
879
|
+
describe '.for_feature' do
|
880
|
+
let(:feature) { FactoryBot.create(:remote_execution_feature, job_template: trying_job_template_1) }
|
881
|
+
let(:host) { FactoryBot.create(:host) }
|
882
|
+
let(:bookmark) { Bookmark.create!(:query => 'b', :name => 'bookmark', :public => true, :controller => 'hosts') }
|
883
|
+
|
884
|
+
context 'specifying hosts' do
|
885
|
+
it 'takes a bookmarked search' do
|
886
|
+
composer = JobInvocationComposer.for_feature(feature.label, bookmark, {})
|
887
|
+
assert_equal bookmark.id, composer.params['targeting']['bookmark_id']
|
888
|
+
end
|
889
|
+
|
890
|
+
it 'takes an array of host ids' do
|
891
|
+
composer = JobInvocationComposer.for_feature(feature.label, [host.id], {})
|
892
|
+
assert_match(/#{host.name}/, composer.params['targeting']['search_query'])
|
893
|
+
end
|
894
|
+
|
895
|
+
it 'takes a single host object' do
|
896
|
+
composer = JobInvocationComposer.for_feature(feature.label, host, {})
|
897
|
+
assert_match(/#{host.name}/, composer.params['targeting']['search_query'])
|
898
|
+
end
|
899
|
+
|
900
|
+
it 'takes an array of host FQDNs' do
|
901
|
+
composer = JobInvocationComposer.for_feature(feature.label, [host.fqdn], {})
|
902
|
+
assert_match(/#{host.name}/, composer.params['targeting']['search_query'])
|
903
|
+
end
|
904
|
+
|
905
|
+
it 'takes a search query string' do
|
906
|
+
composer = JobInvocationComposer.for_feature(feature.label, 'host.example.com', {})
|
907
|
+
assert_equal 'host.example.com', composer.search_query
|
908
|
+
end
|
909
|
+
end
|
910
|
+
end
|
911
|
+
|
828
912
|
describe '#resolve_job_category and #resolve job_templates' do
|
829
913
|
let(:setting_template) { as_admin { FactoryBot.create(:job_template, :name => 'trying setting', :job_category => 'fluff') } }
|
830
914
|
let(:other_template) { as_admin { FactoryBot.create(:job_template, :name => 'trying something', :job_category => 'fluff') } }
|
@@ -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
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
3
|
+
import { Wizard } from '@patternfly/react-core';
|
4
|
+
import { get } from 'foremanReact/redux/API';
|
5
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
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';
|
12
|
+
|
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;
|
40
|
+
const steps = [
|
41
|
+
{
|
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,
|
77
|
+
},
|
78
|
+
{
|
79
|
+
name: __('Review Details'),
|
80
|
+
component: <p>Review Details</p>,
|
81
|
+
nextButtonText: 'Run',
|
82
|
+
canJumpTo: isTemplate,
|
83
|
+
},
|
84
|
+
];
|
85
|
+
return (
|
86
|
+
<Wizard
|
87
|
+
onClose={() => history.goBack()}
|
88
|
+
navAriaLabel="Run Job steps"
|
89
|
+
steps={steps}
|
90
|
+
height="100%"
|
91
|
+
className="job-wizard"
|
92
|
+
/>
|
93
|
+
);
|
94
|
+
};
|
95
|
+
|
96
|
+
export default JobWizard;
|
@@ -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
|
+
`;
|