foreman-tasks 12.0.0 → 12.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_tests.yml +1 -0
  3. data/app/assets/javascripts/foreman-tasks/locale/de/foreman_tasks.js +58 -22
  4. data/app/assets/javascripts/foreman-tasks/locale/en/foreman_tasks.js +56 -20
  5. data/app/assets/javascripts/foreman-tasks/locale/es/foreman_tasks.js +59 -23
  6. data/app/assets/javascripts/foreman-tasks/locale/fr/foreman_tasks.js +61 -25
  7. data/app/assets/javascripts/foreman-tasks/locale/ja/foreman_tasks.js +61 -25
  8. data/app/assets/javascripts/foreman-tasks/locale/ka/foreman_tasks.js +60 -24
  9. data/app/assets/javascripts/foreman-tasks/locale/ko/foreman_tasks.js +61 -25
  10. data/app/assets/javascripts/foreman-tasks/locale/pt_BR/foreman_tasks.js +59 -23
  11. data/app/assets/javascripts/foreman-tasks/locale/ru/foreman_tasks.js +58 -22
  12. data/app/assets/javascripts/foreman-tasks/locale/zh_CN/foreman_tasks.js +61 -25
  13. data/app/assets/javascripts/foreman-tasks/locale/zh_TW/foreman_tasks.js +57 -21
  14. data/app/controllers/foreman_tasks/api/tasks_controller.rb +4 -19
  15. data/app/controllers/foreman_tasks/tasks_controller.rb +4 -5
  16. data/app/models/foreman_tasks/task.rb +1 -1
  17. data/app/views/foreman_tasks/api/tasks/dependency_summary.json.rabl +1 -3
  18. data/app/views/foreman_tasks/api/tasks/show.json.rabl +4 -1
  19. data/config/routes.rb +3 -3
  20. data/foreman-tasks.gemspec +3 -1
  21. data/lib/foreman_tasks/engine.rb +1 -1
  22. data/lib/foreman_tasks/tasks/export_tasks.rake +1 -1
  23. data/lib/foreman_tasks/version.rb +1 -1
  24. data/locale/de/LC_MESSAGES/foreman_tasks.mo +0 -0
  25. data/locale/de/foreman_tasks.po +59 -23
  26. data/locale/en/LC_MESSAGES/foreman_tasks.mo +0 -0
  27. data/locale/en/foreman_tasks.po +56 -20
  28. data/locale/es/LC_MESSAGES/foreman_tasks.mo +0 -0
  29. data/locale/es/foreman_tasks.po +59 -23
  30. data/locale/foreman_tasks.pot +173 -100
  31. data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
  32. data/locale/fr/foreman_tasks.po +61 -25
  33. data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
  34. data/locale/ja/foreman_tasks.po +61 -25
  35. data/locale/ka/LC_MESSAGES/foreman_tasks.mo +0 -0
  36. data/locale/ka/foreman_tasks.po +60 -24
  37. data/locale/ko/LC_MESSAGES/foreman_tasks.mo +0 -0
  38. data/locale/ko/foreman_tasks.po +61 -25
  39. data/locale/pt_BR/LC_MESSAGES/foreman_tasks.mo +0 -0
  40. data/locale/pt_BR/foreman_tasks.po +59 -23
  41. data/locale/ru/LC_MESSAGES/foreman_tasks.mo +0 -0
  42. data/locale/ru/foreman_tasks.po +58 -22
  43. data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
  44. data/locale/zh_CN/foreman_tasks.po +61 -25
  45. data/locale/zh_TW/LC_MESSAGES/foreman_tasks.mo +0 -0
  46. data/locale/zh_TW/foreman_tasks.po +57 -21
  47. data/test/controllers/api/tasks_controller_test.rb +29 -3
  48. data/test/controllers/tasks_controller_test.rb +46 -2
  49. data/webpack/ForemanTasks/Components/TaskActions/TaskAction.test.js +8 -0
  50. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +8 -2
  51. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.test.js +25 -33
  52. data/webpack/ForemanTasks/Components/TaskActions/index.js +24 -3
  53. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +6 -6
  54. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsActions.test.js +9 -0
  55. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetailsActions.test.js.snap +16 -11
  56. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/TasksTimeRow.js +2 -3
  57. data/webpack/ForemanTasks/Components/TasksDashboard/Components/TasksTimeRow/__snapshots__/TasksTimeRow.test.js.snap +4 -8
  58. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboard.js +2 -4
  59. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboard.scss +0 -3
  60. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboard.test.js.snap +2 -5
  61. data/webpack/ForemanTasks/Components/{common/ActionButtons/ActionButton.js → TasksTable/Components/CellActionButton.js} +36 -21
  62. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/index.test.js +32 -29
  63. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createBulkTaskModal.js +14 -18
  64. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createTaskModal.js +13 -15
  65. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js +7 -7
  66. data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +16 -23
  67. data/webpack/ForemanTasks/Components/TasksTable/TasksColumns.js +56 -0
  68. data/webpack/ForemanTasks/Components/TasksTable/TasksIndexPage.js +277 -3
  69. data/webpack/ForemanTasks/Components/TasksTable/TasksModals.js +96 -0
  70. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +10 -18
  71. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +3 -3
  72. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksBulkActions.test.js +130 -63
  73. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksIndexPage.test.js +315 -8
  74. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +214 -41
  75. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +20 -12
  76. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksBulkActions.test.js.snap +18 -35
  77. data/webpack/ForemanTasks/ForemanTasksReducers.js +0 -4
  78. data/webpack/Routes/routes.js +22 -0
  79. data/webpack/Routes/routes.test.js +95 -0
  80. data/webpack/global_index.js +10 -0
  81. data/webpack/index.js +0 -18
  82. data/webpack/test_setup.js +1 -0
  83. metadata +15 -89
  84. data/app/controllers/foreman_tasks/react_controller.rb +0 -17
  85. data/app/views/foreman_tasks/layouts/react.html.erb +0 -13
  86. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js +0 -30
  87. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js +0 -66
  88. data/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap +0 -33
  89. data/webpack/ForemanTasks/Components/TasksTable/Components/SelectAllAlert.js +0 -49
  90. data/webpack/ForemanTasks/Components/TasksTable/Components/TableSelectionCell.js +0 -32
  91. data/webpack/ForemanTasks/Components/TasksTable/Components/TableSelectionHeaderCell.js +0 -38
  92. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/SelectAllAlert.test.js +0 -29
  93. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/TableSelectionCell.test.js +0 -15
  94. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/TableSelectionHeaderCell.test.js +0 -15
  95. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/SelectAllAlert.test.js.snap +0 -81
  96. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/TableSelectionCell.test.js.snap +0 -14
  97. data/webpack/ForemanTasks/Components/TasksTable/Components/__test__/__snapshots__/TableSelectionHeaderCell.test.js.snap +0 -15
  98. data/webpack/ForemanTasks/Components/TasksTable/SubTasksPage.js +0 -40
  99. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +0 -163
  100. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +0 -108
  101. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +0 -234
  102. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +0 -20
  103. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +0 -68
  104. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSchema.js +0 -85
  105. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +0 -63
  106. data/webpack/ForemanTasks/Components/TasksTable/__tests__/SubTasksPage.test.js +0 -20
  107. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.test.js +0 -9
  108. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableActions.test.js +0 -65
  109. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTablePage.test.js +0 -35
  110. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +0 -87
  111. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +0 -48
  112. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +0 -39
  113. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTable.test.js.snap +0 -52
  114. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableActions.test.js.snap +0 -40
  115. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +0 -368
  116. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +0 -116
  117. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionCellFormatter.test.js.snap +0 -15
  118. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionNameCellFormatter.test.js.snap +0 -10
  119. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/dateCellFormmatter.test.js.snap +0 -9
  120. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/durationCellFormmatter.test.js.snap +0 -18
  121. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionCellFormatter.test.js.snap +0 -12
  122. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionHeaderCellFormatter.test.js.snap +0 -11
  123. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/actionCellFormatter.test.js +0 -11
  124. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/actionNameCellFormatter.test.js +0 -8
  125. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/dateCellFormmatter.test.js +0 -7
  126. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/durationCellFormmatter.test.js +0 -12
  127. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionCellFormatter.test.js +0 -12
  128. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionHeaderCellFormatter.test.js +0 -12
  129. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionCellFormatter.js +0 -19
  130. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionNameCellFormatter.js +0 -9
  131. data/webpack/ForemanTasks/Components/TasksTable/formatters/dateCellFormmatter.js +0 -7
  132. data/webpack/ForemanTasks/Components/TasksTable/formatters/durationCellFormmatter.js +0 -7
  133. data/webpack/ForemanTasks/Components/TasksTable/formatters/index.js +0 -7
  134. data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionCellFormatter.js +0 -17
  135. data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionHeaderCellFormatter.js +0 -11
  136. data/webpack/ForemanTasks/Components/TasksTable/index.js +0 -42
  137. data/webpack/ForemanTasks/Components/common/ActionButtons/ActionButton.test.js +0 -101
  138. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/ActionButton.test.js.snap +0 -95
  139. data/webpack/ForemanTasks/ForemanTasks.js +0 -11
  140. data/webpack/ForemanTasks/ForemanTasks.test.js +0 -10
  141. data/webpack/ForemanTasks/Routes/ForemanTasksRouter.js +0 -14
  142. data/webpack/ForemanTasks/Routes/ForemanTasksRouter.test.js +0 -26
  143. data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.js +0 -23
  144. data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.test.js +0 -16
  145. data/webpack/ForemanTasks/Routes/__snapshots__/ForemanTasksRouter.test.js.snap +0 -16
  146. data/webpack/ForemanTasks/Routes/__snapshots__/ForemanTasksRoutes.test.js.snap +0 -37
  147. data/webpack/ForemanTasks/__snapshots__/ForemanTasks.test.js.snap +0 -7
  148. data/webpack/ForemanTasks/index.js +0 -1
  149. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -7
  150. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -6
  151. data/webpack/__mocks__/foremanReact/common/urlHelpers.js +0 -1
  152. data/webpack/__mocks__/foremanReact/components/Layout/LayoutActions.js +0 -2
  153. data/webpack/__mocks__/foremanReact/components/Pagination/index.js +0 -2
  154. data/webpack/__mocks__/foremanReact/components/ToastsList/index.js +0 -8
  155. data/webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js +0 -3
  156. data/webpack/__mocks__/foremanReact/components/common/MessageBox.js +0 -4
  157. data/webpack/__mocks__/foremanReact/components/common/dates/LongDateTime.js +0 -5
  158. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +0 -3
  159. data/webpack/__mocks__/foremanReact/components/common/table/actionsHelpers/actionTypeCreator.js +0 -7
  160. data/webpack/__mocks__/foremanReact/components/common/table.js +0 -5
  161. data/webpack/__mocks__/foremanReact/constants.js +0 -24
  162. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +0 -10
  163. data/webpack/__mocks__/foremanReact/redux/API/index.js +0 -10
  164. data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware.js +0 -5
  165. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +0 -10
  166. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js +0 -5
