foreman-tasks 12.2.1 → 12.2.2
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/TaskDetails/Components/Dependencies.js +69 -58
- data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +170 -43
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Dependencies.test.js +82 -21
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Locks.test.js +256 -23
- metadata +1 -2
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +0 -116
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79e3003ee203359edba19b75adac7c38435b94081969faba3ad211601c559345
|
|
4
|
+
data.tar.gz: b3da633082735d6adf5a5d569744d3d5a6fa1d18d05568e7bc048b922205c674
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6614edd5d7578167024f5df26bc5f950862dd9a2411a75cfd3f591b19755acbc40a1ca4321eff752800023923bb574d0095aa8c81ab4fa953e030999cf3d4547
|
|
7
|
+
data.tar.gz: fcdd9f699f2a55d3e27bf014f8dbee0dd0e92e3d4f6b6b6921f0a4ba9841cf2cd8aa2392d0376667fa34a85ec41b53ad6e6ff25dae9443fcee9bd625350e82f2
|
|
@@ -1,55 +1,75 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import { capitalize } from 'lodash';
|
|
3
4
|
import {
|
|
4
|
-
Alert,
|
|
5
|
-
AlertVariant,
|
|
6
5
|
Grid,
|
|
7
6
|
GridItem,
|
|
7
|
+
Text,
|
|
8
|
+
TextVariants,
|
|
8
9
|
Title,
|
|
9
10
|
} from '@patternfly/react-core';
|
|
10
11
|
import { Table, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
11
12
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
12
13
|
|
|
13
|
-
const DependencyTable = ({ title, tasks }) =>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
<a href={`/foreman_tasks/tasks/${task.id}`}>
|
|
36
|
-
{task.humanized || task.action}
|
|
37
|
-
</a>
|
|
38
|
-
</Td>
|
|
39
|
-
<Td>{task.state}</Td>
|
|
40
|
-
<Td>{task.result}</Td>
|
|
14
|
+
const DependencyTable = ({ title, tasks, ouiaSectionId }) => (
|
|
15
|
+
<React.Fragment>
|
|
16
|
+
<Title headingLevel="h3" size="lg" ouiaId={`${ouiaSectionId}-title`}>
|
|
17
|
+
{title}
|
|
18
|
+
</Title>
|
|
19
|
+
<Grid hasGutter>
|
|
20
|
+
<GridItem span={12} xl={8}>
|
|
21
|
+
{tasks.length === 0 ? (
|
|
22
|
+
<Text component={TextVariants.small} ouiaId={`${ouiaSectionId}-none`}>
|
|
23
|
+
{__('None')}
|
|
24
|
+
</Text>
|
|
25
|
+
) : (
|
|
26
|
+
<Table
|
|
27
|
+
aria-label={title}
|
|
28
|
+
variant="compact"
|
|
29
|
+
ouiaId={`${ouiaSectionId}-table`}
|
|
30
|
+
>
|
|
31
|
+
<Thead>
|
|
32
|
+
<Tr ouiaId={`${ouiaSectionId}-table-header`}>
|
|
33
|
+
<Th>{__('Name')}</Th>
|
|
34
|
+
<Th>{__('State')}</Th>
|
|
35
|
+
<Th>{__('Result')}</Th>
|
|
41
36
|
</Tr>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
</Thead>
|
|
38
|
+
<Tbody>
|
|
39
|
+
{tasks.map(task => (
|
|
40
|
+
<Tr
|
|
41
|
+
key={task.id}
|
|
42
|
+
ouiaId={`${ouiaSectionId}-table-row-${task.id}`}
|
|
43
|
+
>
|
|
44
|
+
<Td dataLabel={__('Name')}>
|
|
45
|
+
<Text
|
|
46
|
+
component={TextVariants.a}
|
|
47
|
+
href={`/foreman_tasks/tasks/${task.id}`}
|
|
48
|
+
ouiaId={`${ouiaSectionId}-task-link-${task.id}`}
|
|
49
|
+
>
|
|
50
|
+
{task.humanized || task.action}
|
|
51
|
+
</Text>
|
|
52
|
+
</Td>
|
|
53
|
+
<Td dataLabel={__('State')}>
|
|
54
|
+
{capitalize(String(task.state || ''))}
|
|
55
|
+
</Td>
|
|
56
|
+
<Td dataLabel={__('Result')}>
|
|
57
|
+
{capitalize(String(task.result || ''))}
|
|
58
|
+
</Td>
|
|
59
|
+
</Tr>
|
|
60
|
+
))}
|
|
61
|
+
</Tbody>
|
|
62
|
+
</Table>
|
|
63
|
+
)}
|
|
64
|
+
</GridItem>
|
|
65
|
+
</Grid>
|
|
66
|
+
</React.Fragment>
|
|
67
|
+
);
|
|
49
68
|
|
|
50
69
|
DependencyTable.propTypes = {
|
|
51
70
|
title: PropTypes.string.isRequired,
|
|
52
71
|
tasks: PropTypes.array,
|
|
72
|
+
ouiaSectionId: PropTypes.string.isRequired,
|
|
53
73
|
};
|
|
54
74
|
|
|
55
75
|
DependencyTable.defaultProps = {
|
|
@@ -57,27 +77,18 @@ DependencyTable.defaultProps = {
|
|
|
57
77
|
};
|
|
58
78
|
|
|
59
79
|
const Dependencies = ({ dependsOn, blocks }) => (
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{__(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<Grid hasGutter>
|
|
73
|
-
<GridItem span={6}>
|
|
74
|
-
<DependencyTable title={__('Depends on')} tasks={dependsOn} />
|
|
75
|
-
</GridItem>
|
|
76
|
-
<GridItem span={6}>
|
|
77
|
-
<DependencyTable title={__('Blocks')} tasks={blocks} />
|
|
78
|
-
</GridItem>
|
|
79
|
-
</Grid>
|
|
80
|
-
</div>
|
|
80
|
+
<React.Fragment>
|
|
81
|
+
<DependencyTable
|
|
82
|
+
title={__('Task depends on')}
|
|
83
|
+
tasks={dependsOn}
|
|
84
|
+
ouiaSectionId="task-dependencies-depends-on"
|
|
85
|
+
/>
|
|
86
|
+
<DependencyTable
|
|
87
|
+
title={__('Task blocks')}
|
|
88
|
+
tasks={blocks}
|
|
89
|
+
ouiaSectionId="task-dependencies-blocks"
|
|
90
|
+
/>
|
|
91
|
+
</React.Fragment>
|
|
81
92
|
);
|
|
82
93
|
|
|
83
94
|
Dependencies.propTypes = {
|
|
@@ -1,56 +1,183 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
EmptyStateBody,
|
|
6
|
+
EmptyStateHeader,
|
|
7
|
+
EmptyStateIcon,
|
|
8
|
+
EmptyStateVariant,
|
|
9
|
+
Flex,
|
|
10
|
+
FlexItem,
|
|
11
|
+
Grid,
|
|
12
|
+
GridItem,
|
|
13
|
+
Icon,
|
|
14
|
+
Text,
|
|
15
|
+
TextContent,
|
|
16
|
+
TextVariants,
|
|
17
|
+
Title,
|
|
18
|
+
} from '@patternfly/react-core';
|
|
19
|
+
import { Table, Tbody, Tr, Td } from '@patternfly/react-table';
|
|
20
|
+
import { LockIcon, LockOpenIcon } from '@patternfly/react-icons';
|
|
21
|
+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
5
22
|
|
|
6
|
-
const
|
|
7
|
-
|
|
23
|
+
const LocksSection = ({
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
items,
|
|
27
|
+
RowIcon,
|
|
28
|
+
ouiaSectionId,
|
|
29
|
+
}) => (
|
|
30
|
+
<Flex direction={{ default: 'column' }}>
|
|
31
|
+
<FlexItem spacer={{ default: 'spacerSm' }}>
|
|
32
|
+
<Flex
|
|
33
|
+
alignItems={{ default: 'alignItemsCenter' }}
|
|
34
|
+
spaceItems={{ default: 'spaceItemsSm' }}
|
|
35
|
+
>
|
|
36
|
+
<FlexItem>
|
|
37
|
+
<Title
|
|
38
|
+
headingLevel="h3"
|
|
39
|
+
size="lg"
|
|
40
|
+
style={{ margin: '0' }}
|
|
41
|
+
ouiaId={`${ouiaSectionId}-title`}
|
|
42
|
+
>
|
|
43
|
+
{title}
|
|
44
|
+
</Title>
|
|
45
|
+
</FlexItem>
|
|
46
|
+
<FlexItem>
|
|
47
|
+
<RowIcon aria-hidden />
|
|
48
|
+
</FlexItem>
|
|
49
|
+
</Flex>
|
|
50
|
+
</FlexItem>
|
|
51
|
+
<FlexItem>
|
|
52
|
+
<TextContent>
|
|
53
|
+
<Text
|
|
54
|
+
component={TextVariants.p}
|
|
55
|
+
ouiaId={`${ouiaSectionId}-description`}
|
|
56
|
+
>
|
|
57
|
+
{description}
|
|
58
|
+
</Text>
|
|
59
|
+
</TextContent>
|
|
60
|
+
</FlexItem>
|
|
61
|
+
<FlexItem>
|
|
62
|
+
<Grid>
|
|
63
|
+
<GridItem span={5}>
|
|
64
|
+
<Table
|
|
65
|
+
aria-label={title}
|
|
66
|
+
variant="compact"
|
|
67
|
+
ouiaId={`${ouiaSectionId}-table`}
|
|
68
|
+
>
|
|
69
|
+
<Tbody>
|
|
70
|
+
{items.map((lock, index) => (
|
|
71
|
+
<Tr
|
|
72
|
+
key={`${lock.resource_type}-${lock.resource_id}-${index}`}
|
|
73
|
+
ouiaId={`${ouiaSectionId}-row-${index}`}
|
|
74
|
+
>
|
|
75
|
+
<Td>
|
|
76
|
+
<Flex
|
|
77
|
+
alignItems={{ default: 'alignItemsCenter' }}
|
|
78
|
+
spaceItems={{ default: 'spaceItemsSm' }}
|
|
79
|
+
flexWrap={{ default: 'nowrap' }}
|
|
80
|
+
>
|
|
81
|
+
<FlexItem>
|
|
82
|
+
<Icon size="sm">
|
|
83
|
+
<RowIcon />
|
|
84
|
+
</Icon>
|
|
85
|
+
</FlexItem>
|
|
86
|
+
<FlexItem>
|
|
87
|
+
{lock.link ? (
|
|
88
|
+
<a
|
|
89
|
+
href={lock.link}
|
|
90
|
+
data-ouia-component-id={`${ouiaSectionId}-resource-type-link-${index}`}
|
|
91
|
+
>
|
|
92
|
+
{lock.resource_type}
|
|
93
|
+
</a>
|
|
94
|
+
) : (
|
|
95
|
+
lock.resource_type
|
|
96
|
+
)}
|
|
97
|
+
</FlexItem>
|
|
98
|
+
</Flex>
|
|
99
|
+
</Td>
|
|
100
|
+
<Td>{sprintf(__('id: %s'), String(lock.resource_id))}</Td>
|
|
101
|
+
</Tr>
|
|
102
|
+
))}
|
|
103
|
+
</Tbody>
|
|
104
|
+
</Table>
|
|
105
|
+
</GridItem>
|
|
106
|
+
</Grid>
|
|
107
|
+
</FlexItem>
|
|
108
|
+
</Flex>
|
|
109
|
+
);
|
|
8
110
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
111
|
+
LocksSection.propTypes = {
|
|
112
|
+
title: PropTypes.string.isRequired,
|
|
113
|
+
description: PropTypes.string.isRequired,
|
|
114
|
+
items: PropTypes.array.isRequired,
|
|
115
|
+
RowIcon: PropTypes.elementType.isRequired,
|
|
116
|
+
ouiaSectionId: PropTypes.string.isRequired,
|
|
12
117
|
};
|
|
13
118
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
119
|
+
const Locks = ({ locks }) => {
|
|
120
|
+
const nonExclusive = locks.filter(l => !l.exclusive);
|
|
121
|
+
const exclusive = locks.filter(l => l.exclusive);
|
|
17
122
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
123
|
+
if (locks.length === 0) {
|
|
124
|
+
return (
|
|
125
|
+
<EmptyState variant={EmptyStateVariant.lg}>
|
|
126
|
+
<EmptyStateHeader
|
|
127
|
+
headingLevel="h3"
|
|
128
|
+
titleText={__('No resources')}
|
|
129
|
+
icon={<EmptyStateIcon icon={LockOpenIcon} />}
|
|
130
|
+
/>
|
|
131
|
+
<EmptyStateBody>
|
|
132
|
+
{__(
|
|
133
|
+
'No resources currently associated with this task. Locking resources prevents conflicting tasks from running simultaneously. Other tasks must wait until this process completes.'
|
|
134
|
+
)}
|
|
135
|
+
</EmptyStateBody>
|
|
136
|
+
</EmptyState>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<Flex
|
|
142
|
+
direction={{ default: 'column' }}
|
|
143
|
+
gap={{ default: 'gap2xl' }}
|
|
144
|
+
data-ouia-component-id="task-locks-populated"
|
|
145
|
+
>
|
|
146
|
+
{nonExclusive.length > 0 && (
|
|
147
|
+
<LocksSection
|
|
148
|
+
title={__('Non-exclusive resources')}
|
|
149
|
+
description={__(
|
|
150
|
+
"Other tasks can access the resource simultaneously. This lock tracks the task's relationship to the resource without blocking others."
|
|
151
|
+
)}
|
|
152
|
+
items={nonExclusive}
|
|
153
|
+
RowIcon={LockOpenIcon}
|
|
154
|
+
ouiaSectionId="task-locks-non-exclusive"
|
|
155
|
+
/>
|
|
23
156
|
)}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</Col>
|
|
46
|
-
))}
|
|
47
|
-
</Row>
|
|
48
|
-
</Card.Grid>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
157
|
+
{exclusive.length > 0 && (
|
|
158
|
+
<LocksSection
|
|
159
|
+
title={__('Exclusive resources')}
|
|
160
|
+
description={__(
|
|
161
|
+
'Only this task can access the resource. Other tasks must wait until this process completes.'
|
|
162
|
+
)}
|
|
163
|
+
items={exclusive}
|
|
164
|
+
RowIcon={LockIcon}
|
|
165
|
+
ouiaSectionId="task-locks-exclusive"
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
</Flex>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const lockShape = PropTypes.shape({
|
|
173
|
+
exclusive: PropTypes.bool,
|
|
174
|
+
resource_type: PropTypes.string,
|
|
175
|
+
resource_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
176
|
+
link: PropTypes.string,
|
|
177
|
+
});
|
|
51
178
|
|
|
52
179
|
Locks.propTypes = {
|
|
53
|
-
locks: PropTypes.
|
|
180
|
+
locks: PropTypes.arrayOf(lockShape),
|
|
54
181
|
};
|
|
55
182
|
|
|
56
183
|
Locks.defaultProps = {
|
|
@@ -5,16 +5,21 @@ import '@testing-library/jest-dom';
|
|
|
5
5
|
import Dependencies from '../Dependencies';
|
|
6
6
|
|
|
7
7
|
describe('Dependencies', () => {
|
|
8
|
-
it('renders
|
|
8
|
+
it('renders both sections with empty placeholders when there are no tasks', () => {
|
|
9
9
|
render(<Dependencies dependsOn={[]} blocks={[]} />);
|
|
10
|
+
|
|
11
|
+
expect(
|
|
12
|
+
screen.getByRole('heading', { name: /task depends on/i })
|
|
13
|
+
).toBeInTheDocument();
|
|
10
14
|
expect(
|
|
11
|
-
screen.getByRole('heading', { name: /task
|
|
15
|
+
screen.getByRole('heading', { name: /task blocks/i })
|
|
12
16
|
).toBeInTheDocument();
|
|
13
|
-
|
|
14
|
-
expect(
|
|
17
|
+
|
|
18
|
+
expect(screen.getAllByText(/^none$/i)).toHaveLength(2);
|
|
19
|
+
expect(screen.queryByRole('grid')).not.toBeInTheDocument();
|
|
15
20
|
});
|
|
16
21
|
|
|
17
|
-
it('renders
|
|
22
|
+
it('renders depends-on tasks in the first grid', () => {
|
|
18
23
|
const dependsOn = [
|
|
19
24
|
{
|
|
20
25
|
id: '123',
|
|
@@ -31,15 +36,39 @@ describe('Dependencies', () => {
|
|
|
31
36
|
result: 'pending',
|
|
32
37
|
},
|
|
33
38
|
];
|
|
39
|
+
|
|
34
40
|
render(<Dependencies dependsOn={dependsOn} blocks={[]} />);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
expect(
|
|
41
|
+
|
|
42
|
+
const dependsGrid = screen.getByRole('grid', {
|
|
43
|
+
name: /task depends on/i,
|
|
44
|
+
});
|
|
45
|
+
expect(screen.queryAllByRole('grid')).toHaveLength(1);
|
|
46
|
+
|
|
47
|
+
expect(
|
|
48
|
+
within(dependsGrid).getByRole('columnheader', { name: /^name$/i })
|
|
49
|
+
).toBeInTheDocument();
|
|
50
|
+
|
|
51
|
+
const bodyRows = within(dependsGrid)
|
|
52
|
+
.getAllByRole('row')
|
|
53
|
+
.slice(1);
|
|
54
|
+
expect(bodyRows).toHaveLength(2);
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
screen.getByRole('link', { name: 'Foo Bar Action' })
|
|
58
|
+
).toHaveAttribute('href', '/foreman_tasks/tasks/123');
|
|
59
|
+
expect(
|
|
60
|
+
screen.getByRole('link', { name: 'Baz Qux Action' })
|
|
61
|
+
).toHaveAttribute('href', '/foreman_tasks/tasks/456');
|
|
62
|
+
|
|
63
|
+
expect(within(dependsGrid).getByText('Stopped')).toBeInTheDocument();
|
|
64
|
+
expect(within(dependsGrid).getByText('Success')).toBeInTheDocument();
|
|
65
|
+
expect(within(dependsGrid).getByText('Running')).toBeInTheDocument();
|
|
66
|
+
expect(within(dependsGrid).getByText('Pending')).toBeInTheDocument();
|
|
67
|
+
|
|
68
|
+
expect(screen.getAllByText(/^none$/i)).toHaveLength(1);
|
|
40
69
|
});
|
|
41
70
|
|
|
42
|
-
it('renders blocks in the second
|
|
71
|
+
it('renders blocks in the second grid', () => {
|
|
43
72
|
const blocks = [
|
|
44
73
|
{
|
|
45
74
|
id: '789',
|
|
@@ -49,14 +78,28 @@ describe('Dependencies', () => {
|
|
|
49
78
|
result: 'warning',
|
|
50
79
|
},
|
|
51
80
|
];
|
|
81
|
+
|
|
52
82
|
render(<Dependencies dependsOn={[]} blocks={blocks} />);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
expect(
|
|
56
|
-
|
|
83
|
+
|
|
84
|
+
const blocksGrid = screen.getByRole('grid', { name: /task blocks/i });
|
|
85
|
+
expect(screen.queryAllByRole('grid')).toHaveLength(1);
|
|
86
|
+
|
|
87
|
+
const bodyRows = within(blocksGrid)
|
|
88
|
+
.getAllByRole('row')
|
|
89
|
+
.slice(1);
|
|
90
|
+
expect(bodyRows).toHaveLength(1);
|
|
91
|
+
|
|
92
|
+
expect(screen.getByRole('link', { name: 'Test Action' })).toHaveAttribute(
|
|
93
|
+
'href',
|
|
94
|
+
'/foreman_tasks/tasks/789'
|
|
95
|
+
);
|
|
96
|
+
expect(within(blocksGrid).getByText('Paused')).toBeInTheDocument();
|
|
97
|
+
expect(within(blocksGrid).getByText('Warning')).toBeInTheDocument();
|
|
98
|
+
|
|
99
|
+
expect(screen.getAllByText(/^none$/i)).toHaveLength(1);
|
|
57
100
|
});
|
|
58
101
|
|
|
59
|
-
it('renders both
|
|
102
|
+
it('renders both grids when dependsOn and blocks are present', () => {
|
|
60
103
|
const dependsOn = [
|
|
61
104
|
{
|
|
62
105
|
id: '123',
|
|
@@ -82,11 +125,29 @@ describe('Dependencies', () => {
|
|
|
82
125
|
result: 'error',
|
|
83
126
|
},
|
|
84
127
|
];
|
|
128
|
+
|
|
85
129
|
render(<Dependencies dependsOn={dependsOn} blocks={blocks} />);
|
|
86
|
-
|
|
87
|
-
expect(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
expect(
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
screen.getByRole('grid', { name: /task depends on/i })
|
|
133
|
+
).toBeInTheDocument();
|
|
134
|
+
expect(
|
|
135
|
+
screen.getByRole('grid', { name: /task blocks/i })
|
|
136
|
+
).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
expect(screen.getByRole('link', { name: 'Foo Action' })).toHaveAttribute(
|
|
139
|
+
'href',
|
|
140
|
+
'/foreman_tasks/tasks/123'
|
|
141
|
+
);
|
|
142
|
+
expect(screen.getByRole('link', { name: 'Bar Action' })).toHaveAttribute(
|
|
143
|
+
'href',
|
|
144
|
+
'/foreman_tasks/tasks/456'
|
|
145
|
+
);
|
|
146
|
+
expect(screen.getByRole('link', { name: 'Baz Action' })).toHaveAttribute(
|
|
147
|
+
'href',
|
|
148
|
+
'/foreman_tasks/tasks/789'
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(screen.queryByText(/^none$/i)).not.toBeInTheDocument();
|
|
91
152
|
});
|
|
92
153
|
});
|
|
@@ -1,28 +1,261 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
2
4
|
|
|
3
5
|
import Locks from '../Locks';
|
|
4
6
|
|
|
5
|
-
const fixtures = {
|
|
6
|
-
'render without Props': {},
|
|
7
|
-
'render with Props': {
|
|
8
|
-
locks: [
|
|
9
|
-
{
|
|
10
|
-
name: 'task_owner',
|
|
11
|
-
exclusive: false,
|
|
12
|
-
resource_type: 'User',
|
|
13
|
-
resource_id: 4,
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: 'task_owner2',
|
|
17
|
-
exclusive: false,
|
|
18
|
-
resource_type: 'User',
|
|
19
|
-
resource_id: 2,
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
7
|
describe('Locks', () => {
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
it('renders empty state when there are no locks', () => {
|
|
9
|
+
const { container } = render(<Locks locks={[]} />);
|
|
10
|
+
expect(
|
|
11
|
+
screen.getByRole('heading', { name: /no resources/i })
|
|
12
|
+
).toBeInTheDocument();
|
|
13
|
+
expect(
|
|
14
|
+
screen.getByText(
|
|
15
|
+
/no resources currently associated with this task/i
|
|
16
|
+
)
|
|
17
|
+
).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders non-exclusive section with rows when there are only non-exclusive locks', () => {
|
|
21
|
+
render(
|
|
22
|
+
<Locks
|
|
23
|
+
locks={[
|
|
24
|
+
{
|
|
25
|
+
name: 'task_owner',
|
|
26
|
+
exclusive: false,
|
|
27
|
+
resource_type: 'User',
|
|
28
|
+
resource_id: 4,
|
|
29
|
+
link: null,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'task_owner2',
|
|
33
|
+
exclusive: false,
|
|
34
|
+
resource_type: 'User',
|
|
35
|
+
resource_id: 2,
|
|
36
|
+
link: null,
|
|
37
|
+
},
|
|
38
|
+
]}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
expect(
|
|
42
|
+
screen.getByRole('heading', { name: 'Non-exclusive resources' })
|
|
43
|
+
).toBeInTheDocument();
|
|
44
|
+
expect(
|
|
45
|
+
screen.getByText(
|
|
46
|
+
/other tasks can access the resource simultaneously/i
|
|
47
|
+
)
|
|
48
|
+
).toBeInTheDocument();
|
|
49
|
+
expect(screen.getAllByText('User')).toHaveLength(2);
|
|
50
|
+
expect(screen.getByText('id: 4')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByText('id: 2')).toBeInTheDocument();
|
|
52
|
+
expect(
|
|
53
|
+
screen.queryByRole('heading', { name: 'Exclusive resources' })
|
|
54
|
+
).not.toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('renders exclusive section when there are only exclusive locks', () => {
|
|
58
|
+
render(
|
|
59
|
+
<Locks
|
|
60
|
+
locks={[
|
|
61
|
+
{
|
|
62
|
+
name: 'host_lock',
|
|
63
|
+
exclusive: true,
|
|
64
|
+
resource_type: 'Host',
|
|
65
|
+
resource_id: 1,
|
|
66
|
+
link: '/hosts/1',
|
|
67
|
+
},
|
|
68
|
+
]}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
expect(
|
|
72
|
+
screen.getByRole('heading', { name: 'Exclusive resources' })
|
|
73
|
+
).toBeInTheDocument();
|
|
74
|
+
expect(
|
|
75
|
+
screen.getByText(
|
|
76
|
+
/only this task can access the resource/i
|
|
77
|
+
)
|
|
78
|
+
).toBeInTheDocument();
|
|
79
|
+
expect(screen.getByRole('link', { name: 'Host' })).toHaveAttribute(
|
|
80
|
+
'href',
|
|
81
|
+
'/hosts/1'
|
|
82
|
+
);
|
|
83
|
+
expect(screen.getByText('id: 1')).toBeInTheDocument();
|
|
84
|
+
expect(
|
|
85
|
+
screen.queryByRole('heading', { name: 'Non-exclusive resources' })
|
|
86
|
+
).not.toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('renders both sections when locks are mixed', () => {
|
|
90
|
+
render(
|
|
91
|
+
<Locks
|
|
92
|
+
locks={[
|
|
93
|
+
{
|
|
94
|
+
name: 'a',
|
|
95
|
+
exclusive: false,
|
|
96
|
+
resource_type: 'Smart proxy',
|
|
97
|
+
resource_id: 7,
|
|
98
|
+
link: null,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'b',
|
|
102
|
+
exclusive: true,
|
|
103
|
+
resource_type: 'Host managed',
|
|
104
|
+
resource_id: 1,
|
|
105
|
+
link: null,
|
|
106
|
+
},
|
|
107
|
+
]}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
expect(
|
|
111
|
+
screen.getByRole('heading', { name: 'Non-exclusive resources' })
|
|
112
|
+
).toBeInTheDocument();
|
|
113
|
+
expect(
|
|
114
|
+
screen.getByRole('heading', { name: 'Exclusive resources' })
|
|
115
|
+
).toBeInTheDocument();
|
|
116
|
+
expect(
|
|
117
|
+
screen.getByText(
|
|
118
|
+
/other tasks can access the resource simultaneously/i
|
|
119
|
+
)
|
|
120
|
+
).toBeInTheDocument();
|
|
121
|
+
expect(
|
|
122
|
+
screen.getByText(/only this task can access the resource/i)
|
|
123
|
+
).toBeInTheDocument();
|
|
124
|
+
expect(screen.getByText('Smart proxy')).toBeInTheDocument();
|
|
125
|
+
expect(screen.getByText('Host managed')).toBeInTheDocument();
|
|
126
|
+
expect(screen.getByText('id: 7')).toBeInTheDocument();
|
|
127
|
+
expect(screen.getByText('id: 1')).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('renders a link for non-exclusive locks when lock.link is set', () => {
|
|
131
|
+
render(
|
|
132
|
+
<Locks
|
|
133
|
+
locks={[
|
|
134
|
+
{
|
|
135
|
+
name: 'proxy',
|
|
136
|
+
exclusive: false,
|
|
137
|
+
resource_type: 'Smart proxy',
|
|
138
|
+
resource_id: 7,
|
|
139
|
+
link: '/smart_proxies/7',
|
|
140
|
+
},
|
|
141
|
+
]}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
const link = screen.getByRole('link', { name: 'Smart proxy' });
|
|
145
|
+
expect(link).toHaveAttribute('href', '/smart_proxies/7');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('formats string resource_id values in the row label', () => {
|
|
149
|
+
render(
|
|
150
|
+
<Locks
|
|
151
|
+
locks={[
|
|
152
|
+
{
|
|
153
|
+
name: 'x',
|
|
154
|
+
exclusive: false,
|
|
155
|
+
resource_type: 'Custom',
|
|
156
|
+
resource_id: 'uuid-abc',
|
|
157
|
+
link: null,
|
|
158
|
+
},
|
|
159
|
+
]}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
expect(screen.getByText('id: uuid-abc')).toBeInTheDocument();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('sets ouia ids on populated container, tables, rows, and resource links', () => {
|
|
166
|
+
const { container } = render(
|
|
167
|
+
<Locks
|
|
168
|
+
locks={[
|
|
169
|
+
{
|
|
170
|
+
name: 'ne',
|
|
171
|
+
exclusive: false,
|
|
172
|
+
resource_type: 'A',
|
|
173
|
+
resource_id: 1,
|
|
174
|
+
link: '/non-exclusive/1',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'ex',
|
|
178
|
+
exclusive: true,
|
|
179
|
+
resource_type: 'B',
|
|
180
|
+
resource_id: 2,
|
|
181
|
+
link: '/exclusive/2',
|
|
182
|
+
},
|
|
183
|
+
]}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
expect(
|
|
187
|
+
container.querySelector('[data-ouia-component-id="task-locks-populated"]')
|
|
188
|
+
).toBeInTheDocument();
|
|
189
|
+
expect(
|
|
190
|
+
container.querySelector(
|
|
191
|
+
'[data-ouia-component-id="task-locks-non-exclusive-table"]'
|
|
192
|
+
)
|
|
193
|
+
).toBeInTheDocument();
|
|
194
|
+
expect(
|
|
195
|
+
container.querySelector(
|
|
196
|
+
'[data-ouia-component-id="task-locks-exclusive-table"]'
|
|
197
|
+
)
|
|
198
|
+
).toBeInTheDocument();
|
|
199
|
+
expect(
|
|
200
|
+
container.querySelector(
|
|
201
|
+
'[data-ouia-component-id="task-locks-non-exclusive-row-0"]'
|
|
202
|
+
)
|
|
203
|
+
).toBeInTheDocument();
|
|
204
|
+
expect(
|
|
205
|
+
container.querySelector(
|
|
206
|
+
'[data-ouia-component-id="task-locks-exclusive-row-0"]'
|
|
207
|
+
)
|
|
208
|
+
).toBeInTheDocument();
|
|
209
|
+
expect(
|
|
210
|
+
container.querySelector(
|
|
211
|
+
'[data-ouia-component-id="task-locks-non-exclusive-resource-type-link-0"]'
|
|
212
|
+
)
|
|
213
|
+
).toBeInTheDocument();
|
|
214
|
+
expect(
|
|
215
|
+
container.querySelector(
|
|
216
|
+
'[data-ouia-component-id="task-locks-exclusive-resource-type-link-0"]'
|
|
217
|
+
)
|
|
218
|
+
).toBeInTheDocument();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('does not render resource_type as a link when lock.link is absent', () => {
|
|
222
|
+
render(
|
|
223
|
+
<Locks
|
|
224
|
+
locks={[
|
|
225
|
+
{
|
|
226
|
+
name: 'ex',
|
|
227
|
+
exclusive: true,
|
|
228
|
+
resource_type: 'Host managed',
|
|
229
|
+
resource_id: 1,
|
|
230
|
+
link: null,
|
|
231
|
+
},
|
|
232
|
+
]}
|
|
233
|
+
/>
|
|
234
|
+
);
|
|
235
|
+
expect(
|
|
236
|
+
screen.queryByRole('link', { name: 'Host managed' })
|
|
237
|
+
).not.toBeInTheDocument();
|
|
238
|
+
expect(screen.getByText('Host managed')).toBeInTheDocument();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('does not set resource-type link ouia id when lock.link is absent', () => {
|
|
242
|
+
const { container } = render(
|
|
243
|
+
<Locks
|
|
244
|
+
locks={[
|
|
245
|
+
{
|
|
246
|
+
name: 'ne',
|
|
247
|
+
exclusive: false,
|
|
248
|
+
resource_type: 'Thing',
|
|
249
|
+
resource_id: 1,
|
|
250
|
+
link: null,
|
|
251
|
+
},
|
|
252
|
+
]}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
expect(
|
|
256
|
+
container.querySelector(
|
|
257
|
+
'[data-ouia-component-id="task-locks-non-exclusive-resource-type-link-0"]'
|
|
258
|
+
)
|
|
259
|
+
).not.toBeInTheDocument();
|
|
260
|
+
});
|
|
28
261
|
});
|
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: 12.2.
|
|
4
|
+
version: 12.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ivan Nečas
|
|
@@ -389,7 +389,6 @@ files:
|
|
|
389
389
|
- webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js
|
|
390
390
|
- webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js
|
|
391
391
|
- webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js
|
|
392
|
-
- webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap
|
|
393
392
|
- webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js
|
|
394
393
|
- webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss
|
|
395
394
|
- webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`Locks rendering render with Props 1`] = `
|
|
4
|
-
<div>
|
|
5
|
-
<Alert
|
|
6
|
-
className=""
|
|
7
|
-
onDismiss={null}
|
|
8
|
-
type="info"
|
|
9
|
-
>
|
|
10
|
-
You can find resource locks on this page. Exclusive lock marked with locked icon means that no other task can use locked resource while this task is running. Non-exclusive lock marked with unlocked icon means other tasks can access the resource freely, it is only used to indicate the relation of this task with the resource
|
|
11
|
-
</Alert>
|
|
12
|
-
<CardGrid
|
|
13
|
-
className=""
|
|
14
|
-
matchHeight={false}
|
|
15
|
-
>
|
|
16
|
-
<Row
|
|
17
|
-
bsClass="row"
|
|
18
|
-
componentClass="div"
|
|
19
|
-
>
|
|
20
|
-
<Col
|
|
21
|
-
bsClass="col"
|
|
22
|
-
componentClass="div"
|
|
23
|
-
key="0"
|
|
24
|
-
md={4}
|
|
25
|
-
sm={4}
|
|
26
|
-
xs={6}
|
|
27
|
-
>
|
|
28
|
-
<ConditionalLink
|
|
29
|
-
link={null}
|
|
30
|
-
>
|
|
31
|
-
<Card
|
|
32
|
-
accented={true}
|
|
33
|
-
aggregated={false}
|
|
34
|
-
aggregatedMini={false}
|
|
35
|
-
cardRef={null}
|
|
36
|
-
className="card-pf-aggregate-status"
|
|
37
|
-
matchHeight={false}
|
|
38
|
-
>
|
|
39
|
-
<CardTitle
|
|
40
|
-
className=""
|
|
41
|
-
>
|
|
42
|
-
<span
|
|
43
|
-
className="fa fa-unlock-alt"
|
|
44
|
-
/>
|
|
45
|
-
User
|
|
46
|
-
</CardTitle>
|
|
47
|
-
<CardBody
|
|
48
|
-
className=""
|
|
49
|
-
>
|
|
50
|
-
id:4
|
|
51
|
-
<br />
|
|
52
|
-
</CardBody>
|
|
53
|
-
</Card>
|
|
54
|
-
</ConditionalLink>
|
|
55
|
-
</Col>
|
|
56
|
-
<Col
|
|
57
|
-
bsClass="col"
|
|
58
|
-
componentClass="div"
|
|
59
|
-
key="1"
|
|
60
|
-
md={4}
|
|
61
|
-
sm={4}
|
|
62
|
-
xs={6}
|
|
63
|
-
>
|
|
64
|
-
<ConditionalLink
|
|
65
|
-
link={null}
|
|
66
|
-
>
|
|
67
|
-
<Card
|
|
68
|
-
accented={true}
|
|
69
|
-
aggregated={false}
|
|
70
|
-
aggregatedMini={false}
|
|
71
|
-
cardRef={null}
|
|
72
|
-
className="card-pf-aggregate-status"
|
|
73
|
-
matchHeight={false}
|
|
74
|
-
>
|
|
75
|
-
<CardTitle
|
|
76
|
-
className=""
|
|
77
|
-
>
|
|
78
|
-
<span
|
|
79
|
-
className="fa fa-unlock-alt"
|
|
80
|
-
/>
|
|
81
|
-
User
|
|
82
|
-
</CardTitle>
|
|
83
|
-
<CardBody
|
|
84
|
-
className=""
|
|
85
|
-
>
|
|
86
|
-
id:2
|
|
87
|
-
<br />
|
|
88
|
-
</CardBody>
|
|
89
|
-
</Card>
|
|
90
|
-
</ConditionalLink>
|
|
91
|
-
</Col>
|
|
92
|
-
</Row>
|
|
93
|
-
</CardGrid>
|
|
94
|
-
</div>
|
|
95
|
-
`;
|
|
96
|
-
|
|
97
|
-
exports[`Locks rendering render without Props 1`] = `
|
|
98
|
-
<div>
|
|
99
|
-
<Alert
|
|
100
|
-
className=""
|
|
101
|
-
onDismiss={null}
|
|
102
|
-
type="info"
|
|
103
|
-
>
|
|
104
|
-
You can find resource locks on this page. Exclusive lock marked with locked icon means that no other task can use locked resource while this task is running. Non-exclusive lock marked with unlocked icon means other tasks can access the resource freely, it is only used to indicate the relation of this task with the resource
|
|
105
|
-
</Alert>
|
|
106
|
-
<CardGrid
|
|
107
|
-
className=""
|
|
108
|
-
matchHeight={false}
|
|
109
|
-
>
|
|
110
|
-
<Row
|
|
111
|
-
bsClass="row"
|
|
112
|
-
componentClass="div"
|
|
113
|
-
/>
|
|
114
|
-
</CardGrid>
|
|
115
|
-
</div>
|
|
116
|
-
`;
|