foreman-tasks 13.0.0 → 13.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/foreman_tasks/troubleshooting_help_generator.rb +1 -1
  3. data/lib/foreman_tasks/version.rb +1 -1
  4. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +8 -70
  5. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +115 -104
  6. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.scss +45 -0
  7. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js +54 -0
  8. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +236 -131
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +27 -33
  10. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +83 -67
  11. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js +118 -1
  12. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +190 -20
  13. data/webpack/ForemanTasks/Components/TaskDetails/ExecutionDetails.js +65 -0
  14. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +48 -51
  15. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +0 -39
  16. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +0 -5
  17. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/ExecutionDetails.test.js +194 -0
  18. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.fixtures.js +99 -0
  19. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +129 -8
  20. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.js +174 -0
  21. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.scss +5 -0
  22. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage.js +10 -33
  23. data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsHeader.test.js +142 -0
  24. data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsPage.test.js +100 -88
  25. data/webpack/ForemanTasks/{Components/TaskDetails → Routes/ShowTaskDetails}/index.js +7 -8
  26. data/webpack/Routes/routes.js +1 -1
  27. data/webpack/Routes/routes.test.js +1 -1
  28. data/webpack/index.js +0 -5
  29. metadata +8 -2
@@ -1,18 +1,32 @@
1
1
  import React from 'react';
2
- import { render, screen, within } from '@testing-library/react';
2
+ import { screen, within } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom';
4
- import { combineReducers, configureStore } from '@reduxjs/toolkit';
5
4
  import { createMemoryHistory } from 'history';
6
5
  import { Router } from 'react-router-dom';
7
6
  import { IntlProvider } from 'react-intl';
8
- import { Provider } from 'react-redux';
9
7
 
10
- import breadcrumbBarReducer from 'foremanReact/components/BreadcrumbBar/BreadcrumbBarReducer';
8
+ import { rtlHelpers } from 'foremanReact/common/testHelpers';
11
9
  import { STATUS } from 'foremanReact/constants';
12
- import intervalsReducer from 'foremanReact/redux/middlewares/IntervalMiddleware/IntervalReducer';
13
10
 
14
- import { FOREMAN_TASK_DETAILS, VIEW_FOREMAN_TASKS } from '../../../Components/TaskDetails/TaskDetailsConstants';
15
- import TaskDetailsPage from '../TaskDetailsPage';
11
+ import {
12
+ FOREMAN_TASK_DETAILS,
13
+ VIEW_FOREMAN_TASKS,
14
+ } from '../../../Components/TaskDetails/TaskDetailsConstants';
15
+ import TaskDetailsPage from '../index';
16
+
17
+ const { renderWithStore } = rtlHelpers;
18
+
19
+ jest.mock('../../../Components/TaskDetails/TaskDetailsActions', () => {
20
+ const actual = jest.requireActual(
21
+ '../../../Components/TaskDetails/TaskDetailsActions'
22
+ );
23
+
24
+ return {
25
+ ...actual,
26
+ taskReloadStart: jest.fn(() => () => undefined),
27
+ taskReloadStop: jest.fn(() => () => undefined),
28
+ };
29
+ });
16
30
 
17
31
  const mockUseForemanPermissions = jest.fn(
18
32
  () => new Set(['view_foreman_tasks'])
@@ -23,8 +37,6 @@ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
23
37
  useForemanPermissions: (...args) => mockUseForemanPermissions(...args),
24
38
  }));
25
39
 
