foreman-tasks 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_tasks/api/tasks_controller.rb +12 -5
  3. data/app/lib/foreman_tasks/concerns/polling_action_extensions.rb +12 -0
  4. data/app/models/setting/foreman_tasks.rb +6 -1
  5. data/app/services/ui_notifications/tasks/task_bulk_cancel.rb +36 -0
  6. data/app/services/ui_notifications/tasks/task_bulk_resume.rb +38 -0
  7. data/db/seeds.d/30-notification_blueprints.rb +14 -0
  8. data/foreman-tasks.gemspec +1 -0
  9. data/gemfile.d/foreman-tasks.rb +1 -0
  10. data/lib/foreman_tasks/engine.rb +1 -0
  11. data/lib/foreman_tasks/version.rb +1 -1
  12. data/script/travis_run_js_tests.sh +2 -2
  13. data/test/lib/concerns/polling_action_extensions_test.rb +34 -0
  14. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +1 -3
  15. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardConstants.js +5 -0
  16. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardHelper.js +3 -2
  17. data/webpack/ForemanTasks/Components/TasksTable/Components/SelectAllAlert.js +43 -0
  18. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/SelectAllAlert.test.js +29 -0
  19. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/SelectAllAlert.test.js.snap +75 -0
  20. data/webpack/ForemanTasks/Components/TasksTable/SubTasksPage.js +2 -1
  21. data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +164 -0
  22. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +24 -10
  23. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActionHelpers.js +52 -0
  24. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +66 -128
  25. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +11 -1
  26. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +4 -3
  27. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +64 -12
  28. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +21 -2
  29. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +6 -0
  30. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksBulkActions.test.js +112 -0
  31. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +5 -3
  32. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableActionHelpers.test.js +46 -0
  33. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableActions.test.js +19 -41
  34. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +17 -1
  35. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTablePage.test.js +9 -1
  36. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +22 -1
  37. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +5 -3
  38. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksBulkActions.test.js.snap +229 -0
  39. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +5 -3
  40. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableActions.test.js.snap +39 -124
  41. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +40 -16
  42. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +34 -0
  43. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionHeaderCellFormatter.test.js +1 -1
  44. data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionHeaderCellFormatter.js +2 -2
  45. data/webpack/ForemanTasks/Components/TasksTable/index.js +8 -2
  46. data/webpack/ForemanTasks/Components/common/ToastTypesConstants.js +11 -0
  47. metadata +31 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 933e58fce5919b151351e1a148a2360faea964f688a79b5db85223dd62653f51
4
- data.tar.gz: 53b98db8cf8f6dc19c489a78a4eb923389413464dcb9fa4ca732888a032063ee
3
+ metadata.gz: c5c591217c85cb51b8bfa6372cac7b6eca2010a6b574da5f85c970ad83392641
4
+ data.tar.gz: 49e5c517005966476ee6ffca2d61998a8fb9ce89f5c621e8fcecb8ed5dc6917c
5
5
  SHA512:
6
- metadata.gz: e3a7334ab5a5efb4dd3957ead30a520836311def55777ffba66b60c272b93137ab1fbdad7a9c32ab70c23a9b5308442a6a0c1a48da00dc4d4eb8868cd9c931e9
7
- data.tar.gz: df1a0dc013edb5c5baebb3e97a275b8ac9075963546cbb318743c368db9d373fc3277739a690a346b79db72397f437be86c1159cfd78cf2ae407ab7ecde8ee75
6
+ metadata.gz: c31a093250bca2a31d50c5d9f85dd2c80cb35cfaa4d48a24c96a471c3d218cf0f6cf2b1511074b0bd5c1349d738e49a0cd5f0d5ffeba1d98035958eb02c39fbb
7
+ data.tar.gz: a548fb2f051c5173066e009539a17d768b353a52ddd66b43816b4d308b910d173b1264b525745621b18023a0111e9c2b25e4508668f406c4df1f82f6c0ec8801
@@ -95,7 +95,8 @@ module ForemanTasks
95
95
  resumed = []
