foreman_remote_execution 7.2.2 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) 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/models/remote_execution_provider.rb +1 -1
  8. data/app/views/templates/script/package_action.erb +8 -3
  9. data/config/routes.rb +3 -1
  10. data/lib/foreman_remote_execution/engine.rb +5 -5
  11. data/lib/foreman_remote_execution/version.rb +1 -1
  12. data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
  13. data/test/helpers/remote_execution_helper_test.rb +4 -0
  14. data/test/unit/job_invocation_report_template_test.rb +1 -1
  15. data/test/unit/job_invocation_test.rb +1 -2
  16. data/test/unit/remote_execution_provider_test.rb +0 -22
  17. data/webpack/JobWizard/JobWizard.js +154 -20
  18. data/webpack/JobWizard/JobWizard.scss +43 -1
  19. data/webpack/JobWizard/JobWizardConstants.js +11 -1
  20. data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
  21. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
  22. data/webpack/JobWizard/__tests__/fixtures.js +73 -0
  23. data/webpack/JobWizard/__tests__/integration.test.js +17 -3
  24. data/webpack/JobWizard/autofill.js +8 -1
  25. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
  26. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
  27. data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
  28. data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
  29. data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
  30. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
  31. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
  32. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  33. data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
  34. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
  35. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
  36. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
  37. data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
  38. data/webpack/JobWizard/steps/form/Formatter.js +7 -8
  39. data/webpack/JobWizard/submit.js +8 -3
  40. data/webpack/Routes/routes.js +8 -2
  41. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
  42. data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
  43. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
  44. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
  45. data/webpack/react_app/extend/Fills.js +4 -4
  46. metadata +8 -6
  47. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
  48. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
  49. data/webpack/JobWizard/steps/Schedule/index.js +0 -178
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7d32320885b2dc2343cf6efc7c1f7b0f4ac1e0668ea3d0c2dc817c691f1e7d2
4
- data.tar.gz: 0655a041907bc30339ac53479220adb5434b20f7e6f38d5ce843dfb675f375cd
3
+ metadata.gz: aa3c6d67549990da3bbd1d7cbf55408bd4a1dc4d7fc5cbcec8db99eded747964
4
+ data.tar.gz: ca4e519773be6b0493341fd25ba55e8d4345367c88e0b068183ebe66d020e22e
5
5
  SHA512:
6
- metadata.gz: 307bb3022d6fca4c61897443448638d84069ce943abb08d63b4e7de99cebdcf4f9956b9aed33c6e3effe2dad9a897c8981e80c775866ea5f32081ddf8950606d
7
- data.tar.gz: b32a13df6917c8d41832b66dce88ecdc55383e296e250cbdecfb935f162b5e6e42fb694d57a7338ef6c5c102be9c6a6d2c2e95e1b04df9ac7bfa115b469364c1
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
@@ -9,7 +9,7 @@ class RemoteExecutionProvider
9
9
 
10
10
  def registered_name
11
11
  klass = self
12
- ::RemoteExecutionProvider.providers.key(klass)
12
+ providers.key(klass)
13
13
  end
14
14
 
15
15
  def proxy_feature
@@ -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.2'.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
@@ -56,28 +56,6 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
56
56
  end
57
57
  end
58
58
 
59
- describe '.proxy_feature' do
60
- # rubocop:disable Naming/ConstantName
61
- it 'handles provider subclasses properly' do
62
- old = ::RemoteExecutionProvider
63
-
64
- class P2 < old
65
- end
66
- ::RemoteExecutionProvider = P2
67
-
68
- class CustomProvider < ::RemoteExecutionProvider
69
- end
70
-
71
- ::RemoteExecutionProvider.register('custom', CustomProvider)
72
-
73
- feature = CustomProvider.proxy_feature
74
- _(feature).must_equal 'custom'
75
- ensure
76
- ::RemoteExecutionProvider = old
77
- end
78
- # rubocop:enable Naming/ConstantName
79
- end
80
-
81
59
  describe '.provider_proxy_features' do
82
60
  it 'returns correct values' do
83
61
  RemoteExecutionProvider.stubs(:providers).returns(
@@ -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';