foreman-tasks 1.1.2 → 2.0.2

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/README.md +2 -0
  4. data/app/controllers/foreman_tasks/api/tasks_controller.rb +33 -1
  5. data/app/controllers/foreman_tasks/tasks_controller.rb +9 -14
  6. data/app/lib/actions/proxy_action.rb +1 -1
  7. data/app/models/foreman_tasks/task.rb +4 -0
  8. data/app/models/foreman_tasks/task/dynflow_task.rb +3 -1
  9. data/app/models/setting/foreman_tasks.rb +1 -1
  10. data/app/services/ui_notifications/tasks/task_bulk_stop.rb +36 -0
  11. data/app/views/foreman_tasks/api/tasks/details.json.rabl +0 -1
  12. data/app/views/foreman_tasks/api/tasks/show.json.rabl +2 -0
  13. data/config/routes.rb +1 -0
  14. data/db/migrate/20200517215015_rename_bookmarks_controller.rb +35 -0
  15. data/db/migrate/20200519093217_drop_dynflow_allow_dangerous_actions_setting.foreman_tasks.rb +5 -0
  16. data/db/migrate/20200611090846_add_task_lock_index_on_resource_type_and_task_id.rb +3 -3
  17. data/db/seeds.d/30-notification_blueprints.rb +7 -0
  18. data/lib/foreman_tasks/engine.rb +2 -7
  19. data/lib/foreman_tasks/version.rb +1 -1
  20. data/locale/action_names.rb +1 -1
  21. data/locale/en/LC_MESSAGES/foreman_tasks.mo +0 -0
  22. data/locale/en/foreman_tasks.po +270 -54
  23. data/locale/foreman_tasks.pot +630 -292
  24. data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
  25. data/locale/fr/foreman_tasks.po +817 -0
  26. data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
  27. data/locale/ja/foreman_tasks.po +817 -0
  28. data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
  29. data/locale/zh_CN/foreman_tasks.po +816 -0
  30. data/package.json +1 -0
  31. data/script/npm_link_foreman_js.sh +26 -0
  32. data/webpack/ForemanTasks/Components/TaskActions/TaskAction.test.js +60 -0
  33. data/webpack/ForemanTasks/Components/{TasksTable/TasksTableActionHelpers.js → TaskActions/TaskActionHelpers.js} +21 -6
  34. data/webpack/ForemanTasks/Components/{TasksTable/__tests__/TasksTableActionHelpers.test.js → TaskActions/TaskActionHelpers.test.js} +2 -2
  35. data/webpack/ForemanTasks/Components/TaskActions/TaskActionsConstants.js +16 -0
  36. data/webpack/ForemanTasks/Components/TaskActions/UnlockModals.js +60 -0
  37. data/webpack/ForemanTasks/Components/TaskActions/UnlockModals.test.js +14 -0
  38. data/webpack/ForemanTasks/Components/TaskActions/__snapshots__/TaskAction.test.js.snap +233 -0
  39. data/webpack/ForemanTasks/Components/TaskActions/__snapshots__/UnlockModals.test.js.snap +25 -0
  40. data/webpack/ForemanTasks/Components/TaskActions/index.js +115 -0
  41. data/webpack/ForemanTasks/Components/TaskDetails/Components/RunningSteps.js +17 -3
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +149 -167
  43. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/RunningSteps.test.js +8 -1
  44. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +68 -3
  45. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +0 -1
  46. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/RunningSteps.test.js.snap +1 -1
  47. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +124 -76
  48. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +35 -5
  49. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +3 -14
  50. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +40 -16
  51. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsConstants.js +3 -4
  52. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsReducer.js +0 -6
  53. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +4 -10
  54. data/webpack/ForemanTasks/Components/TaskDetails/TasksDetailsHelper.js +6 -1
  55. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.fixtures.js +8 -0
  56. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +7 -1
  57. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsActions.test.js +18 -2
  58. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +30 -13
  59. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetailsActions.test.js.snap +91 -0
  60. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/integration.test.js +13 -4
  61. data/webpack/ForemanTasks/Components/TaskDetails/index.js +6 -8
  62. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardActions.js +1 -1
  63. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardActions.test.js +2 -2
  64. data/webpack/ForemanTasks/Components/TasksTable/Components/ActionSelectButton.js +14 -1
  65. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModal.js +83 -0
  66. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalActions.js +106 -0
  67. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalReducer.js +38 -0
  68. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js +46 -0
  69. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModal.test.js +36 -0
  70. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalActions.test.js +205 -0
  71. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalReducer.test.js +27 -0
  72. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js +55 -0
  73. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModal.test.js.snap +41 -0
  74. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalReducer.test.js.snap +19 -0
  75. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap +32 -0
  76. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js +29 -0
  77. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/ActionSelectButton.test.js +1 -0
  78. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/ActionSelectButton.test.js.snap +11 -0
  79. data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +135 -35
  80. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +13 -9
  81. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +26 -66
  82. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +10 -12
  83. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +30 -96
  84. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSchema.js +2 -2
  85. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +8 -4
  86. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksBulkActions.test.js +50 -2
  87. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +3 -12
  88. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableActions.test.js +22 -26
  89. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTablePage.test.js +2 -1
  90. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +3 -14
  91. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksBulkActions.test.js.snap +155 -0
  92. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +3 -14
  93. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableActions.test.js.snap +17 -124
  94. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +63 -133
  95. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionCellFormatter.test.js.snap +1 -0
  96. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionCellFormatter.test.js.snap +2 -0
  97. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/actionCellFormatter.test.js +1 -1
  98. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionCellFormatter.test.js +1 -1
  99. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionCellFormatter.js +10 -7
  100. data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionCellFormatter.js +7 -0
  101. data/webpack/ForemanTasks/Components/TasksTable/index.js +2 -2
  102. data/webpack/ForemanTasks/Components/common/ActionButtons/ActionButton.js +55 -19
  103. data/webpack/ForemanTasks/Components/common/ActionButtons/ActionButton.test.js +75 -19
  104. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/ActionButton.test.js.snap +88 -21
  105. data/webpack/ForemanTasks/Components/common/ClickConfirmation/ClickConfirmation.scss +9 -0
  106. data/webpack/ForemanTasks/Components/common/ClickConfirmation/ClickConfirmation.test.js +44 -0
  107. data/webpack/ForemanTasks/Components/common/ClickConfirmation/__snapshots__/ClickConfirmation.test.js.snap +52 -0
  108. data/webpack/ForemanTasks/Components/common/ClickConfirmation/index.js +59 -66
  109. data/webpack/ForemanTasks/Components/common/{ToastTypesConstants.js → ToastsHelpers/ToastTypesConstants.js} +0 -0
  110. data/webpack/ForemanTasks/Components/common/ToastsHelpers/index.js +15 -0
  111. data/webpack/ForemanTasks/ForemanTasksReducers.js +2 -0
  112. data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.test.js +2 -1
  113. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +2 -2
  114. data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +17 -3
  115. data/webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js +3 -0
  116. data/webpack/__mocks__/foremanReact/{API.js → redux/API.js} +1 -1
  117. metadata +41 -21
  118. data/webpack/ForemanTasks/Components/TasksTable/Components/CancelConfirm.js +0 -53
  119. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmationModals.js +0 -56
  120. data/webpack/ForemanTasks/Components/TasksTable/Components/ResumeConfirm.js +0 -52
  121. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/CancelConfirm.test.js +0 -26
  122. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/ConfirmationModals.test.js +0 -24
  123. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/ResumeConfirm.test.js +0 -26
  124. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/CancelConfirm.test.js.snap +0 -65
  125. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/ConfirmationModals.test.js.snap +0 -30
  126. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/ResumeConfirm.test.js.snap +0 -63
  127. data/webpack/ForemanTasks/Components/common/ActionButtons/CancelButton.js +0 -23
  128. data/webpack/ForemanTasks/Components/common/ActionButtons/CancelButton.test.js +0 -26
  129. data/webpack/ForemanTasks/Components/common/ActionButtons/ResumeButton.js +0 -23
  130. data/webpack/ForemanTasks/Components/common/ActionButtons/ResumeButton.test.js +0 -27
  131. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/CancelButton.test.js.snap +0 -15
  132. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/ResumeButton.test.js.snap +0 -15