96
96
  failed = []
97
97
  skipped = []
98
- bulk_scope.each do |task|
98
+ filtered_scope = bulk_scope
99
+ filtered_scope.each do |task|
99
100
  if task.resumable?
100
101
  begin
101
102
  ForemanTasks.dynflow.world.execute(task.execution_plan.id)
@@ -107,7 +108,10 @@ module ForemanTasks
107
108
  skipped << task_hash(task)
108
109
  end
109
110
  end
110
-
111
+ if params[:search]
112
+ notification = UINotifications::Tasks::TaskBulkResume.new(filtered_scope.first, resumed.length, failed.length, skipped.length)
113
+ notification.deliver!
114
+ end
111
115
  render :json => {
112
116
  total: resumed.length + failed.length + skipped.length,
113
117
  resumed: resumed,
@@ -123,10 +127,13 @@ module ForemanTasks
123
127
  if params[:search].nil? && params[:task_ids].nil?
124
128
  raise BadRequest, _('Please provide at least one of search or task_ids parameters in the request')
125
129
  end
126
-
127
- cancelled, skipped = bulk_scope.partition(&:cancellable?)
128
-
130
+ filtered_scope = bulk_scope
131
+ cancelled, skipped = filtered_scope.partition(&:cancellable?)
129
132
  cancelled.each(&:cancel)
133
+ if params[:search]
134
+ notification = UINotifications::Tasks::TaskBulkCancel.new(filtered_scope.first, cancelled.length, skipped.length)
135
+ notification.deliver!
136
+ end
130
137
  render :json => {
131
138
  total: cancelled.length + skipped.length,
132
139
  cancelled: cancelled,
@@ -0,0 +1,12 @@
1
+ module ForemanTasks
2
+ module Concerns
3
+ module PollingActionExtensions
4
+ def poll_intervals
5
+ multiplier = Setting[:foreman_tasks_polling_multiplier] || 1
6
+
7
+ # Prevent the intervals from going below 0.5 seconds
8
+ super.map { |interval| [interval * multiplier, 0.5].max }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -13,7 +13,12 @@ class Setting::ForemanTasks < Setting
13
13
  N_('Url pointing to the task troubleshooting documentation. '\
14
14
  'It should contain %{label} placeholder, that will be replaced with normalized task label '\
15
15
  '(restricted to only alphanumeric characters)). %{version} placeholder is also available.'),
16
- nil)
16
+ nil),
17
+ set('foreman_tasks_polling_multiplier',
18
+ N_('Polling multiplier which is used to multiply the default polling intervals. '\
19
+ 'This can be used to prevent polling too frequently for long running tasks.'),
20
+ 1,
21
+ N_("Polling intervals multiplier"))
17
22
  ]
18
23
  end
19
24
 
