foreman_remote_execution 4.2.2 → 4.5.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/.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 +72 -17
- data/app/models/remote_execution_provider.rb +17 -2
- 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/app/views/template_invocations/show.html.erb +30 -23
- data/config/routes.rb +5 -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 -2
- 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 +38 -5
- 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 -20
- 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
|
+
`;
|