@@ -0,0 +1,205 @@
1
+ import taskActions from '../ConfirmModalActions';
2
+ import {
3
+ CANCEL_MODAL,
4
+ RESUME_MODAL,
5
+ CANCEL_SELECTED_MODAL,
6
+ RESUME_SELECTED_MODAL,
7
+ FORCE_UNLOCK_SELECTED_MODAL,
8
+ } from '../../../TasksTableConstants';
9
+
10
+ import { FORCE_UNLOCK_MODAL } from '../../../../TaskActions/TaskActionsConstants';
11
+
12
+ import {
13
+ resumeTask,
14
+ cancelTask,
15
+ forceCancelTask,
16
+ } from '../../../TasksTableActions';
17
+ import {
18
+ bulkCancelBySearch,
19
+ bulkCancelById,
20
+ bulkResumeBySearch,
21
+ bulkResumeById,
22
+ bulkForceCancelBySearch,
23
+ bulkForceCancelById,
24
+ } from '../../../TasksBulkActions';
25
+
26
+ jest.mock('../../../TasksBulkActions');
27
+ jest.mock('../../../TasksTableActions');
28
+
29
+ const bulkCancelBySearchMock = 'bulkCancelBySearchMock';
30
+ const bulkCancelByIdMock = 'bulkCancelByIdMock';
31
+ const bulkResumeBySearchMock = 'bulkResumeBySearchMock';
32
+ const bulkResumeByIdMock = 'bulkResumeByIdMock';
33
+ const bulkForceCancelBySearchMock = 'bulkForceCancelBySearchMock';
34
+ const bulkForceCancelByIdMock = 'bulkForceCancelByIdMock';
35
+ const resumeTaskMock = 'resumeTaskMock';
36
+ const cancelTaskMock = 'cancelTaskMock';
37
+ const forceCancelTaskMock = 'forceCancelTaskMock';
38
+
39
+ bulkCancelBySearch.mockImplementation(() => bulkCancelBySearchMock);
40
+ bulkCancelById.mockImplementation(() => bulkCancelByIdMock);
41
+ bulkResumeBySearch.mockImplementation(() => bulkResumeBySearchMock);
42
+ bulkResumeById.mockImplementation(() => bulkResumeByIdMock);
43
+ bulkForceCancelBySearch.mockImplementation(() => bulkForceCancelBySearchMock);
44
+ bulkForceCancelById.mockImplementation(() => bulkForceCancelByIdMock);
45
+ resumeTask.mockImplementation(() => resumeTaskMock);
46
+ cancelTask.mockImplementation(() => cancelTaskMock);
47
+ forceCancelTask.mockImplementation(() => forceCancelTaskMock);
48
+
49
+ const url = 'some-url';
50
+ const query = 'some-query';
51
+ const parentTaskID = 'some-parentTaskID';
52
+
53
+ const runWithGetState = (state, action, dispatch, ...params) => {
54
+ const getState = () => state;
55
+ action(...params)(dispatch, getState);
56
+ };
57
+
58
+ const clickedState = {
59
+ foremanTasks: {
60
+ tasksTable: {
61
+ tasksTableQuery: {
62
+ clicked: { taskId: 'some-id', taskName: 'some-name' },
63
+ },
64
+ },
65
+ },
66
+ };
67
+
68
+ const selectedState = {
69
+ foremanTasks: {
70
+ tasksTable: {
71
+ tasksTableQuery: {
72
+ allRowsSelected: false,
73
+ selectedRows: [1, 2, 3],
74
+ },
75
+ tasksTableContent: {
76
+ results: [
77
+ {
78
+ id: 1,
79
+ action: 'action1',
80
+ available_actions: { cancellable: true },
81
+ },
82
+ {
83
+ id: 2,
84
+ action: 'action2',
85
+ available_actions: { resumable: true },
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ },
91
+ };
92
+ const allRowsState = {
93
+ foremanTasks: {
94
+ tasksTable: {
95
+ tasksTableQuery: {
96
+ allRowsSelected: true,
97
+ },
98
+ },
99
+ },
100
+ };
101
+ describe('ConfirmModalActions', () => {
102
+ const dispatch = jest.fn();
103
+
104
+ beforeEach(() => dispatch.mockClear());
105
+ it('run CANCEL_MODAL', () => {
106
+ runWithGetState(clickedState, taskActions[CANCEL_MODAL], dispatch, {
107
+ url,
108
+ parentTaskID,
109
+ });
110
+ expect(dispatch).toBeCalledWith(cancelTaskMock);
111
+ });
112
+ it('run RESUME_MODAL', () => {
113
+ runWithGetState(clickedState, taskActions[RESUME_MODAL], dispatch, {
114
+ url,
115
+ parentTaskID,
116
+ });
117
+ expect(dispatch).toBeCalledWith(resumeTaskMock);
118
+ });
119
+ it('run FORCE_UNLOCK_MODAL', () => {
120
+ runWithGetState(clickedState, taskActions[FORCE_UNLOCK_MODAL], dispatch, {
121
+ url,
122
+ parentTaskID,
123
+ });
124
+ expect(dispatch).toBeCalledWith(forceCancelTaskMock);
125
+ });
126
+ it('run CANCEL_SELECTED_MODAL by id', () => {
127
+ runWithGetState(
128
+ selectedState,
129
+ taskActions[CANCEL_SELECTED_MODAL],
130
+ dispatch,
131
+ {
132
+ url,
133
+ query,
134
+ parentTaskID,
135
+ }
136
+ );
137
+ expect(dispatch).toBeCalledWith(bulkCancelByIdMock);
138
+ });
139
+
140
+ it('run CANCEL_SELECTED_MODAL by search', () => {
141
+ runWithGetState(
142
+ allRowsState,
143
+ taskActions[CANCEL_SELECTED_MODAL],
144
+ dispatch,
145
+ {
146
+ url,
147
+ query,
148
+ parentTaskID,
149
+ }
150
+ );
151
+ expect(dispatch).toBeCalledWith(bulkCancelBySearchMock);
152
+ });
153
+ it('run RESUME_SELECTED_MODAL by id', () => {
154
+ runWithGetState(
155
+ selectedState,
156
+ taskActions[RESUME_SELECTED_MODAL],
157
+ dispatch,
158
+ {
159
+ url,
160
+ query,
161
+ parentTaskID,
162
+ }
163
+ );
164
+ expect(dispatch).toBeCalledWith(bulkResumeByIdMock);
165
+ });
166
+ it('run RESUME_SELECTED_MODAL by search', () => {
167
+ runWithGetState(
168
+ allRowsState,
169
+ taskActions[RESUME_SELECTED_MODAL],
170
+ dispatch,
171
+ {
172
+ url,
173
+ query,
174
+ parentTaskID,
175
+ }
176
+ );
177
+ expect(dispatch).toBeCalledWith(bulkResumeBySearchMock);
178
+ });
179
+ it('run FORCE_UNLOCK_SELECTED_MODAL by id', () => {
180
+ runWithGetState(
181
+ selectedState,
182
+ taskActions[FORCE_UNLOCK_SELECTED_MODAL],
183
+ dispatch,
184
+ {
185
+ url,
186
+ query,
187
+ parentTaskID,
188
+ }
189
+ );
190
+ expect(dispatch).toBeCalledWith(bulkForceCancelByIdMock);
191
+ });
192
+ it('run FORCE_UNLOCK_SELECTED_MODAL by search', () => {
193
+ runWithGetState(
194
+ allRowsState,
195
+ taskActions[FORCE_UNLOCK_SELECTED_MODAL],
196
+ dispatch,
197
+ {
198
+ url,
199
+ query,
200
+ parentTaskID,
201
+ }
202
+ );
203
+ expect(dispatch).toBeCalledWith(bulkForceCancelBySearchMock);
204
+ });
205
+ });
@@ -0,0 +1,27 @@
1
+ import { testReducerSnapshotWithFixtures } from '@theforeman/test';
2
+ import {
3
+ UPDATE_MODAL,
4
+ CANCEL_MODAL,
5
+ RESUME_SELECTED_MODAL,
6
+ } from '../../../TasksTableConstants';
7
+
8
+ import reducer from '../ConfirmModalReducer';
9
+
10
+ const fixtures = {
11
+ 'should return the initial state': {},
12
+ 'should handle UPDATE_MODAL to cancel': {
13
+ action: {
14
+ type: UPDATE_MODAL,
15
+ payload: { modalID: CANCEL_MODAL },
16
+ },
17
+ },
18
+ 'should handle UPDATE_MODAL to resume': {
19
+ action: {
20
+ type: UPDATE_MODAL,
21
+ payload: { modalID: RESUME_SELECTED_MODAL },
22
+ },
23
+ },
24
+ };
25
+
26
+ describe('ConfirmModalReducer reducer', () =>
27
+ testReducerSnapshotWithFixtures(reducer, fixtures));
@@ -0,0 +1,55 @@
1
+ import { testSelectorsSnapshotWithFixtures } from '@theforeman/test';
2
+ import {
3
+ selectActionText,
4
+ selectActionState,
5
+ selectActionType,
6
+ selectSelectedTasks,
7
+ selectSelectedRowsLen,
8
+ } from '../ConfirmModalSelectors';
9
+ import { CANCEL_MODAL } from '../../../TasksTableConstants';
10
+
11
+ const state = {
12
+ foremanTasks: {
13
+ confirmModal: {
14
+ actionText: 'some-text',
15
+ actionState: 'some-state',
16
+ actionType: 'some-type',
17
+ },
18
+ tasksTable: {
19
+ tasksTableContent: {
20
+ results: [
21
+ {
22
+ id: 1,
23
+ action: 'action1',
24
+ available_actions: { cancellable: true },
25
+ can_edit: true,
26
+ },
27
+ { id: 2, action: 'action2', available_actions: { resumable: true } },
28
+ ],
29
+ itemCount: 10,
30
+ },
31
+ tasksTableQuery: { selectedRows: [1, 2, 3] },
32
+ },
33
+ },
34
+ };
35
+
36
+ const fixtures = {
37
+ 'should select actionText': () => selectActionText(state),
38
+ 'should select actionState': () => selectActionState(state),
39
+ 'should select actionType': () => selectActionType(state),
40
+ 'should select selectedTasks': () => selectSelectedTasks(state),
41
+ 'should select selectedRowsLen 1': () =>
42
+ selectSelectedRowsLen({
43
+ ...state,
44
+ foremanTasks: { confirmModal: { actionType: CANCEL_MODAL } },
45
+ }),
46
+ 'should select selectedRowsLen all': () => selectSelectedRowsLen(state),
47
+ 'should select selectedRowsLen some': () =>
48
+ selectSelectedRowsLen({
49
+ ...state,
50
+ tasksTable: { tasksTableQuery: { allRowsSelected: true } },
51
+ }),
52
+ };
53
+
54
+ describe('TasksDashboard - Selectors', () =>
55
+ testSelectorsSnapshotWithFixtures(fixtures));
@@ -0,0 +1,41 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ConfirmModal renders ConfirmModal 1`] = `
4
+ <ForemanModal
5
+ id="modalID"
6
+ title="Some Text Selected Tasks"
7
+ >
8
+ This will some text 1 task(s), putting them in the some state state. Are you sure?
9
+ <Component>
10
+ <Button
11
+ active={false}
12
+ block={false}
13
+ bsClass="btn"
14
+ bsStyle="default"
15
+ disabled={false}
16
+ onClick={[MockFunction]}
17
+ >
18
+ No
19
+ </Button>
20
+ <Button
21
+ active={false}
22
+ block={false}
23
+ bsClass="btn"
24
+ bsStyle="primary"
25
+ className="confirm-button"
26
+ disabled={false}
27
+ onClick={[Function]}
28
+ >
29
+ Yes
30
+ </Button>
31
+ </Component>
32
+ </ForemanModal>
33
+ `;
34
+
35
+ exports[`ConfirmModal renders ConfirmModal for unlock 1`] = `
36
+ <ForceUnlockModal
37
+ id="modalID"
38
+ onClick={[Function]}
39
+ selectedRowsLen={1}
40
+ />
41
+ `;
@@ -0,0 +1,19 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ConfirmModalReducer reducer should handle UPDATE_MODAL to cancel 1`] = `
4
+ Object {
5
+ "actionState": "stopped",
6
+ "actionText": "cancel",
7
+ "actionType": "cancelConfirmModal",
8
+ }
9
+ `;
10
+
11
+ exports[`ConfirmModalReducer reducer should handle UPDATE_MODAL to resume 1`] = `
12
+ Object {
13
+ "actionState": "running",
14
+ "actionText": "resume",
15
+ "actionType": "resumeSelectedConfirmModal",
16
+ }
17
+ `;
18
+
19
+ exports[`ConfirmModalReducer reducer should return the initial state 1`] = `Object {}`;
@@ -0,0 +1,32 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`TasksDashboard - Selectors should select actionState 1`] = `"some-state"`;
4
+
5
+ exports[`TasksDashboard - Selectors should select actionText 1`] = `"some-text"`;
6
+
7
+ exports[`TasksDashboard - Selectors should select actionType 1`] = `"some-type"`;
8
+
9
+ exports[`TasksDashboard - Selectors should select selectedRowsLen 1 1`] = `1`;
10
+
11
+ exports[`TasksDashboard - Selectors should select selectedRowsLen all 1`] = `3`;
12
+
13
+ exports[`TasksDashboard - Selectors should select selectedRowsLen some 1`] = `3`;
14
+
15
+ exports[`TasksDashboard - Selectors should select selectedTasks 1`] = `
16
+ Array [
17
+ Object {
18
+ "canEdit": true,
19
+ "id": 1,
20
+ "isCancellable": true,
21
+ "isResumable": undefined,
22
+ "name": "action1",
23
+ },
24
+ Object {
25
+ "canEdit": undefined,
26
+ "id": 2,
27
+ "isCancellable": undefined,
28
+ "isResumable": true,
29
+ "name": "action2",
30
+ },
31
+ ]
32
+ `;
@@ -0,0 +1,29 @@
1
+ import { connect } from 'react-redux';
2
+ import { bindActionCreators } from 'redux';
3
+ import { ConfirmModal } from './ConfirmModal';
4
+ import reducer from './ConfirmModalReducer';
5
+ import tasksActions from './ConfirmModalActions';
6
+ import {
7
+ selectActionText,
8
+ selectActionState,
9
+ selectActionType,
10
+ selectClicked,
11
+ selectSelectedRowsLen,
12
+ } from './ConfirmModalSelectors';
13
+ import { selectAllRowsSelected } from '../../TasksTableSelectors';
14
+
15
+ const mapStateToProps = state => ({
16
+ actionText: selectActionText(state),
17
+ actionType: selectActionType(state),
18
+ actionState: selectActionState(state),
19
+ allRowsSelected: selectAllRowsSelected(state),
20
+ clicked: selectClicked(state),
21
+ selectedRowsLen: selectSelectedRowsLen(state),
22
+ });
23
+
24
+ const mapDispatchToProps = dispatch =>
25
+ bindActionCreators(tasksActions, dispatch);
26
+
27
+ export const reducers = { confirmModal: reducer };
28
+
29
+ export default connect(mapStateToProps, mapDispatchToProps)(ConfirmModal);
@@ -6,6 +6,7 @@ const fixtures = {
6
6
  'renders with minimal props': {
7
7
  onCancel: jest.fn(),
8
8
  onResume: jest.fn(),
9
+ onForceCancel: jest.fn(),
9
10
  },
10
11
  };
11
12
 
@@ -28,5 +28,16 @@ exports[`ActionSelectButton renders with minimal props 1`] = `
28
28
  >
29
29
  Resume Selected
30
30
  </MenuItem>
31
+ <MenuItem
32
+ bsClass="dropdown"
33
+ disabled={false}
34
+ divider={false}
35
+ eventKey="3"
36
+ header={false}
37
+ onClick={[MockFunction]}
38
+ title="Force Cancel selected tasks"
39
+ >
40
+ Force Cancel Selected
41
+ </MenuItem>
31
42
  </DropdownButton>
32
43
  `;
@@ -1,7 +1,11 @@
1
- import API from 'foremanReact/API';
1
+ import { API } from 'foremanReact/redux/API';
2
2
  import { addToast } from 'foremanReact/redux/actions/toasts';
3
- import { translate as __ } from 'foremanReact/common/I18n';
4
- import { TOAST_TYPES } from '../common/ToastTypesConstants';
3
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
4
+ import {
5
+ BULK_CANCEL_PATH,
6
+ BULK_RESUME_PATH,
7
+ BULK_FORCE_CANCEL_PATH,
8
+ } from './TasksTableConstants';
5
9
  import {
6
10
  TASKS_RESUME_REQUEST,
7
11
  TASKS_RESUME_SUCCESS,
@@ -9,16 +13,23 @@ import {
9
13
  TASKS_CANCEL_REQUEST,
10
14
  TASKS_CANCEL_SUCCESS,
11
15
  TASKS_CANCEL_FAILURE,
12
- BULK_CANCEL_PATH,
13
- BULK_RESUME_PATH,
14
- } from './TasksTableConstants';
16
+ TASKS_FORCE_CANCEL_REQUEST,
17
+ TASKS_FORCE_CANCEL_SUCCESS,
18
+ TASKS_FORCE_CANCEL_FAILURE,
19
+ } from '../TaskActions/TaskActionsConstants';
15
20
  import { reloadPage } from './TasksTableActions';
16
21
  import {
17
22
  convertDashboardQuery,
18
23
  resumeToastInfo,
19
24
  cancelToastInfo,
20
25
  toastDispatch,
21
- } from './TasksTableActionHelpers';
26
+ } from '../TaskActions/TaskActionHelpers';
27
+ import {
28
+ successToastData,
29
+ errorToastData,
30
+ warningToastData,
31
+ infoToastData,
32
+ } from '../common/ToastsHelpers';
22
33
 
23
34
  export const bulkByIdRequest = (resumeTasks, path) => {
24
35
  const ids = resumeTasks.map(task => task.id);
@@ -41,10 +52,9 @@ export const bulkBySearchRequest = ({ query, parentTaskID, path }) => {
41
52
  const handleErrorResume = (error, dispatch) => {
42
53
  dispatch({ type: TASKS_RESUME_FAILURE, error });
43
54
  dispatch(
44
- addToast({
45
- type: TOAST_TYPES.ERROR,
46
- message: `${__(`Cannot resume tasks at the moment`)} ${error}`,
47
- })
55
+ addToast(
56
+ errorToastData(`${__(`Cannot resume tasks at the moment`)} ${error}`)
57
+ )
48
58
  );
49
59
  };
50
60
 
@@ -53,13 +63,12 @@ export const bulkResumeById = ({
53
63
  url,
54
64
  parentTaskID,
55
65
  }) => async dispatch => {
56
- const resumeTasks = selected.filter(task => task.isResumable);
66
+ const resumeTasks = selected.filter(task => task.isResumable && task.canEdit);
57
67
  if (resumeTasks.length < selected.length)
58
68
  dispatch(
59
- addToast({
60
- type: TOAST_TYPES.WARNING,
61
- message: __('Not all the selected tasks can be resumed'),
62
- })
69
+ addToast(
70
+ warningToastData(__('Not all the selected tasks can be resumed'))
71
+ )
63
72
  );
64
73
  if (resumeTasks.length) {
65
74
  dispatch({ type: TASKS_RESUME_REQUEST });
@@ -78,7 +87,7 @@ export const bulkResumeById = ({
78
87
  });
79
88
  });
80
89
  if (data.resumed) {
81
- reloadPage(url, parentTaskID, dispatch);
90
+ reloadPage(url, parentTaskID)(dispatch);
82
91
  }
83
92
  } catch (error) {
84
93
  handleErrorResume(error, dispatch);
@@ -92,10 +101,9 @@ export const bulkResumeBySearch = ({
92
101
  }) => async dispatch => {
93
102
  dispatch({ type: TASKS_RESUME_REQUEST });
94
103
  dispatch(
95
- addToast({
96
- type: 'info',
97
- message: __('Resuming selected tasks, this might take a while'),
98
- })
104
+ addToast(
105
+ infoToastData(__('Resuming selected tasks, this might take a while'))
106
+ )
99
107
  );
100
108
  await bulkBySearchRequest({ query, path: BULK_RESUME_PATH, parentTaskID });
101
109
  };
@@ -103,10 +111,9 @@ export const bulkResumeBySearch = ({
103
111
  const handleErrorCancel = (error, dispatch) => {
104
112
  dispatch({ type: TASKS_CANCEL_FAILURE, error });
105
113
  dispatch(
106
- addToast({
107
- type: TOAST_TYPES.ERROR,
108
- message: `${__(`Cannot cancel tasks at the moment`)} ${error}`,
109
- })
114
+ addToast(
115
+ errorToastData(`${__(`Cannot cancel tasks at the moment`)} ${error}`)
116
+ )
110
117
  );
111
118
  };
112
119
 
@@ -116,10 +123,9 @@ export const bulkCancelBySearch = ({
116
123
  }) => async dispatch => {
117
124
  dispatch({ type: TASKS_CANCEL_REQUEST });
118
125
  dispatch(
119
- addToast({
120
- type: 'info',
121
- message: __('Canceling selected tasks, this might take a while'),
122
- })
126
+ addToast(
127
+ infoToastData(__('Canceling selected tasks, this might take a while'))
128
+ )
123
129
  );
124
130
  await bulkBySearchRequest({ query, path: BULK_CANCEL_PATH, parentTaskID });
125
131
  };
@@ -129,13 +135,14 @@ export const bulkCancelById = ({
129
135
  url,
130
136
  parentTaskID,
131
137
  }) => async dispatch => {
132
- const cancelTasks = selected.filter(task => task.isCancellable);
138
+ const cancelTasks = selected.filter(
139
+ task => task.isCancellable && task.canEdit
140
+ );
133
141
  if (cancelTasks.length < selected.length)
134
142
  dispatch(
135
- addToast({
136
- type: TOAST_TYPES.WARNING,
137
- message: __('Not all the selected tasks can be cancelled'),
138
- })
143
+ addToast(
144
+ warningToastData(__('Not all the selected tasks can be cancelled'))
145
+ )
139
146
  );
140
147
  if (cancelTasks.length) {
141
148
  dispatch({ type: TASKS_CANCEL_REQUEST });
@@ -155,10 +162,103 @@ export const bulkCancelById = ({
155
162
  });
156
163
  });
157
164
  if (data.cancelled) {
158
- reloadPage(url, parentTaskID, dispatch);
165
+ reloadPage(url, parentTaskID)(dispatch);
159
166
  }
160
167
  } catch (error) {
161
168
  handleErrorCancel(error, dispatch);
162
169
  }
163
170
  }
164
171
  };
172
+
173
+ const handleErrorForceCancel = (error, dispatch) => {
174
+ dispatch({ type: TASKS_FORCE_CANCEL_FAILURE, error });
175
+ dispatch(
176
+ addToast(
177
+ errorToastData(
178
+ `${__(`Cannot force cancel tasks at the moment`)} ${error}`
179
+ )
180
+ )
181
+ );
182
+ };
183
+
184
+ export const bulkForceCancelById = ({
185
+ selected,
186
+ url,
187
+ parentTaskID,
188
+ }) => async dispatch => {
189
+ const stopTasks = selected.filter(task => task.state !== 'stopped');
190
+ const authorisedTasks = stopTasks.filter(task => task.canEdit);
191
+ if (authorisedTasks.length < stopTasks.length)
192
+ dispatch(
193
+ addToast(
194
+ warningToastData(
195
+ sprintf(
196
+ 'User has no permission for %s task(s)',
197
+ stopTasks.length - authorisedTasks.length
198
+ )
199
+ )
200
+ )
201
+ );
202
+ if (stopTasks.length < selected.length)
203
+ dispatch(
204
+ addToast(
205
+ warningToastData(
206
+ sprintf(
207
+ '%s task(s) are already stopped',
208
+ selected.length - stopTasks.length
209
+ )
210
+ )
211
+ )
212
+ );
213
+ if (authorisedTasks.length > 0) {
214
+ dispatch({ type: TASKS_FORCE_CANCEL_REQUEST });
215
+ try {
216
+ const { data } = await bulkByIdRequest(
217
+ authorisedTasks,
218
+ BULK_FORCE_CANCEL_PATH
219
+ );
220
+ dispatch({ type: TASKS_FORCE_CANCEL_SUCCESS });
221
+ if (data.stopped_length) {
222
+ dispatch(
223
+ addToast(
224
+ successToastData(
225
+ sprintf('%s task(s) cancelled with force', data.stopped_length)
226
+ )
227
+ )
228
+ );
229
+ }
230
+ if (data.skipped_length > 0)
231
+ dispatch(
232
+ addToast(
233
+ warningToastData(
234
+ sprintf('%s task(s) are already stopped', data.skipped_length)
235
+ )
236
+ )
237
+ );
238
+ if (data.stopped_length > 0) {
239
+ reloadPage(url, parentTaskID)(dispatch);
240
+ }
241
+ } catch (error) {
242
+ handleErrorForceCancel(error, dispatch);
243
+ }
244
+ }
245
+ };
246
+
247
+ export const bulkForceCancelBySearch = ({
248
+ query,
249
+ parentTaskID,
250
+ }) => async dispatch => {
251
+ dispatch({ type: TASKS_FORCE_CANCEL_REQUEST });
252
+ dispatch(
253
+ addToast(
254
+ infoToastData(
255
+ __('Canceling with force selected tasks, this might take a while')
256
+ )
257
+ )
258
+ );
259
+ await bulkBySearchRequest({
260
+ query,
261
+ path: BULK_FORCE_CANCEL_PATH,
262
+ parentTaskID,
263
+ });
264
+ };