foreman-tasks 1.0.1 → 1.1.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -12
  3. data/.rubocop_todo.yml +34 -116
  4. data/app/controllers/foreman_tasks/api/recurring_logics_controller.rb +20 -1
  5. data/app/controllers/foreman_tasks/api/tasks_controller.rb +29 -9
  6. data/app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb +1 -1
  7. data/app/controllers/foreman_tasks/recurring_logics_controller.rb +19 -0
  8. data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +1 -3
  9. data/app/lib/actions/helpers/humanizer.rb +1 -3
  10. data/app/lib/actions/proxy_action.rb +33 -12
  11. data/app/models/foreman_tasks/concerns/action_triggering.rb +1 -1
  12. data/app/models/foreman_tasks/recurring_logic.rb +1 -0
  13. data/app/models/foreman_tasks/remote_task.rb +1 -0
  14. data/app/models/foreman_tasks/task.rb +4 -0
  15. data/app/models/foreman_tasks/task/dynflow_task.rb +1 -1
  16. data/app/models/foreman_tasks/task/search.rb +11 -1
  17. data/app/services/foreman_tasks/troubleshooting_help_generator.rb +0 -4
  18. data/app/views/foreman_tasks/api/recurring_logics/base.json.rabl +2 -1
  19. data/app/views/foreman_tasks/api/tasks/details.json.rabl +1 -0
  20. data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
  21. data/app/views/foreman_tasks/recurring_logics/index.html.erb +30 -0
  22. data/app/views/foreman_tasks/tasks/show.html.erb +3 -0
  23. data/config/routes.rb +7 -0
  24. data/foreman-tasks.gemspec +4 -6
  25. data/lib/foreman_tasks/dynflow/console_authorizer.rb +2 -2
  26. data/lib/foreman_tasks/engine.rb +15 -13
  27. data/lib/foreman_tasks/tasks/cleanup.rake +1 -1
  28. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  29. data/lib/foreman_tasks/test_extensions.rb +1 -1
  30. data/lib/foreman_tasks/version.rb +1 -1
  31. data/locale/action_names.rb +1 -1
  32. data/package.json +1 -2
  33. data/script/rails +2 -2
  34. data/test/factories/task_factory.rb +34 -2
  35. data/test/foreman_tasks_test_helper.rb +4 -0
  36. data/test/unit/actions/action_with_sub_plans_test.rb +1 -1
  37. data/test/unit/task_test.rb +160 -74
  38. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +4 -0
  39. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +3 -12
  40. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +1 -0
  41. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +3 -1
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +2 -6
  43. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +4 -1
  44. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +1 -0
  45. data/webpack/ForemanTasks/Components/TaskDetails/index.js +2 -0
  46. data/webpack/ForemanTasks/Components/TasksTable/SubTasksPage.js +3 -1
  47. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +87 -21
  48. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +7 -7
  49. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +31 -22
  50. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +2 -1
  51. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableActions.test.js +44 -46
  52. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +3 -1
  53. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +2 -1
  54. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableActions.test.js.snap +61 -5
  55. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +6 -2
  56. metadata +10 -10
@@ -1,97 +1,133 @@
1
1
  require 'foreman_tasks_test_helper'
2
2
 
3
3
  class TasksTest < ActiveSupport::TestCase
4
- describe 'filtering by current user' do
5
- before do
6
- @original_current_user = User.current
7
- @user_one = FactoryBot.create(:user)
8
- @user_two = FactoryBot.create(:user)
4
+ describe 'search' do
5
+ describe 'by current user' do
6
+ before do
7
+ @original_current_user = User.current
8
+ @user_one = FactoryBot.create(:user)
9
+ @user_two = FactoryBot.create(:user)
9
10
 
10
- @task_one = FactoryBot.create(:some_task, :user => @user_one)
11
- FactoryBot.create(:some_task, :user => @user_two)
11
+ @task_one = FactoryBot.create(:some_task, :user => @user_one)
12
+ FactoryBot.create(:some_task, :user => @user_two)
12
13
 
13
- User.current = @user_one
14
- end
15
- after { User.current = @original_current_user }
14
+ User.current = @user_one
15
+ end
16
+ after { User.current = @original_current_user }
16
17
 