@@ -15,96 +15,87 @@ jest.mock('foremanReact/components/common/table', () => ({
15
15
 
16
16
  jest.mock('foremanReact/redux/API');
17
17
 
18
+ jest.mock('foremanReact/components/ToastsList', () => ({
19
+ addToast: toast => ({
20
+ type: 'TOASTS_ADD',
21
+ payload: {
22
+ message: toast,
23
+ },
24
+ }),
25
+ }));
26
+
18
27
  const task = {
19
28
  id: 'some-id',
20
29
  name: 'some-name',
21
- canEdit: true,
30
+ action: 'some-action',
31
+ can_edit: true,
22
32
  };
23
33
 
24
34
  const fixtures = {
25
35
  'handles bulkResumeById requests that fail': () => {
26
- const selected = [{ ...task, isResumable: true }];
36
+ const selected = [{ ...task, available_actions: { resumable: true } }];
27
37
 
28
38
  API.post.mockImplementation(() =>
29
39
  Promise.reject(new Error('Network Error'))
30
40
  );
31
- return bulkResumeById({ selected, url: 'some-url' });
32
- },
33
- 'handles resumable bulkResumeById requests': () => {
34
- const selected = [{ ...task, isResumable: true }];
35
-
36
- API.post.mockImplementation(() => ({
37
- data: {
38
- resumed: [{ action: 'I am resumed' }],
39
- failed: [{ action: 'I am failed' }],
40
- },
41
- }));
42
- return bulkResumeById({ selected, url: 'some-url' });
43
- },
44
- 'handles bulkCancelById requests': () => {
45
- const selected = [{ ...task, isCancellable: true }];
46
-
47
- API.post.mockImplementation(() => ({
48
- data: {
49
- cancelled: [{ action: 'I am cancelled' }],
50
- },
51
- }));
52
- return bulkCancelById({ selected, url: 'some-url' });
41
+ return bulkResumeById({ selected, reloadPage: jest.fn() });
53
42
  },
54
43
  'handles skipped bulkResumeById requests': () => {
55
- const selected = [{ ...task, isResumable: true }];
44
+ const selected = [{ ...task, available_actions: { resumable: true } }];
56
45
 
57
46
  API.post.mockImplementation(() => ({
58
47
  data: {
59
48
  skipped: [{ action: 'I am skipped' }],
60
49
  },
61
50
  }));
62
- return bulkResumeById({ selected, url: 'some-url' });
51
+ return bulkResumeById({ selected, reloadPage: jest.fn() });
63
52
  },
64
53
  'handles skipped bulkCancelById requests': () => {
65
- const selected = [{ ...task, isCancellable: true }];
66
-
54
+ const selected = [{ ...task, available_actions: { cancellable: true } }];
67
55
  API.post.mockImplementation(() => ({
68
56
  data: {
69
57
  skipped: [{ action: 'I am skipped' }],
70
58
  },
71
59
  }));
72
- return bulkCancelById({ selected, url: 'some-url' });
60
+ return bulkCancelById({ selected, reloadPage: jest.fn() });
73
61
  },
74
-
75
- 'handles bulkForceCancelById requests': () => {
76
- const selected = [{ ...task, isCancellable: true }];
77
-
78
- API.post.mockImplementation(() => ({
79
- data: {
80
- stopped_length: 2,
81
- skipped_length: 4,
82
- },
83
- }));
84
- return bulkForceCancelById({ selected, url: 'some-url' });
85
- },
86
-
87
62
  'handles bulkForceCancelById requests that fail': () => {
88
- const selected = [{ ...task, isResumable: true }];
63
+ const selected = [
64
+ {
65
+ ...task,
66
+ state: 'running',
67
+ can_edit: true,
68
+ },
69
+ ];
89
70
 
90
71
  API.post.mockImplementation(() =>
91
72
  Promise.reject(new Error('Network Error'))
92
73
  );
93
- return bulkForceCancelById({ selected, url: 'some-url' });
74
+ return bulkForceCancelById({
75
+ selected,
76
+ reloadPage: jest.fn(),
77
+ });
94
78
  },
95
79
 
96
80
  'handles bulkCancelById requests that are not cancellable': () => {
97
- const selected = [{ ...task, isCancellable: false }];
98
- return bulkCancelById({ selected, url: 'some-url' });
81
+ const selected = [{ ...task, available_actions: { cancellable: false } }];
82
+ return bulkCancelById({ selected, reloadPage: jest.fn() });
99
83
  },
100
84
  'handles bulkResumeById requests that are not resumable': () => {
101
- const selected = [{ ...task, isResumable: false, isCancellable: false }];
102
- return bulkResumeById({ selected, url: 'some-url' });
85
+ const selected = [
86
+ { ...task, available_actions: { resumable: false, cancellable: false } },
87
+ ];
88
+ return bulkResumeById({ selected, reloadPage: jest.fn() });
103
89
  },
104
90
 
105
91
  'handles bulkForceCancelById requests that are stopped': () => {
106
- const selected = [{ ...task, isResumable: false, state: 'stopped' }];
107
- return bulkForceCancelById({ selected, url: 'some-url' });
92
+ const selected = [
93
+ { ...task, available_actions: { resumable: false }, state: 'stopped' },
94
+ ];
95
+ return bulkForceCancelById({
96
+ selected,
97
+ reloadPage: jest.fn(),
98
+ });
108
99
  },
109
100
 
110
101
  'handles bulkCancelBySearch requests': () => {
@@ -115,8 +106,7 @@ const fixtures = {
115
106
  },
116
107
  }));
117
108
  return bulkCancelBySearch({
118
- query: { search: {} },
119
- url: 'some-url',
109
+ query: '',
120
110
  parentTaskID: 'parent',
121
111
  });
122
112
  },
@@ -130,31 +120,108 @@ const fixtures = {
130
120
  },
131
121
  }));