@@ -0,0 +1,36 @@
1
+ module UINotifications
2
+ module Tasks
3
+ class TaskBulkCancel < ::UINotifications::Base
4
+ def initialize(task, cancelled_length, skipped_length)
5
+ @subject = task
6
+ @cancelled_length = cancelled_length
7
+ @skipped_length = skipped_length
8
+ end
9
+
10
+ def create
11
+ Notification.create!(
12
+ initiator: initiator,
13
+ audience: audience,
14
+ subject: subject,
15
+ notification_blueprint: blueprint,
16
+ message: message,
17
+ notification_recipients: [NotificationRecipient.create({ :user => User.current })]
18
+ )
19
+ end
20
+
21
+ def audience
22
+ Notification::AUDIENCE_GLOBAL
23
+ end
24
+
25
+ def message
26
+ ('%{cancelled} Tasks were cancelled. %{skipped} Tasks were skipped. ' %
27
+ { cancelled: @cancelled_length,
28
+ skipped: @skipped_length })
29
+ end
30
+
31
+ def blueprint
32
+ @blueprint ||= NotificationBlueprint.find_by(name: 'tasks_bulk_cancel')
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ module UINotifications
2
+ module Tasks
3
+ class TaskBulkResume < ::UINotifications::Base
4
+ def initialize(task, resumed_length, failed_length, skipped_length)
5
+ @subject = task
6
+ @resumed_length = resumed_length
7
+ @failed_length = failed_length
8
+ @skipped_length = skipped_length
9
+ end
10
+
11
+ def create
12
+ Notification.create!(
13
+ initiator: initiator,
14
+ audience: audience,
15
+ subject: subject,
16
+ notification_blueprint: blueprint,
17
+ message: message,
18
+ notification_recipients: [NotificationRecipient.create({ :user => User.current })]
19
+ )
20
+ end
21
+
22
+ def audience
23
+ Notification::AUDIENCE_USER
24
+ end
25
+
26
+ def message
27
+ ('%{resumed} Tasks were resumed. %{failed} Tasks failed to resume. %{skipped} Tasks were skipped. ' %
28
+ { resumed: @resumed_length,
29
+ failed: @failed_length,
30
+ skipped: @skipped_length })
31
+ end
32
+
33
+ def blueprint
34
+ @blueprint ||= NotificationBlueprint.find_by(name: 'tasks_bulk_resume')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -27,6 +27,20 @@ blueprints = [
27
27
  title: N_('Task Details')
28
28
  ]
29
29
  }
30
+ },
31
+
32
+ {
33
+ group: N_('Tasks'),
34
+ name: 'tasks_bulk_resume',
35
+ level: 'info',
36
+ message: "DYNAMIC",
37
+ },
38
+
39
+ {
40
+ group: N_('Tasks'),
41
+ name: 'tasks_bulk_cancel',
42
+ level: 'info',
43
+ message: "DYNAMIC",
30
44
  }
31
45
  ]
32
46
 
@@ -34,4 +34,5 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
34
34
  s.add_dependency "sinatra" # for Dynflow web console
35
35
 
36
36
  s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
37
+ s.add_development_dependency 'sqlite3'
37
38
  end
@@ -0,0 +1 @@
1
+ gem 'sqlite3'
@@ -145,6 +145,7 @@ module ForemanTasks
145
145
 
146
146
  Authorizer.prepend AuthorizerExt
147
147
  User.include ForemanTasks::Concerns::UserExtensions
148
+ ::Dynflow::Action::Polling.prepend ForemanTasks::Concerns::PollingActionExtensions
148
149
  end
149
150
 
150
151
  rake_tasks do
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '1.1.1'.freeze
3
3
  end
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  set -ev
3
- if [[ $( git diff --name-only HEAD~1..HEAD webpack/ .travis.yml .babelrc .eslintrc package.json | wc -l ) -ne 0 ]]; then
3
+ if [[ $( git diff --name-only HEAD~1..HEAD webpack/ .travis.yml babel.config.js .eslintrc package.json | wc -l ) -ne 0 ]]; then
4
4
  npm run test;
5
- npm run coveralls;
5
+ npm run publish-coverage;
6
6
  npm run lint;
7
7
  fi
