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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/js_ci.yml +29 -0
  3. data/.github/workflows/{ci.yml → ruby_ci.yml} +22 -50
  4. data/.prettierrc +4 -0
  5. data/.rubocop.yml +13 -49
  6. data/.rubocop_todo.yml +326 -102
  7. data/Gemfile +1 -4
  8. data/app/controllers/api/v2/job_invocations_controller.rb +28 -23
  9. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  10. data/app/controllers/job_templates_controller.rb +4 -4
  11. data/app/controllers/ui_job_wizard_controller.rb +30 -0
  12. data/app/helpers/job_invocations_helper.rb +2 -2
  13. data/app/helpers/remote_execution_helper.rb +35 -8
  14. data/app/lib/actions/remote_execution/run_host_job.rb +68 -5
  15. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  16. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
  17. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +5 -5
  18. data/app/models/host_status/execution_status.rb +12 -5
  19. data/app/models/invocation_provider_input_value.rb +12 -0
  20. data/app/models/job_invocation.rb +28 -8
  21. data/app/models/job_invocation_composer.rb +72 -17
  22. data/app/models/remote_execution_provider.rb +17 -2
  23. data/app/models/setting/remote_execution.rb +10 -0
  24. data/app/models/ssh_execution_provider.rb +4 -4
  25. data/app/models/template_invocation.rb +2 -0
  26. data/app/overrides/execution_interface.rb +8 -8
  27. data/app/overrides/subnet_proxies.rb +6 -6
  28. data/app/services/renderer_methods.rb +12 -0
  29. data/app/views/job_invocations/_form.html.erb +8 -0
  30. data/app/views/template_invocations/show.html.erb +30 -23
  31. data/config/routes.rb +5 -0
  32. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  33. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  34. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  35. data/extra/cockpit/foreman-cockpit-session +6 -6
  36. data/foreman_remote_execution.gemspec +1 -2
  37. data/lib/foreman_remote_execution/engine.rb +22 -7
  38. data/lib/foreman_remote_execution/version.rb +1 -1
  39. data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
  40. data/locale/action_names.rb +1 -0
  41. data/locale/de/foreman_remote_execution.po +77 -27
  42. data/locale/en/foreman_remote_execution.po +77 -27
  43. data/locale/en_GB/foreman_remote_execution.po +77 -27
  44. data/locale/es/foreman_remote_execution.po +77 -27
  45. data/locale/foreman_remote_execution.pot +241 -163
  46. data/locale/fr/foreman_remote_execution.po +77 -27
  47. data/locale/ja/foreman_remote_execution.po +77 -27
  48. data/locale/ko/foreman_remote_execution.po +77 -27
  49. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  50. data/locale/ru/foreman_remote_execution.po +77 -27
  51. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  52. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  53. data/package.json +4 -2
  54. data/test/functional/api/v2/job_invocations_controller_test.rb +38 -5
  55. data/test/functional/api/v2/registration_controller_test.rb +4 -13
  56. data/test/functional/ui_job_wizard_controller_test.rb +16 -0
  57. data/test/helpers/remote_execution_helper_test.rb +16 -0
  58. data/test/unit/job_invocation_composer_test.rb +86 -2
  59. data/test/unit/job_invocation_report_template_test.rb +57 -0
  60. data/webpack/JobWizard/JobWizard.js +96 -0
  61. data/webpack/JobWizard/JobWizard.scss +14 -0
  62. data/webpack/JobWizard/JobWizardConstants.js +6 -0
  63. data/webpack/JobWizard/JobWizardSelectors.js +38 -0
  64. data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
  65. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
  66. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  67. data/webpack/JobWizard/__tests__/fixtures.js +26 -0
  68. data/webpack/JobWizard/__tests__/integration.test.js +156 -0
  69. data/webpack/JobWizard/index.js +32 -0
  70. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
  71. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
  72. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
  73. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
  74. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
  75. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +52 -0
  76. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
  77. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
  78. data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
  79. data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
  80. data/webpack/JobWizard/steps/form/SelectField.js +48 -0
  81. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
  82. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
  83. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
  84. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
  85. data/webpack/Routes/routes.js +12 -0
  86. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  87. data/webpack/__mocks__/foremanReact/history.js +1 -0
  88. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  89. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  90. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  91. data/webpack/global_index.js +10 -0
  92. data/webpack/index.js +3 -4
  93. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
  94. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  95. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  96. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  97. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  98. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  99. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  100. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +1 -1
  101. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +0 -3
  102. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  103. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -1
  104. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  105. data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
  106. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  107. data/webpack/react_app/extend/reducers.js +5 -0
  108. metadata +58 -20
  109. 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 '#from_api_params' do
608
- let(:composer) { JobInvocationComposer.from_api_params(params) }
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
+ `;