132
122
  return bulkResumeBySearch({
133
- query: { search: {} },
134
- url: 'some-url',
123
+ query: '',
135
124
  parentTaskID: 'parent',
136
125
  });
137
126
  },
138
127
  'handles bulkForceCancelBySearch requests': () =>
139
128
  bulkForceCancelBySearch({
140
- query: { search: {} },
141
- url: 'some-url',
129
+ query: '',
142
130
  parentTaskID: 'parent',
143
131
  }),
144
132
  'handles bulkCancelById requests with canEdit false': () => {
145
- const selected = [{ ...task, isCancellable: true, canEdit: false }];
146
- return bulkCancelById({ selected, url: 'some-url' });
133
+ const selected = [
134
+ {
135
+ ...task,
136
+ available_actions: { cancellable: true },
137
+ can_edit: false,
138
+ },
139
+ ];
140
+ return bulkCancelById({ selected, reloadPage: jest.fn() });
147
141
  },
148
142
  'handles bulkResumeById requests with canEdit false': () => {
149
- const selected = [{ ...task, isResumable: false, canEdit: false }];
150
- return bulkResumeById({ selected, url: 'some-url' });
143
+ const selected = [
144
+ {
145
+ ...task,
146
+ available_actions: { resumable: true },
147
+ can_edit: false,
148
+ },
149
+ ];
150
+ return bulkResumeById({ selected, reloadPage: jest.fn() });
151
151
  },
