foreman_remote_execution 4.5.1 → 4.7.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/ruby_ci.yml +7 -0
 - data/app/controllers/ui_job_wizard_controller.rb +7 -0
 - data/app/graphql/types/job_invocation.rb +16 -0
 - data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
 - data/app/helpers/remote_execution_helper.rb +9 -3
 - data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
 - data/app/models/job_invocation_composer.rb +3 -3
 - data/app/models/job_template.rb +1 -1
 - data/app/models/remote_execution_feature.rb +5 -1
 - data/app/models/remote_execution_provider.rb +1 -1
 - data/app/views/templates/ssh/module_action.erb +1 -0
 - data/app/views/templates/ssh/power_action.erb +2 -0
 - data/app/views/templates/ssh/puppet_run_once.erb +1 -0
 - data/foreman_remote_execution.gemspec +2 -4
 - data/lib/foreman_remote_execution/engine.rb +3 -0
 - data/lib/foreman_remote_execution/version.rb +1 -1
 - data/test/graphql/queries/job_invocation_query_test.rb +31 -0
 - data/test/graphql/queries/job_invocations_query_test.rb +35 -0
 - data/test/unit/concerns/host_extensions_test.rb +4 -4
 - data/test/unit/input_template_renderer_test.rb +1 -89
 - data/test/unit/job_invocation_composer_test.rb +1 -12
 - data/webpack/JobWizard/JobWizard.js +28 -8
 - data/webpack/JobWizard/JobWizard.scss +39 -0
 - data/webpack/JobWizard/JobWizardConstants.js +10 -0
 - data/webpack/JobWizard/JobWizardSelectors.js +9 -0
 - data/webpack/JobWizard/__tests__/fixtures.js +104 -2
 - data/webpack/JobWizard/__tests__/integration.test.js +13 -85
 - data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
 - data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
 - data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
 - data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
 - data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
 - data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
 - data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
 - data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
 - data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
 - data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
 - data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
 - data/webpack/JobWizard/steps/Schedule/index.js +41 -0
 - data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
 - data/webpack/JobWizard/steps/form/Formatter.js +149 -0
 - data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
 - data/webpack/JobWizard/steps/form/SelectField.js +14 -2
 - data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
 - data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
 - data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
 - data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +72 -66
 - data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
 - data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
 - data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
 - data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
 - data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
 - metadata +23 -27
 - data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
 - data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
 - data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
 - data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
 - data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
 - data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
 - data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
 - data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
 - data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
 
| 
         @@ -350,12 +350,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase 
     | 
|
| 
       350 
350 
     | 
    
         
             
                  end
         
     | 
| 
       351 
351 
     | 
    
         | 
| 
       352 
352 
     | 
    
         
             
                  describe '#available_bookmarks' do
         
     | 
| 
       353 
     | 
    
         
            -
                    it 'obeys authorization' do
         
     | 
| 
       354 
     | 
    
         
            -
                      composer
         
     | 
| 
       355 
     | 
    
         
            -
                      Bookmark.expects(:authorized).with(:view_bookmarks).returns(Bookmark.where({}))
         
     | 
| 
       356 
     | 
    
         
            -
                      composer.available_bookmarks
         
     | 
| 
       357 
     | 
    
         
            -
                    end
         
     | 
| 
       358 
     | 
    
         
            -
             
     | 
| 
       359 
353 
     | 
    
         
             
                    context 'there are hostgroups and hosts bookmark' do
         
     | 
| 
       360 
354 
     | 
    
         
             
                      let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
         
     | 
| 
       361 
355 
     | 
    
         
             
                      let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
         
     | 
| 
         @@ -931,12 +925,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase 
     | 
|
| 
       931 
925 
     | 
    
         | 
| 
       932 
926 
     | 
    
         
             
                context 'with template in setting present' do
         
     | 
| 
       933 
927 
     | 
    
         
             
                  before do
         
     | 
| 
       934 
     | 
    
         
            -
                     
     | 
| 
       935 
     | 
    
         
            -
                      :setting,
         
     | 
| 
       936 
     | 
    
         
            -
                      :name => 'remote_execution_form_job_template',
         
     | 
| 
       937 
     | 
    
         
            -
                      :category => 'Setting::RemoteExecution',
         
     | 
| 
       938 
     | 
    
         
            -
                      :value => setting_template.name
         
     | 
| 
       939 
     | 
    
         
            -
                    )
         
     | 
| 
      
 928 
     | 
    
         
            +
                    Setting[:remote_execution_form_job_template] = setting_template.name
         
     | 
