foreman-tasks 12.2.4 → 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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_tasks/tasks_controller.rb +0 -5
  3. data/app/services/foreman_tasks/troubleshooting_help_generator.rb +1 -1
  4. data/config/routes.rb +3 -2
  5. data/lib/foreman_tasks/engine.rb +2 -2
  6. data/lib/foreman_tasks/version.rb +1 -1
  7. data/test/controllers/tasks_controller_test.rb +0 -9
  8. data/test/foreman_tasks_test_helper.rb +2 -2
  9. data/test/integration/tasks_test.rb +17 -0
  10. data/test/test_plugin_helper.rb +8 -0
  11. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +8 -70
  12. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +115 -104
  13. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.scss +45 -0
  14. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js +54 -0
  15. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +237 -177
  16. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskSkeleton.js +1 -6
  17. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +27 -35
  18. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +83 -69
  19. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js +118 -1
  20. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +190 -20
  21. data/webpack/ForemanTasks/Components/TaskDetails/ExecutionDetails.js +65 -0
  22. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +76 -65
  23. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +1 -44
  24. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsConstants.js +2 -1
  25. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +20 -10
  26. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/ExecutionDetails.test.js +194 -0
  27. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.fixtures.js +100 -1
  28. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +226 -18
  29. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetailsSelectors.test.js +81 -0
  30. data/webpack/ForemanTasks/Components/common/taskResultIcon.js +53 -0
  31. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.js +174 -0
  32. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.scss +5 -0
  33. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage.js +51 -0
  34. data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsHeader.test.js +142 -0
  35. data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsPage.test.js +277 -0
  36. data/webpack/ForemanTasks/{Components/TaskDetails → Routes/ShowTaskDetails}/index.js +13 -12
  37. data/webpack/Routes/routes.js +6 -0
  38. data/webpack/Routes/routes.test.js +16 -5
  39. data/webpack/index.js +0 -5
  40. metadata +16 -3
  41. data/app/views/foreman_tasks/tasks/show.html.erb +0 -18
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
4
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
5
+ import TaskDetails from '../../Components/TaskDetails/TaskDetails';
6
+ import TaskDetailsHeader from './TaskDetailsHeader';
7
+
8
+ const TaskDetailsPage = props => {
9
+ const { match, action } = props;
10
+ const { id } = match.params;
11
+ const headerText = action
12
+ ? sprintf(__('Details of %s task'), action)
13
+ : __('Task Details');
14
+
15
+ return (
16
+ <PageLayout
17
+ customHeader={<TaskDetailsHeader {...props} />}
18
+ header={headerText}
19
+ searchable={false}
20
+ breadcrumbOptions={{
21
+ breadcrumbItems: [
22
+ { caption: __('Tasks'), url: '/foreman_tasks/tasks' },
23
+ { caption: action || id },
24
+ ],
25
+ isSwitchable: true,
26
+ resource: {
27
+ nameField: 'action',
28
+ resourceUrl: '/foreman_tasks/api/tasks',
29
+ switcherItemUrl: '/foreman_tasks/tasks/:id',
30
+ },
31
+ }}
32
+ >
33
+ <TaskDetails {...props} />
34
+ </PageLayout>
35
+ );
36
+ };
37
+
38
+ TaskDetailsPage.propTypes = {
39
+ match: PropTypes.shape({
40
+ params: PropTypes.shape({
41
+ id: PropTypes.string.isRequired,
42
+ }).isRequired,
43
+ }).isRequired,
44
+ action: PropTypes.string,
45
+ };
46
+
47
+ TaskDetailsPage.defaultProps = {
48
+ action: '',
49
+ };
50
+
51
+ export default TaskDetailsPage;
@@ -0,0 +1,142 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { STATUS } from 'foremanReact/constants';
5
+
6
+ import TaskDetailsHeader from '../TaskDetailsHeader';
7
+ import { VIEW_FOREMAN_TASKS } from '../../../Components/TaskDetails/TaskDetailsConstants';
8
+
9
+ const mockUseForemanPermissions = jest.fn(
10
+ () => new Set(['view_foreman_tasks'])
11
+ );
12
+
13
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
14
+ ...jest.requireActual('foremanReact/Root/Context/ForemanContext'),
15
+ useForemanPermissions: (...args) => mockUseForemanPermissions(...args),
16
+ }));
17
+
18
+ const baseHeaderProps = {
19
+ id: 'test-id',
20
+ action: 'Refresh hosts',
21
+ state: 'running',
22
+ result: 'pending',
23
+ taskReload: false,
24
+ taskReloadStart: jest.fn(),
25
+ taskReloadStop: jest.fn(),
26
+ forceCancelTaskRequest: jest.fn(),
27
+ unlockTaskRequest: jest.fn(),
28
+ cancelTaskRequest: jest.fn(),
29
+ resumeTaskRequest: jest.fn(),
30
+ canEdit: true,
31
+ dynflowEnableConsole: true,
32
+ externalId: 'ext-99',
33
+ executionPlan: {},
34
+ apiStatus: STATUS.RESOLVED,
35
+ };
36
+
37
+ const expectHeaderActionsVisible = () => {
38
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
39
+ expect(
40
+ screen.getByRole('link', { name: /dynflow console/i })
41
+ ).toBeInTheDocument();
42
+ expect(
43
+ screen.getByRole('button', { name: /^task actions$/i })
44
+ ).toBeInTheDocument();
45
+ };
46
+
47
+ const expectHeaderActionsHidden = () => {
48
+ expect(
49
+ screen.queryByRole('button', { name: /cancel/i })
50
+ ).not.toBeInTheDocument();
51
+ expect(
52
+ screen.queryByRole('link', { name: /dynflow console/i })
53
+ ).not.toBeInTheDocument();
54
+ expect(
55
+ screen.queryByRole('button', { name: /^task actions$/i })
56
+ ).not.toBeInTheDocument();
57
+ };
58
+
59
+ describe('TaskDetailsHeader', () => {
60
+ beforeEach(() => {
61
+ mockUseForemanPermissions.mockImplementation(
62
+ () => new Set(['view_foreman_tasks'])
63
+ );
64
+ });
65
+
66
+ afterEach(() => {
67
+ jest.clearAllMocks();
68
+ });
69
+
70
+ it('renders action heading and exposes cancel, dynflow link, and task actions toggle', () => {
71
+ render(<TaskDetailsHeader {...baseHeaderProps} />);
72
+
73
+ expect(
74
+ screen.getByRole('heading', { level: 1, name: 'Refresh hosts' })
75
+ ).toBeInTheDocument();
76
+ expectHeaderActionsVisible();
77
+ });
78
+
79
+ it('shows running status icon next to the title when task state is running', () => {
80
+ render(
81
+ <TaskDetailsHeader
82
+ {...baseHeaderProps}
83
+ action="Running job"
84
+ state="running"
85
+ result="pending"
86
+ />
87
+ );
88
+
89
+ expect(
90
+ screen.getByRole('heading', { level: 1, name: 'Running job' })
91
+ ).toBeInTheDocument();
92
+ expect(screen.getByTitle('Running')).toBeInTheDocument();
93
+ });
94
+
95
+ it('shows error status icon next to the title when task stopped with error', () => {
96
+ render(
97
+ <TaskDetailsHeader
98
+ {...baseHeaderProps}
99
+ action="Failed job"
100
+ state="stopped"
101
+ result="error"
102
+ />
103
+ );
104
+
105
+ expect(
106
+ screen.getByRole('heading', { level: 1, name: 'Failed job' })
107
+ ).toBeInTheDocument();
108
+ expect(screen.getByTitle('Error')).toBeInTheDocument();
109
+ });
110
+
111
+ it('uses generic title when action is unset', () => {
112
+ render(<TaskDetailsHeader {...baseHeaderProps} action="" />);
113
+
114
+ expect(
115
+ screen.getByRole('heading', { level: 1, name: 'Task Details' })
116
+ ).toBeInTheDocument();
117
+ });
118
+
119
+ it.each([
120
+ [
121
+ `${VIEW_FOREMAN_TASKS} is absent`,
122
+ () => {
123
+ mockUseForemanPermissions.mockImplementation(() => new Set());
124
+ },
125
+ {},
126
+ ],
127
+ [
128
+ 'apiStatus is ERROR',
129
+ () => {},
130
+ { apiStatus: STATUS.ERROR },
131
+ ],
132
+ ])('hides task actions when %s', (_label, setupPermissions, propOverrides) => {
133
+ setupPermissions();
134
+
135
+ render(<TaskDetailsHeader {...baseHeaderProps} {...propOverrides} />);
136
+
137
+ expect(
138
+ screen.getByRole('heading', { level: 1, name: 'Refresh hosts' })
139
+ ).toBeInTheDocument();
140
+ expectHeaderActionsHidden();
141
+ });
142
+ });
@@ -0,0 +1,277 @@
1
+ import React from 'react';
2
+ import { screen, within } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { createMemoryHistory } from 'history';
5
+ import { Router } from 'react-router-dom';
6
+ import { IntlProvider } from 'react-intl';
7
+
8
+ import { rtlHelpers } from 'foremanReact/common/testHelpers';
9
+ import { STATUS } from 'foremanReact/constants';
10
+
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
+ });
30
+
31
+ const mockUseForemanPermissions = jest.fn(
32
+ () => new Set(['view_foreman_tasks'])
33
+ );
34
+
35
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
36
+ ...jest.requireActual('foremanReact/Root/Context/ForemanContext'),
37
+ useForemanPermissions: (...args) => mockUseForemanPermissions(...args),
38
+ }));
39
+
40
+ const routerPropsBase = {
41
+ history: { push: jest.fn(), replace: jest.fn(), go: jest.fn() },
42
+ location: {
43
+ pathname: '/foreman_tasks/tasks/task-route-id',
44
+ search: '',
45
+ hash: '',
46
+ state: undefined,
47
+ },
48
+ };
49
+
50
+ const matchDefault = {
51
+ params: { id: 'task-route-id' },
52
+ path: '/foreman_tasks/tasks/:id',
53
+ url: '/foreman_tasks/tasks/task-route-id',
54
+ isExact: true,
55
+ };
56
+
57
+ const baseTaskPayload = {
58
+ action: '',
59
+ input: [],
60
+ output: {},
61
+ locks: [],
62
+ links: [],
63
+ depends_on: [],
64
+ blocks: [],
65
+ failed_steps: [],
66
+ running_steps: [],
67
+ execution_plan: {},
68
+ state: 'running',
69
+ result: '',
70
+ };
71
+
72
+ const createStoreForTaskPayload = (overrides, apiStatus = STATUS.RESOLVED) => ({
73
+ API: {
74
+ [FOREMAN_TASK_DETAILS]: {
75
+ response: { ...baseTaskPayload, ...overrides },
76
+ status: apiStatus,
77
+ payload: {},
78
+ },
79
+ },
80
+ });
81
+
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
+ };
90
+
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
+ ) => {
100
+ const history = createMemoryHistory({
101
+ initialEntries: [`/foreman_tasks/tasks/${matchDefault.params.id}`],
102
+ });
103
+
104
+ window.history.pushState(
105
+ {},
106
+ '',
107
+ `/foreman_tasks/tasks/${matchDefault.params.id}`
108
+ );
109
+
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)
122
+ );
123
+ };
124
+
125
+ describe('TaskDetailsPage', () => {
126
+ beforeEach(() => {
127
+ mockUseForemanPermissions.mockImplementation(
128
+ () => new Set(['view_foreman_tasks'])
129
+ );
130
+ });
131
+
132
+ afterEach(() => {
133
+ jest.clearAllMocks();
134
+ });
135
+
136
+ describe('permissions', () => {
137
+ it(`renders the task details chrome when ${VIEW_FOREMAN_TASKS} is present`, () => {
138
+ mockUseForemanPermissions.mockImplementation(
139
+ () => new Set(['edit_foreman_tasks', VIEW_FOREMAN_TASKS])
140
+ );
141
+
142
+ renderPage({ action: 'Run job' });
143
+
144
+ expect(
145
+ screen.getByRole('navigation', { name: 'Breadcrumb' })
146
+ ).toBeInTheDocument();
147
+ expect(
148
+ screen.getByRole('heading', {
149
+ level: 1,
150
+ name: 'Run job',
151
+ })
152
+ ).toBeInTheDocument();
153
+ expect(
154
+ screen.queryByRole('heading', { name: /Permission denied/i })
155
+ ).not.toBeInTheDocument();
156
+ });
157
+
158
+ it(`shows ResourceLoadFailedEmptyState and lists ${VIEW_FOREMAN_TASKS} when it is absent`, () => {
159
+ mockUseForemanPermissions.mockImplementation(() => new Set());
160
+
161
+ renderPage({ action: 'Hidden task' });
162
+
163
+ expect(
164
+ screen.getByRole('heading', { name: /Permission denied/i })
165
+ ).toBeInTheDocument();
166
+ expect(
167
+ screen.getByText(
168
+ /You do not have permission to view the task with id task-route-id/
169
+ )
170
+ ).toBeInTheDocument();
171
+ expect(screen.getByText(VIEW_FOREMAN_TASKS)).toBeInTheDocument();
172
+ expect(
173
+ screen.getByRole('button', { name: /Back to tasks/i })
174
+ ).toBeInTheDocument();
175
+ expect(
176
+ screen.getByRole('navigation', { name: 'Breadcrumb' })
177
+ ).toBeInTheDocument();
178
+ expect(
179
+ screen.queryByRole('tab', { name: /^task$/i })
180
+ ).not.toBeInTheDocument();
181
+ expectHeaderActionsHidden();
182
+ });
183
+
184
+ it('denies access when user only has edit_foreman_tasks without view', () => {
185
+ mockUseForemanPermissions.mockImplementation(
186
+ () => new Set(['edit_foreman_tasks'])
187
+ );
188
+
189
+ renderPage({});
190
+
191
+ expect(
192
+ screen.getByRole('heading', { name: /Permission denied/i })
193
+ ).toBeInTheDocument();
194
+ expect(screen.getByText(VIEW_FOREMAN_TASKS)).toBeInTheDocument();
195
+ expect(
196
+ screen.getByRole('navigation', { name: 'Breadcrumb' })
197
+ ).toBeInTheDocument();
198
+ expect(
199
+ screen.queryByRole('tab', { name: /^task$/i })
200
+ ).not.toBeInTheDocument();
201
+ expectHeaderActionsHidden();
202
+ });
203
+
204
+ it('hides header actions when the task API returns an error', () => {
205
+ renderPage({ action: 'Failed load' }, {}, STATUS.ERROR);
206
+
207
+ expect(
208
+ screen.getByRole('heading', { name: /unable to load task/i })
209
+ ).toBeInTheDocument();
210
+ expectHeaderActionsHidden();
211
+ });
212
+ });
213
+
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();
220
+
221
+ expect(screen.getByRole('navigation', { name: 'Breadcrumb' })).toHaveTextContent(
222
+ 'Tasks'
223
+ );
224
+ expect(
225
+ within(screen.getByRole('navigation', { name: 'Breadcrumb' })).getByText(
226
+ /task-route-id/
227
+ )
228
+ ).toBeInTheDocument();
229
+
230
+ const titleArea = getHeaderTitleArea('Task Details');
231
+
232
+ expect(within(titleArea).getByTitle('Running')).toBeInTheDocument();
233
+ expect(within(titleArea).queryByTitle('Error')).not.toBeInTheDocument();
234
+ });
235
+
236
+ it('uses task action for title and breadcrumb when loaded', () => {
237
+ renderPage({ action: 'Refresh hosts' });
238
+
239
+ expect(
240
+ screen.getByRole('heading', { level: 1, name: 'Refresh hosts' })
241
+ ).toBeInTheDocument();
242
+ expect(
243
+ screen.getByRole('button', { name: /cancel/i })
244
+ ).toBeInTheDocument();
245
+
246
+ expect(screen.getByRole('link', { name: /^Tasks$/ })).toHaveAttribute(
247
+ 'href',
248
+ '/foreman_tasks/tasks'
249
+ );
250
+ expect(
251
+ within(screen.getByRole('navigation', { name: 'Breadcrumb' })).getByText(
252
+ 'Refresh hosts'
253
+ )
254
+ ).toBeInTheDocument();
255
+ });
256
+
257
+ it('shows error status icon when task is stopped with error result', () => {
258
+ renderPage({
259
+ action: 'Some action',
260
+ state: 'stopped',
261
+ result: 'error',
262
+ });
263
+
264
+ expect(
265
+ screen.getByRole('heading', { level: 1, name: 'Some action' })
266
+ ).toBeInTheDocument();
267
+ expect(
268
+ within(getHeaderTitleArea('Some action')).getByTitle('Error')
269
+ ).toBeInTheDocument();
270
+
271
+ expect(
272
+ within(screen.getByRole('navigation', { name: 'Breadcrumb' })).getByText(
273
+ 'Some action'
274
+ )
275
+ ).toBeInTheDocument();
276
+ });
277
+ });
@@ -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,
@@ -33,14 +31,17 @@ import {
33
31
  selectExternalId,
34
32
  selectDynflowEnableConsole,
35
33
  selectCanEdit,
36
- selectStatus,
37
- selectAPIError,
34
+ selectAPIStatus,
35
+ selectAPIErrorMessage,
36
+ selectAPIErrorCode,
38
37
  selectIsLoading,
39
38
  selectDependsOn,
40
39
  selectBlocks,
41
- } from './TaskDetailsSelectors';
40
+ } from '../../Components/TaskDetails/TaskDetailsSelectors';
41
+ import TaskDetailsPage from './TaskDetailsPage';
42
42
 
