foreman_remote_execution 7.2.1 → 8.0.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +2 -2
  3. data/app/controllers/ui_job_wizard_controller.rb +15 -0
  4. data/app/helpers/remote_execution_helper.rb +1 -1
  5. data/app/models/job_invocation.rb +2 -4
  6. data/app/models/job_invocation_composer.rb +5 -2
  7. data/app/views/templates/script/package_action.erb +8 -3
  8. data/config/routes.rb +3 -1
  9. data/lib/foreman_remote_execution/engine.rb +5 -5
  10. data/lib/foreman_remote_execution/version.rb +1 -1
  11. data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
  12. data/test/helpers/remote_execution_helper_test.rb +4 -0
  13. data/test/unit/job_invocation_report_template_test.rb +1 -1
  14. data/test/unit/job_invocation_test.rb +1 -2
  15. data/webpack/JobWizard/JobWizard.js +154 -20
  16. data/webpack/JobWizard/JobWizard.scss +43 -1
  17. data/webpack/JobWizard/JobWizardConstants.js +11 -1
  18. data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
  19. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
  20. data/webpack/JobWizard/__tests__/fixtures.js +73 -0
  21. data/webpack/JobWizard/__tests__/integration.test.js +17 -3
  22. data/webpack/JobWizard/autofill.js +8 -1
  23. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
  24. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
  25. data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
  26. data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
  27. data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
  28. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
  29. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
  30. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  31. data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
  32. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
  33. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
  34. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
  35. data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
  36. data/webpack/JobWizard/steps/form/Formatter.js +7 -8
  37. data/webpack/JobWizard/submit.js +8 -3
  38. data/webpack/Routes/routes.js +8 -2
  39. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
  40. data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
  41. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
  42. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
  43. data/webpack/react_app/extend/Fills.js +4 -4
  44. metadata +8 -6
  45. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
  46. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
  47. data/webpack/JobWizard/steps/Schedule/index.js +0 -178
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 893050d86f8ab746da3173f25cc9a062454bf9262316f0a6fbb5f89a6282c15f
4
- data.tar.gz: 02e623b7202ec926f0d744cbfadbbe5f86a7950cd3665c91a3b7ec74a2b02cd2
3
+ metadata.gz: aa3c6d67549990da3bbd1d7cbf55408bd4a1dc4d7fc5cbcec8db99eded747964
4
+ data.tar.gz: ca4e519773be6b0493341fd25ba55e8d4345367c88e0b068183ebe66d020e22e
5
5
  SHA512:
6
- metadata.gz: c488b0d6edc98ce77e66edefc2c0f298c323bfd0aa6f0d46a3dbbc4b4642bbe9ac8f2112c237460095f47c5494d5ad7112482475fc886d596e9698fe0cac128f
7
- data.tar.gz: eb73a3df7acbfa047699d349fcb03393850b5ddded2ed31b3cd30e38b15d3a516e608a17e33f29237271e012c9de69bb7929801de4f8605d8a4b3b59590b73e8
6
+ metadata.gz: bbeb56766cc65ed9fcd587f260fb29fca965b961b0787230f0510166702351cb5ff0c78387b3f818b9741f281519f33931bc8d0aa36a923d92e0d50f620d113e
7
+ data.tar.gz: 3238dc8bc7cdbaa361a6c077e5d611839cb549424ad98daea9e68ac81a932dd6c8de2f233b31f019cc7a1d037b6fea4a656ca3d7e789eb9a794170fb26ac1f68
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Setup Ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: 2.5
19
+ ruby-version: 2.7
20
20
  bundler-cache: true
21
21
  cache-version: 1
22
22
  rubygems: 3.0.0
@@ -38,7 +38,7 @@ jobs:
38
38
  fail-fast: false
39
39
  matrix:
40
40
  foreman-core-branch: [develop]
41
- ruby-version: [2.5, 2.7]
41
+ ruby-version: [2.7]
42
42
  node-version: [12]
43
43
  steps:
44
44
  - run: sudo apt-get update
@@ -57,4 +57,19 @@ class UiJobWizardController < ApplicationController
57
57
  render :json => { :results =>
58
58
  resource_list.sort_by { |r| r[:name] }.take(100), :subtotal => resource_list.count}