| 
       940 
929 
     | 
    
         
             
                  end
         
     | 
| 
       941 
930 
     | 
    
         | 
| 
       942 
931 
     | 
    
         
             
                  it 'should resolve category to the setting value' do
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            /* eslint-disable camelcase */
         
     | 
| 
       1 
2 
     | 
    
         
             
            import React, { useState, useEffect, useCallback } from 'react';
         
     | 
| 
       2 
3 
     | 
    
         
             
            import { useDispatch, useSelector } from 'react-redux';
         
     | 
| 
       3 
4 
     | 
    
         
             
            import { Wizard } from '@patternfly/react-core';
         
     | 
| 
         @@ -8,6 +9,7 @@ import CategoryAndTemplate from './steps/CategoryAndTemplate/'; 
     | 
|
| 
       8 
9 
     | 
    
         
             
            import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
         
     | 
| 
       9 
10 
     | 
    
         
             
            import { JOB_TEMPLATE } from './JobWizardConstants';
         
     | 
| 
       10 
11 
     | 
    
         
             
            import { selectTemplateError } from './JobWizardSelectors';
         
     | 
| 
      
 12 
     | 
    
         
            +
            import Schedule from './steps/Schedule/';
         
     | 
| 
       11 
13 
     | 
    
         
             
            import './JobWizard.scss';
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         
             
            export const JobWizard = () => {
         
     | 
| 
         @@ -16,13 +18,31 @@ export const JobWizard = () => { 
     | 
|
| 
       16 
18 
     | 
    
         
             
              const [advancedValues, setAdvancedValues] = useState({});
         
     | 
| 
       17 
19 
     | 
    
         
             
              const dispatch = useDispatch();
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
              const setDefaults = useCallback( 
     | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
              const setDefaults = useCallback(
         
     | 
| 
      
 22 
     | 
    
         
            +
                ({
         
     | 
| 
      
 23 
     | 
    
         
            +
                  data: {
         
     | 
| 
      
 24 
     | 
    
         
            +
                    advanced_template_inputs,
         
     | 
| 
      
 25 
     | 
    
         
            +
                    effective_user,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    job_template: { executionTimeoutInterval, description_format },
         
     | 
| 
      
 27 
     | 
    
         
            +
                  },
         
     | 
| 
      
 28 
     | 
    
         
            +
                }) => {
         
     | 
| 
      
 29 
     | 
    
         
            +
                  const advancedTemplateValues = {};
         
     | 
| 
      
 30 
     | 
    
         
            +
                  const advancedInputs = advanced_template_inputs;
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if (advancedInputs) {
         
     | 
| 
      
 32 
     | 
    
         
            +
                    advancedInputs.forEach(input => {
         
     | 
| 
      
 33 
     | 
    
         
            +
                      advancedTemplateValues[input.name] = input?.default || '';
         
     | 
| 
      
 34 
     | 
    
         
            +
                    });
         
     | 
| 
      
 35 
     | 
    
         
            +
                  }
         
     | 
| 
      
 36 
     | 
    
         
            +
                  setAdvancedValues(currentAdvancedValues => ({
         
     | 
| 
      
 37 
     | 
    
         
            +
                    ...currentAdvancedValues,
         
     | 
| 
      
 38 
     | 
    
         
            +
                    effectiveUserValue: effective_user?.value || '',
         
     | 
| 
      
 39 
     | 
    
         
            +
                    timeoutToKill: executionTimeoutInterval || '',
         
     | 
| 
      
 40 
     | 
    
         
            +
                    templateValues: advancedTemplateValues,
         
     | 
| 
      
 41 
     | 
    
         
            +
                    description: description_format || '',
         
     | 
| 
      
 42 
     | 
    
         
            +
                  }));
         
     | 
| 
      
 43 
     | 
    
         
            +
                },
         
     | 
| 
      
 44 
     | 
    
         
            +
                []
         
     | 
| 
      
 45 
     | 
    
         
            +
              );
         
     | 
| 
       26 
46 
     | 
    
         
             
              useEffect(() => {
         
     | 
| 
       27 
47 
     | 
    
         
             
                if (jobTemplateID) {
         
     | 
| 
       28 
48 
     | 
    
         
             
                  dispatch(
         
     | 
| 
         @@ -72,7 +92,7 @@ export const JobWizard = () => { 
     | 
|
| 
       72 
92 
     | 
    
         
             
                },
         
     | 
| 
       73 
93 
     | 
    
         
             
                {
         
     | 
| 
       74 
94 
     | 
    
         
             
                  name: __('Schedule'),
         
     | 
| 
       75 
     | 
    
         
            -
                  component: < 
     | 
| 
      
 95 
     | 
    
         
            +
                  component: <Schedule />,
         
     | 
| 
       76 
96 
     | 
    
         
             
                  canJumpTo: isTemplate,
         
     | 
| 
       77 
97 
     | 
    
         
             
                },
         
     | 
| 
       78 
98 
     | 
    
         
             
                {
         
     | 
| 
         @@ -10,5 +10,44 @@ 
     | 
|
| 
       10 
10 
     | 
    
         
             
                .advanced-fields-title {
         
     | 
| 
       11 
11 
     | 
    
         
             
                  margin-bottom: 10px;
         
     | 
| 
       12 
12 
     | 
    
         
             
                }
         
     | 
| 
      
 13 
     | 
    
         
            +
                #advanced-fields-job-template {
         
     | 
| 
      
 14 
     | 
    
         
            +
                  .foreman-search-field {
         
     | 
| 
      
 15 
     | 
    
         
            +
                    // Giving pf3 search bar a pf4 look
         
     | 
| 
      
 16 
     | 
    
         
            +
                    .search-bar {
         
     | 
| 
      
 17 
     | 
    
         
            +
                      display: block;
         
     | 
| 
      
 18 
     | 
    
         
            +
                    }
         
     | 
| 
      
 19 
     | 
    
         
            +
                    .input-group-btn {
         
     | 
| 
      
 20 
     | 
    
         
            +
                      display: none;
         
     | 
| 
      
 21 
     | 
    
         
            +
                    }
         
     | 
| 
      
 22 
     | 
    
         
            +
                    li {
         
     | 
| 
      
 23 
     | 
    
         
            +
                      font-size: 16px;
         
     | 
| 
      
 24 
     | 
    
         
            +
                    }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    input {
         
     | 
| 
      
 26 
     | 
    
         
            +
                      font-size: 16px;
         
     | 
| 
      
 27 
     | 
    
         
            +
                      height: 36px;
         
     | 
| 
      
 28 
     | 
    
         
            +
                    }
         
     | 
| 
      
 29 
     | 
    
         
            +
                    .foreman-autocomplete .autocomplete-focus-shortcut {
         
     | 
| 
      
 30 
     | 
    
         
            +
                      top: 8px;
         
     | 
| 
      
 31 
     | 
    
         
            +
                      font-size: 16px;
         
     | 
| 
      
 32 
     | 
    
         
            +
                    }
         
     | 
| 
      
 33 
     | 
    
         
            +
                    .foreman-autocomplete .autocomplete-aux {
         
     | 
| 
      
 34 
     | 
    
         
            +
                      top: 8px;
         
     | 
| 
      
 35 
     | 
    
         
            +
                      font-size: 16px;
         
     | 
| 
      
 36 
     | 
    
         
            +
                      .autocomplete-clear-button {
         
     | 
| 
      
 37 
     | 
    
         
            +
                        font-size: 16px;
         
     | 
| 
      
 38 
     | 
    
         
            +
                      }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    }
         
     | 
| 
      
 40 
     | 
    
         
            +
                  }
         
     | 
| 
      
 41 
     | 
    
         
            +
                }
         
     | 
| 
      
 42 
     | 
    
         
            +
              }
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              .schedule-tab {
         
     | 
| 
      
 45 
     | 
    
         
            +
                input[type='radio'],
         
     | 
| 
      
 46 
     | 
    
         
            +
                input[type='checkbox'] {
         
     | 
| 
      
 47 
     | 
    
         
            +
                  margin: 0;
         
     | 
| 
      
 48 
     | 
    
         
            +
                }
         
     | 
| 
      
 49 
     | 
    
         
            +
                .advanced-scheduling-button {
         
     | 
| 
      
 50 
     | 
    
         
            +
                  text-align: start;
         
     | 
| 
      
 51 
     | 
    
         
            +
                }
         
     | 
| 
       13 
52 
     | 
    
         
             
              }
         
     | 
| 
       14 
53 
     | 
    
         
             
            }
         
     | 
| 
         @@ -1,6 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import { translate as __ } from 'foremanReact/common/I18n';
         
     | 
| 
       1 
2 
     | 
    
         
             
            import { foremanUrl } from 'foremanReact/common/helpers';
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            export const JOB_TEMPLATES = 'JOB_TEMPLATES';
         
     | 
| 
       4 
5 
     | 
    
         
             
            export const JOB_CATEGORIES = 'JOB_CATEGORIES';
         
     | 
| 
       5 
6 
     | 
    
         
             
            export const JOB_TEMPLATE = 'JOB_TEMPLATE';
         
     | 
| 
       6 
7 
     | 
    
         
             
            export const templatesUrl = foremanUrl('/api/v2/job_templates');
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            export const repeatTypes = {
         
     | 
| 
      
 10 
     | 
    
         
            +
              noRepeat: __('Does not repeat'),
         
     | 
| 
      
 11 
     | 
    
         
            +
              cronline: __('Cronline'),
         
     | 
| 
      
 12 
     | 
    
         
            +
              monthly: __('Monthly'),
         
     | 
| 
      
 13 
     | 
    
         
            +
              weekly: __('Weekly'),
         
     | 
| 
      
 14 
     | 
    
         
            +
              daily: __('Daily'),
         
     | 
| 
      
 15 
     | 
    
         
            +
              hourly: __('Hourly'),
         
     | 
| 
      
 16 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -36,3 +36,12 @@ export const selectTemplateError = state => 
     | 
|
| 
       36 
36 
     | 
    
         | 
| 
       37 
37 
     | 
    
         
             
            export const selectJobTemplate = state =>
         
     | 
| 
       38 
38 
     | 
    
         
             
              selectAPIResponse(state, JOB_TEMPLATE);
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            export const selectEffectiveUser = state =>
         
     | 
| 
      
 41 
     | 
    
         
            +
              selectAPIResponse(state, JOB_TEMPLATE).effective_user;
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            export const selectAdvancedTemplateInputs = state =>
         
     | 
| 
      
 44 
     | 
    
         
            +
              selectAPIResponse(state, JOB_TEMPLATE).advanced_template_inputs || [];
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            export const selectTemplateInputs = state =>
         
     | 
| 
      
 47 
     | 
    
         
            +
              selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
         
     | 
| 
         @@ -1,6 +1,8 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            import configureMockStore from 'redux-mock-store';
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            export const jobTemplate = {
         
     | 
| 
       2 
4 
     | 
    
         
             
              id: 178,
         
     | 
| 
       3 
     | 
    
         
            -
              name: ' 
     | 
| 
      
 5 
     | 
    
         
            +
              name: 'template1',
         
     | 
| 
       4 
6 
     | 
    
         
             
              template:
         
     | 
| 
       5 
7 
     | 
    
         
             
                "---\n- hosts: all\n  tasks:\n    - shell:\n        cmd: |\n<%=       indent(10) { input('command') } %>\n      register: out\n    - debug: var=out",
         
     | 
| 
       6 
8 
     | 
    
         
             
              snippet: false,
         
     | 
| 
         @@ -23,4 +25,104 @@ export const jobTemplateResponse = { 
     | 
|
| 
       23 
25 
     | 
    
         
             
                overridable: true,
         
     | 
| 
       24 
26 
     | 
    
         
             
                current_user: false,
         
     | 
| 
       25 
27 
     | 
    
         
             
              },
         
     | 
| 
      
 28 
     | 
    
         
            +
              advanced_template_inputs: [
         
     | 
| 
      
 29 
     | 
    
         
            +
                {
         
     | 
| 
      
 30 
     | 
    
         
            +
                  name: 'adv plain hidden',
         
     | 
| 
      
 31 
     | 
    
         
            +
                  required: true,
         
     | 
| 
      
 32 
     | 
    
         
            +
                  input_type: 'user',
         
     | 
| 
      
 33 
     | 
    
         
            +
                  description: 'some Description',
         
     | 
| 
      
 34 
     | 
    
         
            +
                  advanced: true,
         
     | 
| 
      
 35 
     | 
    
         
            +
                  value_type: 'plain',
         
     | 
| 
      
 36 
     | 
    
         
            +
                  resource_type: 'ansible_roles',
         
     | 
| 
      
 37 
     | 
    
         
            +
                  default: 'Default val',
         
     | 
| 
      
 38 
     | 
    
         
            +
                  hidden_value: true,
         
     | 
| 
      
 39 
     | 
    
         
            +
                },
         
     | 
| 
      
 40 
     | 
    
         
            +
                {
         
     | 
| 
      
 41 
     | 
    
         
            +
                  name: 'adv plain select',
         
     | 
| 
      
 42 
     | 
    
         
            +
                  required: false,
         
     | 
| 
      
 43 
     | 
    
         
            +
                  input_type: 'user',
         
     | 
| 
      
 44 
     | 
    
         
            +
                  options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
         
     | 
| 
      
 45 
     | 
    
         
            +
                  advanced: true,
         
     | 
| 
      
 46 
     | 
    
         
            +
                  value_type: 'plain',
         
     | 
| 
      
 47 
     | 
    
         
            +
                  resource_type: 'ansible_roles',
         
     | 
| 
      
 48 
     | 
    
         
            +
                  default: '',
         
     | 
| 
      
 49 
     | 
    
         
            +
                  hidden_value: false,
         
     | 
| 
      
 50 
     | 
    
         
            +
                },
         
     | 
| 
      
 51 
     | 
    
         
            +
                {
         
     | 
| 
      
 52 
     | 
    
         
            +
                  name: 'adv search',
         
     | 
| 
      
 53 
     | 
    
         
            +
                  required: false,
         
     | 
| 
      
 54 
     | 
    
         
            +
                  options: '',
         
     | 
| 
      
 55 
     | 
    
         
            +
                  advanced: true,
         
     | 
| 
      
 56 
     | 
    
         
            +
                  value_type: 'search',
         
     | 
| 
      
 57 
     | 
    
         
            +
                  resource_type: 'foreman_tasks/tasks',
         
     | 
| 
      
 58 
     | 
    
         
            +
                  default: '',
         
     | 
| 
      
 59 
     | 
    
         
            +
                  hidden_value: false,
         
     | 
| 
      
 60 
     | 
    
         
            +
                },
         
     | 
| 
      
 61 
     | 
    
         
            +
                {
         
     | 
| 
      
 62 
     | 
    
         
            +
                  name: 'adv date',
         
     | 
| 
      
 63 
     | 
    
         
            +
                  required: false,
         
     | 
| 
      
 64 
     | 
    
         
            +
                  options: '',
         
     | 
| 
      
 65 
     | 
    
         
            +
                  advanced: true,
         
     | 
| 
      
 66 
     | 
    
         
            +
                  value_type: 'date',
         
     | 
| 
      
 67 
     | 
    
         
            +
                  resource_type: 'ansible_roles',
         
     | 
| 
      
 68 
     | 
    
         
            +
                  default: '',
         
     | 
| 
      
 69 
     | 
    
         
            +
                  hidden_value: false,
         
     | 
| 
      
 70 
     | 
    
         
            +
                },
         
     | 
| 
      
 71 
     | 
    
         
            +
              ],
         
     | 
| 
      
 72 
     | 
    
         
            +
              template_inputs: [
         
     | 
| 
      
 73 
     | 
    
         
            +
                {
         
     | 
| 
      
 74 
     | 
    
         
            +
                  name: 'plain hidden',
         
     | 
| 
      
 75 
     | 
    
         
            +
                  required: true,
         
     | 
| 
      
 76 
     | 
    
         
            +
                  input_type: 'user',
         
     | 
| 
      
 77 
     | 
    
         
            +
                  description: 'some Description',
         
     | 
| 
      
 78 
     | 
    
         
            +
                  advanced: false,
         
     | 
| 
      
 79 
     | 
    
         
            +
                  value_type: 'plain',
         
     | 
| 
      
 80 
     | 
    
         
            +
                  resource_type: 'ansible_roles',
         
     | 
| 
      
 81 
     | 
    
         
            +
                  default: 'Default val',
         
     | 
| 
      
 82 
     | 
    
         
            +
                  hidden_value: true,
         
     | 
| 
      
 83 
     | 
    
         
            +
                },
         
     | 
| 
      
 84 
     | 
    
         
            +
              ],
         
     | 
| 
      
 85 
     | 
    
         
            +
            };
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            export const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            export const testSetup = (selectors, api) => {
         
     | 
| 
      
 90 
     | 
    
         
            +
              jest.spyOn(api, 'get');
         
     | 
| 
      
 91 
     | 
    
         
            +
              jest.spyOn(selectors, 'selectJobTemplate');
         
     | 
| 
      
 92 
     | 
    
         
            +
              jest.spyOn(selectors, 'selectJobTemplates');
         
     | 
| 
      
 93 
     | 
    
         
            +
              jest.spyOn(selectors, 'selectJobCategories');
         
     | 
| 
      
 94 
     | 
    
         
            +
              jest.spyOn(selectors, 'selectJobCategoriesStatus');
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              selectors.selectJobCategories.mockImplementation(() => jobCategories);
         
     | 
| 
      
 97 
     | 
    
         
            +
              selectors.selectJobTemplates.mockImplementation(() => [
         
     | 
| 
      
 98 
     | 
    
         
            +
                jobTemplate,
         
     | 
| 
      
 99 
     | 
    
         
            +
                { ...jobTemplate, id: 2, name: 'template2' },
         
     | 
| 
      
 100 
     | 
    
         
            +
              ]);
         
     | 
| 
      
 101 
     | 
    
         
            +
              const mockStore = configureMockStore([]);
         
     | 
| 
      
 102 
     | 
    
         
            +
              const store = mockStore({});
         
     | 
| 
      
 103 
     | 
    
         
            +
              return store;
         
     | 
| 
      
 104 
     | 
    
         
            +
            };
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
            export const mockTemplate = selectors => {
         
     | 
| 
      
 107 
     | 
    
         
            +
              selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
         
     | 
| 
      
 108 
     | 
    
         
            +
              selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
         
     | 
| 
      
 109 
     | 
    
         
            +
            };
         
     | 
| 
      
 110 
     | 
    
         
            +
            export const mockApi = api => {
         
     | 
| 
      
 111 
     | 
    
         
            +
              api.get.mockImplementation(({ handleSuccess, ...action }) => {
         
     | 
| 
      
 112 
     | 
    
         
            +
                if (action.key === 'JOB_CATEGORIES') {
         
     | 
| 
      
 113 
     | 
    
         
            +
                  handleSuccess &&
         
     | 
| 
      
 114 
     | 
    
         
            +
                    handleSuccess({ data: { job_categories: jobCategories } });
         
     | 
| 
      
 115 
     | 
    
         
            +
                } else if (action.key === 'JOB_TEMPLATE') {
         
     | 
| 
      
 116 
     | 
    
         
            +
                  handleSuccess &&
         
     | 
| 
      
 117 
     | 
    
         
            +
                    handleSuccess({
         
     | 
| 
      
 118 
     | 
    
         
            +
                      data: jobTemplateResponse,
         
     | 
| 
      
 119 
     | 
    
         
            +
                    });
         
     | 
| 
      
 120 
     | 
    
         
            +
                } else if (action.key === 'JOB_TEMPLATES') {
         
     | 
| 
      
 121 
     | 
    
         
            +
                  handleSuccess &&
         
     | 
| 
      
 122 
     | 
    
         
            +
                    handleSuccess({
         
     | 
| 
      
 123 
     | 
    
         
            +
                      data: { results: [jobTemplate] },
         
     | 
| 
      
 124 
     | 
    
         
            +
                    });
         
     | 
| 
      
 125 
     | 
    
         
            +
                }
         
     | 
| 
      
 126 
     | 
    
         
            +
                return { type: 'get', ...action };
         
     | 
| 
      
 127 
     | 
    
         
            +
              });
         
     | 
| 
       26 
128 
     | 
    
         
             
            };
         
     | 
| 
         @@ -1,40 +1,27 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import React from 'react';
         
     | 
| 
       2 
2 
     | 
    
         
             
            import { Provider } from 'react-redux';
         
     | 
| 
       3 
     | 
    
         
            -
            import configureMockStore from 'redux-mock-store';
         
     | 
| 
       4 
3 
     | 
    
         
             
            import { mount } from '@theforeman/test';
         
     | 
| 
       5 
4 
     | 
    
         
             
            import { render, fireEvent, screen, act } from '@testing-library/react';
         
     | 
| 
       6 
5 
     | 
    
         
             
            import * as api from 'foremanReact/redux/API';
         
     | 
| 
       7 
6 
     | 
    
         
             
            import { JobWizard } from '../JobWizard';
         
     | 
| 
       8 
7 
     | 
    
         
             
            import * as selectors from '../JobWizardSelectors';
         
     | 
| 
       9 
     | 
    
         
            -
            import { 
     | 
| 
      
 8 
     | 
    
         
            +
            import {
         
     | 
| 
      
 9 
     | 
    
         
            +
              testSetup,
         
     | 
| 
      
 10 
     | 
    
         
            +
              mockApi,
         
     | 
| 
      
 11 
     | 
    
         
            +
              jobCategories,
         
     | 
| 
      
 12 
     | 
    
         
            +
              jobTemplateResponse as jobTemplate,
         
     | 
| 
      
 13 
     | 
    
         
            +
            } from './fixtures';
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
            jest.spyOn(selectors, 'selectJobTemplate');
         
     | 
| 
       13 
     | 
    
         
            -
            jest.spyOn(selectors, 'selectJobTemplates');
         
     | 
| 
       14 
     | 
    
         
            -
            jest.spyOn(selectors, 'selectJobCategories');
         
     | 
| 
       15 
     | 
    
         
            -
            jest.spyOn(selectors, 'selectJobCategoriesStatus');
         
     | 
| 
      
 15 
     | 
    
         
            +
            const store = testSetup(selectors, api);
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
            selectors.selectJobTemplate.mockImplementation(() => {});
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
            api.get.mockImplementation(({ handleSuccess, ...action }) => {
         
     | 
| 
       20 
20 
     | 
    
         
             
              if (action.key === 'JOB_CATEGORIES') {
         
     | 
| 
       21 
21 
     | 
    
         
             
                handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
         
     | 
| 
       22 
     | 
    
         
            -
              } else if (action.key === 'JOB_TEMPLATE') {
         
     | 
| 
       23 
     | 
    
         
            -
                handleSuccess &&
         
     | 
| 
       24 
     | 
    
         
            -
                  handleSuccess({
         
     | 
| 
       25 
     | 
    
         
            -
                    data: jobTemplate,
         
     | 
| 
       26 
     | 
    
         
            -
                  });
         
     | 
| 
       27 
22 
     | 
    
         
             
              }
         
     | 
| 
       28 
23 
     | 
    
         
             
              return { type: 'get', ...action };
         
     | 
| 
       29 
24 
     | 
    
         
             
            });
         
     | 
| 
       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 
25 
     | 
    
         
             
            describe('Job wizard fill', () => {
         
     | 
| 
       39 
26 
     | 
    
         
             
              it('should select template', async () => {
         
     | 
| 
       40 
27 
     | 
    
         
             
                const wrapper = mount(
         
     | 
| 
         @@ -51,7 +38,10 @@ describe('Job wizard fill', () => { 
     | 
|
| 
       51 
38 
     | 
    
         
             
                selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
         
     | 
| 
       52 
39 
     | 
    
         
             
                wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
         
     | 
| 
       53 
40 
     | 
    
         
             
                await act(async () => {
         
     | 
| 
       54 
     | 
    
         
            -
                  await wrapper 
     | 
| 
      
 41 
     | 
    
         
            +
                  await wrapper
         
     | 
| 
      
 42 
     | 
    
         
            +
                    .find('.pf-c-select__menu-item')
         
     | 
| 
      
 43 
     | 
    
         
            +
                    .first()
         
     | 
| 
      
 44 
     | 
    
         
            +
                    .simulate('click');
         
     | 
| 
       55 
45 
     | 
    
         
             
                  await wrapper.update();
         
     | 
| 
       56 
46 
     | 
    
         
             
                });
         
     | 
| 
       57 
47 
     | 
    
         
             
                expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
         
     | 
| 
         @@ -60,74 +50,12 @@ describe('Job wizard fill', () => { 
     | 
|
| 
       60 
50 
     | 
    
         
             
                );
         
     | 
| 
       61 
51 
     | 
    
         
             
              });
         
     | 
| 
       62 
52 
     | 
    
         | 
| 
       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 
53 
     | 
    
         
             
              it('have all steps', async () => {
         
     | 
| 
       110 
54 
     | 
    
         
             
                selectors.selectJobCategoriesStatus.mockImplementation(() => null);
         
     | 
| 
       111 
55 
     | 
    
         
             
                selectors.selectJobTemplate.mockRestore();
         
     | 
| 
       112 
56 
     | 
    
         
             
                selectors.selectJobTemplates.mockRestore();
         
     | 
| 
       113 
57 
     | 
    
         
             
                selectors.selectJobCategories.mockRestore();
         
     | 
| 
       114 
     | 
    
         
            -
                api 
     | 
| 
       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 
     | 
    
         
            -
                });
         
     | 
| 
      
 58 
     | 
    
         
            +
                mockApi(api);
         
     | 
| 
       131 
59 
     | 
    
         | 
| 
       132 
60 
     | 
    
         
             
                render(
         
     | 
| 
       133 
61 
     | 
    
         
             
                  <Provider store={store}>
         
     | 
| 
         @@ -3,7 +3,11 @@ import PropTypes from 'prop-types'; 
     | 
|
| 
       3 
3 
     | 
    
         
             
            import { useSelector } from 'react-redux';
         
     | 
| 
       4 
4 
     | 
    
         
             
            import { Title, Form } from '@patternfly/react-core';
         
     | 
| 
       5 
5 
     | 
    
         
             
            import { translate as __ } from 'foremanReact/common/I18n';
         
     | 
| 
       6 
     | 
    
         
            -
            import { 
     | 
| 
      
 6 
     | 
    
         
            +
            import {
         
     | 
| 
      
 7 
     | 
    
         
            +
              selectEffectiveUser,
         
     | 
| 
      
 8 
     | 
    
         
            +
              selectAdvancedTemplateInputs,
         
     | 
| 
      
 9 
     | 
    
         
            +
              selectTemplateInputs,
         
     | 
| 
      
 10 
     | 
    
         
            +
            } from '../../JobWizardSelectors';
         
     | 
| 
       7 
11 
     | 
    
         
             
            import {
         
     | 
| 
       8 
12 
     | 
    
         
             
              EffectiveUserField,
         
     | 
| 
       9 
13 
     | 
    
         
             
              TimeoutToKillField,
         
     | 
| 
         @@ -12,17 +16,25 @@ import { 
     | 
|
| 
       12 
16 
     | 
    
         
             
              EffectiveUserPasswordField,
         
     | 
| 
       13 
17 
     | 
    
         
             
              ConcurrencyLevelField,
         
     | 
| 
       14 
18 
     | 
    
         
             
              TimeSpanLevelField,
         
     | 
| 
      
 19 
     | 
    
         
            +
              TemplateInputsFields,
         
     | 
| 
       15 
20 
     | 
    
         
             
            } from './Fields';
         
     | 
| 
      
 21 
     | 
    
         
            +
            import { DescriptionField } from './DescriptionField';
         
     | 
| 
       16 
22 
     | 
    
         | 
| 
       17 
23 
     | 
    
         
             
            export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
         
     | 
| 
       18 
     | 
    
         
            -
              const  
     | 
| 
       19 
     | 
    
         
            -
              const  
     | 
| 
      
 24 
     | 
    
         
            +
              const effectiveUser = useSelector(selectEffectiveUser);
         
     | 
| 
      
 25 
     | 
    
         
            +
              const advancedTemplateInputs = useSelector(selectAdvancedTemplateInputs);
         
     | 
| 
      
 26 
     | 
    
         
            +
              const templateInputs = useSelector(selectTemplateInputs);
         
     | 
| 
       20 
27 
     | 
    
         
             
              return (
         
     | 
| 
       21 
28 
     | 
    
         
             
                <>
         
     | 
| 
       22 
29 
     | 
    
         
             
                  <Title headingLevel="h2" className="advanced-fields-title">
         
     | 
| 
       23 
30 
     | 
    
         
             
                    {__('Advanced Fields')}
         
     | 
| 
       24 
31 
     | 
    
         
             
                  </Title>
         
     | 
| 
       25 
     | 
    
         
            -
                  <Form>
         
     | 
| 
      
 32 
     | 
    
         
            +
                  <Form id="advanced-fields-job-template" autoComplete="off">
         
     | 
| 
      
 33 
     | 
    
         
            +
                    <TemplateInputsFields
         
     | 
| 
      
 34 
     | 
    
         
            +
                      inputs={advancedTemplateInputs}
         
     | 
| 
      
 35 
     | 
    
         
            +
                      value={advancedValues.templateValues}
         
     | 
| 
      
 36 
     | 
    
         
            +
                      setValue={newValue => setAdvancedValues({ templateValues: newValue })}
         
     | 
| 
      
 37 
     | 
    
         
            +
                    />
         
     | 
| 
       26 
38 
     | 
    
         
             
                    {effectiveUser?.overridable && (
         
     | 
| 
       27 
39 
     | 
    
         
             
                      <EffectiveUserField
         
     | 
| 
       28 
40 
     | 
    
         
             
                        value={advancedValues.effectiveUserValue}
         
     | 
| 
         @@ -33,6 +45,11 @@ export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => { 
     | 
|
| 
       33 
45 
     | 
    
         
             
                        }
         
     | 
| 
       34 
46 
     | 
    
         
             
                      />
         
     | 
| 
       35 
47 
     | 
    
         
             
                    )}
         
     | 
| 
      
 48 
     | 
    
         
            +
                    <DescriptionField
         
     | 
| 
      
 49 
     | 
    
         
            +
                      inputs={templateInputs}
         
     | 
| 
      
 50 
     | 
    
         
            +
                      value={advancedValues.description}
         
     | 
| 
      
 51 
     | 
    
         
            +
                      setValue={newValue => setAdvancedValues({ description: newValue })}
         
     | 
| 
      
 52 
     | 
    
         
            +
                    />
         
     | 
| 
       36 
53 
     | 
    
         
             
                    <TimeoutToKillField
         
     | 
| 
       37 
54 
     | 
    
         
             
                      value={advancedValues.timeoutToKill}
         
     | 
| 
       38 
55 
     | 
    
         
             
                      setValue={newValue =>
         
     |