17
- test 'can search the tasks by current_user' do
18
- assert_equal [@task_one], ForemanTasks::Task.search_for('owner.id = current_user')
19
- end
18
+ test 'can search the tasks by current_user' do
19
+ assert_equal [@task_one], ForemanTasks::Task.search_for('owner.id = current_user')
20
+ end
20
21
 
21
- test 'can search by implicit search' do
22
- assert_equal [@task_one], ForemanTasks::Task.search_for(@task_one.label)
23
- end
22
+ test 'can search by implicit search' do
23
+ assert_equal [@task_one], ForemanTasks::Task.search_for(@task_one.label)
24
+ end
24
25
 
25
- test 'can search the tasks by current_user in combination with implicit search' do
26
- assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id = current_user AND #{@task_one.label}")
27
- end
26
+ test 'can search the tasks by current_user in combination with implicit search' do
27
+ assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id = current_user AND #{@task_one.label}")
28
+ end
28
29
 
29
- test 'can search the tasks by user' do
30
- assert_equal [@task_one], ForemanTasks::Task.search_for("user = #{@user_one.login}")
31
- end
30
+ test 'can search the tasks by user' do
31
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user = #{@user_one.login}")
32
+ end
32
33
 
33
- test 'cannot search by arbitrary key' do
34
- proc { ForemanTasks::Task.search_for('user.my_key ~ 5') }.must_raise(ScopedSearch::QueryNotSupported)
35
- proc { ForemanTasks::Task.search_for('user. = 5') }.must_raise(ScopedSearch::QueryNotSupported)
36
- end
34
+ test 'cannot search by arbitrary key' do
35
+ proc { ForemanTasks::Task.search_for('user.my_key ~ 5') }.must_raise(ScopedSearch::QueryNotSupported)
36
+ proc { ForemanTasks::Task.search_for('user. = 5') }.must_raise(ScopedSearch::QueryNotSupported)
37
+ end
37
38
 
38
- test 'can search the tasks by negated user' do
39
- assert_equal [@task_one], ForemanTasks::Task.search_for("user != #{@user_two.login}")
40
- assert_equal [@task_one], ForemanTasks::Task.search_for("user <> #{@user_two.login}")
41
- SecureRandom.stubs(:hex).returns('abc')
42
- assert_equal ForemanTasks::Task.search_for("user != #{@user_two.login}").to_sql,
43
- ForemanTasks::Task.search_for("user <> #{@user_two.login}").to_sql
44
- end
39
+ test 'can search the tasks by negated user' do
40
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user != #{@user_two.login}")
41
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user <> #{@user_two.login}")
42
+ SecureRandom.stubs(:hex).returns('abc')
43
+ assert_equal ForemanTasks::Task.search_for("user != #{@user_two.login}").to_sql,
44
+ ForemanTasks::Task.search_for("user <> #{@user_two.login}").to_sql
45
+ end
45
46
 
46
- test 'can search the tasks by user\'s id' do
47
- assert_equal [@task_one], ForemanTasks::Task.search_for("user.id = #{@user_one.id}")
48
- assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id = #{@user_one.id}")
49
- assert_equal [@task_one], ForemanTasks::Task.search_for("user.id != #{@user_two.id}")
50
- assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id != #{@user_two.id}")
51
- end
47
+ test 'can search the tasks by user\'s id' do
48
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user.id = #{@user_one.id}")
49
+ assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id = #{@user_one.id}")
50
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user.id != #{@user_two.id}")
51
+ assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id != #{@user_two.id}")
52
+ end
52
53
 