59
59
  end
60
+
61
+ def job_invocation
62
+ job = JobInvocation.authorized.find(params[:id])
63
+ composer = JobInvocationComposer.from_job_invocation(job, params).params
64
+ job_template_inputs = JobTemplate.authorized.find(composer[:template_invocations][0][:template_id]).template_inputs_with_foreign
65
+ inputs = Hash[job_template_inputs.map { |input| ["inputs[#{input[:name]}]", (composer[:template_invocations][0][:input_values].find { |value| value[:template_input_id] == input[:id] })[:value]] }]
66
+ job_organization = Taxonomy.find_by(id: job.task.input[:current_organization_id])
67
+ job_location = Taxonomy.find_by(id: job.task.input[:current_location_id])
68
+ render :json => {
69
+ :job => composer,
70
+ :job_organization => job_organization,
71
+ :job_location => job_location,
72
+ :inputs => inputs,
73
+ }
74
+ end
60
75
  end
@@ -246,7 +246,7 @@ module RemoteExecutionHelper
246
246
  def job_report_template
247
247
  template = ReportTemplate.where(name: Setting['remote_execution_job_invocation_report_template']).first
248
248
 
249
- template if template.template_inputs.where(name: 'job_id').exists?
249
+ template if template && template.template_inputs.where(name: 'job_id').exists?
250
250
  end
251
251
 
252
252
  def job_report_template_parameters(job_invocation, template)
@@ -62,16 +62,14 @@ class JobInvocation < ApplicationRecord
62
62
 
63
63
  has_many :targeted_hosts, :through => :targeting, :source => :hosts
64
64
  scoped_search :on => 'targeted_host_id', :rename => 'targeted_host_id', :operators => ['= '],
65
- :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host,
66
- :validator => ScopedSearch::Validators::INTEGER
65
+ :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host
67
66
 
68
67
  scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
69
68
  :complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
70
69
 
71
70
  scoped_search :relation => :recurring_logic, :on => 'purpose', :rename => 'recurring_logic.purpose'
72
71
 
73
- scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id',
74
- :validator => ScopedSearch::Validators::INTEGER
72
+ scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
75
73
 
76
74
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
77
75
  :ext_method => :search_by_recurring_logic, :only_explicit => true,
@@ -441,8 +441,11 @@ class JobInvocationComposer
441
441
  end
442
442
 
443
443
  def valid?
444
- targeting.valid? & job_invocation.valid? & !pattern_template_invocations.map(&:valid?).include?(false) &
445
- triggering.valid?
444
+ unless triggering.valid?
445
+ job_invocation.errors.add(:triggering, 'is invalid')
446
+ return false
447
+ end
448
+ targeting.valid? & job_invocation.valid? & !pattern_template_invocations.map(&:valid?).include?(false)
446
449
  end
447
450
 
448
451
  def save
@@ -16,6 +16,11 @@ template_inputs:
16
16
  input_type: user
17
17
  required: true
18
18
  options: "install\nupdate\nremove\ngroup install\ngroup remove"
19
+ - name: options
20
+ description: Additional options for the package manager
21
+ input_type: user
22
+ required: false
23
+ advanced: true
19
24
  - name: package
20
25
  description: The name of the package, if any
21
26
  input_type: user