43
- const mapStateToProps = state => ({
43
+ const mapStateToProps = (state, ownProps) => ({
44
+ id: ownProps.match.params.id,
44
45
  startAt: selectStartAt(state),
45
46
  startBefore: selectStartBefore(state),
46
47
  startedAt: selectStartedAt(state),
@@ -49,7 +50,6 @@ const mapStateToProps = state => ({
49
50
  output: selectOutput(state),
50
51
  resumable: selectResumable(state),
51
52
  cancellable: selectCancellable(state),
52
- errors: selectErrors(state),
53
53
  progress: selectProgress(state),
54
54
  username: selectUsername(state),
55
55
  label: selectLabel(state),
@@ -70,8 +70,9 @@ const mapStateToProps = state => ({
70
70
  externalId: selectExternalId(state),
71
71
  dynflowEnableConsole: selectDynflowEnableConsole(state),
72
72
  canEdit: selectCanEdit(state),
73
- status: selectStatus(state),
74
- APIerror: selectAPIError(state),
73
+ apiStatus: selectAPIStatus(state),
74
+ apiErrorMessage: selectAPIErrorMessage(state),
75
+ apiErrorCode: selectAPIErrorCode(state),
75
76
  isLoading: selectIsLoading(state),
76
77
  dependsOn: selectDependsOn(state),
77
78
  blocks: selectBlocks(state),
@@ -80,4 +81,4 @@ const mapStateToProps = state => ({
80
81
  const mapDispatchToProps = dispatch =>
81
82
  bindActionCreators({ ...taskActions, ...taskDetailsActions }, dispatch);
82
83
 
83
- export default connect(mapStateToProps, mapDispatchToProps)(TaskDetails);
84
+ export default connect(mapStateToProps, mapDispatchToProps)(TaskDetailsPage);
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import TasksTableIndexPage from '../ForemanTasks/Components/TasksTable/TasksIndexPage';
3
+ import TaskDetailsPage from '../ForemanTasks/Routes/ShowTaskDetails';
3
4
 
4
5
  const ForemanTasksRoutes = [
5
6
  {
@@ -7,6 +8,11 @@ const ForemanTasksRoutes = [
7
8
  exact: true,
8
9
  render: props => <TasksTableIndexPage {...props} />,
9
10
  },
11
+ {
12
+ path: '/foreman_tasks/tasks/:id',
13
+ exact: true,
14
+ render: props => <TaskDetailsPage {...props} />,
15
+ },
10
16
  {
11
17
  path: '/foreman_tasks/tasks/:id/sub_tasks',
12
18
  exact: true,
@@ -12,6 +12,14 @@ jest.mock(
12
12
  }
13
13
  );
14
14
 
15
+ jest.mock(
16
+ '../ForemanTasks/Routes/ShowTaskDetails',
17
+ () =>
18
+ function TaskDetailsPageStub() {
19
+ return <div data-testid="task-details-page-stub" />;
20
+ }
21
+ );
22
+
15
23
  const routerProps = {
16
24
  history: { push: jest.fn(), replace: jest.fn(), go: jest.fn() },
17
25
  location: {
@@ -34,6 +42,7 @@ describe('ForemanTasks routes', () => {
34
42
  ForemanTasksRoutes.map(({ path, exact }) => ({ path, exact }))
35
43
  ).toEqual([
36
44
  { path: '/foreman_tasks/tasks', exact: true },
45
+ { path: '/foreman_tasks/tasks/:id', exact: true },
37
46
  { path: '/foreman_tasks/tasks/:id/sub_tasks', exact: true },
38
47
  ]);
39
48
  });
@@ -53,9 +62,9 @@ describe('ForemanTasks routes', () => {
53
62
  ...routerProps,
54
63
  match: {
55
64
  ...routerProps.match,
56
- params: { id: '7' },
57
- path: '/foreman_tasks/tasks/:id/sub_tasks',
58
- url: '/foreman_tasks/tasks/7/sub_tasks',
65
+ params: { id: '99' },
66
+ path: '/foreman_tasks/tasks/:id',
67
+ url: '/foreman_tasks/tasks/99',
59
68
  },
60
69
  },
61
70
  ];
@@ -63,8 +72,10 @@ describe('ForemanTasks routes', () => {
63
72
  ForemanTasksRoutes.forEach((route, index) => {
64
73
  const { unmount } = render(route.render(propsByIndex[index]));
65
74
 
66
- if (index === 2) {
67
- expect(screen.getByTestId('show-task-stub')).toBeInTheDocument();
75
+ if (index === 1) {
76
+ expect(
77
+ screen.getByTestId('task-details-page-stub')
78
+ ).toBeInTheDocument();
68
79
  } else {
69
80
  expect(
70
81
  screen.getByTestId('tasks-table-index-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
- });