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
@@ -0,0 +1,164 @@
1
+ import API from 'foremanReact/API';
2
+ import { addToast } from 'foremanReact/redux/actions/toasts';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { TOAST_TYPES } from '../common/ToastTypesConstants';
5
+ import {
6
+ TASKS_RESUME_REQUEST,
7
+ TASKS_RESUME_SUCCESS,
8
+ TASKS_RESUME_FAILURE,
9
+ TASKS_CANCEL_REQUEST,
10
+ TASKS_CANCEL_SUCCESS,
11
+ TASKS_CANCEL_FAILURE,
12
+ BULK_CANCEL_PATH,
13
+ BULK_RESUME_PATH,
14
+ } from './TasksTableConstants';
15
+ import { reloadPage } from './TasksTableActions';
16
+ import {
17
+ convertDashboardQuery,
18
+ resumeToastInfo,
19
+ cancelToastInfo,
20
+ toastDispatch,
21
+ } from './TasksTableActionHelpers';
22
+
23
+ export const bulkByIdRequest = (resumeTasks, path) => {
24
+ const ids = resumeTasks.map(task => task.id);
25
+ const url = `/foreman_tasks/api/tasks/${path}`;
26
+ const data = { task_ids: ids };
27
+ return API.post(url, data);
28
+ };
29
+
30
+ export const bulkBySearchRequest = ({ query, parentTaskID, path }) => {
31
+ const url = `/foreman_tasks/api/tasks/${path}`;
32
+ if (parentTaskID) {
33
+ query.search = query.search
34
+ ? ` ${query.search} and parent_task_id=${parentTaskID}`
35
+ : `parent_task_id=${parentTaskID}`;
36
+ }
37
+ const searchParam = { search: convertDashboardQuery(query) };
38
+ return API.post(url, searchParam);
39
+ };
40
+
41
+ const handleErrorResume = (error, dispatch) => {
42
+ dispatch({ type: TASKS_RESUME_FAILURE, error });
43
+ dispatch(
44
+ addToast({
45
+ type: TOAST_TYPES.ERROR,
46
+ message: `${__(`Cannot resume tasks at the moment`)} ${error}`,
47
+ })
48
+ );
49
+ };
50
+
51
+ export const bulkResumeById = ({
52
+ selected,
53
+ url,
54
+ parentTaskID,
55
+ }) => async dispatch => {
56
+ const resumeTasks = selected.filter(task => task.isResumable);
57
+ if (resumeTasks.length < selected.length)
58
+ dispatch(
59
+ addToast({
60
+ type: TOAST_TYPES.WARNING,
61
+ message: __('Not all the selected tasks can be resumed'),
62
+ })
63
+ );
64
+ if (resumeTasks.length) {
65
+ dispatch({ type: TASKS_RESUME_REQUEST });
66
+ try {
67
+ const { data } = await bulkByIdRequest(resumeTasks, BULK_RESUME_PATH);
68
+ dispatch({ type: TASKS_RESUME_SUCCESS });
69
+ ['resumed', 'failed', 'skipped'].forEach(type => {
70
+ data[type] &&
71
+ data[type].forEach(task => {
72
+ toastDispatch({
73
+ type,
74
+ name: task.action,
75
+ toastInfo: resumeToastInfo,
76
+ dispatch,
77
+ });
78
+ });
79
+ });
80
+ if (data.resumed) {
81
+ reloadPage(url, parentTaskID, dispatch);
82
+ }
83
+ } catch (error) {
84
+ handleErrorResume(error, dispatch);
85
+ }
86
+ }
87
+ };
88
+
89
+ export const bulkResumeBySearch = ({
90
+ query,
91
+ parentTaskID,
92
+ }) => async dispatch => {
93
+ dispatch({ type: TASKS_RESUME_REQUEST });
94
+ dispatch(
95
+ addToast({
96
+ type: 'info',
97
+ message: __('Resuming selected tasks, this might take a while'),
98
+ })
99
+ );
100
+ await bulkBySearchRequest({ query, path: BULK_RESUME_PATH, parentTaskID });
101
+ };
102
+
103
+ const handleErrorCancel = (error, dispatch) => {
104
+ dispatch({ type: TASKS_CANCEL_FAILURE, error });
105
+ dispatch(
106
+ addToast({
107
+ type: TOAST_TYPES.ERROR,
108
+ message: `${__(`Cannot cancel tasks at the moment`)} ${error}`,
109
+ })
110
+ );
111
+ };
112
+
113
+ export const bulkCancelBySearch = ({
114
+ query,
115
+ parentTaskID,
116
+ }) => async dispatch => {
117
+ dispatch({ type: TASKS_CANCEL_REQUEST });
118
+ dispatch(
119
+ addToast({
120
+ type: 'info',
121
+ message: __('Canceling selected tasks, this might take a while'),
122
+ })
123
+ );
124
+ await bulkBySearchRequest({ query, path: BULK_CANCEL_PATH, parentTaskID });
125
+ };
126
+
127
+ export const bulkCancelById = ({
128
+ selected,
129
+ url,
130
+ parentTaskID,
131
+ }) => async dispatch => {
132
+ const cancelTasks = selected.filter(task => task.isCancellable);
133
+ if (cancelTasks.length < selected.length)
134
+ dispatch(
135
+ addToast({
136
+ type: TOAST_TYPES.WARNING,
137
+ message: __('Not all the selected tasks can be cancelled'),
138
+ })
139
+ );
140
+ if (cancelTasks.length) {
141
+ dispatch({ type: TASKS_CANCEL_REQUEST });
142
+ try {
143
+ const { data } = await bulkByIdRequest(cancelTasks, BULK_CANCEL_PATH);
144
+ dispatch({ type: TASKS_CANCEL_SUCCESS });
145
+
146
+ ['cancelled', 'skipped'].forEach(type => {
147
+ data[type] &&
148
+ data[type].forEach(task => {
149
+ toastDispatch({
150
+ type,
151
+ name: task.action,
152
+ toastInfo: cancelToastInfo,
153
+ dispatch,
154
+ });
155
+ });
156
+ });
157
+ if (data.cancelled) {
158
+ reloadPage(url, parentTaskID, dispatch);
159
+ }
160
+ } catch (error) {
161
+ handleErrorCancel(error, dispatch);
162
+ }
163
+ }
164
+ };
@@ -18,33 +18,45 @@ const TasksTable = ({
18
18
  itemCount,
19
19
  pagination,
20
20
  selectedRows,
21
- selectAllRows,
21
+ selectPage,
22
22
  unselectAllRows,
23
23
  selectRow,
24
24
  unselectRow,
25
25
  openClickedModal,
26
26
  modalProps,
27
+ allRowsSelected,
27
28
  }) => {
28
- const url = history.location.pathname + history.location.search;
29
+ const { search, pathname } = history.location;
30
+ const url = pathname + search;
29
31
  const uriQuery = getURIQuery(url);
30
32
 
31
33
  useEffect(() => {
32
34
  getTableItems(url);
33
35
  }, [getTableItems, url]);
34
36
 
37
+ useEffect(() => {
38
+ unselectAllRows();
39
+ }, [unselectAllRows, search]);
40
+
35
41
  const getSelectionController = () => {
36
- const checkAllRowsSelected = () => results.length === selectedRows.length;
42
+ const checkAllPageSelected = () =>
43
+ allRowsSelected || results.length === selectedRows.length;
37
44
  return {
38
- allRowsSelected: () => checkAllRowsSelected(),
39
- selectAllRows: () => {
40
- if (checkAllRowsSelected()) unselectAllRows();
41
- else selectAllRows(results);
45
+ allRowsSelected,
46
+ allPageSelected: () => checkAllPageSelected(),
47
+ selectPage: () => {
48
+ if (checkAllPageSelected()) unselectAllRows();
49
+ else {
50
+ selectPage(results);
51
+ }
42
52
  },
43
53
  selectRow: ({ rowData: { id } }) => {
44
- if (selectedRows.includes(id)) unselectRow(id);
54
+ if (selectedRows.includes(id) || allRowsSelected)
55
+ unselectRow(id, allRowsSelected && results);
45
56
  else selectRow(id);
46
57
  },
47
- isSelected: ({ rowData }) => selectedRows.includes(rowData.id),
58
+ isSelected: ({ rowData }) =>
59
+ allRowsSelected || selectedRows.includes(rowData.id),
48
60
  };
49
61
  };
50
62
 
@@ -135,7 +147,7 @@ TasksTable.propTypes = {
135
147
  history: PropTypes.object.isRequired,
136
148
  openClickedModal: PropTypes.func.isRequired,
137
149
  selectedRows: PropTypes.array,
138
- selectAllRows: PropTypes.func.isRequired,
150
+ selectPage: PropTypes.func.isRequired,
139
151
  unselectAllRows: PropTypes.func.isRequired,
140
152
  selectRow: PropTypes.func.isRequired,
141
153
  unselectRow: PropTypes.func.isRequired,
@@ -145,6 +157,7 @@ TasksTable.propTypes = {
145
157
  cancelModal: PropTypes.object,
146
158
  resumeModal: PropTypes.object,
147
159
  }).isRequired,
160
+ allRowsSelected: PropTypes.bool,
148
161
  };
149
162
 
150
163
  TasksTable.defaultProps = {
@@ -155,6 +168,7 @@ TasksTable.defaultProps = {
155
168
  perPage: 20,
156
169
  },
157
170
  selectedRows: [],
171
+ allRowsSelected: false,
158
172
  };
159
173
 
160
174
  export default TasksTable;
@@ -0,0 +1,52 @@
1
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
+ import { addToast } from 'foremanReact/redux/actions/toasts';
3
+ import { TASKS_DASHBOARD_JS_QUERY_MODES } from '../TasksDashboard/TasksDashboardConstants';
4
+ import { timeToHoursNumber } from '../TasksDashboard/TasksDashboardHelper';
5
+
6
+ export const convertDashboardQuery = query => {
7
+ const {
8
+ time_mode: timeMode,
9
+ time_horizon: timeHorizon,
10
+ state,
11
+ result,
12
+ search,
13
+ } = query;
14
+
15
+ const hours = timeToHoursNumber(timeHorizon);
16
+ const timestamp = new Date(new Date() - hours * 60 * 60 * 1000);
17
+ let dashboardTime = '';
18
+ const stateQuery = state ? `state=${state}` : '';
19
+ const resultQuery = result ? `result=${result}` : '';
20
+ if (timeMode === TASKS_DASHBOARD_JS_QUERY_MODES.RECENT) {
21
+ dashboardTime = `(state_updated_at>${timestamp.toISOString()} or state_updated_at = NULL)`;
22
+ } else if (timeMode === TASKS_DASHBOARD_JS_QUERY_MODES.OLDER) {
23
+ dashboardTime = `(state_updated_at>${timestamp.toISOString()})`;
24
+ }
25
+ const newQuery = [stateQuery, resultQuery, search, dashboardTime]
26
+ .filter(Boolean)
27
+ .join(' and ');
28
+ return newQuery;
29
+ };
30
+
31
+ export const resumeToastInfo = {
32
+ resumed: { type: 'success', text: __('was resumed') },
33
+ failed: { type: 'error', text: __('could not be resumed') },
34
+ skipped: { type: 'warning', text: __('task has to be resumable') },
35
+ };
36
+
37
+ export const cancelToastInfo = {
38
+ cancelled: { type: 'success', text: __('was cancelled') },
39
+ skipped: { type: 'warning', text: __('task has to be cancellable') },
40
+ };
41
+
42
+ export const toastDispatch = ({ type, name, toastInfo, dispatch }) => {
43
+ dispatch(
44
+ addToast({
45
+ type: toastInfo[type].type,
46
+ message: sprintf('%(name)s Task execution %(type)s', {
47
+ name,
48
+ type: toastInfo[type].text,
49
+ }),
50
+ })
51
+ );
52
+ };
@@ -2,24 +2,39 @@ 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 __, sprintf } from 'foremanReact/common/I18n';
6
- import URI from 'urijs';
5
+ import { sprintf } from 'foremanReact/common/I18n';
7
6
  import {
8
7
  TASKS_TABLE_ID,
9
8
  SELECT_ROWS,
10
9
  UNSELECT_ALL_ROWS,
10
+ SELECT_ALL_ROWS,
11
11
  UNSELECT_ROWS,
12
12
  UPDATE_CLICKED,
13
+ OPEN_SELECT_ALL,
13
14
  TASKS_RESUME_REQUEST,
14
15
  TASKS_RESUME_SUCCESS,
15
16
  TASKS_RESUME_FAILURE,
17
+ TASKS_CANCEL_REQUEST,
18
+ TASKS_CANCEL_SUCCESS,
19
+ TASKS_CANCEL_FAILURE,
16
20
  } from './TasksTableConstants';