@@ -87,7 +92,7 @@ handle_zypp_res_codes () {
87
92
 
88
93
  # Action
89
94
  <% if package_manager == 'yum' -%>
90
- yum -y <%= action %> <%= input("package") %>
95
+ yum -y <%= input("options") %> <%= action %> <%= input("package") %>
91
96
  <% elsif package_manager == 'apt' -%>
92
97
  <%-
93
98
  action = 'install' if action == 'group install'
@@ -103,7 +108,7 @@ handle_zypp_res_codes () {
103
108
  [ -x "$(command -v subscription-manager)" ] && subscription-manager refresh
104
109
  export DEBIAN_FRONTEND=noninteractive
105
110
  apt-get -y update
106
- apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= action %> <%= input("package") %>
111
+ apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= input("options") %> <%= action %> <%= input("package") %>
107
112
  <% elsif package_manager == 'zypper' -%>
108
113
  <%-
109
114
  if action == "group install"
@@ -113,7 +118,7 @@ handle_zypp_res_codes () {
113
118
  end
114
119
  -%>
115
120
  zypper refresh
116
- zypper -n <%= action %> <%= input("package") %>
121
+ zypper -n <%= action %> <%= input("options") %> <%= input("package") %>
117
122
  handle_zypp_res_codes $?
118
123
  <% end -%>
119
124
  RETVAL=$?
data/config/routes.rb CHANGED
@@ -46,8 +46,10 @@ Rails.application.routes.draw do
46
46
  get 'ui_job_wizard/categories', to: 'ui_job_wizard#categories'
47
47
  get 'ui_job_wizard/template/:id', to: 'ui_job_wizard#template'
48
48
  get 'ui_job_wizard/resources', to: 'ui_job_wizard#resources'
49
+ get 'ui_job_wizard/job_invocation', to: 'ui_job_wizard#job_invocation'
49
50
 
50
- match '/experimental/job_wizard', to: 'react#index', :via => [:get]
51
+ match '/experimental/job_wizard/new', to: 'react#index', :via => [:get]
52
+ match '/experimental/job_wizard/:id/rerun', to: 'react#index', :via => [:get]
51
53
 
52
54
  namespace :api, :defaults => {:format => 'json'} do
53
55
  scope '(:apiv)', :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
@@ -47,7 +47,7 @@ module ForemanRemoteExecution
47
47
 
48
48
  initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
49
49
  Foreman::Plugin.register :foreman_remote_execution do
50
- requires_foreman '>= 3.3'
50
+ requires_foreman '>= 3.4'
51
51
  register_global_js_file 'global'
52
52
 
53
53
  apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
@@ -152,7 +152,7 @@ module ForemanRemoteExecution
152
152
  setting 'remote_execution_job_invocation_report_template',
153
153
  type: :string,
154
154
  description: N_('Select a report template used for generating a report for a particular remote execution job'),
155
- default: 'Job invocation - report template',
155
+ default: 'Job - Invocation Report',
156
156
  full_name: N_('Job Invocation Report Template'),
157
157
  collection: proc { ForemanRemoteExecution.job_invocation_report_templates_select }
158
158
  end
@@ -164,7 +164,7 @@ module ForemanRemoteExecution
164
164
  :'api/v2/job_templates' => [:index, :show, :revision, :export],
165
165
  :'api/v2/template_inputs' => [:index, :show],
166
166
  :'api/v2/foreign_input_sets' => [:index, :show],
167
- :ui_job_wizard => [:categories, :template, :resources]}, :resource_type => 'JobTemplate'
167
+ :ui_job_wizard => [:categories, :template, :resources, :job_invocation]}, :resource_type => 'JobTemplate'
168
168
  permission :create_job_templates, { :job_templates => [:new, :create, :clone_template, :import],
169
169
  :'api/v2/job_templates' => [:create, :clone, :import] }, :resource_type => 'JobTemplate'
170
170
  permission :edit_job_templates, { :job_templates => [:edit, :update],
@@ -181,7 +181,7 @@ module ForemanRemoteExecution
181
181
  permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search], :template_invocations => [:show],
182
182
  'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs] }, :resource_type => 'JobInvocation'
183
183
  permission :view_template_invocations, { :template_invocations => [:show],
184
- 'api/v2/template_invocations' => [:template_invocations] }, :resource_type => 'TemplateInvocation'
184
+ 'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation'
185
185
  permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
186
186
  permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
187
187
  permission :cancel_job_invocations, { :job_invocations => [:cancel], 'api/v2/job_invocations' => [:cancel] }, :resource_type => 'JobInvocation'
@@ -242,7 +242,7 @@ module ForemanRemoteExecution
242
242
  url_hash: { controller: 'job_wizard', action: :index },
243
243
  caption: N_('Job wizard'),
244
244
  parent: :lab_features_menu,
245
- url: '/experimental/job_wizard',
245
+ url: '/experimental/job_wizard/new',
246
246
  after: :host_wizard
247
247
 
248
248
  register_custom_status HostStatus::ExecutionStatus
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '7.2.1'.freeze
2
+ VERSION = '8.0.0'.freeze
3
3
  end
@@ -82,6 +82,14 @@ module Api
82
82
  assert_response :success
83
83
  end
84
84
 
85
+ test 'should propagate errors from triggering' do
86
+ @attrs[:recurrence] = { cron_line: 'foo' }
87
+ post :create, params: { job_invocation: @attrs }
88
+ invocation = ActiveSupport::JSON.decode(@response.body)
89
+ assert_match(/foo is not valid format of cron line/, invocation['error']['message'])
90
+ assert_response 500
91
+ end
92
+
85
93
  test 'should create with schedule' do
86
94
  @attrs[:scheduling] = { start_at: Time.now.to_s }
87
95
  post :create, params: { job_invocation: @attrs }
@@ -37,5 +37,9 @@ class RemoteExecutionHelperTest < ActionView::TestCase
37
37
 
38
38
  assert_equal template.id, found_template.id
39
39
  end
40
+
41
+ it 'should not crash if the template cannot be found' do
42
+ assert_nil job_report_template
43
+ end
40
44
  end
41
45
  end
@@ -9,7 +9,7 @@ class JobReportTemplateTest < ActiveSupport::TestCase
9
9
 
10
10
  context 'with valid job invocation report template' do
11
11
  let(:job_invocation_template) do
12
- file_path = File.read(File.expand_path(Rails.root + "app/views/unattended/report_templates/job_invocation_-_report_template.erb"))
12
+ file_path = File.read(File.expand_path(Rails.root + "app/views/unattended/report_templates/job_-_invocation_report.erb"))
13
13
  template = ReportTemplate.import_without_save("Job Invocation Report Template", file_path)
14
14
  template.save!
15
15
  template
@@ -95,9 +95,8 @@ class JobInvocationTest < ActiveSupport::TestCase
95
95
  end
96
96
 
97
97
  it 'truncates generated description to 255 characters' do
98
- column_limit = 255
98
+ column_limit = 255 # There is a 255 character limit on the database level
99
99
  expected_result = 'a' * column_limit
100
- JobInvocation.columns_hash['description'].expects(:limit).returns(column_limit)
101
100
  job_invocation.description_format = '%{job_category}'
102
101
  job_invocation.job_category = 'a' * 1000
103
102
  job_invocation.generate_description
@@ -1,6 +1,8 @@
1
+ /* eslint-disable max-lines */
1
2
  /* eslint-disable camelcase */
2
3
  import React, { useState, useEffect, useCallback } from 'react';
3
4
  import { useDispatch, useSelector } from 'react-redux';
5
+ import PropTypes from 'prop-types';
4
6
  import { Wizard } from '@patternfly/react-core';
5
7
  import { get } from 'foremanReact/redux/API';
6
8
  import history from 'foremanReact/history';
@@ -14,6 +16,7 @@ import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
14
16
  import {
15
17
  JOB_TEMPLATE,
16
18
  WIZARD_TITLES,
19
+ SCHEDULE_TYPES,
17
20
  initialScheduleState,
18
21
  } from './JobWizardConstants';
19
22
  import {
@@ -23,7 +26,9 @@ import {
23
26
  selectRouterSearch,
24
27
  selectIsLoading,
25
28
  } from './JobWizardSelectors';
26
- import Schedule from './steps/Schedule/';
29
+ import { ScheduleType } from './steps/Schedule/ScheduleType';
30
+ import { ScheduleFuture } from './steps/Schedule/ScheduleFuture';
31
+ import { ScheduleRecurring } from './steps/Schedule/ScheduleRecurring';
27
32
  import HostsAndInputs from './steps/HostsAndInputs/';
28
33
  import ReviewDetails from './steps/ReviewDetails/';
29
34
  import { useValidation } from './validation';
@@ -31,9 +36,11 @@ import { useAutoFill } from './autofill';
31
36
  import { submit } from './submit';
32
37
  import './JobWizard.scss';
33
38
 
34
- export const JobWizard = () => {
35
- const [jobTemplateID, setJobTemplateID] = useState(null);
36
- const [category, setCategory] = useState('');
39
+ export const JobWizard = ({ rerunData }) => {
40
+ const [jobTemplateID, setJobTemplateID] = useState(
41
+ rerunData?.template_invocations?.[0]?.template_id
42
+ );
43
+ const [category, setCategory] = useState(rerunData?.job_category || '');
37
44
  const [advancedValues, setAdvancedValues] = useState({ templateValues: {} });
38
45
  const [templateValues, setTemplateValues] = useState({}); // TODO use templateValues in advanced fields - description https://github.com/theforeman/foreman_remote_execution/pull/605
39
46
  const [scheduleValue, setScheduleValue] = useState(initialScheduleState);
@@ -43,14 +50,22 @@ export const JobWizard = () => {
43
50
  hostGroups: [],
44
51
  });
45
52
  const [hostsSearchQuery, setHostsSearchQuery] = useState('');
46
- const [fills, setFills] = useState(useSelector(selectRouterSearch));
53
+ const routerSearch = useSelector(selectRouterSearch);
54
+ const [fills, setFills] = useState(
55
+ rerunData
56
+ ? {
57
+ search: rerunData?.targeting?.search_query,
58
+ ...rerunData.inputs,
59
+ }
60
+ : routerSearch
61
+ );
47
62
  const dispatch = useDispatch();
48
63
 
49
64
  const setDefaults = useCallback(
50
65
  ({
51
66
  data: {
52
- template_inputs,
53
- advanced_template_inputs,
67
+ template_inputs = [],
68
+ advanced_template_inputs = [],
54
69
  effective_user,
55
70
  job_template: {
56
71
  name,
@@ -58,6 +73,9 @@ export const JobWizard = () => {
58
73
  description_format,
59
74
  job_category,
60
75
  },
76
+ randomized_ordering,
77
+ ssh_user,
78
+ concurrency_control = {},
61
79
  },
62
80
  }) => {
63
81
  if (!category.length) {
@@ -98,23 +116,43 @@ export const JobWizard = () => {
98
116
  timeoutToKill: execution_timeout_interval || '',
99
117
  templateValues: advancedTemplateValues,
100
118
  description: generateDefaultDescription() || '',
101
- isRandomizedOrdering: false,
119
+ isRandomizedOrdering: randomized_ordering,
120
+ sshUser: ssh_user || '',
121
+ timeSpan: concurrency_control.time_span || '',
122
+ concurrencyLevel: concurrency_control.level || '',
102
123
  };
103
124
  });
104
125
  },
105
126
  [category.length]
106
127
  );
128
+ useEffect(() => {
129
+ if (rerunData) {
130
+ setDefaults({
131
+ data: {
132
+ effective_user: {
133
+ value: rerunData.template_invocations[0].effective_user,
134
+ },
135
+ job_template: {
136
+ execution_timeout_interval: rerunData.execution_timeout_interval,
137
+ },
138
+ randomized_ordering: rerunData.targeting.randomized_ordering,
139
+ ssh_user: rerunData.ssh_user,
140
+ concurrency_control: rerunData.concurrency_control,
141
+ },
142
+ });
143
+ }
144
+ }, [rerunData, setDefaults]);
107
145
  useEffect(() => {
108
146
  if (jobTemplateID) {
109
147
  dispatch(
110
148
  get({
111
149
  key: JOB_TEMPLATE,
112
150
  url: `/ui_job_wizard/template/${jobTemplateID}`,
113
- handleSuccess: setDefaults,
151
+ handleSuccess: rerunData ? () => {} : setDefaults,
114
152
  })
115
153
  );
116
154
  }
117
- }, [jobTemplateID, setDefaults, dispatch]);
155
+ }, [rerunData, jobTemplateID, setDefaults, dispatch]);
118
156
 
119
157
  const [valid, setValid] = useValidation({
120
158
  advancedValues,
@@ -127,6 +165,7 @@ export const JobWizard = () => {
127
165
  setHostsSearchQuery,
128
166
  setJobTemplateID,
129
167
  setTemplateValues,
168
+ setAdvancedValues,
130
169
  });
131
170
  const templateError = !!useSelector(selectTemplateError);
132
171
  const templateResponse = useSelector(selectJobTemplate);
@@ -148,7 +187,7 @@ export const JobWizard = () => {
148
187
  setJobTemplate={setJobTemplateID}
149
188
  category={category}
150
189
  setCategory={setCategory}
151
- isFeature={!!fills.feature}
190
+ isCategoryPreselected={!!rerunData || !!fills.feature}
152
191
  />
153
192
  ),
154
193
  enableNext: isTemplate,
@@ -187,18 +226,80 @@ export const JobWizard = () => {
187
226
  },
188
227
  {
189
228
  name: WIZARD_TITLES.schedule,
190
- component: (
191
- <Schedule
192
- scheduleValue={scheduleValue}
193
- setScheduleValue={setScheduleValue}
194
- setValid={newValue => {
195
- setValid(currentValid => ({ ...currentValid, schedule: newValue }));
196
- }}
197
- />
198
- ),
199
229
  canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
200
230
  enableNext:
201
231
  isTemplate && valid.hostsAndInputs && valid.advanced && valid.schedule,
232
+ steps: [
233
+ {
234
+ name: WIZARD_TITLES.typeOfExecution,
235
+ component: (
236
+ <ScheduleType
237
+ scheduleType={scheduleValue.scheduleType}
238
+ isTypeStatic={scheduleValue.isTypeStatic}
239
+ setScheduleValue={setScheduleValue}
240
+ setValid={newValue => {
241
+ setValid(currentValid => ({
242
+ ...currentValid,
243
+ schedule: newValue,
244
+ }));
245
+ }}
246
+ />
247
+ ),
248
+ canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
249
+
250
+ enableNext: isTemplate && valid.hostsAndInputs && valid.advanced,
251
+ },
252
+ ...(scheduleValue.scheduleType === SCHEDULE_TYPES.FUTURE
253
+ ? [
254
+ {
255
+ name: SCHEDULE_TYPES.FUTURE,
256
+ component: (
257
+ <ScheduleFuture
258
+ scheduleValue={scheduleValue}
259
+ setScheduleValue={setScheduleValue}
260
+ setValid={newValue => {
261
+ setValid(currentValid => ({
262
+ ...currentValid,
263
+ schedule: newValue,
264
+ }));
265
+ }}
266
+ />
267
+ ),
268
+ canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
269
+ enableNext:
270
+ isTemplate &&
271
+ valid.hostsAndInputs &&
272
+ valid.advanced &&
273
+ valid.schedule,
274
+ },
275
+ ]
276
+ : []),
277
+ ...(scheduleValue.scheduleType === SCHEDULE_TYPES.RECURRING
278
+ ? [
279
+ {
280
+ name: SCHEDULE_TYPES.RECURRING,
281
+ component: (
282
+ <ScheduleRecurring
283
+ scheduleValue={scheduleValue}
284
+ setScheduleValue={setScheduleValue}
285
+ setValid={newValue => {
286
+ setValid(currentValid => ({
287
+ ...currentValid,
288
+ schedule: newValue,
289
+ }));
290
+ }}
291
+ />
292
+ ),
293
+ canJumpTo: isTemplate && valid.hostsAndInputs && valid.advanced,
294
+ enableNext:
295
+ isTemplate &&
296
+ valid.hostsAndInputs &&
297
+ valid.advanced &&
298
+ valid.schedule,
299
+ },
300
+ ]
301
+ : []),
302
+ ],
202
303
  },
203
304
  {
204
305
  name: WIZARD_TITLES.review,
@@ -250,4 +351,37 @@ export const JobWizard = () => {
250
351
  );
251
352
  };
252
353
 
354
+ JobWizard.propTypes = {
355
+ rerunData: PropTypes.shape({
356
+ job_category: PropTypes.string,
357
+ targeting: PropTypes.shape({
358
+ search_query: PropTypes.string,
359
+ targeting_type: PropTypes.string,
360
+ randomized_ordering: PropTypes.bool,
361
+ }),
362
+ triggering: PropTypes.shape({
363
+ mode: PropTypes.string,
364
+ start_at: PropTypes.string,
365
+ start_before: PropTypes.string,
366
+ }),
367
+ ssh_user: PropTypes.string,
368
+ concurrency_control: PropTypes.shape({
369
+ level: PropTypes.number,
370
+ time_span: PropTypes.number,
371
+ }),
372
+ execution_timeout_interval: PropTypes.number,
373
+ remote_execution_feature_id: PropTypes.string,
374
+ template_invocations: PropTypes.arrayOf(
375
+ PropTypes.shape({
376
+ template_id: PropTypes.number,
377
+ effective_user: PropTypes.string,
378
+ input_values: PropTypes.array,
379
+ })
380
+ ),
381
+ inputs: PropTypes.object,
382
+ }),
383
+ };
384
+ JobWizard.defaultProps = {
385
+ rerunData: null,
386
+ };
253
387
  export default JobWizard;
@@ -71,8 +71,42 @@
71
71
  max-height: 300px;
72
72
  overflow: scroll;
73
73
  }
74
+ .schedule-radio label {
75
+ width: 100%;
76
+ }
77
+ .schedule-radio input {
78
+ align-self: center;
79
+ }
80
+ .schedule-radio-repeat-text {
81
+ width: 100px;
82
+ display: inline-block;
83
+ margin-right: 5px;
84
+ align-self: center;
85
+ }
86
+ .schedule-radio-title {
87
+ width: 80px;
88
+ display: inline-block;
89
+ align-self: center;
90
+ }
91
+ .schedule-radio-occurences {
92
+ display: inline-block;
93
+ align-self: center;
94
+ }
95
+ .scheudle-radio-wrapper {
96
+ display: flex;
97
+ }
98
+ }
99
+ .future-schedule-tab {
100
+ .clear-datetime-button {
101
+ margin-left: 10px;
102
+ align-self: center;
103
+ font-size: var(--pf-global--FontSize--md);
104
+ }
105
+ .pf-c-form__group-control {
106
+ display: flex;
107
+ flex-wrap: wrap;
108
+ }
74
109
  }
