foreman-tasks 12.2.2 → 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/config/routes.rb +0 -1
- 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/RunningSteps.js +152 -44
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Errors.test.js +159 -11
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/RunningSteps.test.js +39 -0
- data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +3 -1
- 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
- data/webpack/Routes/routes.js +0 -5
- data/webpack/Routes/routes.test.js +0 -18
- metadata +2 -11
- 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
- data/webpack/ForemanTasks/Routes/ShowTask/ShowTask.js +0 -10
- data/webpack/ForemanTasks/Routes/ShowTask/__tests__/ShowTask.test.js +0 -14
- data/webpack/ForemanTasks/Routes/ShowTask/__tests__/__snapshots__/ShowTask.test.js.snap +0 -12
- data/webpack/ForemanTasks/Routes/ShowTask/index.js +0 -1
- data/webpack/ForemanTasks/Routes/ShowTask/showTask.scss +0 -0
|
@@ -1,68 +1,174 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Alert,
|
|
5
|
+
AlertVariant,
|
|
6
|
+
Button,
|
|
7
|
+
EmptyState,
|
|
8
|
+
EmptyStateBody,
|
|
9
|
+
EmptyStateHeader,
|
|
10
|
+
EmptyStateIcon,
|
|
11
|
+
EmptyStateVariant,
|
|
12
|
+
Flex,
|
|
13
|
+
FlexItem,
|
|
14
|
+
Grid,
|
|
15
|
+
GridItem,
|
|
16
|
+
Stack,
|
|
17
|
+
StackItem,
|
|
18
|
+
} from '@patternfly/react-core';
|
|
19
|
+
import { HourglassStartIcon } from '@patternfly/react-icons';
|
|
4
20
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
5
21
|
|
|
22
|
+
const RunningStepDetailBlock = ({ label, children }) => (
|
|
23
|
+
<GridItem span={12}>
|
|
24
|
+
<Flex
|
|
25
|
+
direction={{ default: 'column' }}
|
|
26
|
+
spaceItems={{ default: 'spaceItemsXs' }}
|
|
27
|
+
>
|
|
28
|
+
<FlexItem>
|
|
29
|
+
<strong>{label}</strong>
|
|
30
|
+
</FlexItem>
|
|
31
|
+
<FlexItem>{children}</FlexItem>
|
|
32
|
+
</Flex>
|
|
33
|
+
</GridItem>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
RunningStepDetailBlock.propTypes = {
|
|
37
|
+
label: PropTypes.node.isRequired,
|
|
38
|
+
children: PropTypes.node.isRequired,
|
|
39
|
+
};
|
|
40
|
+
|
|
6
41
|
const RunningSteps = ({
|
|
42
|
+
executionPlan,
|
|
43
|
+
result,
|
|
7
44
|
runningSteps,
|
|
8
45
|
id,
|
|
9
46
|
cancelStep,
|
|
10
47
|
taskReload,
|
|
11
48
|
taskReloadStart,
|
|
12
49
|
}) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
50
|
+
const planState = executionPlan?.state;
|
|
51
|
+
const resultIsPending = String(result) === 'pending';
|
|
52
|
+
|
|
53
|
+
if (!runningSteps.length) {
|
|
54
|
+
if (planState === 'running' && resultIsPending) {
|
|
55
|
+
return (
|
|
17
56
|
<Alert
|
|
18
57
|
variant={AlertVariant.warning}
|
|
19
58
|
isInline
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
title={sprintf(__('Running step %s'), i + 1)}
|
|
59
|
+
ouiaId="running-steps-suspended-pending"
|
|
60
|
+
title={__('Temporarily suspended step(s)')}
|
|
23
61
|
>
|
|
24
|
-
{
|
|
25
|
-
<p>
|
|
26
|
-
<Button
|
|
27
|
-
variant="danger"
|
|
28
|
-
size="sm"
|
|
29
|
-
ouiaId={`running-step-cancel-button-${i}`}
|
|
30
|
-
onClick={() => {
|
|
31
|
-
if (!taskReload) {
|
|
32
|
-
taskReloadStart(id);
|
|
33
|
-
}
|
|
34
|
-
cancelStep(id, step.id);
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
{__('Cancel')}
|
|
38
|
-
</Button>
|
|
39
|
-
</p>
|
|
40
|
-
)}
|
|
41
|
-
|
|
42
|
-
<p>
|
|
43
|
-
<span>{__('Action')}:</span>
|
|
44
|
-
<span />
|
|
45
|
-
</p>
|
|
46
|
-
<pre>{step.action_class}</pre>
|
|
47
|
-
<p>
|
|
48
|
-
<span>{__('State')}:</span>
|
|
49
|
-
<span>{step.state}</span>
|
|
50
|
-
</p>
|
|
51
|
-
<span>{__('Input')}:</span>
|
|
52
|
-
<span>
|
|
53
|
-
<pre>{step.input}</pre>
|
|
54
|
-
</span>
|
|
55
|
-
<span>{__('Output')}:</span>
|
|
56
|
-
<span>
|
|
57
|
-
<pre>{step.output}</pre>
|
|
58
|
-
</span>
|
|
62
|
+
{__('The task is still being processed. Please wait.')}
|
|
59
63
|
</Alert>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (planState === 'planned' && resultIsPending) {
|
|
68
|
+
return (
|
|
69
|
+
<Grid>
|
|
70
|
+
<GridItem span={12}>
|
|
71
|
+
<Flex
|
|
72
|
+
direction={{ default: 'column' }}
|
|
73
|
+
alignItems={{ default: 'alignItemsCenter' }}
|
|
74
|
+
justifyContent={{ default: 'justifyContentCenter' }}
|
|
75
|
+
fullWidth={{ default: 'fullWidth' }}
|
|
76
|
+
>
|
|
77
|
+
<FlexItem>
|
|
78
|
+
<EmptyState variant={EmptyStateVariant.full}>
|
|
79
|
+
<EmptyStateHeader
|
|
80
|
+
titleText={__('Planned task')}
|
|
81
|
+
headingLevel="h2"
|
|
82
|
+
icon={<EmptyStateIcon icon={HourglassStartIcon} />}
|
|
83
|
+
/>
|
|
84
|
+
<EmptyStateBody>
|
|
85
|
+
{__('The task has not started yet.')}
|
|
86
|
+
</EmptyStateBody>
|
|
87
|
+
</EmptyState>
|
|
88
|
+
</FlexItem>
|
|
89
|
+
</Flex>
|
|
90
|
+
</GridItem>
|
|
91
|
+
</Grid>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return <span>{__('No running steps')}</span>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Stack hasGutter>
|
|
100
|
+
{runningSteps.map((step, i) => (
|
|
101
|
+
<StackItem key={step.id || i}>
|
|
102
|
+
<Alert
|
|
103
|
+
variant={AlertVariant.warning}
|
|
104
|
+
isInline
|
|
105
|
+
title={sprintf(__('Running step %s'), i + 1)}
|
|
106
|
+
ouiaId={`running-step-${i}`}
|
|
107
|
+
>
|
|
108
|
+
<Grid hasGutter>
|
|
109
|
+
{step.cancellable && (
|
|
110
|
+
<GridItem span={12}>
|
|
111
|
+
<Flex>
|
|
112
|
+
<FlexItem>
|
|
113
|
+
<Button
|
|
114
|
+
ouiaId={`running-step-${i}-cancel`}
|
|
115
|
+
variant="danger"
|
|
116
|
+
size="sm"
|
|
117
|
+
onClick={() => {
|
|
118
|
+
if (!taskReload) {
|
|
119
|
+
taskReloadStart(id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
cancelStep(id, step.id);
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{__('Cancel')}
|
|
126
|
+
</Button>
|
|
127
|
+
</FlexItem>
|
|
128
|
+
</Flex>
|
|
129
|
+
</GridItem>
|
|
130
|
+
)}
|
|
131
|
+
<GridItem span={12}>
|
|
132
|
+
<Flex
|
|
133
|
+
direction={{ default: 'row' }}
|
|
134
|
+
spaceItems={{ default: 'spaceItemsSm' }}
|
|
135
|
+
alignItems={{ default: 'alignItemsBaseline' }}
|
|
136
|
+
>
|
|
137
|
+
<FlexItem>
|
|
138
|
+
<strong>{`${__('Action')}:`}</strong>
|
|
139
|
+
</FlexItem>
|
|
140
|
+
<FlexItem>{step.action_class}</FlexItem>
|
|
141
|
+
</Flex>
|
|
142
|
+
</GridItem>
|
|
143
|
+
<GridItem span={12}>
|
|
144
|
+
<Flex
|
|
145
|
+
direction={{ default: 'row' }}
|
|
146
|
+
spaceItems={{ default: 'spaceItemsSm' }}
|
|
147
|
+
alignItems={{ default: 'alignItemsBaseline' }}
|
|
148
|
+
>
|
|
149
|
+
<FlexItem>
|
|
150
|
+
<strong>{`${__('State')}:`}</strong>
|
|
151
|
+
</FlexItem>
|
|
152
|
+
<FlexItem>{step.state}</FlexItem>
|
|
153
|
+
</Flex>
|
|
154
|
+
</GridItem>
|
|
155
|
+
<RunningStepDetailBlock label={__('Input')}>
|
|
156
|
+
<pre>{step.input}</pre>
|
|
157
|
+
</RunningStepDetailBlock>
|
|
158
|
+
<RunningStepDetailBlock label={__('Output')}>
|
|
159
|
+
<pre>{step.output}</pre>
|
|
160
|
+
</RunningStepDetailBlock>
|
|
161
|
+
</Grid>
|
|
162
|
+
</Alert>
|
|
163
|
+
</StackItem>
|
|
60
164
|
))}
|
|
61
|
-
</
|
|
165
|
+
</Stack>
|
|
62
166
|
);
|
|
63
167
|
};
|
|
64
168
|
|
|
65
169
|
RunningSteps.propTypes = {
|
|
170
|
+
executionPlan: PropTypes.shape({ state: PropTypes.string }),
|
|
171
|
+
result: PropTypes.string,
|
|
66
172
|
runningSteps: PropTypes.array,
|
|
67
173
|
id: PropTypes.string.isRequired,
|
|
68
174
|
cancelStep: PropTypes.func.isRequired,
|
|
@@ -72,6 +178,8 @@ RunningSteps.propTypes = {
|
|
|
72
178
|
|
|
73
179
|
RunningSteps.defaultProps = {
|
|
74
180
|
runningSteps: [],
|
|
181
|
+
executionPlan: {},
|
|
182
|
+
result: undefined,
|
|
75
183
|
};
|
|
76
184
|
|
|
77
185
|
export default RunningSteps;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
4
|
|
|
5
5
|
import Errors from '../Errors';
|
|
@@ -20,6 +20,8 @@ const failedStepFixture = {
|
|
|
20
20
|
output: '{}\n',
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
const executionPlan = { state: 'paused', cancellable: false };
|
|
24
|
+
|
|
23
25
|
describe('Errors', () => {
|
|
24
26
|
it('renders warning when execution plan is missing', () => {
|
|
25
27
|
render(<Errors failedSteps={[]} executionPlan={null} />);
|
|
@@ -30,30 +32,176 @@ describe('Errors', () => {
|
|
|
30
32
|
|
|
31
33
|
it('renders success state when there are no failed steps', () => {
|
|
32
34
|
render(<Errors failedSteps={[]} executionPlan={{ state: 'paused' }} />);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
expect(
|
|
36
|
+
screen.getByRole('heading', { level: 2, name: /no errors found/i })
|
|
37
|
+
).toBeInTheDocument();
|
|
38
|
+
expect(
|
|
39
|
+
screen.getByText(/the task finished with no errors or warnings/i)
|
|
40
|
+
).toBeInTheDocument();
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
it('renders failed step details when failedSteps is non-empty', () => {
|
|
38
44
|
const { container } = render(
|
|
39
45
|
<Errors
|
|
40
|
-
executionPlan={
|
|
46
|
+
executionPlan={executionPlan}
|
|
41
47
|
failedSteps={[failedStepFixture]}
|
|
42
48
|
/>
|
|
43
49
|
);
|
|
44
|
-
const
|
|
50
|
+
const errorTab = container.querySelector(
|
|
45
51
|
'[data-ouia-component-id="task-error-0"]'
|
|
46
52
|
);
|
|
47
|
-
expect(
|
|
48
|
-
expect(
|
|
53
|
+
expect(errorTab).toBeInTheDocument();
|
|
54
|
+
expect(
|
|
55
|
+
screen.getByRole('tab', {
|
|
56
|
+
name: /action actions::katello::eventqueue::monitor is already active/i,
|
|
57
|
+
})
|
|
58
|
+
).toBeInTheDocument();
|
|
49
59
|
expect(
|
|
50
|
-
screen.
|
|
60
|
+
screen.getByLabelText(/failed task errors/i)
|
|
51
61
|
).toBeInTheDocument();
|
|
62
|
+
expect(screen.getByText('Input')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByText('Output')).toBeInTheDocument();
|
|
64
|
+
expect(screen.getByText('Exception:')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Backtrace')).toBeInTheDocument();
|
|
52
66
|
expect(screen.getByText(/runtimeerror/i)).toBeInTheDocument();
|
|
67
|
+
expect(screen.getByText(/singleton_lock/i)).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('switches detail pane when a different error tab is clicked', () => {
|
|
71
|
+
const firstStep = {
|
|
72
|
+
...failedStepFixture,
|
|
73
|
+
action_class: 'Action::First',
|
|
74
|
+
input: 'INPUT_FROM_FIRST_STEP',
|
|
75
|
+
output: '{}',
|
|
76
|
+
};
|
|
77
|
+
const secondStep = {
|
|
78
|
+
...failedStepFixture,
|
|
79
|
+
action_class: 'Action::Second',
|
|
80
|
+
error: {
|
|
81
|
+
exception_class: 'StandardError',
|
|
82
|
+
message: 'second step failure',
|
|
83
|
+
backtrace: [],
|
|
84
|
+
},
|
|
85
|
+
input: 'INPUT_FROM_SECOND_STEP',
|
|
86
|
+
output: 'OUTPUT_SECOND',
|
|
87
|
+
state: 'error',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
render(
|
|
91
|
+
<Errors
|
|
92
|
+
executionPlan={executionPlan}
|
|
93
|
+
failedSteps={[firstStep, secondStep]}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(screen.getByText('INPUT_FROM_FIRST_STEP')).toBeInTheDocument();
|
|
98
|
+
expect(screen.queryByText('INPUT_FROM_SECOND_STEP')).not.toBeInTheDocument();
|
|
99
|
+
|
|
100
|
+
const tabs = screen.getAllByRole('tab');
|
|
101
|
+
expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
|
|
102
|
+
expect(tabs[1]).toHaveAttribute('aria-selected', 'false');
|
|
103
|
+
|
|
104
|
+
fireEvent.click(tabs[1]);
|
|
105
|
+
|
|
106
|
+
expect(screen.getByText('INPUT_FROM_SECOND_STEP')).toBeInTheDocument();
|
|
107
|
+
expect(screen.queryByText('INPUT_FROM_FIRST_STEP')).not.toBeInTheDocument();
|
|
108
|
+
expect(tabs[0]).toHaveAttribute('aria-selected', 'false');
|
|
109
|
+
expect(tabs[1]).toHaveAttribute('aria-selected', 'true');
|
|
110
|
+
expect(screen.getByText('OUTPUT_SECOND')).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('clamps selection when failedSteps shrinks', () => {
|
|
114
|
+
const firstStep = {
|
|
115
|
+
...failedStepFixture,
|
|
116
|
+
action_class: 'Action::First',
|
|
117
|
+
input: 'AFTER_CLAMP',
|
|
118
|
+
output: '{}',
|
|
119
|
+
};
|
|
120
|
+
const secondStep = {
|
|
121
|
+
...failedStepFixture,
|
|
122
|
+
action_class: 'Action::Second',
|
|
123
|
+
input: 'REMOVED',
|
|
124
|
+
output: '{}',
|
|
125
|
+
state: 'error',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const { rerender } = render(
|
|
129
|
+
<Errors
|
|
130
|
+
executionPlan={executionPlan}
|
|
131
|
+
failedSteps={[firstStep, secondStep]}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
fireEvent.click(screen.getAllByRole('tab')[1]);
|
|
136
|
+
expect(screen.getByText('REMOVED')).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
rerender(
|
|
139
|
+
<Errors executionPlan={executionPlan} failedSteps={[firstStep]} />
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(screen.getAllByRole('tab')).toHaveLength(1);
|
|
143
|
+
expect(screen.getByText('AFTER_CLAMP')).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('truncates long tab titles to 120 characters with ellipsis', () => {
|
|
147
|
+
const longMessage = 'x'.repeat(150);
|
|
148
|
+
const longStep = {
|
|
149
|
+
...failedStepFixture,
|
|
150
|
+
error: {
|
|
151
|
+
...failedStepFixture.error,
|
|
152
|
+
message: longMessage,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const { container } = render(
|
|
157
|
+
<Errors executionPlan={executionPlan} failedSteps={[longStep]} />
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const tabTitle = container.querySelector('.task-errors-tab-title');
|
|
161
|
+
expect(tabTitle.textContent).toHaveLength(120);
|
|
162
|
+
expect(tabTitle.textContent.endsWith('...')).toBe(true);
|
|
163
|
+
expect(
|
|
164
|
+
screen.getByRole('tab', { name: longMessage })
|
|
165
|
+
).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('uses warning styling for skipped steps', () => {
|
|
169
|
+
const skippedStep = {
|
|
170
|
+
action_class: 'Actions::Example',
|
|
171
|
+
state: 'skipped',
|
|
172
|
+
input: '{}',
|
|
173
|
+
output: '{}',
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const { container } = render(
|
|
177
|
+
<Errors executionPlan={executionPlan} failedSteps={[skippedStep]} />
|
|
178
|
+
);
|
|
179
|
+
|
|
53
180
|
expect(
|
|
54
|
-
screen.
|
|
55
|
-
/action actions::katello::eventqueue::monitor is already active/i
|
|
56
|
-
)
|
|
181
|
+
screen.getByRole('tab', { name: /actions::example/i })
|
|
57
182
|
).toBeInTheDocument();
|
|
183
|
+
|
|
184
|
+
const errorTab = container.querySelector(
|
|
185
|
+
'[data-ouia-component-id="task-error-0"]'
|
|
186
|
+
);
|
|
187
|
+
expect(errorTab.querySelector('.pf-m-warning')).toBeInTheDocument();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('omits exception and backtrace when step has no error object', () => {
|
|
191
|
+
const stepWithoutError = {
|
|
192
|
+
action_class: 'Actions::NoError',
|
|
193
|
+
state: 'error',
|
|
194
|
+
input: 'plain-input',
|
|
195
|
+
output: 'plain-output',
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
render(
|
|
199
|
+
<Errors executionPlan={executionPlan} failedSteps={[stepWithoutError]} />
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(screen.getByText('plain-input')).toBeInTheDocument();
|
|
203
|
+
expect(screen.getByText('plain-output')).toBeInTheDocument();
|
|
204
|
+
expect(screen.queryByText('Exception:')).not.toBeInTheDocument();
|
|
205
|
+
expect(screen.queryByText('Backtrace')).not.toBeInTheDocument();
|
|
58
206
|
});
|
|
59
207
|
});
|
|
@@ -27,6 +27,45 @@ describe('RunningSteps', () => {
|
|
|
27
27
|
expect(screen.getByText(/no running steps/i)).toBeInTheDocument();
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
it('shows suspended warning when plan is running, result pending, no steps', () => {
|
|
31
|
+
render(
|
|
32
|
+
<RunningSteps
|
|
33
|
+
{...baseProps}
|
|
34
|
+
runningSteps={[]}
|
|
35
|
+
executionPlan={{ state: 'running', cancellable: false }}
|
|
36
|
+
result="pending"
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
screen.getByRole('heading', {
|
|
42
|
+
level: 4,
|
|
43
|
+
name: /temporarily suspended step/i,
|
|
44
|
+
})
|
|
45
|
+
).toBeInTheDocument();
|
|
46
|
+
expect(
|
|
47
|
+
screen.getByText(/the task is still being processed/i)
|
|
48
|
+
).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('shows planned empty state when plan is planned, result pending, no steps', () => {
|
|
52
|
+
render(
|
|
53
|
+
<RunningSteps
|
|
54
|
+
{...baseProps}
|
|
55
|
+
runningSteps={[]}
|
|
56
|
+
executionPlan={{ state: 'planned', cancellable: false }}
|
|
57
|
+
result="pending"
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(
|
|
62
|
+
screen.getByRole('heading', { level: 2, name: /planned task/i })
|
|
63
|
+
).toBeInTheDocument();
|
|
64
|
+
expect(
|
|
65
|
+
screen.getByText(/the task has not started yet/i)
|
|
66
|
+
).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
30
69
|
it('renders running step fields and Cancel when step is cancellable', () => {
|
|
31
70
|
const cancelStep = jest.fn();
|
|
32
71
|
render(
|
|
@@ -30,7 +30,7 @@ const TaskDetails = ({
|
|
|
30
30
|
...props
|
|
31
31
|
}) => {
|
|
32
32
|
const id = getTaskID();
|
|
33
|
-
const { taskReload, status, isLoading } = props;
|
|
33
|
+
const { taskReload, status, isLoading, result } = props;
|
|
34
34
|
const [activeTabKey, setActiveTabKey] = useState(1);
|
|
35
35
|
|
|
36
36
|
useEffect(() => {
|
|
@@ -96,6 +96,8 @@ const TaskDetails = ({
|
|
|
96
96
|
ouiaId="task-details-tab-running-steps"
|
|
97
97
|
>
|
|
98
98
|
<RunningSteps
|
|
99
|
+
executionPlan={executionPlan}
|
|
100
|
+
result={result}
|
|
99
101
|
runningSteps={runningSteps}
|
|
100
102
|
id={id}
|
|
101
103
|
cancelStep={cancelStep}
|
|
@@ -1,13 +1,100 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
2
4
|
|
|
5
|
+
import { TASKS_DASHBOARD_AVAILABLE_TIMES } from '../TasksDashboardConstants';
|
|
6
|
+
import { getQueryFromUrl } from '../TasksDashboardHelper';
|
|
3
7
|
import TasksDashboard from '../TasksDashboard';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
'
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
+
jest.mock('../TasksDashboardHelper', () => ({
|
|
10
|
+
...jest.requireActual('../TasksDashboardHelper'),
|
|
11
|
+
getQueryFromUrl: jest.fn(() => ({})),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock(
|
|
15
|
+
'../Components/TasksCardsGrid/Components/TasksDonutChart/TasksDonutChart',
|
|
16
|
+
() => {
|
|
17
|
+
const React = require('react');
|
|
18
|
+
const Stub = () => <div data-testid="tasks-donut-chart-stub" />;
|
|
19
|
+
Stub.displayName = 'TasksDonutChart';
|
|
20
|
+
|
|
21
|
+
return Stub;
|
|
22
|
+
}
|
|
23
|
+
);
|
|
9
24
|
|
|
10
25
|
describe('TasksDashboard', () => {
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
const cardIds = [
|
|
27
|
+
'running-tasks-card',
|
|
28
|
+
'paused-tasks-card',
|
|
29
|
+
'stopped-tasks-card',
|
|
30
|
+
'scheduled-tasks-card',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
jest.clearAllMocks();
|
|
35
|
+
getQueryFromUrl.mockReturnValue({});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders the dashboard grid with time picker and task cards', () => {
|
|
39
|
+
const { container } = render(<TasksDashboard history={{}} />);
|
|
40
|
+
|
|
41
|
+
expect(container.querySelector('.tasks-dashboard-grid')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText('With focus on last')).toBeInTheDocument();
|
|
43
|
+
expect(
|
|
44
|
+
screen.getByRole('button', { name: /24h/i })
|
|
45
|
+
).toBeInTheDocument();
|
|
46
|
+
|
|
47
|
+
cardIds.forEach(id => {
|
|
48
|
+
expect(container.querySelector(`#${id}`)).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('initializes the dashboard and fetches the summary on mount', () => {
|
|
53
|
+
const initializeDashboard = jest.fn();
|
|
54
|
+
const fetchTasksSummary = jest.fn();
|
|
55
|
+
|
|
56
|
+
render(
|
|
57
|
+
<TasksDashboard
|
|
58
|
+
history={{}}
|
|
59
|
+
initializeDashboard={initializeDashboard}
|
|
60
|
+
fetchTasksSummary={fetchTasksSummary}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(initializeDashboard).toHaveBeenCalledWith({
|
|
65
|
+
time: undefined,
|
|
66
|
+
query: {},
|
|
67
|
+
});
|
|
68
|
+
expect(fetchTasksSummary).toHaveBeenCalledWith(
|
|
69
|
+
TASKS_DASHBOARD_AVAILABLE_TIMES.H24,
|
|
70
|
+
''
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('fetches the summary again when time changes', () => {
|
|
75
|
+
const fetchTasksSummary = jest.fn();
|
|
76
|
+
|
|
77
|
+
const { rerender } = render(
|
|
78
|
+
<TasksDashboard
|
|
79
|
+
history={{}}
|
|
80
|
+
fetchTasksSummary={fetchTasksSummary}
|
|
81
|
+
time={TASKS_DASHBOARD_AVAILABLE_TIMES.H24}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
fetchTasksSummary.mockClear();
|
|
86
|
+
|
|
87
|
+
rerender(
|
|
88
|
+
<TasksDashboard
|
|
89
|
+
history={{}}
|
|
90
|
+
fetchTasksSummary={fetchTasksSummary}
|
|
91
|
+
time={TASKS_DASHBOARD_AVAILABLE_TIMES.H12}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(fetchTasksSummary).toHaveBeenCalledWith(
|
|
96
|
+
TASKS_DASHBOARD_AVAILABLE_TIMES.H12,
|
|
97
|
+
''
|
|
98
|
+
);
|
|
99
|
+
});
|
|
13
100
|
});
|