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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3ab9dfe5221351a1c392ddc1ba3c9c8693ba9c7dd6ceea8659aabc30de78d56
4
- data.tar.gz: d666650ff5f3073c9c66e3c37ec6d9c2d28ac08e3abd18f13157261cf0e511b6
3
+ metadata.gz: 79e3003ee203359edba19b75adac7c38435b94081969faba3ad211601c559345
4
+ data.tar.gz: b3da633082735d6adf5a5d569744d3d5a6fa1d18d05568e7bc048b922205c674
5
5
  SHA512:
6
- metadata.gz: 99b91eb15001bc4d8d93605b1e7ffff2d39aab63ae044259d474f84827cf9888233d3e7b7b107e32ab63a7145ed419f5ac163a5c8d8a13857bfa6a2dc2fc935e
7
- data.tar.gz: f95f6d9169b3b442fe9a4c8a20a3426984d997365fb9c7bf2708828e4f7e8ca634f78f70b8f07d02f9e22e92ed597b6f59d4140f36bb3661532f459adfd4baa3
6
+ metadata.gz: 6614edd5d7578167024f5df26bc5f950862dd9a2411a75cfd3f591b19755acbc40a1ca4321eff752800023923bb574d0095aa8c81ab4fa953e030999cf3d4547
7
+ data.tar.gz: fcdd9f699f2a55d3e27bf014f8dbee0dd0e92e3d4f6b6b6921f0a4ba9841cf2cd8aa2392d0376667fa34a85ec41b53ad6e6ff25dae9443fcee9bd625350e82f2
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '12.2.1'.freeze
2
+ VERSION = '12.2.2'.freeze
3
3
  end
@@ -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
- const tableId = title.toLowerCase().replace(/\s+/g, '-');
15
- return (
16
- <div>
17
- <Title headingLevel="h4" size="md" ouiaId={`${tableId}-title`}>
18
- {title}
19
- </Title>
20
- {tasks.length === 0 ? (
21
- <p className="text-muted">{__('None')}</p>
22
- ) : (
23
- <Table aria-label={title} variant="compact" ouiaId={`${tableId}-table`}>
24
- <Thead>
25
- <Tr ouiaId={`${tableId}-table-header`}>
26
- <Th width={50}>{__('Action')}</Th>
27
- <Th width={25}>{__('State')}</Th>
28
- <Th width={25}>{__('Result')}</Th>
29
- </Tr>
30
- </Thead>
31
- <Tbody>
32
- {tasks.map(task => (
33
- <Tr key={task.id} ouiaId={`${tableId}-table-row-${task.id}`}>
34
- <Td>
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
- </Tbody>
44
- </Table>
45
- )}
46
- </div>
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
- <div>
61
- <Alert
62
- variant={AlertVariant.info}
63
- isInline
64
- title={__('Task dependencies')}
65
- ouiaId="task-dependencies-info-alert"
66
- >
67
- {__(
68
- 'This task may have dependencies on other tasks or may be blocking other tasks from executing. Dependencies are established through task chaining relationships.'
69
- )}
70
- </Alert>
71
- <br />
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 { Alert, Card, Row, Col } from 'patternfly-react';
4
- import { translate as __ } from 'foremanReact/common/I18n';
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 ConditionalLink = ({ children, link }) =>
7
- link ? <a href={link}>{children}</a> : children;
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
- ConditionalLink.propTypes = {
10
- children: PropTypes.node.isRequired,
11
- link: PropTypes.string,
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
- ConditionalLink.defaultProps = {
15
- link: null,
16
- };
119
+ const Locks = ({ locks }) => {
120
+ const nonExclusive = locks.filter(l => !l.exclusive);
121
+ const exclusive = locks.filter(l => l.exclusive);
17
122
 
18
- const Locks = ({ locks }) => (
19
- <div>
20
- <Alert type="info">
21
- {__(
22
- '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'
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
- </Alert>
25
- <Card.Grid>
26
- <Row>
27
- {locks.map((lock, key) => (
28
- <Col xs={6} sm={4} md={4} key={key}>
29
- <ConditionalLink link={lock.link}>
30
- <Card className="card-pf-aggregate-status" accented>
31
- <Card.Title>
32
- <span
33
- className={`fa ${
34
- lock.exclusive ? 'fa-lock' : 'fa-unlock-alt'
35
- }`}
36
- />
37
- {lock.resource_type}
38
- </Card.Title>
39
- <Card.Body>
40
- {`id:${lock.resource_id}`}
41
- <br />
42
- </Card.Body>
43
- </Card>
44
- </ConditionalLink>
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.array,
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 info alert and None for both tables when there are no tasks', () => {
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 dependencies/i })
15
+ screen.getByRole('heading', { name: /task blocks/i })
12
16
  ).toBeInTheDocument();
13
- const noneLabels = screen.getAllByText(/^none$/i);
14
- expect(noneLabels.length).toBeGreaterThanOrEqual(2);
17
+
18
+ expect(screen.getAllByText(/^none$/i)).toHaveLength(2);
19
+ expect(screen.queryByRole('grid')).not.toBeInTheDocument();
15
20
  });
16
21
 
17
- it('renders depends_on tasks in the first table', () => {
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
- const table = screen.getByRole('grid', { name: /depends on/i });
36
- expect(within(table).getByText('Foo Bar Action')).toBeInTheDocument();
37
- expect(within(table).getByText('Baz Qux Action')).toBeInTheDocument();
38
- expect(within(table).getByText('stopped')).toBeInTheDocument();
39
- expect(within(table).getByText('success')).toBeInTheDocument();
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 table', () => {
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
- const table = screen.getByRole('grid', { name: /^blocks$/i });
54
- expect(within(table).getByText('Test Action')).toBeInTheDocument();
55
- expect(within(table).getByText('paused')).toBeInTheDocument();
56
- expect(within(table).getByText('warning')).toBeInTheDocument();
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 tables when dependsOn and blocks are present', () => {
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
- expect(screen.getByRole('grid', { name: /depends on/i })).toBeInTheDocument();
87
- expect(screen.getByText('Foo Action')).toBeInTheDocument();
88
- expect(screen.getByRole('grid', { name: /^blocks$/i })).toBeInTheDocument();
89
- expect(screen.getByText('Bar Action')).toBeInTheDocument();
90
- expect(screen.getByText('Baz Action')).toBeInTheDocument();
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 { testComponentSnapshotsWithFixtures } from '@theforeman/test';
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
- describe('rendering', () =>
27
- testComponentSnapshotsWithFixtures(Locks, fixtures));
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.1
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
- `;