21
+ import { TOAST_TYPES } from '../common/ToastTypesConstants';
17
22
  import { getApiPathname } from './TasksTableHelpers';
18
23
  import { fetchTasksSummary } from '../TasksDashboard/TasksDashboardActions';
24
+ import {
25
+ resumeToastInfo,
26
+ cancelToastInfo,
27
+ toastDispatch,
28
+ } from './TasksTableActionHelpers';
19
29
 
20
30
  export const getTableItems = url =>
21
31
  getTableItemsAction(TASKS_TABLE_ID, getURIQuery(url), getApiPathname(url));
22
32
 
33
+ export const reloadPage = (url, parentTaskID, dispatch) => {
34
+ dispatch(getTableItems(url));
35
+ dispatch(fetchTasksSummary(getURIQuery(url).time, parentTaskID));
36
+ };
37
+
23
38
  export const cancelTask = ({
24
39
  taskId,
25
40
  taskName,
@@ -27,32 +42,34 @@ export const cancelTask = ({
27
42
  parentTaskID,
28
43
  }) => async dispatch => {
29
44
  await dispatch(cancelTaskRequest(taskId, taskName));
30
- dispatch(getTableItems(url));
31
- dispatch(fetchTasksSummary(getURIQuery(url).time, parentTaskID));
45
+ reloadPage(url, parentTaskID, dispatch);
32
46
  };
33
47
 
34
48
  export const cancelTaskRequest = (id, name) => async dispatch => {
35
49
  dispatch(
36
50
  addToast({
37
- type: 'info',
38
- message: `${__('Trying to cancel')} "${name}" ${__('task')}`,
51
+ type: TOAST_TYPES.INFO,
52
+ message: sprintf('Trying to cancel %s task', name),
39
53
  })
40
54
  );
55
+ dispatch({ type: TASKS_CANCEL_REQUEST });
41
56
  try {
42
57
  await API.post(`/foreman_tasks/tasks/${id}/cancel`);
43
- dispatch(
44
- addToast({
45
- type: 'success',
46
- message: `"${name}" ${__('Task cancelled')}`,
47
- })
48
- );
58
+ dispatch({ type: TASKS_CANCEL_SUCCESS });
59
+ toastDispatch({
60
+ type: 'cancelled',
61
+ name,
62
+ toastInfo: cancelToastInfo,
63
+ dispatch,
64
+ });
49
65
  } catch ({ response }) {
50
- dispatch(
51
- addToast({
52
- type: 'error',
53
- message: `"${name}" ${__('Task cannot be cancelled at the moment.')}`,
54
- })
55
- );
66
+ dispatch({ type: TASKS_CANCEL_FAILURE, payload: response });
67
+ toastDispatch({
68
+ type: 'skipped',
69
+ name,
70
+ toastInfo: cancelToastInfo,
71
+ dispatch,
72
+ });
56
73
  }
57
74
  };
58
75
 
@@ -63,32 +80,44 @@ export const resumeTask = ({
63
80
  parentTaskID,
64
81
  }) => async dispatch => {
65
82
  await dispatch(resumeTaskRequest(taskId, taskName));
66
- dispatch(getTableItems(url));
67
- dispatch(fetchTasksSummary(getURIQuery(url).time), parentTaskID);
83
+ reloadPage(url, parentTaskID, dispatch);
68
84
  };
69
85
 
70
86
  export const resumeTaskRequest = (id, name) => async dispatch => {
87
+ dispatch({ type: TASKS_RESUME_REQUEST });
71
88
  try {
72
89
  await API.post(`/foreman_tasks/tasks/${id}/resume`);
73
- dispatch(
74
- addToast({
75
- type: 'success',
76
- message: __(`"${name}" ${__('Task execution was resumed')}`),
77
- })
78
- );
90
+
91
+ dispatch({ type: TASKS_RESUME_SUCCESS });
92
+ toastDispatch({
93
+ type: 'resumed',
94
+ name,
95
+ toastInfo: resumeToastInfo,
96
+ dispatch,
97
+ });
79
98
  } catch ({ response }) {
80
- dispatch(
81
- addToast({
82
- type: 'error',
83
- message: __(`Task "${name}" has to be resumable.`),
84
- })
85
- );
99
+ dispatch({ type: TASKS_RESUME_FAILURE, payload: response });
100
+ toastDispatch({
101
+ type: 'failed',
102
+ name,
103
+ toastInfo: resumeToastInfo,
104
+ dispatch,
105
+ });
86
106
  }
87
107
  };
88
108
 
89
- export const selectAllRows = results => ({
90
- type: SELECT_ROWS,
91
- payload: results.map(row => row.id),
109
+ export const selectPage = results => dispatch => {
110
+ dispatch({
111
+ type: SELECT_ROWS,
112
+ payload: results.map(row => row.id),
113
+ });
114
+ dispatch({
115
+ type: OPEN_SELECT_ALL,
116
+ });
117
+ };
118
+
119
+ export const selectAllRows = () => ({
120
+ type: SELECT_ALL_ROWS,
92
121
  });
93
122
 
94
123
  export const unselectAllRows = () => ({
@@ -100,102 +129,11 @@ export const selectRow = id => ({
100
129
  payload: [id],
101
130
  });
102
131
 
103
- export const unselectRow = id => ({
132
+ export const unselectRow = (id, results) => ({
104
133
  type: UNSELECT_ROWS,
105
- payload: id,
134
+ payload: { id, results },
106
135
  });
107
136
 
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 = ({
170
- selected,
171
- url,
172
- parentTaskID,
173
- }) => async dispatch => {
174
- let notAllActionable = false;
175
- let someActionable = false;
176
- const promises = selected.map(task => {
177
- if (task.isCancellable) {
178
- someActionable = true;
179
- return dispatch(cancelTaskRequest(task.id, task.name));
180
- }
181
- notAllActionable = true;
182
- return null;
183
- });
184
-
185
- if (notAllActionable)
186
- dispatch(
187
- addToast({
188
- type: 'warning',
189
- message: __('Not all the selected tasks can be canceled'),
190
- })
191
- );
192
- if (someActionable) {
193
- await Promise.all(promises);
194
- dispatch(getTableItems(url));
195
- dispatch(fetchTasksSummary(getURIQuery(url).time, parentTaskID));
196
- }
197
- };
198
-
199
137
  export const openClickedModal = ({ taskId, taskName, setModalOpen }) => {
200
138
  setModalOpen();
201
139
  return {