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.
- checksums.yaml +4 -4
- data/app/services/foreman_tasks/troubleshooting_help_generator.rb +1 -1
- data/lib/foreman_tasks/version.rb +1 -1
- data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +8 -70
- data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +115 -104
- data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.scss +45 -0
- data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js +54 -0
- data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +236 -131
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +27 -33
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +83 -67
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js +118 -1
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +190 -20
- data/webpack/ForemanTasks/Components/TaskDetails/ExecutionDetails.js +65 -0
- data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +48 -51
- data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +0 -39
- data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +0 -5
- data/webpack/ForemanTasks/Components/TaskDetails/__tests__/ExecutionDetails.test.js +194 -0
- data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.fixtures.js +99 -0
- data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +129 -8
- data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.js +174 -0
- data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.scss +5 -0
- data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage.js +10 -33
- data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsHeader.test.js +142 -0
- data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsPage.test.js +100 -88
- data/webpack/ForemanTasks/{Components/TaskDetails → Routes/ShowTaskDetails}/index.js +7 -8
- data/webpack/Routes/routes.js +1 -1
- data/webpack/Routes/routes.test.js +1 -1
- data/webpack/index.js +0 -5
- metadata +8 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import RunningSteps from './Components/RunningSteps';
|
|
4
|
+
import Errors from './Components/Errors';
|
|
5
|
+
|
|
6
|
+
const ExecutionDetails = ({
|
|
7
|
+
state,
|
|
8
|
+
runningSteps,
|
|
9
|
+
cancelStep,
|
|
10
|
+
id,
|
|
11
|
+
taskReload,
|
|
12
|
+
taskReloadStart,
|
|
13
|
+
executionPlan,
|
|
14
|
+
failedSteps,
|
|
15
|
+
result,
|
|
16
|
+
}) => {
|
|
17
|
+
const showingRunningSteps =
|
|
18
|
+
state === 'running' ||
|
|
19
|
+
state === 'pending' ||
|
|
20
|
+
state === 'paused' ||
|
|
21
|
+
runningSteps.length > 0;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
id="execution-details-panel"
|
|
26
|
+
data-ouia-component-id="execution-details-panel"
|
|
27
|
+
>
|
|
28
|
+
{showingRunningSteps ? (
|
|
29
|
+
<RunningSteps
|
|
30
|
+
executionPlan={executionPlan}
|
|
31
|
+
result={result}
|
|
32
|
+
runningSteps={runningSteps}
|
|
33
|
+
id={id}
|
|
34
|
+
cancelStep={cancelStep}
|
|
35
|
+
taskReload={taskReload}
|
|
36
|
+
taskReloadStart={taskReloadStart}
|
|
37
|
+
/>
|
|
38
|
+
) : (
|
|
39
|
+
<Errors executionPlan={executionPlan} failedSteps={failedSteps} />
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
ExecutionDetails.propTypes = {
|
|
46
|
+
state: PropTypes.string,
|
|
47
|
+
result: PropTypes.string,
|
|
48
|
+
runningSteps: PropTypes.array,
|
|
49
|
+
cancelStep: PropTypes.func.isRequired,
|
|
50
|
+
id: PropTypes.string.isRequired,
|
|
51
|
+
taskReload: PropTypes.bool.isRequired,
|
|
52
|
+
taskReloadStart: PropTypes.func.isRequired,
|
|
53
|
+
executionPlan: PropTypes.shape({}),
|
|
54
|
+
failedSteps: PropTypes.array,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
ExecutionDetails.defaultProps = {
|
|
58
|
+
state: '',
|
|
59
|
+
result: undefined,
|
|
60
|
+
runningSteps: [],
|
|
61
|
+
executionPlan: {},
|
|
62
|
+
failedSteps: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default ExecutionDetails;
|
|
@@ -6,17 +6,22 @@ import { STATUS } from 'foremanReact/constants';
|
|
|
6
6
|
import { usePermissions } from 'foremanReact/common/hooks/Permissions/permissionHooks';
|
|
7
7
|
import { ResourceLoadFailedEmptyState } from 'foremanReact/components/common/EmptyState';
|
|
8
8
|
import Task from './Components/Task';
|
|
9
|
-
import RunningSteps from './Components/RunningSteps';
|
|
10
|
-
import Errors from './Components/Errors';
|
|
11
9
|
import Locks from './Components/Locks';
|
|
12
10
|
import Raw from './Components/Raw';
|
|
11
|
+
import ExecutionDetails from './ExecutionDetails';
|
|
13
12
|
import Dependencies from './Components/Dependencies';
|
|
14
13
|
import { TASKS_PATH, VIEW_FOREMAN_TASKS } from './TaskDetailsConstants';
|
|
15
|
-
import { getTaskID } from './TasksDetailsHelper';
|
|
16
14
|
import { TaskSkeleton } from './Components/TaskSkeleton';
|
|
17
15
|
|
|
18
16
|
import './TaskDetails.scss';
|
|
19
17
|
|
|
18
|
+
export const TASK_DETAILS_TAB_KEYS = Object.freeze({
|
|
19
|
+
EXECUTION: 'execution',
|
|
20
|
+
DEPENDENCIES: 'dependencies',
|
|
21
|
+
LOCKS: 'locks',
|
|
22
|
+
RAW: 'raw',
|
|
23
|
+
});
|
|
24
|
+
|
|
20
25
|
const TaskDetails = ({
|
|
21
26
|
executionPlan,
|
|
22
27
|
failedSteps,
|
|
@@ -31,11 +36,11 @@ const TaskDetails = ({
|
|
|
31
36
|
apiStatus,
|
|
32
37
|
apiErrorMessage,
|
|
33
38
|
apiErrorCode,
|
|
39
|
+
id,
|
|
34
40
|
...props
|
|
35
41
|
}) => {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const [activeTabKey, setActiveTabKey] = useState(1);
|
|
42
|
+
const { taskReload, isLoading } = props;
|
|
43
|
+
const [activeTab, setActiveTab] = useState(TASK_DETAILS_TAB_KEYS.EXECUTION);
|
|
39
44
|
const hasViewPermission = usePermissions([VIEW_FOREMAN_TASKS]);
|
|
40
45
|
|
|
41
46
|
useEffect(() => {
|
|
@@ -76,7 +81,7 @@ const TaskDetails = ({
|
|
|
76
81
|
const cancellable = executionPlan ? executionPlan.cancellable : false;
|
|
77
82
|
const lockRecords = locks.concat(links);
|
|
78
83
|
|
|
79
|
-
const
|
|
84
|
+
const taskProps = {
|
|
80
85
|
...props,
|
|
81
86
|
cancellable,
|
|
82
87
|
resumable,
|
|
@@ -87,49 +92,49 @@ const TaskDetails = ({
|
|
|
87
92
|
|
|
88
93
|
return (
|
|
89
94
|
<div className="task-details-react">
|
|
95
|
+
<section className="task-details-overview-section">
|
|
96
|
+
{isLoading ? <TaskSkeleton /> : <Task {...taskProps} />}
|
|
97
|
+
</section>
|
|
90
98
|
<Tabs
|
|
99
|
+
aria-label={__('Task details')}
|
|
91
100
|
id="task-details-tabs"
|
|
92
101
|
ouiaId="task-details-tabs"
|
|
93
|
-
activeKey={
|
|
94
|
-
onSelect={(_event, tabKey) => setActiveTabKey(tabKey)}
|
|
102
|
+
activeKey={activeTab}
|
|
95
103
|
mountOnEnter
|
|
104
|
+
onSelect={(_e, tabKey) => setActiveTab(tabKey)}
|
|
96
105
|
>
|
|
97
106
|
<Tab
|
|
98
|
-
eventKey={1}
|
|
99
|
-
title={<TabTitleText>{__('Task')}</TabTitleText>}
|
|
100
|
-
aria-label={__('Task')}
|
|
101
|
-
ouiaId="task-details-tab-task"
|
|
102
|
-
>
|
|
103
|
-
{isLoading ? <TaskSkeleton /> : <Task {...taskComponentProps} />}
|
|
104
|
-
</Tab>
|
|
105
|
-
<Tab
|
|
106
|
-
eventKey={2}
|
|
107
|
-
title={<TabTitleText>{__('Running Steps')}</TabTitleText>}
|
|
108
107
|
isDisabled={isLoading}
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
eventKey={TASK_DETAILS_TAB_KEYS.EXECUTION}
|
|
109
|
+
title={<TabTitleText>{__('Execution details')}</TabTitleText>}
|
|
110
|
+
aria-label={__('Execution details')}
|
|
111
|
+
ouiaId="task-details-tab-execution-details"
|
|
111
112
|
>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
{!isLoading && (
|
|
114
|
+
<ExecutionDetails
|
|
115
|
+
state={props.state}
|
|
116
|
+
result={props.result}
|
|
117
|
+
runningSteps={runningSteps}
|
|
118
|
+
cancelStep={cancelStep}
|
|
119
|
+
id={id}
|
|
120
|
+
taskReload={taskReload}
|
|
121
|
+
taskReloadStart={taskReloadStart}
|
|
122
|
+
executionPlan={executionPlan}
|
|
123
|
+
failedSteps={failedSteps}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
121
126
|
</Tab>
|
|
122
127
|
<Tab
|
|
123
|
-
eventKey={
|
|
124
|
-
title={<TabTitleText>{__('Errors')}</TabTitleText>}
|
|
128
|
+
eventKey={TASK_DETAILS_TAB_KEYS.DEPENDENCIES}
|
|
125
129
|
isDisabled={isLoading}
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
title={<TabTitleText>{__('Dependencies')}</TabTitleText>}
|
|
131
|
+
aria-label={__('Dependencies')}
|
|
132
|
+
ouiaId="task-details-tab-dependencies"
|
|
128
133
|
>
|
|
129
|
-
<
|
|
134
|
+
<Dependencies dependsOn={dependsOn} blocks={blocks} />
|
|
130
135
|
</Tab>
|
|
131
136
|
<Tab
|
|
132
|
-
eventKey={
|
|
137
|
+
eventKey={TASK_DETAILS_TAB_KEYS.LOCKS}
|
|
133
138
|
title={<TabTitleText>{__('Locks')}</TabTitleText>}
|
|
134
139
|
isDisabled={isLoading}
|
|
135
140
|
aria-label={__('Locks')}
|
|
@@ -138,16 +143,7 @@ const TaskDetails = ({
|
|
|
138
143
|
<Locks locks={lockRecords} />
|
|
139
144
|
</Tab>
|
|
140
145
|
<Tab
|
|
141
|
-
eventKey={
|
|
142
|
-
title={<TabTitleText>{__('Dependencies')}</TabTitleText>}
|
|
143
|
-
isDisabled={isLoading}
|
|
144
|
-
aria-label={__('Dependencies')}
|
|
145
|
-
ouiaId="task-details-tab-dependencies"
|
|
146
|
-
>
|
|
147
|
-
<Dependencies dependsOn={dependsOn} blocks={blocks} />
|
|
148
|
-
</Tab>
|
|
149
|
-
<Tab
|
|
150
|
-
eventKey={6}
|
|
146
|
+
eventKey={TASK_DETAILS_TAB_KEYS.RAW}
|
|
151
147
|
title={<TabTitleText>{__('Raw')}</TabTitleText>}
|
|
152
148
|
isDisabled={isLoading}
|
|
153
149
|
aria-label={__('Raw')}
|
|
@@ -169,7 +165,9 @@ const TaskDetails = ({
|
|
|
169
165
|
};
|
|
170
166
|
|
|
171
167
|
TaskDetails.propTypes = {
|
|
168
|
+
id: PropTypes.string.isRequired,
|
|
172
169
|
label: PropTypes.string,
|
|
170
|
+
result: PropTypes.string,
|
|
173
171
|
runningSteps: PropTypes.array,
|
|
174
172
|
cancelStep: PropTypes.func.isRequired,
|
|
175
173
|
taskReload: PropTypes.bool.isRequired,
|
|
@@ -181,10 +179,10 @@ TaskDetails.propTypes = {
|
|
|
181
179
|
links: PropTypes.array,
|
|
182
180
|
dependsOn: PropTypes.array,
|
|
183
181
|
blocks: PropTypes.array,
|
|
182
|
+
executionPlan: PropTypes.shape({}),
|
|
183
|
+
failedSteps: PropTypes.array,
|
|
184
184
|
...Task.propTypes,
|
|
185
|
-
...Errors.propTypes,
|
|
186
185
|
...Locks.propTypes,
|
|
187
|
-
...Dependencies.propTypes,
|
|
188
186
|
...Raw.propTypes,
|
|
189
187
|
};
|
|
190
188
|
TaskDetails.defaultProps = {
|
|
@@ -194,11 +192,10 @@ TaskDetails.defaultProps = {
|
|
|
194
192
|
links: [],
|
|
195
193
|
dependsOn: [],
|
|
196
194
|
blocks: [],
|
|
195
|
+
failedSteps: [],
|
|
196
|
+
executionPlan: {},
|
|
197
197
|
...Task.defaultProps,
|
|
198
|
-
...RunningSteps.defaultProps,
|
|
199
|
-
...Errors.defaultProps,
|
|
200
198
|
...Locks.defaultProps,
|
|
201
|
-
...Dependencies.defaultProps,
|
|
202
199
|
...Raw.defaultProps,
|
|
203
200
|
};
|
|
204
201
|
|
|
@@ -6,45 +6,6 @@
|
|
|
6
6
|
.container {
|
|
7
7
|
margin: 0;
|
|
8
8
|
}
|
|
9
|
-
.spin {
|
|
10
|
-
-webkit-animation: spin 1s infinite linear;
|
|
11
|
-
-moz-animation: spin 1s infinite linear;
|
|
12
|
-
-o-animation: spin 1s infinite linear;
|
|
13
|
-
animation: spin 1s infinite linear;
|
|
14
|
-
-webkit-transform-origin: 50% 50%;
|
|
15
|
-
transform-origin: 50% 50%;
|
|
16
|
-
-ms-transform-origin: 50% 50%; /* IE 9 */
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@-moz-keyframes spin {
|
|
20
|
-
from {
|
|
21
|
-
-moz-transform: rotate(0deg);
|
|
22
|
-
}
|
|
23
|
-
to {
|
|
24
|
-
-moz-transform: rotate(360deg);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@-webkit-keyframes spin {
|
|
29
|
-
from {
|
|
30
|
-
-webkit-transform: rotate(0deg);
|
|
31
|
-
}
|
|
32
|
-
to {
|
|
33
|
-
-webkit-transform: rotate(360deg);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@keyframes spin {
|
|
38
|
-
from {
|
|
39
|
-
transform: rotate(0deg);
|
|
40
|
-
}
|
|
41
|
-
to {
|
|
42
|
-
transform: rotate(360deg);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
.dynflow-button > span {
|
|
46
|
-
pointer-events: auto;
|
|
47
|
-
}
|
|
48
9
|
|
|
49
10
|
pre {
|
|
50
11
|
white-space: pre-wrap;
|
|
@@ -39,11 +39,6 @@ export const selectResumable = state =>
|
|
|
39
39
|
export const selectCancellable = state =>
|
|
40
40
|
selectTaskDetailsResponse(state).cancellable || false;
|
|
41
41
|
|
|
42
|
-
export const selectErrors = state => {
|
|
43
|
-
const { humanized } = selectTaskDetailsResponse(state);
|
|
44
|
-
return humanized ? humanized.errors : [];
|
|
45
|
-
};
|
|
46
|
-
|
|
47
42
|
export const selectProgress = state =>
|
|
48
43
|
selectTaskDetailsResponse(state).progress
|
|
49
44
|
? Math.trunc(selectTaskDetailsResponse(state).progress * 100)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
|
|
5
|
+
import ExecutionDetails from '../ExecutionDetails';
|
|
6
|
+
import {
|
|
7
|
+
fixtureFailedExecutionDetail,
|
|
8
|
+
fixtureRunningExecutionDetail,
|
|
9
|
+
} from './TaskDetails.fixtures';
|
|
10
|
+
|
|
11
|
+
const rtlBaseProps = {
|
|
12
|
+
cancelStep: jest.fn(),
|
|
13
|
+
taskReloadStart: jest.fn(),
|
|
14
|
+
id: 'a15dd820-32f1-4ced-9ab7-c0fab8234c47',
|
|
15
|
+
taskReload: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('ExecutionDetails', () => {
|
|
19
|
+
it('renders execution details panel with OUIA id', () => {
|
|
20
|
+
render(
|
|
21
|
+
<ExecutionDetails
|
|
22
|
+
{...rtlBaseProps}
|
|
23
|
+
state="stopped"
|
|
24
|
+
runningSteps={[]}
|
|
25
|
+
executionPlan={{ state: 'stopped', cancellable: false }}
|
|
26
|
+
failedSteps={[]}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(
|
|
31
|
+
document.querySelector('[data-ouia-component-id="execution-details-panel"]')
|
|
32
|
+
).toBeInTheDocument();
|
|
33
|
+
expect(document.getElementById('execution-details-panel')).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('shows Errors pane when stopped with no running steps', () => {
|
|
37
|
+
render(
|
|
38
|
+
<ExecutionDetails
|
|
39
|
+
{...rtlBaseProps}
|
|
40
|
+
state="stopped"
|
|
41
|
+
runningSteps={[]}
|
|
42
|
+
executionPlan={{ state: 'stopped', cancellable: false }}
|
|
43
|
+
failedSteps={[]}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByRole('heading', { name: /^no errors found$/i })).toBeInTheDocument();
|
|
48
|
+
expect(
|
|
49
|
+
screen.getByText(/the task finished with no errors or warnings/i)
|
|
50
|
+
).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('shows RunningSteps when stopped but runningSteps list is non-empty', () => {
|
|
54
|
+
render(
|
|
55
|
+
<ExecutionDetails
|
|
56
|
+
{...rtlBaseProps}
|
|
57
|
+
state="stopped"
|
|
58
|
+
runningSteps={[
|
|
59
|
+
{
|
|
60
|
+
cancellable: false,
|
|
61
|
+
id: 1,
|
|
62
|
+
action_class: 'Actions::Stale',
|
|
63
|
+
state: 'paused',
|
|
64
|
+
input: '{}',
|
|
65
|
+
output: '{}',
|
|
66
|
+
},
|
|
67
|
+
]}
|
|
68
|
+
executionPlan={{ state: 'stopped', cancellable: false }}
|
|
69
|
+
failedSteps={[]}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(
|
|
74
|
+
screen.getByRole('heading', { name: 'Warning alert: Running step 1' })
|
|
75
|
+
).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('forwards executionPlan and result to RunningSteps when state is pending without steps', () => {
|
|
79
|
+
render(
|
|
80
|
+
<ExecutionDetails
|
|
81
|
+
{...rtlBaseProps}
|
|
82
|
+
state="pending"
|
|
83
|
+
runningSteps={[]}
|
|
84
|
+
executionPlan={{ state: 'planned', cancellable: false }}
|
|
85
|
+
result="pending"
|
|
86
|
+
failedSteps={[]}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
screen.getByRole('heading', { level: 2, name: /planned task/i })
|
|
92
|
+
).toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('shows temporarily suspended messaging when pending, plan running, result pending', () => {
|
|
96
|
+
render(
|
|
97
|
+
<ExecutionDetails
|
|
98
|
+
{...rtlBaseProps}
|
|
99
|
+
state="pending"
|
|
100
|
+
runningSteps={[]}
|
|
101
|
+
executionPlan={{ state: 'running', cancellable: false }}
|
|
102
|
+
result="pending"
|
|
103
|
+
failedSteps={[]}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(
|
|
108
|
+
screen.getByRole('heading', {
|
|
109
|
+
level: 4,
|
|
110
|
+
name: /temporarily suspended step/i,
|
|
111
|
+
})
|
|
112
|
+
).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('routes failed steps through Errors pane when stopped', () => {
|
|
116
|
+
render(
|
|
117
|
+
<ExecutionDetails
|
|
118
|
+
{...rtlBaseProps}
|
|
119
|
+
{...fixtureFailedExecutionDetail}
|
|
120
|
+
executionPlan={fixtureFailedExecutionDetail.executionPlan}
|
|
121
|
+
failedSteps={fixtureFailedExecutionDetail.failedSteps}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
screen.getByRole('tab', {
|
|
127
|
+
name: /action actions::katello::eventqueue::monitor is already active/i,
|
|
128
|
+
})
|
|
129
|
+
).toBeInTheDocument();
|
|
130
|
+
expect(
|
|
131
|
+
screen.getByLabelText(/failed task errors/i)
|
|
132
|
+
).toBeInTheDocument();
|
|
133
|
+
expect(screen.getByText('Exception:')).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('renders RunningSteps with cancel when task is running', () => {
|
|
137
|
+
const cancelStep = jest.fn();
|
|
138
|
+
|
|
139
|
+
render(
|
|
140
|
+
<ExecutionDetails
|
|
141
|
+
{...rtlBaseProps}
|
|
142
|
+
cancelStep={cancelStep}
|
|
143
|
+
{...fixtureRunningExecutionDetail}
|
|
144
|
+
runningSteps={[
|
|
145
|
+
{
|
|
146
|
+
...fixtureRunningExecutionDetail.runningSteps[0],
|
|
147
|
+
cancellable: true,
|
|
148
|
+
},
|
|
149
|
+
]}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('shows RunningSteps when state is paused without running steps', () => {
|
|
157
|
+
render(
|
|
158
|
+
<ExecutionDetails
|
|
159
|
+
{...rtlBaseProps}
|
|
160
|
+
state="paused"
|
|
161
|
+
runningSteps={[]}
|
|
162
|
+
executionPlan={{ state: 'paused', cancellable: false }}
|
|
163
|
+
failedSteps={[]}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(screen.getByText(/no running steps/i)).toBeInTheDocument();
|
|
168
|
+
expect(
|
|
169
|
+
screen.queryByRole('heading', { name: /^no errors found$/i })
|
|
170
|
+
).not.toBeInTheDocument();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('shows RunningSteps when state is paused with running steps', () => {
|
|
174
|
+
render(
|
|
175
|
+
<ExecutionDetails
|
|
176
|
+
{...rtlBaseProps}
|
|
177
|
+
state="paused"
|
|
178
|
+
runningSteps={[
|
|
179
|
+
{
|
|
180
|
+
...fixtureRunningExecutionDetail.runningSteps[0],
|
|
181
|
+
state: 'paused',
|
|
182
|
+
},
|
|
183
|
+
]}
|
|
184
|
+
executionPlan={{ state: 'paused', cancellable: false }}
|
|
185
|
+
failedSteps={[]}
|
|
186
|
+
/>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(
|
|
190
|
+
screen.getByRole('heading', { name: 'Warning alert: Running step 1' })
|
|
191
|
+
).toBeInTheDocument();
|
|
192
|
+
expect(screen.getByText('paused')).toBeInTheDocument();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const minProps = {
|
|
2
|
+
id: 'a15dd820-32f1-4ced-9ab7-c0fab8234c47',
|
|
2
3
|
cancelStep: jest.fn(),
|
|
3
4
|
taskReload: false,
|
|
4
5
|
taskReloadStop: jest.fn(),
|
|
@@ -6,3 +7,101 @@ export const minProps = {
|
|
|
6
7
|
taskProgressToggle: jest.fn(),
|
|
7
8
|
apiStatus: 'RESOLVED',
|
|
8
9
|
};
|
|
10
|
+
|
|
11
|
+
/** Props for snapshots that exercise the Execution details tab alongside the overview. */
|
|
12
|
+
export const taskDetailsWithExecutionTabDefaults = {
|
|
13
|
+
...minProps,
|
|
14
|
+
locks: [
|
|
15
|
+
{
|
|
16
|
+
exclusive: false,
|
|
17
|
+
resource_type: 'User',
|
|
18
|
+
resource_id: 4,
|
|
19
|
+
link: null,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
links: [],
|
|
23
|
+
executionPlan: { state: 'stopped', cancellable: false },
|
|
24
|
+
failedSteps: [],
|
|
25
|
+
runningSteps: [],
|
|
26
|
+
dependsOn: [],
|
|
27
|
+
blocks: [],
|
|
28
|
+
action: 'Refresh foo',
|
|
29
|
+
state: 'stopped',
|
|
30
|
+
result: 'success',
|
|
31
|
+
progress: 100,
|
|
32
|
+
startAt: '',
|
|
33
|
+
startedAt: '',
|
|
34
|
+
endedAt: '',
|
|
35
|
+
startBefore: '',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const SAMPLE_FAILED_STEP = {
|
|
39
|
+
error: {
|
|
40
|
+
exception_class: 'RuntimeError',
|
|
41
|
+
message:
|
|
42
|
+
'Action Actions::Katello::EventQueue::Monitor is already active',
|
|
43
|
+
backtrace: [
|
|
44
|
+
"/home/example/gems/dynflow-1.2.3/lib/dynflow/action/singleton.rb:15:in `rescue in singleton_lock!'",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
action_class: 'Actions::Katello::EventQueue::Monitor',
|
|
48
|
+
state: 'error',
|
|
49
|
+
input:
|
|
50
|
+
'{"locale"=>"en",\n "current_request_id"=>nil,\n "current_user_id"=>4}\n',
|
|
51
|
+
output: '{}\n',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const SAMPLE_RUNNING_STEP = {
|
|
55
|
+
cancellable: false,
|
|
56
|
+
id: 1,
|
|
57
|
+
action_class: 'Actions::DynflowExample',
|
|
58
|
+
state: 'running',
|
|
59
|
+
input: '{}',
|
|
60
|
+
output: '{}',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const SAMPLE_DEPENDS_ON = [
|
|
64
|
+
{
|
|
65
|
+
id: '123',
|
|
66
|
+
action: 'Actions::FooBar',
|
|
67
|
+
humanized: 'Foo Bar Action',
|
|
68
|
+
state: 'stopped',
|
|
69
|
+
result: 'success',
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export const fixtureFailedExecutionDetail = {
|
|
74
|
+
...taskDetailsWithExecutionTabDefaults,
|
|
75
|
+
result: 'error',
|
|
76
|
+
state: 'stopped',
|
|
77
|
+
failedSteps: [SAMPLE_FAILED_STEP],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const fixtureRunningExecutionDetail = {
|
|
81
|
+
...taskDetailsWithExecutionTabDefaults,
|
|
82
|
+
state: 'running',
|
|
83
|
+
runningSteps: [SAMPLE_RUNNING_STEP],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const fixtureWithOverviewMessages = {
|
|
87
|
+
...taskDetailsWithExecutionTabDefaults,
|
|
88
|
+
help: '<strong>See logs</strong> for more.',
|
|
89
|
+
output: {
|
|
90
|
+
messages: ['partial output'],
|
|
91
|
+
result: 'error',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const fixtureWithDependencies = {
|
|
96
|
+
...taskDetailsWithExecutionTabDefaults,
|
|
97
|
+
dependsOn: SAMPLE_DEPENDS_ON,
|
|
98
|
+
blocks: [
|
|
99
|
+
{
|
|
100
|
+
id: '789',
|
|
101
|
+
action: 'Actions::Test',
|
|
102
|
+
humanized: 'Test Action',
|
|
103
|
+
state: 'paused',
|
|
104
|
+
result: 'warning',
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|