75
-
76
110
  .pf-c-date-picker {
77
111
  vertical-align: top;
78
112
  }
@@ -108,4 +142,12 @@
108
142
  display: none;
109
143
  }
110
144
  }
145
+ .pf-c-radio__body {
146
+ font-size: var(--pf-c-radio__label--FontSize);
147
+ }
111
148
  }
149
+
150
+ .job-wizard-alert.pf-c-alert.pf-m-warning {
151
+ margin-bottom: 10px;
152
+ margin-top: 10px;
153
+ }
@@ -16,23 +16,31 @@ export const repeatTypes = {
16
16
  hourly: __('Hourly'),
17
17
  };
18
18
 
19
+ export const SCHEDULE_TYPES = {
20
+ NOW: __('Immediate execution'),
21
+ FUTURE: __('Future execution'),
22
+ RECURRING: __('Recurring execution'),
23
+ };
24
+
19
25
  export const WIZARD_TITLES = {
20
26
  categoryAndTemplate: __('Category and Template'),
21
27
  hostsAndInputs: __('Target hosts and inputs'),
22
28
  advanced: __('Advanced fields'),
23
29
  schedule: __('Schedule'),
24
30
  review: __('Review details'),
31
+ typeOfExecution: __('Type of execution'),
25
32
  };
26
33
 
27
34
  export const initialScheduleState = {
28
35
  repeatType: repeatTypes.noRepeat,
36
+ scheduleType: SCHEDULE_TYPES.NOW,
29
37
  repeatAmount: '',
30
38
  repeatData: {},
31
39
  startsAt: '',
32
40
  startsBefore: '',
33
41
  ends: '',
34
42
  isFuture: false,
35
- isNeverEnds: false,
43
+ isNeverEnds: true,
36
44
  isTypeStatic: true,
37
45
  purpose: '',
38
46
  };
@@ -59,3 +67,5 @@ export const HOSTS_TO_PREVIEW_AMOUNT = 20;
59
67
  export const DEBOUNCE_HOST_COUNT = 700;
60
68
  export const HOST_IDS = 'HOST_IDS';
61
69
  export const REX_FEATURE = 'REX_FEATURE';
70
+
71
+ export const JOB_API_KEY = 'JOB_API_KEY';