53
- test 'can search by array of user ids' do
54
- assert_equal [@task_one], ForemanTasks::Task.search_for("user.id ^ (#{@user_one.id})")
55
- assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id ^ (#{@user_one.id})")
56
- assert_equal [@task_one], ForemanTasks::Task.search_for("user.id !^ (#{@user_two.id})")
57
- assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id !^ (#{@user_two.id})")
58
- end
54
+ test 'can search by array of user ids' do
55
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user.id ^ (#{@user_one.id})")
56
+ assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id ^ (#{@user_one.id})")
57
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user.id !^ (#{@user_two.id})")
58
+ assert_equal [@task_one], ForemanTasks::Task.search_for("owner.id !^ (#{@user_two.id})")
59
+ end
59
60
 
60
- test 'cannot glob on user\'s id' do
61
- proc { ForemanTasks::Task.search_for("user.id ~ something") }.must_raise(ScopedSearch::QueryNotSupported)
62
- proc { ForemanTasks::Task.search_for("user.id ~ 5") }.must_raise(ScopedSearch::QueryNotSupported)
63
- end
61
+ test 'cannot glob on user\'s id' do
62
+ proc { ForemanTasks::Task.search_for("user.id ~ something") }.must_raise(ScopedSearch::QueryNotSupported)
63
+ proc { ForemanTasks::Task.search_for("user.id ~ 5") }.must_raise(ScopedSearch::QueryNotSupported)
64
+ end
64
65
 
65
- test 'can search the tasks by user with wildcards' do
66
- part = @user_one.login[1..-1] # search for '*ser1' if login is 'user1'
67
- # The following two should be equivalent
68
- assert_equal [@task_one], ForemanTasks::Task.search_for("user ~ #{part}")
69
- assert_equal [@task_one], ForemanTasks::Task.search_for("user ~ *#{part}*")
70
- SecureRandom.stubs(:hex).returns('abc')
71
- assert_equal ForemanTasks::Task.search_for("user ~ #{part}").to_sql,
72
- ForemanTasks::Task.search_for("user ~ *#{part}*").to_sql
73
- end
66
+ test 'can search the tasks by user with wildcards' do
67
+ part = @user_one.login[1..-1] # search for '*ser1' if login is 'user1'
68
+ # The following two should be equivalent
69
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user ~ #{part}")
70
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user ~ *#{part}*")
71
+ SecureRandom.stubs(:hex).returns('abc')
72
+ assert_equal ForemanTasks::Task.search_for("user ~ #{part}").to_sql,
73
+ ForemanTasks::Task.search_for("user ~ *#{part}*").to_sql
74
+ end
74
75
 
75
- test 'can search the tasks by user with negated wildcards' do
76
- part = @user_two.login[1..-1] # search for '*ser1' if login is 'user1'
77
- # The following two should be equivalent
78
- assert_equal [@task_one], ForemanTasks::Task.search_for("user !~ #{part}")
79
- assert_equal [@task_one], ForemanTasks::Task.search_for("user !~ *#{part}*")
80
- SecureRandom.stubs(:hex).returns('abc')
81
- assert_equal ForemanTasks::Task.search_for("user !~ #{part}").to_sql,
82
- ForemanTasks::Task.search_for("user !~ *#{part}*").to_sql
83
- end
76
+ test 'can search the tasks by user with negated wildcards' do
77
+ part = @user_two.login[1..-1] # search for '*ser1' if login is 'user1'
78
+ # The following two should be equivalent
79
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user !~ #{part}")
80
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user !~ *#{part}*")
81
+ SecureRandom.stubs(:hex).returns('abc')
82
+ assert_equal ForemanTasks::Task.search_for("user !~ #{part}").to_sql,
83
+ ForemanTasks::Task.search_for("user !~ *#{part}*").to_sql
84
+ end
84
85
 
85
- test 'can search the tasks by array' do
86
- assert_equal [@task_one], ForemanTasks::Task.search_for("user ^ (this_user, #{@user_one.login}, that_user)")
87
- end
86
+ test 'can search the tasks by array' do
87
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user ^ (this_user, #{@user_one.login}, that_user)")
88
+ end
89
+
90
+ test 'can search the tasks by negated array' do
91
+ assert_equal [@task_one], ForemanTasks::Task.search_for("user !^ (this_user, #{@user_two.login}, that_user)")
92
+ end
88
93
 
89
- test 'can search the tasks by negated array' do
90
- assert_equal [@task_one], ForemanTasks::Task.search_for("user !^ (this_user, #{@user_two.login}, that_user)")
94
+ test 'properly returns username' do
95
+ assert_equal @task_one.username, @user_one.login
96
+ end
91
97
  end
92
98
 
93
- test 'properly returns username' do
94
- assert_equal @task_one.username, @user_one.login
99
+ describe 'by duration' do
100
+ before do
101
+ @task_one, @task_two = FactoryBot.create_list(:dynflow_task, 2).sort_by(&:id)
102
+ end
103
+
104
+ let(:scope) { ForemanTasks::Task.order(:id) }
105
+
106
+ it 'can search by seconds ' do
107
+ skip unless on_postgresql?
108
+ _(scope.search_for('duration < 2')).must_be :empty?
109
+ _(scope.search_for('duration = 2')).must_equal [@task_one, @task_two]
110
+ _(scope.search_for('duration < "2 seconds"')).must_be :empty?
111
+ _(scope.search_for('duration > "2 seconds"')).must_be :empty?
112
+ _(scope.search_for('duration = "2 seconds"')).must_equal [@task_one, @task_two]
113
+ _(scope.search_for('duration <= "2 seconds"')).must_equal [@task_one, @task_two]
114
+ _(scope.search_for('duration >= "2 seconds"')).must_equal [@task_one, @task_two]
115
+ end
116
+
117
+ it 'can search by other time intervals' do
118
+ skip unless on_postgresql?
119
+ %w[minutes hours days months years].each do |interval|
120
+ _(scope.search_for("duration < \"2 #{interval}\"")).must_equal [@task_one, @task_two]
121
+ _(scope.search_for("duration > \"2 #{interval}\"")).must_be :empty?
122
+ _(scope.search_for("duration = \"2 #{interval}\"")).must_be :empty?
123
+ _(scope.search_for("duration <= \"2 #{interval}\"")).must_equal [@task_one, @task_two]
124
+ _(scope.search_for("duration >= \"2 #{interval}\"")).must_be :empty?
125
+ end
126
+ end
127
+
128
+ it 'raises an exception if duration is unknown' do
129
+ proc { ForemanTasks::Task.search_for('duration = "25 potatoes"') }.must_raise ScopedSearch::QueryNotSupported
130
+ end
95
131
  end
96
132
  end
97
133
 
@@ -248,4 +284,54 @@ class TasksTest < ActiveSupport::TestCase
248
284
  task.execution_type.must_equal 'Delayed'
249
285
  end
250
286
  end
287
+
288
+ describe 'search for resource_ids' do
289
+ it 'finds tasks' do
290
+ label = 'label1'
291
+ resource_ids = [1, 2]
292
+ resource_type = 'restype1'
293
+
294
+ task1_old = FactoryBot.create(
295
+ :task_with_locks,
296
+ started_at: '2019-10-01 11:15:55',
297
+ ended_at: '2019-10-01 11:15:57',
298
+ resource_id: 1,
299
+ label: label,
300
+ resource_type: resource_type
301
+ )
302
+ task1_new = FactoryBot.create(
303
+ :task_with_locks,
304
+ started_at: '2019-10-02 11:15:55',
305
+ ended_at: '2019-10-02 11:15:57',
306
+ resource_id: 1,
307
+ label: label,
308
+ resource_type: resource_type
309
+ )
310
+ task2 = FactoryBot.create(
311
+ :task_with_locks,
312
+ started_at: '2019-10-03 11:15:55',
313
+ ended_at: '2019-10-03 11:15:57',
314
+ resource_id: 2,
315
+ label: label,
316
+ resource_type: resource_type
317
+ )
318
+ task3 = FactoryBot.create(
319
+ :task_with_locks,
320
+ started_at: '2019-10-03 11:15:55',
321
+ ended_at: '2019-10-03 11:15:57',
322
+ resource_id: 3,
323
+ label: label,
324
+ resource_type: 'another_type'
325
+ )
326
+
327
+ result = ForemanTasks::Task.search_for(
328
+ "resource_id ^ (#{resource_ids.join(',')}) and resource_type = #{resource_type}"
329
+ ).distinct
330
+ assert_equal 3, result.length
331
+ assert_includes result, task1_old
332
+ assert_includes result, task1_new
333
+ assert_includes result, task2
334
+ assert_not_includes result, task3
335
+ end
336
+ end
251
337
  end
@@ -42,6 +42,7 @@ class Task extends Component {
42
42
  cancelTaskRequest,
43
43
  resumeTaskRequest,
44
44
  action,
45
+ dynflowEnableConsole,
45
46
  } = this.props;
46
47
  const modalUnlock = (
47
48
  <ClickConfirmation
@@ -99,6 +100,7 @@ class Task extends Component {
99
100
  <Button
100
101
  bsSize="small"
101
102
  href={`/foreman_tasks/dynflow/${externalId}`}
103
+ disabled={!dynflowEnableConsole}
102
104
  >
103
105
  {__('Dynflow console')}
104
106
  </Button>
@@ -188,6 +190,7 @@ Task.propTypes = {
188
190
  toggleForceUnlockModal: PropTypes.func,
189
191
  cancelTaskRequest: PropTypes.func,
190
192
  resumeTaskRequest: PropTypes.func,
193
+ dynflowEnableConsole: PropTypes.bool,
191
194
  };
192
195
 
193
196
  Task.defaultProps = {
@@ -210,6 +213,7 @@ Task.defaultProps = {
210
213
  toggleForceUnlockModal: () => null,
211
214
  cancelTaskRequest: () => null,
212
215
  resumeTaskRequest: () => null,
216
+ dynflowEnableConsole: false,
213
217
  };
214
218
 
215
219
  export default Task;
@@ -2,8 +2,8 @@ import React, { Component } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Grid, Row, Col, ProgressBar } from 'patternfly-react';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
- import EllipsisWithTooltip from 'react-ellipsis-with-tooltip';
6
5
  import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
6
+ import ReactHtmlParser from 'react-html-parser';
7
7
 
8
8
  class TaskInfo extends Component {
9
9
  isDelayed = () => {
@@ -69,9 +69,7 @@ class TaskInfo extends Component {
69
69
  [
70
70
  {
71
71
  title: 'Name',
72
- value: (
73
- <EllipsisWithTooltip>{action || __('N/A')}</EllipsisWithTooltip>
74
- ),
72
+ value: action || __('N/A'),
75
73
  },
76
74
  {
77
75
  title: 'Start at',
@@ -173,14 +171,7 @@ class TaskInfo extends Component {
173
171
  <b>{__('Troubleshooting')}</b>
174
172
  </span>
175
173
  </p>
176
- <p>
177
- {help.split('\n').map((item, i) => (
178
- <React.Fragment key={i}>
179
- {item}
180
- <br />
181
- </React.Fragment>
182
- ))}
183
- </p>
174
+ <p>{ReactHtmlParser(help)}</p>
184
175
  </Col>
185
176
  </Row>
186
177
  )}
@@ -9,6 +9,7 @@ const fixtures = {
9
9
  state: 'paused',
10
10
  hasSubTasks: true,
11
11
  allowDangerousActions: true,
12
+ dynflowEnableConsole: true,
12
13
  },
13
14
  };
14
15
 
@@ -115,6 +115,7 @@ exports[`Task rendering render with some Props 1`] = `
115
115
  allowDangerousActions={true}
116
116
  cancelTaskRequest={[Function]}
117
117
  cancellable={false}
118
+ dynflowEnableConsole={true}
118
119
  endedAt=""
119
120
  error={Array []}
120
121
  externalId=""
@@ -205,7 +206,7 @@ exports[`Task rendering render without Props 1`] = `
205
206
  bsClass="btn"
206
207
  bsSize="small"
207
208
  bsStyle="default"
208
- disabled={false}
209
+ disabled={true}
209
210
  href="/foreman_tasks/dynflow/"
210
211
  >
211
212
  Dynflow console
@@ -229,6 +230,7 @@ exports[`Task rendering render without Props 1`] = `
229
230
  allowDangerousActions={false}
230
231
  cancelTaskRequest={[Function]}
231
232
  cancellable={false}
233
+ dynflowEnableConsole={false}
232
234
  endedAt=""
233
235
  error={Array []}
234
236
  externalId=""
@@ -32,9 +32,7 @@ exports[`TaskInfo rendering render with Props 1`] = `
32
32
  sm={6}
33
33
  >
34
34
  <span>
35
- <EllipisWithTooltip>
36
- Monitor Event Queue
37
- </EllipisWithTooltip>
35
+ Monitor Event Queue
38
36
  </span>
39
37
  </Col>
40
38
  <Col
@@ -335,9 +333,7 @@ exports[`TaskInfo rendering render without Props 1`] = `
335
333
  sm={6}
336
334
  >
337
335
  <span>
338
- <EllipisWithTooltip>
339
- N/A
340
- </EllipisWithTooltip>
336
+ N/A
341
337
  </span>
342
338
  </Col>
343
339
  <Col
@@ -30,7 +30,7 @@ export const selectErrors = state => {
30
30
 
31
31
  export const selectProgress = state =>
32
32
  selectTaskDetails(state).progress
33
- ? selectTaskDetails(state).progress.toFixed(4) * 100
33
+ ? parseFloat((selectTaskDetails(state).progress * 100).toFixed(2))
34
34
  : 0;
35
35
 
36
36
  export const selectUsername = state =>
@@ -83,3 +83,6 @@ export const selectShowForceUnlockModal = state =>
83
83
 
84
84
  export const selectExternalId = state =>
85
85
  selectTaskDetails(state).external_id || null;
86
+
87
+ export const selectDynflowEnableConsole = state =>
88
+ selectTaskDetails(state).dynflow_enable_console || false;
@@ -18,6 +18,7 @@ exports[`TaskDetails rendering render without Props 1`] = `
18
18
  allowDangerousActions={false}
19
19
  cancelTaskRequest={[Function]}
20
20
  cancellable={false}
21
+ dynflowEnableConsole={false}
21
22
  endedAt=""
22
23
  error={Array []}
23
24
  executionPlan={Object {}}
@@ -33,6 +33,7 @@ import {
33
33
  selectShowUnlockModal,
34
34
  selectShowForceUnlockModal,
35
35
  selectExternalId,
36
+ selectDynflowEnableConsole,
36
37
  } from './TaskDetailsSelectors';
37
38
 
38
39
  const mapStateToProps = state => ({
@@ -65,6 +66,7 @@ const mapStateToProps = state => ({
65
66
  showUnlockModal: selectShowUnlockModal(state),
66
67
  showForceUnlockModal: selectShowForceUnlockModal(state),
67
68
  externalId: selectExternalId(state),
69
+ dynflowEnableConsole: selectDynflowEnableConsole(state),
68
70
  });
69
71
 
70
72
  const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { translate as __ } from 'foremanReact/common/I18n';
3
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
4
4
  import TasksTablePage from './';
5
5
 
6
6
  export const SubTasksPage = props => {
@@ -15,10 +15,12 @@ export const SubTasksPage = props => {
15
15
  { caption: __('Sub tasks') },
16
16
  ],
17
17
  });
18
+ const createHeader = actionName => actionName ? sprintf(__('Sub tasks of %s'), actionName) : __('Sub tasks');
18
19
  return (
19
20
  <TasksTablePage
20
21
  getBreadcrumbs={getBreadcrumbs}
21
22
  parentTaskID={parentTaskID}
23
+ createHeader={createHeader}
22
24
  {...props}
23
25
  />
24
26
  );
@@ -2,15 +2,17 @@ import { getURIQuery } from 'foremanReact/common/helpers';
2
2
  import { getTableItemsAction } from 'foremanReact/components/common/table';
3
3
  import API from 'foremanReact/API';
4
4
  import { addToast } from 'foremanReact/redux/actions/toasts';
5
- import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
6
+ import URI from 'urijs';
6
7
  import {
7
8
  TASKS_TABLE_ID,
8
9
  SELECT_ROWS,
9
10
  UNSELECT_ALL_ROWS,
10
11
  UNSELECT_ROWS,
11
- RESUME,
12
- CANCEL,
13
12
  UPDATE_CLICKED,
13
+ TASKS_RESUME_REQUEST,
14
+ TASKS_RESUME_SUCCESS,
15
+ TASKS_RESUME_FAILURE,
14
16
  } from './TasksTableConstants';
15
17
  import { getApiPathname } from './TasksTableHelpers';
16
18
  import { fetchTasksSummary } from '../TasksDashboard/TasksDashboardActions';
@@ -18,8 +20,13 @@ import { fetchTasksSummary } from '../TasksDashboard/TasksDashboardActions';
18
20
  export const getTableItems = url =>
19
21
  getTableItemsAction(TASKS_TABLE_ID, getURIQuery(url), getApiPathname(url));
20
22
 
21
- export const cancelTask = (id, name, url, parentTaskID) => async dispatch => {
22
- await dispatch(cancelTaskRequest(id, name));
23
+ export const cancelTask = ({
24
+ taskId,
25
+ taskName,
26
+ url,
27
+ parentTaskID,
28
+ }) => async dispatch => {
29
+ await dispatch(cancelTaskRequest(taskId, taskName));
23
30
  dispatch(getTableItems(url));
24
31
  dispatch(fetchTasksSummary(getURIQuery(url).time, parentTaskID));
25
32
  };
@@ -49,8 +56,13 @@ export const cancelTaskRequest = (id, name) => async dispatch => {
49
56
  }
50
57
  };
51
58
 
52
- export const resumeTask = (id, name, url, parentTaskID) => async dispatch => {
53
- await dispatch(resumeTaskRequest(id, name));
59
+ export const resumeTask = ({
60
+ taskId,
61
+ taskName,
62
+ url,
63
+ parentTaskID,
64
+ }) => async dispatch => {
65
+ await dispatch(resumeTaskRequest(taskId, taskName));
54
66
  dispatch(getTableItems(url));
55
67
  dispatch(fetchTasksSummary(getURIQuery(url).time), parentTaskID);
56
68
  };
@@ -93,34 +105,88 @@ export const unselectRow = id => ({
93
105
  payload: id,
94
106
  });
95
107
 
96
- export const actionSelected = (
97
- actionType,
108
+ export const bulkResumeRequest = resumeTasks => {
109
+ const ids = resumeTasks.map(task => task.id);
110
+ const url = new URI('/foreman_tasks/api/tasks/bulk_resume');
111
+ url.setSearch('task_ids[]', ids);
112
+ return API.post(url);
113
+ };
114
+
115
+ export const bulkResume = ({
116
+ selected,
117
+ url,
118
+ parentTaskID,
119
+ }) => async dispatch => {
120
+ const resumeTasks = selected.filter(task => task.isResumable);
121
+ if (resumeTasks.length < selected.length)
122
+ dispatch(
123
+ addToast({
124
+ type: 'warning',
125
+ message: __('Not all the selected tasks can be resumed'),
126
+ })
127
+ );
128
+ if (resumeTasks.length) {
129
+ try {
130
+ dispatch({ type: TASKS_RESUME_REQUEST });
131
+ const { data } = await bulkResumeRequest(resumeTasks);
132
+ dispatch({ type: TASKS_RESUME_SUCCESS });
133
+ const toastInfo = {
134
+ resumed: { type: 'success', text: 'was resumed' },
135
+ failed: { type: 'error', text: 'could not be resumed' },
136
+ };
137
+ const toastDispatch = (type, name) =>
138
+ dispatch(
139
+ addToast({
140
+ type: toastInfo[type].type,
141
+ message: sprintf(__('%(name)s Task execution %(type)s'), {
142
+ name,
143
+ type: toastInfo[type].text,
144
+ }),
145
+ })
146
+ );
147
+
148
+ ['resumed', 'failed'].forEach(type => {
149
+ data[type].forEach(task => {
150
+ toastDispatch(type, task.action);
151
+ });
152
+ });
153
+ if (data.resumed.length) {
154
+ dispatch(getTableItems(url));
155
+ dispatch(fetchTasksSummary(getURIQuery(url).time, parentTaskID));
156
+ }
157
+ } catch (error) {
158
+ dispatch({ type: TASKS_RESUME_FAILURE, error });
159
+ dispatch(
160
+ addToast({
161
+ type: 'error',
162
+ message: `${__(`Cannot resume tasks at the moment`)} ${error}`,
163
+ })
164
+ );
165
+ }
166
+ }
167
+ };
168
+
169
+ export const bulkCancel = ({
98
170
  selected,
99
171
  url,
100
- parentTaskID
101
- ) => async dispatch => {
172
+ parentTaskID,
173
+ }) => async dispatch => {
102
174
  let notAllActionable = false;
103
175
  let someActionable = false;
104
176
  const promises = selected.map(task => {
105
- if (actionType === RESUME && task.isResumeble) {
177
+ if (task.isCancellable) {
106
178
  someActionable = true;
107
- return dispatch(resumeTaskRequest(task.id, task.name, url));
108
- } else if (actionType === CANCEL && task.isCancelleble) {
109
- someActionable = true;
110
- return dispatch(cancelTaskRequest(task.id, task.name, url));
179
+ return dispatch(cancelTaskRequest(task.id, task.name));
111
180
  }
112
181
  notAllActionable = true;
113
182
  return null;
114
183
  });
184
+
115
185
  if (notAllActionable)
116
186
  dispatch(
117
187
  addToast({
118
188
  type: 'warning',
119
- message: __(
120
- `Not all the selected tasks can be ${
121
- actionType === RESUME ? 'resumed' : 'canceled'
122
- }`
123
- ),
189
+ message: __('Not all the selected tasks can be canceled'),
124
190
  })
125
191
  );
126
192
  if (someActionable) {