foreman-tasks 1.1.0 → 1.1.1

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/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}