152
152
  'handles bulkForceCancelById requests with canEdit false': () => {
153
- const selected = [{ ...task, isResumable: false, canEdit: false }];
154
- return bulkForceCancelById({ selected, url: 'some-url' });
153
+ const selected = [
154
+ {
155
+ ...task,
156
+ state: 'running',
157
+ available_actions: { resumable: false },
158
+ can_edit: false,
159
+ },
160
+ ];
161
+ return bulkForceCancelById({ selected, reloadPage: jest.fn() });
155
162
  },
156
163
  };
157
164
 
158
- describe('TasksTable bulk actions', () => {
165
+ describe('TasksBulkActions', () => {
166
+ describe('Test reloadPage callback', () => {
167
+ it('handles resumable bulkResumeById requests', async () => {
168
+ const selected = [{ ...task, available_actions: { resumable: true } }];
169
+
170
+ API.post.mockImplementation(() => ({
171
+ data: {
172
+ resumed: [{ action: 'I am resumed' }],
173
+ failed: [{ action: 'I am failed' }],
174
+ },
175
+ }));
176
+ const reloadPage = jest.fn();
177
+ const action = bulkResumeById({
178
+ selected,
179
+ reloadPage,
180
+ });
181
+ const dispatch = jest.fn();
182
+ await action(dispatch);
183
+ expect(reloadPage).toHaveBeenCalled();
184
+ expect(dispatch.mock.calls).toMatchSnapshot();
185
+ });
186
+ it('handles bulkCancelById requests', async () => {
187
+ const selected = [{ ...task, available_actions: { cancellable: true } }];
188
+ API.post.mockImplementation(() => ({
189
+ data: {
190
+ cancelled: [{ action: 'I am cancelled' }],
191
+ },
192
+ }));
193
+ const reloadPage = jest.fn();
194
+ const action = bulkCancelById({ selected, reloadPage });
195
+ const dispatch = jest.fn();
196
+ await action(dispatch);
197
+ expect(reloadPage).toHaveBeenCalled();
198
+ expect(dispatch.mock.calls).toMatchSnapshot();
199
+ });
200
+ it('handles bulkForceCancelById requests', async () => {
201
+ const selected = [
202
+ {
203
+ ...task,
204
+ state: 'running',
205
+ can_edit: true,
206
+ },
207
+ ];
208
+
209
+ API.post.mockImplementation(() => ({
210
+ data: {
211
+ stopped_length: 2,
212
+ skipped_length: 4,
213
+ },
214
+ }));
215
+ const reloadPage = jest.fn();
216
+ const action = bulkForceCancelById({
217
+ selected,
218
+ reloadPage,
219
+ });
220
+ const dispatch = jest.fn();
221
+ await action(dispatch);
222
+ expect(reloadPage).toHaveBeenCalled();
223
+ expect(dispatch.mock.calls).toMatchSnapshot();
224
+ });
225
+ });
159
226
  testActionSnapshotWithFixtures(fixtures);
160
227
  });
@@ -1,12 +1,319 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
- import { TasksIndexPage } from '../TasksIndexPage';
3
- import { minProps } from './TasksTable.fixtures';
1
+ import React from 'react';
2
+ import {
3
+ screen,
4
+ within,
5
+ fireEvent,
6
+ waitFor,
7
+ render,
8
+ } from '@testing-library/react';
9
+ import '@testing-library/jest-dom';
10
+ import { IntlProvider } from 'react-intl';
4
11
 
5
- const fixtures = {
6
- 'render with minimal props': minProps,
12
+ import { rtlHelpers } from 'foremanReact/common/testHelpers';
13
+
14
+ import TasksTableIndexPage from '../TasksIndexPage';
15
+ import { CellActionButton } from '../Components/CellActionButton';
16
+ import { RESUME_MODAL, FORCE_UNLOCK_MODAL } from '../TasksTableConstants';
17
+ import { tasksSuccessResponse } from './TasksTable.fixtures';
18
+
19
+ const { renderWithStore } = rtlHelpers;
20
+
21
+ jest.mock(
22
+ // C3 is causing issues in the test, and this test is not testing the DonutChart
23
+ '../../TasksDashboard/Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChart.js',
24
+ () => ({
25
+ __esModule: true,
26
+ default: () => <div data-testid="donut-chart" />,
27
+ })
28
+ );
29
+ const mockApiResponse = {
30
+ ...tasksSuccessResponse,
31
+ setAPIOptions: jest.fn(),
32
+ };
33
+
34
+ jest.mock('foremanReact/common/hooks/API/APIHooks', () => ({
35
+ useAPI: jest.fn(() => mockApiResponse),
36
+ }));
37
+
38
+ const mockApiPost = jest.fn(() => Promise.resolve());
39
+ jest.mock('foremanReact/redux/API/API', () => ({
40
+ __esModule: true,
41
+ default: {
42
+ get: () => Promise.resolve({}),
43
+ put: () => Promise.resolve({}),
44
+ post: (...args) => mockApiPost(...args),
45
+ delete: () => Promise.resolve({}),
46
+ patch: () => Promise.resolve({}),
47
+ },
48
+ }));
49
+
50
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
51
+ ...jest.requireActual('foremanReact/Root/Context/ForemanContext'),
52
+ useForemanPermissions: () => new Set(['edit_foreman_tasks']),
53
+ }));
54
+
55
+ const defaultProps = {
56
+ match: { params: { id: null } },
57
+ history: {
58
+ location: { pathname: '/foreman_tasks/tasks', search: '' },
59
+ push: jest.fn(),
60
+ },
7
61
  };
