foreman_remote_execution 4.3.0 → 4.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +27 -22
  3. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  4. data/app/controllers/job_invocations_controller.rb +1 -1
  5. data/app/controllers/job_templates_controller.rb +4 -4
  6. data/app/controllers/ui_job_wizard_controller.rb +12 -0
  7. data/app/helpers/job_invocations_helper.rb +2 -2
  8. data/app/helpers/remote_execution_helper.rb +35 -8
  9. data/app/lib/actions/remote_execution/run_host_job.rb +37 -7
  10. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  11. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +1 -0
  12. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
  13. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  14. data/app/models/host_proxy_invocation.rb +4 -0
  15. data/app/models/host_status/execution_status.rb +5 -5
  16. data/app/models/invocation_provider_input_value.rb +12 -0
  17. data/app/models/job_invocation.rb +35 -12
  18. data/app/models/job_invocation_composer.rb +74 -19
  19. data/app/models/remote_execution_provider.rb +18 -3
  20. data/app/models/setting/remote_execution.rb +11 -1
  21. data/app/models/ssh_execution_provider.rb +4 -4
  22. data/app/models/targeting.rb +5 -1
  23. data/app/models/template_invocation.rb +2 -0
  24. data/app/overrides/execution_interface.rb +8 -8
  25. data/app/overrides/subnet_proxies.rb +6 -6
  26. data/app/services/renderer_methods.rb +12 -0
  27. data/app/views/job_invocations/_form.html.erb +8 -0
  28. data/app/views/job_invocations/index.html.erb +1 -1
  29. data/config/routes.rb +1 -0
  30. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  31. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  32. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  33. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  34. data/extra/cockpit/foreman-cockpit-session +6 -6
  35. data/foreman_remote_execution.gemspec +1 -1
  36. data/lib/foreman_remote_execution/engine.rb +14 -12
  37. data/lib/foreman_remote_execution/version.rb +1 -1
  38. data/lib/tasks/foreman_remote_execution_tasks.rake +1 -18
  39. data/locale/action_names.rb +1 -0
  40. data/locale/de/foreman_remote_execution.po +77 -27
  41. data/locale/en/foreman_remote_execution.po +77 -27
  42. data/locale/en_GB/foreman_remote_execution.po +77 -27
  43. data/locale/es/foreman_remote_execution.po +77 -27
  44. data/locale/foreman_remote_execution.pot +241 -163
  45. data/locale/fr/foreman_remote_execution.po +77 -27
  46. data/locale/ja/foreman_remote_execution.po +77 -27
  47. data/locale/ko/foreman_remote_execution.po +77 -27
  48. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  49. data/locale/ru/foreman_remote_execution.po +77 -27
  50. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  51. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  52. data/package.json +4 -2
  53. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
  54. data/test/helpers/remote_execution_helper_test.rb +16 -0
  55. data/test/unit/job_invocation_composer_test.rb +100 -3
  56. data/test/unit/job_invocation_report_template_test.rb +57 -0
  57. data/test/unit/job_invocation_test.rb +1 -1
  58. data/webpack/JobWizard/JobWizard.js +75 -11
  59. data/webpack/JobWizard/JobWizard.scss +14 -0
  60. data/webpack/JobWizard/JobWizardConstants.js +6 -0
  61. data/webpack/JobWizard/JobWizardSelectors.js +38 -0
  62. data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
  63. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
  64. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  65. data/webpack/JobWizard/__tests__/fixtures.js +26 -0
  66. data/webpack/JobWizard/__tests__/integration.test.js +156 -0
  67. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +93 -0
  68. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +181 -0
  69. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +25 -0
  70. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
  71. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
  72. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +52 -0
  73. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
  74. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
  75. data/webpack/JobWizard/steps/form/FormHelpers.js +19 -0
  76. data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
  77. data/webpack/JobWizard/steps/form/SelectField.js +48 -0
  78. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
  79. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
  80. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
  81. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
  82. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  83. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  84. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  85. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  86. data/webpack/global_index.js +6 -0
  87. data/webpack/index.js +3 -4
  88. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
  89. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  90. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  91. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  92. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  93. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  94. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  95. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  96. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  97. data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
  98. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  99. data/webpack/react_app/extend/reducers.js +5 -0
  100. metadata +49 -8
  101. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  102. data/app/views/api/v2/registration/_form.html.erb +0 -12
  103. 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).with_task.order('job_invocations.id DESC')
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 template'),
10
- component: <p>Category and template</p>,
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 details'),
17
- component: <p>ReviewDetails</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={`${title} steps`}
88
+ navAriaLabel="Run Job steps"
26
89
  steps={steps}
27
- height="70vh"
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
+ });