26
- const TASK_DETAILS_TITLE_ROW_OUIA_ID = 'foreman-tasks-task-details-title-row';
27
-
28
40
  const routerPropsBase = {
29
41
  history: { push: jest.fn(), replace: jest.fn(), go: jest.fn() },
30
42
  location: {
@@ -57,36 +69,37 @@ const baseTaskPayload = {
57
69
  result: '',
58
70
  };
59
71
 
60
- const createStoreForTaskPayload = overrides => ({
72
+ const createStoreForTaskPayload = (overrides, apiStatus = STATUS.RESOLVED) => ({
61
73
  API: {
62
74
  [FOREMAN_TASK_DETAILS]: {
63
75
  response: { ...baseTaskPayload, ...overrides },
64
- status: STATUS.RESOLVED,
76
+ status: apiStatus,
65
77
  payload: {},
66
78
  },
67
79
  },
68
80
  });
69
81
 
70
- const rootReducer = combineReducers({
71
- API: (state = {}, action) => state,
72
- intervals: intervalsReducer,
73
- breadcrumbBar: breadcrumbBarReducer,
74
- foremanTasks: (state = {}, action) => state,
75
- });
82
+ const expectHeaderActionsHidden = () => {
83
+ expect(
84
+ screen.queryByRole('button', { name: /cancel/i })
85
+ ).not.toBeInTheDocument();
86
+ expect(
87
+ screen.queryByRole('button', { name: /^task actions$/i })
88
+ ).not.toBeInTheDocument();
89
+ };
76
90
 
77
- const renderPage = (apiPayloadOverrides = {}, propsOverrides = {}) => {
91
+ const getHeaderTitleArea = title =>
92
+ screen.getByRole('heading', { level: 1, name: title }).parentElement
93
+ .parentElement;
94
+
95
+ const renderPage = (
96
+ apiPayloadOverrides = {},
97
+ propsOverrides = {},
98
+ apiStatus = STATUS.RESOLVED
99
+ ) => {
78
100
  const history = createMemoryHistory({
79
101
  initialEntries: [`/foreman_tasks/tasks/${matchDefault.params.id}`],
80
102
  });
81
- const store = configureStore({
82
- reducer: rootReducer,
83
- preloadedState: createStoreForTaskPayload(apiPayloadOverrides),
84
- middleware: getDefaultMiddleware =>
85
- getDefaultMiddleware({
86
- immutableCheck: false,
87
- serializableCheck: false,
88
- }),
89
- });
90
103
 
91
104
  window.history.pushState(
92
105
  {},
@@ -94,43 +107,30 @@ const renderPage = (apiPayloadOverrides = {}, propsOverrides = {}) => {
94
107
  `/foreman_tasks/tasks/${matchDefault.params.id}`
95
108
  );
96
109
 
97
- return render(
98
- <Router history={history}>
99
- <Provider store={store}>
100
- <IntlProvider locale="en">
101
- <TaskDetailsPage
102
- {...routerPropsBase}
103
- history={history}
104
- match={matchDefault}
105
- {...propsOverrides}
106
- />
107
- </IntlProvider>
108
- </Provider>
109
- </Router>
110
+ return renderWithStore(
111
+ <IntlProvider locale="en">
112
+ <Router history={history}>
113
+ <TaskDetailsPage
114
+ {...routerPropsBase}
115
+ history={history}
116
+ match={matchDefault}
117
+ {...propsOverrides}
118
+ />
119
+ </Router>
120
+ </IntlProvider>,
121
+ createStoreForTaskPayload(apiPayloadOverrides, apiStatus)
110
122
  );
111
123
  };
112
124
 
113
- const breadcrumbTitleHeadings = () =>
114
- screen.getAllByRole('heading', { level: 1 }).filter(
115
- heading => heading.getAttribute('data-ouia-component-id') === 'breadcrumb_title'
116
- );
117
-
118
- /**
119
- * Title row (`customHeader` root `Flex`): same OUIA pattern as `Locks.test.js`.
120
- */
121
- const taskDetailsTitleRegion = container => {
122
- const el = container.querySelector(
123
- `[data-ouia-component-id="${TASK_DETAILS_TITLE_ROW_OUIA_ID}"]`
124
- );
125
-
126
- expect(el).toBeTruthy();
127
-
128
- return el;
129
- };
130
-
131
125
  describe('TaskDetailsPage', () => {
132
126
  beforeEach(() => {
133
- mockUseForemanPermissions.mockImplementation(() => new Set(['view_foreman_tasks']));
127
+ mockUseForemanPermissions.mockImplementation(
128
+ () => new Set(['view_foreman_tasks'])
129
+ );
130
+ });
131
+
132
+ afterEach(() => {
133
+ jest.clearAllMocks();
134
134
  });
135
135
 
136
136
  describe('permissions', () => {
@@ -147,11 +147,12 @@ describe('TaskDetailsPage', () => {
147
147
  expect(
148
148
  screen.getByRole('heading', {
149
149
  level: 1,
150
- name: /Details of Run job task/,
150
+ name: 'Run job',
151
151
  })
152
152
  ).toBeInTheDocument();
153
- expect(screen.queryByRole('heading', { name: /Permission denied/i })).not.toBeInTheDocument();
154
- expect(mockUseForemanPermissions).toHaveBeenCalled();
153
+ expect(
154
+ screen.queryByRole('heading', { name: /Permission denied/i })
155
+ ).not.toBeInTheDocument();
155
156
  });
156
157
 
157
158
  it(`shows ResourceLoadFailedEmptyState and lists ${VIEW_FOREMAN_TASKS} when it is absent`, () => {
@@ -174,7 +175,10 @@ describe('TaskDetailsPage', () => {
174
175
  expect(
175
176
  screen.getByRole('navigation', { name: 'Breadcrumb' })
176
177
  ).toBeInTheDocument();
177
- expect(screen.queryByRole('tab', { name: /^task$/i })).not.toBeInTheDocument();
178
+ expect(
179
+ screen.queryByRole('tab', { name: /^task$/i })
180
+ ).not.toBeInTheDocument();
181
+ expectHeaderActionsHidden();
178
182
  });
179
183
 
180
184
  it('denies access when user only has edit_foreman_tasks without view', () => {
@@ -191,24 +195,28 @@ describe('TaskDetailsPage', () => {
191
195
  expect(
192
196
  screen.getByRole('navigation', { name: 'Breadcrumb' })
193
197
  ).toBeInTheDocument();
194
- expect(screen.queryByRole('tab', { name: /^task$/i })).not.toBeInTheDocument();
198
+ expect(
199
+ screen.queryByRole('tab', { name: /^task$/i })
200
+ ).not.toBeInTheDocument();
201
+ expectHeaderActionsHidden();
195
202
  });
196
- });
197
203
 
204
+ it('hides header actions when the task API returns an error', () => {
205
+ renderPage({ action: 'Failed load' }, {}, STATUS.ERROR);
198
206
 
199
- const expectToolbarHeadingText = substring => {
200
- const headings = breadcrumbTitleHeadings();
201
-
202
- expect(headings.length).toBeGreaterThan(0);
203
-
204
- headings.forEach(heading => {
205
- expect(heading).toHaveTextContent(substring);
207
+ expect(
208
+ screen.getByRole('heading', { name: /unable to load task/i })
209
+ ).toBeInTheDocument();
210
+ expectHeaderActionsHidden();
206
211
  });
207
- };
212
+ });
208
213
 
209
- it('shows generic title and breadcrumb from route id when action is unset', () => {
210
- const page = renderPage({});
211
- expectToolbarHeadingText('Task Details');
214
+ it('shows running status icon when action is unset', () => {
215
+ renderPage({});
216
+
217
+ expect(
218
+ screen.getByRole('heading', { level: 1, name: 'Task Details' })
219
+ ).toBeInTheDocument();
212
220
 
213
221
  expect(screen.getByRole('navigation', { name: 'Breadcrumb' })).toHaveTextContent(
214
222
  'Tasks'
@@ -219,18 +227,21 @@ describe('TaskDetailsPage', () => {
219
227
  )
220
228
  ).toBeInTheDocument();
221
229
 
222
- const titleRegion = taskDetailsTitleRegion(page.container);
230
+ const titleArea = getHeaderTitleArea('Task Details');
223
231
 
224
- expect(
225
- within(titleRegion).getAllByRole('img', { hidden: true }).length
226
- ).toBeGreaterThan(0);
227
- expect(titleRegion.querySelector('[class*="danger"]')).toBeNull();
232
+ expect(within(titleArea).getByTitle('Running')).toBeInTheDocument();
233
+ expect(within(titleArea).queryByTitle('Error')).not.toBeInTheDocument();
228
234
  });
229
235
 
230
236
  it('uses task action for title and breadcrumb when loaded', () => {
231
237
  renderPage({ action: 'Refresh hosts' });
232
238
 
233
- expectToolbarHeadingText('Details of Refresh hosts task');
239
+ expect(
240
+ screen.getByRole('heading', { level: 1, name: 'Refresh hosts' })
241
+ ).toBeInTheDocument();
242
+ expect(
243
+ screen.getByRole('button', { name: /cancel/i })
244
+ ).toBeInTheDocument();
234
245
 
235
246
  expect(screen.getByRole('link', { name: /^Tasks$/ })).toHaveAttribute(
236
247
  'href',
@@ -243,18 +254,19 @@ describe('TaskDetailsPage', () => {
243
254
  ).toBeInTheDocument();
244
255
  });
245
256
 
246
- it('shows error status styling in the heading when task is stopped with error result', () => {
247
- const page = renderPage({
257
+ it('shows error status icon when task is stopped with error result', () => {
258
+ renderPage({
248
259
  action: 'Some action',
249
260
  state: 'stopped',
250
261
  result: 'error',
251
262
  });
252
263
 
253
- expectToolbarHeadingText('Details of Some action task');
254
-
255
264
  expect(
256
- taskDetailsTitleRegion(page.container).querySelector('[class*="danger"]')
257
- ).toBeTruthy();
265
+ screen.getByRole('heading', { level: 1, name: 'Some action' })
266
+ ).toBeInTheDocument();
267
+ expect(
268
+ within(getHeaderTitleArea('Some action')).getByTitle('Error')
269
+ ).toBeInTheDocument();
258
270
 
259
271
  expect(
260
272
  within(screen.getByRole('navigation', { name: 'Breadcrumb' })).getByText(
@@ -1,8 +1,7 @@
1
1
  import { bindActionCreators } from 'redux';
2
2
  import { connect } from 'react-redux';
3
- import TaskDetails from './TaskDetails';
4
- import * as taskDetailsActions from './TaskDetailsActions';
5
- import * as taskActions from '../TaskActions';
3
+ import * as taskDetailsActions from '../../Components/TaskDetails/TaskDetailsActions';
4
+ import * as taskActions from '../../Components/TaskActions';
6
5
  import {
7
6
  selectEndedAt,
8
7
  selectStartAt,
@@ -15,7 +14,6 @@ import {
15
14
  selectResumable,
16
15
  selectCancellable,
17
16
  selectHelp,
18
- selectErrors,
19
17
  selectProgress,
20
18
  selectUsername,
21
19
  selectLabel,
@@ -39,9 +37,11 @@ import {
39
37
  selectIsLoading,
40
38
  selectDependsOn,
41
39
  selectBlocks,
42
- } from './TaskDetailsSelectors';
40
+ } from '../../Components/TaskDetails/TaskDetailsSelectors';
41
+ import TaskDetailsPage from './TaskDetailsPage';
43
42
 
44
- const mapStateToProps = state => ({
43
+ const mapStateToProps = (state, ownProps) => ({
44
+ id: ownProps.match.params.id,
45
45
  startAt: selectStartAt(state),
46
46
  startBefore: selectStartBefore(state),
47
47
  startedAt: selectStartedAt(state),
@@ -50,7 +50,6 @@ const mapStateToProps = state => ({
50
50
  output: selectOutput(state),
51
51
  resumable: selectResumable(state),
52
52
  cancellable: selectCancellable(state),
53
- errors: selectErrors(state),
54
53
  progress: selectProgress(state),
55
54
  username: selectUsername(state),
56
55
  label: selectLabel(state),
@@ -82,4 +81,4 @@ const mapStateToProps = state => ({
82
81
  const mapDispatchToProps = dispatch =>
83
82
  bindActionCreators({ ...taskActions, ...taskDetailsActions }, dispatch);
84
83
 
85
- export default connect(mapStateToProps, mapDispatchToProps)(TaskDetails);
84
+ export default connect(mapStateToProps, mapDispatchToProps)(TaskDetailsPage);
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import TasksTableIndexPage from '../ForemanTasks/Components/TasksTable/TasksIndexPage';
3
- import TaskDetailsPage from '../ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage';
3
+ import TaskDetailsPage from '../ForemanTasks/Routes/ShowTaskDetails';
4
4
 
5
5
  const ForemanTasksRoutes = [
6
6
  {
@@ -13,7 +13,7 @@ jest.mock(
13
13
  );
14
14
 
15
15
  jest.mock(
16
- '../ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage',
16
+ '../ForemanTasks/Routes/ShowTaskDetails',
17
17
  () =>
18
18
  function TaskDetailsPageStub() {
19
19
  return <div data-testid="task-details-page-stub" />;
data/webpack/index.js CHANGED
@@ -3,13 +3,8 @@
3
3
  /* eslint-disable import/extensions */
4
4
  import componentRegistry from 'foremanReact/components/componentRegistry';
5
5
  import TasksDashboard from './ForemanTasks/Components/TasksDashboard';
6
- import TaskDetails from './ForemanTasks/Components/TaskDetails';
7
6
 
8
7
  componentRegistry.register({
9
8
  name: 'TasksDashboard',
10
9
  type: TasksDashboard,
11
10
  });
12
- componentRegistry.register({
13
- name: 'TaskDetails',
14
- type: TaskDetails,
15
- });
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.0.0
4
+ version: 13.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
@@ -378,6 +378,7 @@ files:
378
378
  - webpack/ForemanTasks/Components/TaskDetails/Components/RunningSteps.js
379
379
  - webpack/ForemanTasks/Components/TaskDetails/Components/Task.js
380
380
  - webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js
381
+ - webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.scss
381
382
  - webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js
382
383
  - webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js
383
384
  - webpack/ForemanTasks/Components/TaskDetails/Components/TaskSkeleton.js
@@ -390,17 +391,18 @@ files:
390
391
  - webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js
391
392
  - webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js
392
393
  - webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js
394
+ - webpack/ForemanTasks/Components/TaskDetails/ExecutionDetails.js
393
395
  - webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js
394
396
  - webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss
395
397
  - webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js
396
398
  - webpack/ForemanTasks/Components/TaskDetails/TaskDetailsConstants.js
397
399
  - webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js
398
400
  - webpack/ForemanTasks/Components/TaskDetails/TasksDetailsHelper.js
401
+ - webpack/ForemanTasks/Components/TaskDetails/__tests__/ExecutionDetails.test.js
399
402
  - webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.fixtures.js
400
403
  - webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js
401
404
  - webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsActions.test.js
402
405
  - webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsSelectors.test.js
403
- - webpack/ForemanTasks/Components/TaskDetails/index.js
404
406
  - webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/PausedTasksCard.js
405
407
  - webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/PausedTasksCard/PausedTasksCard.test.js
406
408
  - webpack/ForemanTasks/Components/TasksDashboard/Components/TasksCardsGrid/Components/RunningTasksCard/RunningTasksCard.js
@@ -477,8 +479,12 @@ files:
477
479
  - webpack/ForemanTasks/Components/common/urlHelpers.js
478
480
  - webpack/ForemanTasks/ForemanTasksReducers.js
479
481
  - webpack/ForemanTasks/ForemanTasksSelectors.js
482
+ - webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.js
483
+ - webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.scss
480
484
  - webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage.js
485
+ - webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsHeader.test.js
481
486
  - webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsPage.test.js
487
+ - webpack/ForemanTasks/Routes/ShowTaskDetails/index.js
482
488
  - webpack/Routes/routes.js
483
489
  - webpack/Routes/routes.test.js
484
490
  - webpack/global_index.js