@@ -0,0 +1,34 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ module ForemanTasks
4
+ module Concerns
5
+ class PollingActionExtensionsTest < ::ActiveSupport::TestCase
6
+ class Action < ::Dynflow::Action
7
+ include ::Dynflow::Action::Polling
8
+ end
9
+
10
+ describe 'polling interval tuning' do
11
+ let(:default_intervals) { [0.5, 1, 2, 4, 8, 16] }
12
+
13
+ it 'is extends the polling action module' do
14
+ _(::Dynflow::Action::Polling.ancestors.first).must_equal ForemanTasks::Concerns::PollingActionExtensions
15
+ end
16
+
17
+ it 'does not modify polling intervals by default' do
18
+ _(Action.allocate.poll_intervals).must_equal default_intervals
19
+ end
20
+
21
+ it 'cannot make intervals shorter than 0.5 seconds' do
22
+ Setting.expects(:[]).with(:foreman_tasks_polling_multiplier).returns 0
23
+ _(Action.allocate.poll_intervals).must_equal(default_intervals.map { 0.5 })
24
+ end
25
+
26
+ it 'can be used to make the intervals longer' do
27
+ value = 5
28
+ Setting.expects(:[]).with(:foreman_tasks_polling_multiplier).returns value
29
+ _(Action.allocate.poll_intervals).must_equal(default_intervals.map { |i| i * value })
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -292,9 +292,7 @@ exports[`TaskInfo rendering render with Props 1`] = `
292
292
  </p>
293
293
  <p>
294
294
  A paused task represents a process that has not finished properly. Any task in paused state can lead to potential inconsistency and needs to be resolved.
295
- <br />
296
- The recommended approach is to investigate the error messages below and in 'errors' tab, address the primary cause of the issue and resume the task.
297
- <br />
295
+ The recommended approach is to investigate the error messages below and in 'errors' tab, address the primary cause of the issue and resume the task.
298
296
  </p>
299
297
  </Col>
300
298
  </Row>
@@ -18,6 +18,11 @@ export const TASKS_DASHBOARD_AVAILABLE_QUERY_MODES = {
18
18
  OLDER: 'older',
19
19
  };
20
20
 
21
+ export const TASKS_DASHBOARD_JS_QUERY_MODES = {
22
+ RECENT: 'recent',
23
+ OLDER: 'older',
24
+ };
25
+
21
26
  export const TASKS_DASHBOARD_AVAILABLE_TIMES = {
22
27
  H24: 'H24',
23
28
  H12: 'H12',
@@ -4,6 +4,7 @@ import {
4
4
  TASKS_DASHBOARD_AVAILABLE_TIMES,
5
5
  TASKS_DASHBOARD_QUERY_KEYS_TEXT,
6
6
  TASKS_DASHBOARD_QUERY_VALUES_TEXT,
7
+ TASKS_DASHBOARD_JS_QUERY_MODES,
7
8
  } from './TasksDashboardConstants';
8
9
  import { updateURlQuery } from '../TasksTable/TasksTableHelpers';
9
10
 
@@ -39,8 +40,8 @@ const queryFromUriQuery = uriQuery => {
39
40
  if (uriQuery[uriField]) query[queryField] = uriQuery[uriField];
40
41
  });
41
42
 
42
- if (query.mode === 'recent') {
43
- query.mode = 'last';
43
+ if (query.mode === TASKS_DASHBOARD_JS_QUERY_MODES.RECENT) {
44
+ query.mode = TASKS_DASHBOARD_QUERY_VALUES_TEXT.LAST;
44
45
  }
45
46
 
46
47
  return query;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Alert, Button } from 'patternfly-react';
4
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ export const SelectAllAlert = ({
7
+ itemCount,
8
+ perPage,
9
+ selectAllRows,
10
+ unselectAllRows,
11
+ allRowsSelected,
12
+ }) => {
13
+ const selectAllText = (
14
+ <React.Fragment>
15
+ {sprintf(
16
+ 'All %s tasks on this page are selected',
17
+ Math.min(itemCount, perPage)
18
+ )}
19
+ <Button bsStyle="link" onClick={selectAllRows}>
20
+ {__('Select All')}
21
+ <b> {itemCount} </b> {__('tasks.')}
22
+ </Button>
23
+ </React.Fragment>
24
+ );
25
+ const undoSelectText = (
26
+ <React.Fragment>
27
+ {sprintf(__(`All %s tasks are selected. `), itemCount)}
28
+ <Button bsStyle="link" onClick={unselectAllRows}>
29
+ {__('Undo selection')}
30
+ </Button>
31
+ </React.Fragment>
32
+ );
33
+ const selectAlertText = allRowsSelected ? undoSelectText : selectAllText;
34
+ return <Alert type="info">{selectAlertText}</Alert>;
35
+ };
36
+
37
+ SelectAllAlert.propTypes = {
38
+ allRowsSelected: PropTypes.bool.isRequired,
39
+ itemCount: PropTypes.number.isRequired,
40
+ perPage: PropTypes.number.isRequired,
41
+ selectAllRows: PropTypes.func.isRequired,
42
+ unselectAllRows: PropTypes.func.isRequired,
43
+ };
@@ -0,0 +1,29 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+
3
+ import { SelectAllAlert } from '../SelectAllAlert';
4
+
5
+ const baseProps = {
6
+ itemCount: 7,
7
+ perPage: 5,
8
+ selectAllRows: jest.fn(),
9
+ unselectAllRows: jest.fn(),
10
+ };
11
+ const fixtures = {
12
+ 'renders SelectAllAlert with perPage > itemCout': {
13
+ allRowsSelected: false,
14
+ itemCount: 7,
15
+ perPage: 10,
16
+ ...baseProps,
17
+ },
18
+ 'renders SelectAllAlert without all rows selected': {
19
+ allRowsSelected: false,
20
+ ...baseProps,
21
+ },
22
+ 'renders SelectAllAlert with all rows selected': {
23
+ allRowsSelected: true,
24
+ ...baseProps,
25
+ },
26
+ };
27
+
28
+ describe('SelectAllAlert', () =>
29
+ testComponentSnapshotsWithFixtures(SelectAllAlert, fixtures));
@@ -0,0 +1,75 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`SelectAllAlert renders SelectAllAlert with all rows selected 1`] = `
4
+ <Alert
5
+ className=""
6
+ onDismiss={null}
7
+ type="info"
8
+ >
9
+ All 7 tasks are selected.
10
+ <Button
11
+ active={false}
12
+ block={false}
13
+ bsClass="btn"
14
+ bsStyle="link"
15
+ disabled={false}
16
+ onClick={[MockFunction]}
17
+ >
18
+ Undo selection
19
+ </Button>
20
+ </Alert>
21
+ `;
22
+
23
+ exports[`SelectAllAlert renders SelectAllAlert with perPage > itemCout 1`] = `
24
+ <Alert
25
+ className=""
26
+ onDismiss={null}
27
+ type="info"
28
+ >
29
+ All 5 tasks on this page are selected
30
+ <Button
31
+ active={false}
32
+ block={false}
33
+ bsClass="btn"
34
+ bsStyle="link"
35
+ disabled={false}
36
+ onClick={[MockFunction]}
37
+ >
38
+ Select All
39
+ <b>
40
+
41
+ 7
42
+
43
+ </b>
44
+
45
+ tasks.
46
+ </Button>
47
+ </Alert>
48
+ `;
49
+
50
+ exports[`SelectAllAlert renders SelectAllAlert without all rows selected 1`] = `
51
+ <Alert
52
+ className=""
53
+ onDismiss={null}
54
+ type="info"
55
+ >
56
+ All 5 tasks on this page are selected
57
+ <Button
58
+ active={false}
59
+ block={false}
60
+ bsClass="btn"
61
+ bsStyle="link"
62
+ disabled={false}
63
+ onClick={[MockFunction]}
64
+ >
65
+ Select All
66
+ <b>
67
+
68
+ 7
69
+
70
+ </b>
71
+
72
+ tasks.
73
+ </Button>
74
+ </Alert>
75
+ `;
@@ -15,7 +15,8 @@ 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
+ const createHeader = actionName =>
19
+ actionName ? sprintf(__('Sub tasks of %s'), actionName) : __('Sub tasks');
19
20
  return (
20
21
  <TasksTablePage
21
22
  getBreadcrumbs={getBreadcrumbs}