foreman-tasks 12.2.3 → 12.2.4
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.
- checksums.yaml +4 -4
- data/lib/foreman_tasks/version.rb +1 -1
- data/webpack/ForemanTasks/Components/TaskActions/TaskAction.test.js +212 -42
- data/webpack/ForemanTasks/Components/TaskDetails/Components/Errors.js +224 -55
- data/webpack/ForemanTasks/Components/TaskDetails/Components/Errors.scss +41 -0
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Errors.test.js +159 -11
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboard.test.js +94 -7
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardActions.test.js +97 -16
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardReducer.test.js +112 -46
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboardSelectors.test.js +103 -15
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +1 -1
- metadata +2 -6
- data/webpack/ForemanTasks/Components/TaskActions/__snapshots__/TaskAction.test.js.snap +0 -233
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboard.test.js.snap +0 -51
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardActions.test.js.snap +0 -151
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardReducer.test.js.snap +0 -275
- data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardSelectors.test.js.snap +0 -119
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23f5d844abe59e29a92b15f62c0770e4c3c1b577a1315232c422d976fa77a07e
|
|
4
|
+
data.tar.gz: 5d6316649f6cb75894b966301e5d8abefa579d472ddb33052d06e9d32210b869
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b16c2a1cd40296f38805bddb2a96879ed09d4288e7dc03c8061ba6b561333c96f9e9aa213b583349bce844580b85cc24833ca49f617e67730d3e6b28edf4773
|
|
7
|
+
data.tar.gz: 2b147d1a8a95389cb7c6a12c4fefea577cdec04f8c0ca66315b4411e62f968b1255b4a82438c014300bfd8d67eb0b9a0bea6fcaf277f1eb2d7058a8feaf5375f
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { testActionSnapshotWithFixtures } from '@theforeman/test';
|
|
2
1
|
import { API } from 'foremanReact/redux/API';
|
|
3
2
|
import {
|
|
4
3
|
cancelTaskRequest,
|
|
@@ -6,6 +5,20 @@ import {
|
|
|
6
5
|
forceCancelTaskRequest,
|
|
7
6
|
unlockTaskRequest,
|
|
8
7
|
} from './';
|
|
8
|
+
import {
|
|
9
|
+
TASKS_CANCEL_REQUEST,
|
|
10
|
+
TASKS_CANCEL_SUCCESS,
|
|
11
|
+
TASKS_CANCEL_FAILURE,
|
|
12
|
+
TASKS_RESUME_REQUEST,
|
|
13
|
+
TASKS_RESUME_SUCCESS,
|
|
14
|
+
TASKS_RESUME_FAILURE,
|
|
15
|
+
TASKS_FORCE_CANCEL_REQUEST,
|
|
16
|
+
TASKS_FORCE_CANCEL_SUCCESS,
|
|
17
|
+
TASKS_FORCE_CANCEL_FAILURE,
|
|
18
|
+
TASKS_UNLOCK_REQUEST,
|
|
19
|
+
TASKS_UNLOCK_SUCCESS,
|
|
20
|
+
TASKS_UNLOCK_FAILURE,
|
|
21
|
+
} from './TaskActionsConstants';
|
|
9
22
|
|
|
10
23
|
jest.mock('foremanReact/components/common/table', () => ({
|
|
11
24
|
getTableItemsAction: jest.fn(controller => controller),
|
|
@@ -21,48 +34,205 @@ jest.mock('foremanReact/components/ToastsList', () => ({
|
|
|
21
34
|
}),
|
|
22
35
|
}));
|
|
23
36
|
|
|
24
|
-
const
|
|
37
|
+
const taskId = 'some-id';
|
|
38
|
+
const taskName = 'some-name';
|
|
25
39
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
const toastAction = (message, type) => ({
|
|
41
|
+
type: 'TOASTS_ADD',
|
|
42
|
+
payload: {
|
|
43
|
+
message: {
|
|
44
|
+
message,
|
|
45
|
+
type,
|
|
46
|
+
},
|
|
33
47
|
},
|
|
48
|
+
});
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
describe('Task actions', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
API.post.mockReset();
|
|
53
|
+
API.post.mockResolvedValue({ data: 'some-data' });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('cancelTaskRequest', () => {
|
|
57
|
+
it('dispatches success actions and toasts when cancel succeeds', async () => {
|
|
58
|
+
const dispatch = jest.fn();
|
|
59
|
+
|
|
60
|
+
await cancelTaskRequest(taskId, taskName)(dispatch);
|
|
61
|
+
|
|
62
|
+
expect(API.post).toHaveBeenCalledWith(
|
|
63
|
+
`/foreman_tasks/tasks/${taskId}/cancel`
|
|
64
|
+
);
|
|
65
|
+
expect(dispatch).toHaveBeenCalledTimes(4);
|
|
66
|
+
expect(dispatch.mock.calls[0][0]).toEqual(
|
|
67
|
+
toastAction('Trying to cancel some-name task', 'info')
|
|
68
|
+
);
|
|
69
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
70
|
+
type: TASKS_CANCEL_REQUEST,
|
|
71
|
+
});
|
|
72
|
+
expect(dispatch.mock.calls[2][0]).toEqual({
|
|
73
|
+
type: TASKS_CANCEL_SUCCESS,
|
|
74
|
+
});
|
|
75
|
+
expect(dispatch.mock.calls[3][0]).toEqual(
|
|
76
|
+
toastAction('some-name Task execution was cancelled', 'success')
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('dispatches failure actions and warning toast when cancel fails', async () => {
|
|
81
|
+
API.post.mockRejectedValue(new Error('Network Error'));
|
|
82
|
+
const dispatch = jest.fn();
|
|
83
|
+
|
|
84
|
+
await cancelTaskRequest(taskId, taskName)(dispatch);
|
|
85
|
+
|
|
86
|
+
expect(dispatch).toHaveBeenCalledTimes(4);
|
|
87
|
+
expect(dispatch.mock.calls[0][0]).toEqual(
|
|
88
|
+
toastAction('Trying to cancel some-name task', 'info')
|
|
89
|
+
);
|
|
90
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
91
|
+
type: TASKS_CANCEL_REQUEST,
|
|
92
|
+
});
|
|
93
|
+
expect(dispatch.mock.calls[2][0]).toEqual({
|
|
94
|
+
type: TASKS_CANCEL_FAILURE,
|
|
95
|
+
payload: expect.any(Error),
|
|
96
|
+
});
|
|
97
|
+
expect(dispatch.mock.calls[3][0]).toEqual(
|
|
98
|
+
toastAction(
|
|
99
|
+
'some-name Task execution task has to be cancellable',
|
|
100
|
+
'warning'
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('resumeTaskRequest', () => {
|
|
107
|
+
it('dispatches success actions and toast when resume succeeds', async () => {
|
|
108
|
+
const dispatch = jest.fn();
|
|
109
|
+
|
|
110
|
+
await resumeTaskRequest(taskId, taskName)(dispatch);
|
|
111
|
+
|
|
112
|
+
expect(API.post).toHaveBeenCalledWith(
|
|
113
|
+
`/foreman_tasks/tasks/${taskId}/resume`
|
|
114
|
+
);
|
|
115
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
116
|
+
expect(dispatch.mock.calls[0][0]).toEqual({
|
|
117
|
+
type: TASKS_RESUME_REQUEST,
|
|
118
|
+
});
|
|
119
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
120
|
+
type: TASKS_RESUME_SUCCESS,
|
|
121
|
+
});
|
|
122
|
+
expect(dispatch.mock.calls[2][0]).toEqual(
|
|
123
|
+
toastAction('some-name Task execution was resumed', 'success')
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('dispatches failure actions and error toast when resume fails', async () => {
|
|
128
|
+
API.post.mockRejectedValue(new Error('Network Error'));
|
|
129
|
+
const dispatch = jest.fn();
|
|
130
|
+
|
|
131
|
+
await resumeTaskRequest(taskId, taskName)(dispatch);
|
|
132
|
+
|
|
133
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
134
|
+
expect(dispatch.mock.calls[0][0]).toEqual({
|
|
135
|
+
type: TASKS_RESUME_REQUEST,
|
|
136
|
+
});
|
|
137
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
138
|
+
type: TASKS_RESUME_FAILURE,
|
|
139
|
+
payload: expect.any(Error),
|
|
140
|
+
});
|
|
141
|
+
expect(dispatch.mock.calls[2][0]).toEqual(
|
|
142
|
+
toastAction('some-name Task execution could not be resumed', 'error')
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('forceCancelTaskRequest', () => {
|
|
148
|
+
it('dispatches success actions and toast when force cancel succeeds', async () => {
|
|
149
|
+
const dispatch = jest.fn();
|
|
150
|
+
|
|
151
|
+
await forceCancelTaskRequest(taskId, taskName)(dispatch);
|
|
152
|
+
|
|
153
|
+
expect(API.post).toHaveBeenCalledWith(
|
|
154
|
+
`/foreman_tasks/tasks/${taskId}/force_unlock`
|
|
155
|
+
);
|
|
156
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
157
|
+
expect(dispatch.mock.calls[0][0]).toEqual({
|
|
158
|
+
type: TASKS_FORCE_CANCEL_REQUEST,
|
|
159
|
+
});
|
|
160
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
161
|
+
type: TASKS_FORCE_CANCEL_SUCCESS,
|
|
162
|
+
});
|
|
163
|
+
expect(dispatch.mock.calls[2][0]).toEqual(
|
|
164
|
+
toastAction(
|
|
165
|
+
'some-name Task execution resources were unlocked with force.',
|
|
166
|
+
'success'
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('dispatches failure actions and warning toast when force cancel fails', async () => {
|
|
172
|
+
API.post.mockRejectedValue(new Error('Network Error'));
|
|
173
|
+
const dispatch = jest.fn();
|
|
174
|
+
|
|
175
|
+
await forceCancelTaskRequest(taskId, taskName)(dispatch);
|
|
176
|
+
|
|
177
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
178
|
+
expect(dispatch.mock.calls[0][0]).toEqual({
|
|
179
|
+
type: TASKS_FORCE_CANCEL_REQUEST,
|
|
180
|
+
});
|
|
181
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
182
|
+
type: TASKS_FORCE_CANCEL_FAILURE,
|
|
183
|
+
});
|
|
184
|
+
expect(dispatch.mock.calls[2][0]).toEqual(
|
|
185
|
+
toastAction(
|
|
186
|
+
'some-name Task execution cannot be cancelled with force at the moment.',
|
|
187
|
+
'warning'
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('unlockTaskRequest', () => {
|
|
194
|
+
it('dispatches success actions and toast when unlock succeeds', async () => {
|
|
195
|
+
const dispatch = jest.fn();
|
|
196
|
+
|
|
197
|
+
await unlockTaskRequest(taskId, taskName)(dispatch);
|
|
198
|
+
|
|
199
|
+
expect(API.post).toHaveBeenCalledWith(
|
|
200
|
+
`/foreman_tasks/tasks/${taskId}/unlock`
|
|
201
|
+
);
|
|
202
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
203
|
+
expect(dispatch.mock.calls[0][0]).toEqual({
|
|
204
|
+
type: TASKS_UNLOCK_REQUEST,
|
|
205
|
+
});
|
|
206
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
207
|
+
type: TASKS_UNLOCK_SUCCESS,
|
|
208
|
+
});
|
|
209
|
+
expect(dispatch.mock.calls[2][0]).toEqual(
|
|
210
|
+
toastAction(
|
|
211
|
+
'some-name Task execution resources were unlocked ',
|
|
212
|
+
'success'
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('dispatches failure actions and warning toast when unlock fails', async () => {
|
|
218
|
+
API.post.mockRejectedValue(new Error('Network Error'));
|
|
219
|
+
const dispatch = jest.fn();
|
|
220
|
+
|
|
221
|
+
await unlockTaskRequest(taskId, taskName)(dispatch);
|
|
222
|
+
|
|
223
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
224
|
+
expect(dispatch.mock.calls[0][0]).toEqual({
|
|
225
|
+
type: TASKS_UNLOCK_REQUEST,
|
|
226
|
+
});
|
|
227
|
+
expect(dispatch.mock.calls[1][0]).toEqual({
|
|
228
|
+
type: TASKS_UNLOCK_FAILURE,
|
|
229
|
+
});
|
|
230
|
+
expect(dispatch.mock.calls[2][0]).toEqual(
|
|
231
|
+
toastAction(
|
|
232
|
+
'some-name Task execution resources cannot be unlocked at the moment.',
|
|
233
|
+
'warning'
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
68
238
|
});
|
|
@@ -1,69 +1,238 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
AlertVariant,
|
|
7
|
+
CodeBlock,
|
|
8
|
+
CodeBlockCode,
|
|
9
|
+
EmptyState,
|
|
10
|
+
EmptyStateBody,
|
|
11
|
+
EmptyStateHeader,
|
|
12
|
+
Icon,
|
|
13
|
+
EmptyStateVariant,
|
|
14
|
+
Flex,
|
|
15
|
+
FlexItem,
|
|
16
|
+
Grid,
|
|
17
|
+
GridItem,
|
|
18
|
+
Split,
|
|
19
|
+
SplitItem,
|
|
20
|
+
Stack,
|
|
21
|
+
StackItem,
|
|
22
|
+
Tab,
|
|
23
|
+
Tabs,
|
|
24
|
+
TabTitleIcon,
|
|
25
|
+
TabTitleText,
|
|
26
|
+
} from '@patternfly/react-core';
|
|
27
|
+
import {
|
|
28
|
+
CheckCircleIcon,
|
|
29
|
+
ExclamationCircleIcon,
|
|
30
|
+
} from '@patternfly/react-icons';
|
|
4
31
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
5
32
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
33
|
+
import './Errors.scss';
|
|
34
|
+
|
|
35
|
+
const isStoppedStep = step =>
|
|
36
|
+
['skipped', 'skipping'].includes(String(step.state ?? ''));
|
|
37
|
+
|
|
38
|
+
const STEP_SUMMARY_MAX_LENGTH = 120;
|
|
39
|
+
|
|
40
|
+
const getStepSummary = step =>
|
|
41
|
+
step.error?.message || step.action_class || __('Unknown error');
|
|
42
|
+
|
|
43
|
+
const truncateStepSummary = summary => {
|
|
44
|
+
if (summary.length <= STEP_SUMMARY_MAX_LENGTH) {
|
|
45
|
+
return summary;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `${summary.slice(0, STEP_SUMMARY_MAX_LENGTH - 3)}...`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getStepStatus = step => (isStoppedStep(step) ? 'warning' : 'danger');
|
|
52
|
+
|
|
53
|
+
const ErrorTabTitle = ({ step }) => {
|
|
54
|
+
const status = getStepStatus(step);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<TabTitleIcon>
|
|
59
|
+
<Icon status={status}>
|
|
60
|
+
<ExclamationCircleIcon />
|
|
61
|
+
</Icon>
|
|
62
|
+
</TabTitleIcon>
|
|
63
|
+
<TabTitleText
|
|
64
|
+
className={classNames(
|
|
65
|
+
'task-errors-tab-title',
|
|
66
|
+
`task-errors-tab-title--${status}`
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{truncateStepSummary(getStepSummary(step))}
|
|
70
|
+
</TabTitleText>
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
ErrorTabTitle.propTypes = {
|
|
76
|
+
step: PropTypes.shape({
|
|
77
|
+
action_class: PropTypes.string,
|
|
78
|
+
state: PropTypes.string,
|
|
79
|
+
error: PropTypes.shape({
|
|
80
|
+
message: PropTypes.string,
|
|
81
|
+
}),
|
|
82
|
+
}).isRequired,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const ErrorDetailSection = ({ label, children }) => (
|
|
86
|
+
<StackItem className="task-errors-detail-section">
|
|
87
|
+
<Flex
|
|
88
|
+
direction={{ default: 'column' }}
|
|
89
|
+
spaceItems={{ default: 'spaceItemsXs' }}
|
|
90
|
+
>
|
|
91
|
+
<FlexItem className="task-errors-detail-label">
|
|
92
|
+
<strong>{label}</strong>
|
|
93
|
+
</FlexItem>
|
|
94
|
+
<FlexItem>
|
|
95
|
+
<CodeBlock className="task-errors-codeblock">
|
|
96
|
+
<CodeBlockCode className="task-errors-codeblock-code">
|
|
97
|
+
{children}
|
|
98
|
+
</CodeBlockCode>
|
|
99
|
+
</CodeBlock>
|
|
100
|
+
</FlexItem>
|
|
101
|
+
</Flex>
|
|
102
|
+
</StackItem>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
ErrorDetailSection.propTypes = {
|
|
106
|
+
label: PropTypes.node.isRequired,
|
|
107
|
+
children: PropTypes.node.isRequired,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const ErrorDetailsPane = ({ step }) => {
|
|
111
|
+
if (!step) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Stack>
|
|
117
|
+
{step.error && (
|
|
118
|
+
<>
|
|
119
|
+
<ErrorDetailSection label={`${__('Exception')}:`}>
|
|
120
|
+
{step.error.exception_class}: {step.error.message}
|
|
121
|
+
</ErrorDetailSection>
|
|
122
|
+
<ErrorDetailSection label={__('Backtrace')}>
|
|
123
|
+
{(step.error.backtrace || []).join('\n')}
|
|
124
|
+
</ErrorDetailSection>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
<ErrorDetailSection label={__('Input')}>{step.input}</ErrorDetailSection>
|
|
128
|
+
<ErrorDetailSection label={__('Output')}>
|
|
129
|
+
{step.output}
|
|
130
|
+
</ErrorDetailSection>
|
|
131
|
+
</Stack>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
ErrorDetailsPane.propTypes = {
|
|
136
|
+
step: PropTypes.shape({
|
|
137
|
+
input: PropTypes.node,
|
|
138
|
+
output: PropTypes.node,
|
|
139
|
+
error: PropTypes.shape({
|
|
140
|
+
exception_class: PropTypes.string,
|
|
141
|
+
message: PropTypes.string,
|
|
142
|
+
backtrace: PropTypes.array,
|
|
143
|
+
}),
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
ErrorDetailsPane.defaultProps = {
|
|
148
|
+
step: null,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const Errors = ({ executionPlan, failedSteps }) => {
|
|
152
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
setSelectedIndex(idx => {
|
|
156
|
+
if (idx >= failedSteps.length) {
|
|
157
|
+
return Math.max(0, failedSteps.length - 1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return idx;
|
|
161
|
+
});
|
|
162
|
+
}, [failedSteps.length]);
|
|
163
|
+
|
|
164
|
+
if (!executionPlan) {
|
|
9
165
|
return (
|
|
10
166
|
<Alert
|
|
11
|
-
|
|
12
|
-
|
|
167
|
+
title={__('Execution plan data not available ')}
|
|
168
|
+
variant={AlertVariant.danger}
|
|
13
169
|
ouiaId="task-errors-plan-missing"
|
|
14
|
-
|
|
15
|
-
>
|
|
16
|
-
{__('Execution plan data not available ')}
|
|
17
|
-
</Alert>
|
|
170
|
+
/>
|
|
18
171
|
);
|
|
19
|
-
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!failedSteps.length) {
|
|
20
175
|
return (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
176
|
+
<Grid>
|
|
177
|
+
<GridItem span={12}>
|
|
178
|
+
<Flex
|
|
179
|
+
direction={{ default: 'column' }}
|
|
180
|
+
alignItems={{ default: 'alignItemsCenter' }}
|
|
181
|
+
justifyContent={{ default: 'justifyContentCenter' }}
|
|
182
|
+
fullWidth={{ default: 'fullWidth' }}
|
|
183
|
+
>
|
|
184
|
+
<FlexItem>
|
|
185
|
+
<EmptyState variant={EmptyStateVariant.full}>
|
|
186
|
+
<EmptyStateHeader
|
|
187
|
+
titleText={__('No errors found')}
|
|
188
|
+
headingLevel="h2"
|
|
189
|
+
icon={
|
|
190
|
+
<Icon size="xl" status="success">
|
|
191
|
+
<CheckCircleIcon />
|
|
192
|
+
</Icon>
|
|
193
|
+
}
|
|
194
|
+
/>
|
|
195
|
+
<EmptyStateBody>
|
|
196
|
+
{__('The task finished with no errors or warnings.')}
|
|
197
|
+
</EmptyStateBody>
|
|
198
|
+
</EmptyState>
|
|
199
|
+
</FlexItem>
|
|
200
|
+
</Flex>
|
|
201
|
+
</GridItem>
|
|
202
|
+
</Grid>
|
|
27
203
|
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const selectedStep = failedSteps[selectedIndex];
|
|
207
|
+
|
|
28
208
|
return (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
209
|
+
<Split hasGutter>
|
|
210
|
+
<SplitItem className="task-errors-tabs-split">
|
|
211
|
+
<Tabs
|
|
212
|
+
id="task-errors-tabs"
|
|
213
|
+
ouiaId="task-errors-tabs"
|
|
214
|
+
activeKey={selectedIndex}
|
|
215
|
+
className="task-errors-tabs"
|
|
216
|
+
onSelect={(_event, tabIndex) => setSelectedIndex(Number(tabIndex))}
|
|
217
|
+
isVertical
|
|
218
|
+
isBox
|
|
219
|
+
aria-label={__('Failed task errors')}
|
|
37
220
|
>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<span>
|
|
54
|
-
<pre>
|
|
55
|
-
{step.error.exception_class}: {step.error.message}
|
|
56
|
-
</pre>
|
|
57
|
-
</span>
|
|
58
|
-
<span>{__('Backtrace')}:</span>
|
|
59
|
-
<span>
|
|
60
|
-
<pre>{(step.error.backtrace || []).join('\n')}</pre>
|
|
61
|
-
</span>
|
|
62
|
-
</React.Fragment>
|
|
63
|
-
)}
|
|
64
|
-
</Alert>
|
|
65
|
-
))}
|
|
66
|
-
</div>
|
|
221
|
+
{failedSteps.map((step, i) => (
|
|
222
|
+
<Tab
|
|
223
|
+
key={`${step.action_class}-${i}`}
|
|
224
|
+
eventKey={i}
|
|
225
|
+
ouiaId={`task-error-${i}`}
|
|
226
|
+
title={<ErrorTabTitle step={step} />}
|
|
227
|
+
aria-label={getStepSummary(step)}
|
|
228
|
+
/>
|
|
229
|
+
))}
|
|
230
|
+
</Tabs>
|
|
231
|
+
</SplitItem>
|
|
232
|
+
<SplitItem isFilled>
|
|
233
|
+
<ErrorDetailsPane step={selectedStep} />
|
|
234
|
+
</SplitItem>
|
|
235
|
+
</Split>
|
|
67
236
|
);
|
|
68
237
|
};
|
|
69
238
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.task-details-react {
|
|
2
|
+
.task-errors-detail-section {
|
|
3
|
+
margin-top: 1rem;
|
|
4
|
+
margin-left: 1.5rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.task-errors-detail-label {
|
|
8
|
+
padding-bottom: 0.25rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.task-errors-tabs-split {
|
|
12
|
+
flex: 0 0 min(33%, 20rem);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
.task-errors-codeblock {
|
|
17
|
+
background-color: transparent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.task-errors-codeblock-code {
|
|
21
|
+
margin: -1rem;
|
|
22
|
+
background-color: transparent;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.task-errors-tab-title {
|
|
26
|
+
word-break: break-all;
|
|
27
|
+
font-weight: var(--pf-v5-global--FontWeight--bold);
|
|
28
|
+
|
|
29
|
+
&--danger {
|
|
30
|
+
color: var(--pf-v5-global--danger-color--200);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&--warning {
|
|
34
|
+
color: var(--pf-v5-global--warning-color--200);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.task-errors-tabs {
|
|
39
|
+
--pf-v5-c-tabs--m-vertical--MaxWidth: 100%;
|
|
40
|
+
}
|
|
41
|
+
}
|