8
62
 
9
- describe('TasksIndexPage', () => {
10
- describe('rendering', () =>
11
- testComponentSnapshotsWithFixtures(TasksIndexPage, fixtures));
63
+ describe('TasksTableIndexPage', () => {
64
+ const renderPage = (props = defaultProps) =>
65
+ renderWithStore(
66
+ <IntlProvider locale="en">
67
+ <TasksTableIndexPage {...props} />
68
+ </IntlProvider>
69
+ );
70
+ it('should render the Tasks header when not viewing subtasks', () => {
71
+ renderPage();
72
+
73
+ expect(screen.getByRole('heading', { name: 'Tasks' })).toBeInTheDocument();
74
+ });
75
+
76
+ it('should render the Sub tasks header and breadcrumb when viewing subtasks', () => {
77
+ const propsWithParent = {
78
+ ...defaultProps,
79
+ match: { params: { id: 'parent-123' } },
80
+ };
81
+
82
+ renderPage(propsWithParent);
83
+
84
+ expect(screen.getAllByText('Sub tasks')).toHaveLength(2);
85
+ });
86
+
87
+ it('renders each row with the correct data', () => {
88
+ renderPage();
89
+
90
+ const { results } = tasksSuccessResponse.response;
91
+
92
+ results.forEach(task => {
93
+ const actionLink = screen.getByRole('link', { name: task.action });
94
+ const row = actionLink.closest('tr');
95
+ expect(row).toBeInTheDocument();
96
+
97
+ const rowScope = within(row);
98
+
99
+ expect(actionLink).toHaveAttribute(
100
+ 'href',
101
+ `/foreman_tasks/tasks/${task.id}`
102
+ );
103
+ expect(
104
+ rowScope.getByRole('link', { name: task.action })
105
+ ).toBeInTheDocument();
106
+ expect(rowScope.getByText(task.state)).toBeInTheDocument();
107
+ expect(rowScope.getByText(task.result)).toBeInTheDocument();
108
+
109
+ const hasNoStartDate = task.started_at == null;
110
+ if (hasNoStartDate) {
111
+ expect(rowScope.getAllByText('N/A')).toHaveLength(2);
112
+ const durationCell = rowScope.getByTitle('Task was canceled');
113
+ expect(durationCell).toBeInTheDocument();
114
+ expect(durationCell).toHaveClass('param-value');
115
+ expect(durationCell).toHaveTextContent('N/A');
116
+ }
117
+ });
118
+ });
119
+
120
+ describe('row modal open, close, and action', () => {
121
+ const { results } = tasksSuccessResponse.response;
122
+ const resumableTask = results.find(
123
+ r => r.available_actions && r.available_actions.resumable
124
+ );
125
+ const cancelOnlyTask = results.find(
126
+ r =>
127
+ r.available_actions?.cancellable &&
128
+ !r.available_actions?.resumable &&
129
+ r.state === 'stopped'
130
+ );
131
+ const forceCancelTask = results.find(r => r.state !== 'stopped');
132
+
133
+ const clickRowActionButton = (rowActionLabel, buttonName) => {
134
+ const row = screen
135
+ .getByRole('link', { name: rowActionLabel })
136
+ .closest('tr');
137
+ const rowScope = within(row);
138
+ fireEvent.click(rowScope.getByRole('button', { name: buttonName }));
139
+ };
140
+
141
+ const openResumeModal = async () => {
142
+ clickRowActionButton(resumableTask.action, 'Resume');
143
+ await waitFor(() => {
144
+ expect(
145
+ screen.getByRole('heading', { name: 'Resume Task' })
146
+ ).toBeInTheDocument();
147
+ });
148
+ };
149
+
150
+ const openCancelModal = async () => {
151
+ clickRowActionButton(cancelOnlyTask.action, 'Cancel');
152
+ await waitFor(() => {
153
+ expect(
154
+ screen.getByRole('heading', { name: 'Cancel Task' })
155
+ ).toBeInTheDocument();
156
+ });
157
+ };
158
+
159
+ const openForceUnlockModal = async () => {
160
+ clickRowActionButton(forceCancelTask.action, 'Force Cancel');
161
+ await waitFor(() => {
162
+ expect(
163
+ screen.getByRole('heading', { name: 'Force Unlock Task' })
164
+ ).toBeInTheDocument();
165
+ });
166
+ };
167
+
168
+ beforeEach(() => {
169
+ mockApiPost.mockClear();
170
+ });
171
+
172
+ it('opens Resume modal when Resume row action button is clicked', async () => {
173
+ renderPage();
174
+ expect(
175
+ screen.queryByRole('heading', { name: 'Resume Task' })
176
+ ).not.toBeInTheDocument();
177
+
178
+ await openResumeModal();
179
+ });
180
+
181
+ it('closes modal when No is clicked', async () => {
182
+ renderPage();
183
+ await openResumeModal();
184
+ fireEvent.click(screen.getByRole('button', { name: 'No' }));
185
+
186
+ await waitFor(() => {
187
+ expect(
188
+ screen.queryByRole('heading', { name: 'Resume Task' })
189
+ ).not.toBeInTheDocument();
190
+ });
191
+ });
192
+
193
+ it('calls API when Yes is clicked in Resume modal', async () => {
194
+ renderPage();
195
+ await openResumeModal();
196
+
197
+ fireEvent.click(screen.getByRole('button', { name: 'Yes' }));
198
+
199
+ await waitFor(() => {
200
+ expect(mockApiPost).toHaveBeenCalledTimes(1);
201
+ });
202
+ expect(mockApiPost).toHaveBeenCalledWith(
203
+ `/foreman_tasks/tasks/${resumableTask.id}/resume`
204
+ );
205
+ });
206
+
207
+ it('calls API when Yes is clicked in Cancel modal', async () => {
208
+ renderPage();
209
+ await openCancelModal();
210
+
211
+ fireEvent.click(screen.getByRole('button', { name: 'Yes' }));
212
+
213
+ await waitFor(() => {
214
+ expect(mockApiPost).toHaveBeenCalledTimes(1);
215
+ });
216
+ expect(mockApiPost).toHaveBeenCalledWith(
217
+ `/foreman_tasks/tasks/${cancelOnlyTask.id}/cancel`
218
+ );
219
+ });
220
+
221
+ it('calls API when Yes is clicked in Force Unlock modal', async () => {
222
+ renderPage();
223
+ await openForceUnlockModal();
224
+
225
+ fireEvent.click(screen.getByRole('button', { name: 'Yes' }));
226
+
227
+ await waitFor(() => {
228
+ expect(mockApiPost).toHaveBeenCalledTimes(1);
229
+ });
230
+ expect(mockApiPost).toHaveBeenCalledWith(
231
+ `/foreman_tasks/tasks/${forceCancelTask.id}/force_unlock`
232
+ );
233
+ });
234
+ });
235
+ });
236
+
237
+ describe('CellActionButton', () => {
238
+ const setClickedTask = jest.fn();
239
+ const openModal = jest.fn();
240
+
241
+ const renderCellActionButton = (props = {}) =>
242
+ render(
243
+ <IntlProvider locale="en">
244
+ <CellActionButton
245
+ id="task-id-1"
246
+ action="Fixture action"
247
+ canEdit
248
+ setClickedTask={setClickedTask}
249
+ openModal={openModal}
250
+ {...props}
251
+ />
252
+ </IntlProvider>
253
+ );
254
+
255
+ beforeEach(() => {
256
+ setClickedTask.mockClear();
257
+ openModal.mockClear();
258
+ });
259
+
260
+ it('renders Resume for a resumable task when user can edit and opens resume modal on click', () => {
261
+ renderCellActionButton({
262
+ resumable: true,
263
+ cancellable: false,
264
+ stoppable: false,
265
+ });
266
+ const resumeBtn = screen.getByRole('button', { name: 'Resume' });
267
+ expect(resumeBtn).toBeInTheDocument();
268
+ fireEvent.click(resumeBtn);
269
+ expect(setClickedTask).toHaveBeenCalledWith({
270
+ id: 'task-id-1',
271
+ action: 'Fixture action',
272
+ });
273
+ expect(openModal).toHaveBeenCalledWith(RESUME_MODAL);
274
+ });
275
+
276
+ it('renders Cancel for cancellable non-resumable task when user can edit', () => {
277
+ renderCellActionButton({
278
+ cancellable: true,
279
+ resumable: false,
280
+ stoppable: false,
281
+ });
282
+ expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
283
+ });
284
+
285
+ it('renders no action buttons when user cannot edit', () => {
286
+ renderCellActionButton({
287
+ canEdit: false,
288
+ resumable: true,
289
+ cancellable: true,
290
+ stoppable: true,
291
+ });
292
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
293
+ });
294
+
295
+ it('renders disabled cancel button when no action is available', () => {
296
+ renderCellActionButton({
297
+ resumable: false,
298
+ cancellable: false,
299
+ stoppable: false,
300
+ });
301
+ expect(screen.getByRole('button', { name: 'Cancel' })).toBeDisabled();
302
+ });
303
+
304
+ it('renders Force Cancel when task is stoppable and opens force unlock modal on click', () => {
305
+ renderCellActionButton({
306
+ resumable: false,
307
+ cancellable: false,
308
+ stoppable: true,
309
+ });
310
+ const forceBtn = screen.getByRole('button', { name: 'Force Cancel' });
311
+ expect(forceBtn).toBeInTheDocument();
312
+ fireEvent.click(forceBtn);
313
+ expect(setClickedTask).toHaveBeenCalledWith({
314
+ id: 'task-id-1',
315
+ action: 'Fixture action',
316
+ });
317
+ expect(openModal).toHaveBeenCalledWith(FORCE_UNLOCK_MODAL);
318
+ });
12
319
  });