foreman-tasks 12.2.1 → 12.2.3

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: 200260a81901f7159c565c8188aa71de7b80bc0583b7cf3bf6432a2541130567
4
+ data.tar.gz: 3cb1afe2a7706fe328155ac0b32da0864155dfcb9e97a7b026283ba020f41489
5
5
  SHA512:
6
- metadata.gz: 99b91eb15001bc4d8d93605b1e7ffff2d39aab63ae044259d474f84827cf9888233d3e7b7b107e32ab63a7145ed419f5ac163a5c8d8a13857bfa6a2dc2fc935e
7
- data.tar.gz: f95f6d9169b3b442fe9a4c8a20a3426984d997365fb9c7bf2708828e4f7e8ca634f78f70b8f07d02f9e22e92ed597b6f59d4140f36bb3661532f459adfd4baa3
6
+ metadata.gz: 26157491f3be0ce0e7386008c492476db1e49d28d28934f1139daec25771626d3e7813c3e858b501020f719930ad4fee3fcf6a54850bb661d4104b0628676993
7
+ data.tar.gz: d295b0470a35efb6a20801431614256612fdb16fb9f85f6c2c79ffdfaf0bd384656986d82f9d1bf0072b68366420884fbb552638152ccf9ca397f4c8a20a14c8
data/config/routes.rb CHANGED
@@ -36,7 +36,6 @@ Foreman::Application.routes.draw do
36
36
 
37
37
  match '/tasks', to: '/react#index', via: :get
38
38
  match '/tasks/:id/sub_tasks', to: '/react#index', via: :get
39
- match '/ex_tasks/:id', to: '/react#index', via: :get
40
39
 
41
40
  namespace :api do
42
41
  resources :recurring_logics, :only => [:index, :show, :update] do
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '12.2.1'.freeze
2
+ VERSION = '12.2.3'.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 = {
@@ -1,68 +1,174 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Alert, AlertVariant, Button } from '@patternfly/react-core';
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
- if (!runningSteps.length) return <span>{__('No running steps')}</span>;
14
- return (
15
- <div>
16
- {runningSteps.map((step, i) => (
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
- key={step.id || i}
21
- ouiaId={`running-step-${i}`}
22
- title={sprintf(__('Running step %s'), i + 1)}
59
+ ouiaId="running-steps-suspended-pending"
60
+ title={__('Temporarily suspended step(s)')}
23
61
  >
24
- {step.cancellable && (
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
- </div>
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;