foreman-tasks 13.0.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/foreman_tasks/troubleshooting_help_generator.rb +1 -1
  3. data/lib/foreman_tasks/version.rb +1 -1
  4. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +8 -70
  5. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +115 -104
  6. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.scss +45 -0
  7. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js +54 -0
  8. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +236 -131
  9. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/Task.test.js +27 -33
  10. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +83 -67
  11. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js +118 -1
  12. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskInfo.test.js +190 -20
  13. data/webpack/ForemanTasks/Components/TaskDetails/ExecutionDetails.js +65 -0
  14. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +48 -51
  15. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +0 -39
  16. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +0 -5
  17. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/ExecutionDetails.test.js +194 -0
  18. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.fixtures.js +99 -0
  19. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/TaskDetails.test.js +129 -8
  20. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.js +174 -0
  21. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsHeader.scss +5 -0
  22. data/webpack/ForemanTasks/Routes/ShowTaskDetails/TaskDetailsPage.js +10 -33
  23. data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsHeader.test.js +142 -0
  24. data/webpack/ForemanTasks/Routes/ShowTaskDetails/__tests__/TaskDetailsPage.test.js +100 -88
  25. data/webpack/ForemanTasks/{Components/TaskDetails → Routes/ShowTaskDetails}/index.js +7 -8
  26. data/webpack/Routes/routes.js +1 -1
  27. data/webpack/Routes/routes.test.js +1 -1
  28. data/webpack/index.js +0 -5
  29. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fa716da28a9b6bf38240aa02e47f9ab4a4ab6ba82f98c5d26f7d6066f3149e4
4
- data.tar.gz: eb7ec125870236abf803a65c132b042fd4360f4de5d7d9a5b2bc718ba8672235
3
+ metadata.gz: 270e537ec64af9327b2598da48e8422a56adc1716e45ee5105cb57c9bd2d1078
4
+ data.tar.gz: da3627e82bdecfcd63516cf3e973461b22762023374ee58ac105e7f9e66c2cf7
5
5
  SHA512:
6
- metadata.gz: 0547b82e39eabef4f7205100733272e5bdb7b619721ea1a9b94259403c1be370a4ee34564825ccbca83ed16767e72634dd2a070f288ceed28627c3c58671d726
7
- data.tar.gz: c27122956334571f9b1cc287e8ec6cf322f5f2931be69780539cc58a7f37cbf7f7c4c81800807fc81a79ca8213d1510c127552492ee942c21a1067eb09258a9e
6
+ metadata.gz: dc37e5ebbdb2aa62a131d2828a619f00c68b1c6c262a78df83671801b7047eeafd2bd31fd43e52fff79d7a9b2bb69267a950bd2e71babc6569c20d55a90caab2
7
+ data.tar.gz: ba180fe7ad221035c14474ed9b1cc62795d2d90ff585fdc0304bda52432e43917829d016abd03e18e68ffcb9654f1a73864695d93fe1219214f7a14d43de0033
@@ -67,7 +67,7 @@ module ForemanTasks
67
67
  i.add_line _('A paused task represents a process that has not finished properly. '\
68
68
  'Any task in paused state can lead to potential inconsistency '\
69
69
  'and needs to be resolved.')
70
- i.add_line _("The recommended approach is to investigate the error messages below and in 'errors' tab, "\
70
+ i.add_line _("The recommended approach is to investigate the error messages below and in 'Execution details' tab, "\
71
71
  'address the primary cause of the issue and resume the task.')
72
72
  if (link = troubleshooting_link)
73
73
  i.add_link(link)
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '13.0.0'.freeze
2
+ VERSION = '13.1.0'.freeze
3
3
  end
@@ -1,75 +1,13 @@
1
- import React, { useState } from 'react';
2
- import PropTypes from 'prop-types';
1
+ import React from 'react';
2
+ import { Stack, StackItem } from '@patternfly/react-core';
3
3
  import TaskInfo from './TaskInfo';
4
- import {
5
- ForceUnlockConfirmationModal,
6
- UnlockConfirmationModal,
7
- } from '../../common/ClickConfirmation';
8
- import { TaskButtons } from './TaskButtons';
9
4
 
10
- const Task = props => {
11
- const {
12
- taskReload,
13
- id,
14
- forceCancelTaskRequest,
15
- unlockTaskRequest,
16
- action,
17
- taskReloadStart,
18
- } = props;
19
- const forceUnlock = () => {
20
- if (!taskReload) {
21
- taskReloadStart(id);
22
- }
23
- forceCancelTaskRequest(id, action);
24
- };
25
- const unlock = () => {
26
- if (!taskReload) {
27
- taskReloadStart(id);
28
- }
29
- unlockTaskRequest(id, action);
30
- };
31
- const [unlockModalOpen, setUnlockModalOpen] = useState(false);
32
- const [forceUnlockModalOpen, setForceUnlockModalOpen] = useState(false);
33
-
34
- return (
35
- <React.Fragment>
36
- <UnlockConfirmationModal
37
- onClick={unlock}
38
- isOpen={unlockModalOpen}
39
- setModalClosed={() => setUnlockModalOpen(false)}
40
- />
41
- <ForceUnlockConfirmationModal
42
- onClick={forceUnlock}
43
- isOpen={forceUnlockModalOpen}
44
- setModalClosed={() => setForceUnlockModalOpen(false)}
45
- />
46
- <TaskButtons
47
- taskReloadStart={taskReloadStart}
48
- setUnlockModalOpen={setUnlockModalOpen}
49
- setForceUnlockModalOpen={setForceUnlockModalOpen}
50
- {...props}
51
- />
5
+ const Task = props => (
6
+ <Stack hasGutter>
7
+ <StackItem>
52
8
  <TaskInfo {...props} />
53
- </React.Fragment>
54
- );
55
- };
56
-
57
- Task.propTypes = {
58
- taskReload: PropTypes.bool,
59
- id: PropTypes.string,
60
- forceCancelTaskRequest: PropTypes.func,
61
- unlockTaskRequest: PropTypes.func,
62
- action: PropTypes.string,
63
- taskReloadStart: PropTypes.func,
64
- };
65
-
66
- Task.defaultProps = {
67
- taskReload: false,
68
- id: '',
69
- forceCancelTaskRequest: () => null,
70
- unlockTaskRequest: () => null,
71
- action: '',
72
- taskReloadStart: () => null,
73
- };
9
+ </StackItem>
10
+ </Stack>
11
+ );
74
12
 
75
13
  export default Task;
@@ -1,9 +1,12 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Button, Icon } from '@patternfly/react-core';
4
- import { SyncAltIcon } from '@patternfly/react-icons';
3
+ import { Button, Flex, FlexItem, Icon } from '@patternfly/react-core';
4
+ import { SimpleDropdown } from '@patternfly/react-templates';
5
+ import { EllipsisVIcon, SyncAltIcon } from '@patternfly/react-icons';
5
6
  import { translate as __ } from 'foremanReact/common/I18n';
6
7
 
8
+ import './TaskButtons.scss';
9
+
7
10
  export const TaskButtons = ({
8
11
  canEdit,
9
12
  dynflowEnableConsole,
@@ -30,120 +33,128 @@ export const TaskButtons = ({
30
33
  ? undefined
31
34
  : `dynflow_enable_console ${__('Setting is off')}`;
32
35
 
36
+ const overflowItems = [
37
+ {
38
+ value: 'reload',
39
+ ouiaId: 'task-buttons-reload-button',
40
+ content: (
41
+ <>
42
+ <Icon>
43
+ <SyncAltIcon className={taskReload ? 'spin' : ''} />
44
+ </Icon>
45
+ &nbsp;
46
+ {taskReload ? __('Stop auto-reloading') : __('Start auto-reloading')}
47
+ </>
48
+ ),
49
+ onClick: taskProgressToggle,
50
+ },
51
+ {
52
+ value: 'resume',
53
+ ouiaId: 'task-buttons-resume-button',
54
+ content: __('Resume'),
55
+ onClick: () => {
56
+ if (!taskReload) {
57
+ taskReloadStart(id);
58
+ }
59
+
60
+ resumeTaskRequest(id, action);
61
+ },
62
+ isDisabled: !canEdit || !resumable,
63
+ tooltip: editActionsTitle,
64
+ },
65
+ ...(parentTask
66
+ ? [
67
+ {
68
+ value: 'parent',
69
+ ouiaId: 'task-buttons-parent-button',
70
+ content: __('Parent task'),
71
+ to: `/foreman_tasks/tasks/${parentTask}`,
72
+ },
73
+ ]
74
+ : []),
75
+ ...(hasSubTasks
76
+ ? [
77
+ {
78
+ value: 'subtasks',
79
+ ouiaId: 'task-buttons-subtask-button',
80
+ content: __('Sub tasks'),
81
+ to: `/foreman_tasks/tasks/${id}/sub_tasks`,
82
+ },
83
+ ]
84
+ : []),
85
+ {
86
+ value: 'unlock',
87
+ ouiaId: 'task-buttons-unlock-button',
88
+ content: __('Unlock'),
89
+ onClick: () => setUnlockModalOpen(true),
90
+ isDisabled: !canEdit || state !== 'paused',
91
+ tooltip: editActionsTitle,
92
+ },
93
+ {
94
+ value: 'force-unlock',
95
+ ouiaId: 'task-buttons-force-unlock-button',
96
+ content: __('Force Unlock'),
97
+ onClick: () => setForceUnlockModalOpen(true),
98
+ isDisabled: !canEdit || state === 'stopped',
99
+ tooltip: editActionsTitle,
100
+ },
101
+ ];
102
+
33
103
  return (
34
- <>
35
- <Button
36
- variant="secondary"
37
- ouiaId="task-buttons-reload-button"
38
- className="reload-button"
39
- size="sm"
40
- onClick={taskProgressToggle}
41
- >
42
- <Icon className={taskReload ? 'spin' : ''}>
43
- <SyncAltIcon />
44
- </Icon>
45
- {taskReload ? __('Stop auto-reloading') : __('Start auto-reloading')}
46
- </Button>
47
- <Button
48
- variant="secondary"
49
- ouiaId="task-buttons-dynflow-button"
50
- className="dynflow-button"
51
- size="sm"
52
- component="a"
53
- href={`/foreman_tasks/dynflow/${externalId}`}
54
- isDisabled={!dynflowEnableConsole}
55
- target="_blank"
56
- rel="noopener noreferrer"
57
- >
58
- <span title={dynflowTitle} data-original-title={dynflowTitle}>
59
- {__('Dynflow console')}
60
- </span>
61
- </Button>
62
- <Button
63
- variant="secondary"
64
- ouiaId="task-buttons-resume-button"
65
- className="resume-button"
66
- size="sm"
67
- title={editActionsTitle}
68
- data-original-title={editActionsTitle}
69
- isDisabled={!canEdit || !resumable}
70
- onClick={() => {
71
- if (!taskReload) {
72
- taskReloadStart(id);
73
- }
74
- resumeTaskRequest(id, action);
75
- }}
76
- >
77
- {__('Resume')}
78
- </Button>
79
- <Button
80
- variant="secondary"
81
- ouiaId="task-buttons-cancel-button"
82
- className="cancel-button"
83
- size="sm"
84
- title={editActionsTitle}
85
- data-original-title={editActionsTitle}
86
- isDisabled={!canEdit || !cancellable}
87
- onClick={() => {
88
- if (!taskReload) {
89
- taskReloadStart(id);
90
- }
91
- cancelTaskRequest(id, action);
92
- }}
93
- >
94
- {__('Cancel')}
95
- </Button>
96
- {parentTask && (
104
+ <Flex
105
+ className="task-buttons"
106
+ display={{ default: 'inlineFlex' }}
107
+ alignItems={{ default: 'alignItemsCenter' }}
108
+ spaceItems={{ default: 'spaceItemsSm' }}
109
+ >
110
+ <FlexItem>
97
111
  <Button
98
112
  variant="secondary"
99
- ouiaId="task-buttons-parent-button"
100
- className="parent-button"
113
+ ouiaId="task-buttons-cancel-button"
114
+ className="cancel-button"
101
115
  size="sm"
102
- href={`/foreman_tasks/tasks/${parentTask}`}
103
- component="a"
116
+ title={editActionsTitle}
117
+ data-original-title={editActionsTitle}
118
+ isDisabled={!canEdit || !cancellable}
119
+ onClick={() => {
120
+ if (!taskReload) {
121
+ taskReloadStart(id);
122
+ }
123
+
124
+ cancelTaskRequest(id, action);
125
+ }}
104
126
  >
105
- {__('Parent task')}
127
+ {__('Cancel')}
106
128
  </Button>
107
- )}
108
- {hasSubTasks && (
129
+ </FlexItem>
130
+ <FlexItem>
109
131
  <Button
110
132
  variant="secondary"
111
- ouiaId="task-buttons-subtask-button"
112
- className="subtask-button"
133
+ ouiaId="task-buttons-dynflow-button"
134
+ className="dynflow-button"
113
135
  size="sm"
114
- href={`/foreman_tasks/tasks/${id}/sub_tasks`}
115
136
  component="a"
137
+ href={`/foreman_tasks/dynflow/${externalId}`}
138
+ isDisabled={!dynflowEnableConsole}
139
+ target="_blank"
140
+ rel="noopener noreferrer"
116
141
  >
117
- {__('Sub tasks')}
142
+ <span title={dynflowTitle} data-original-title={dynflowTitle}>
143
+ {__('Dynflow console')}
144
+ </span>
118
145
  </Button>
119
- )}
120
- <Button
121
- variant="secondary"
122
- ouiaId="task-buttons-unlock-button"
123
- className="unlock-button"
124
- size="sm"
125
- isDisabled={!canEdit || state !== 'paused'}
126
- onClick={() => {
127
- setUnlockModalOpen(true);
128
- }}
129
- title={editActionsTitle}
130
- data-original-title={editActionsTitle}
131
- >
132
- {__('Unlock')}
133
- </Button>
134
- <Button
135
- variant="secondary"
136
- ouiaId="task-buttons-force-unlock-button"
137
- className="force-unlock-button"
138
- size="sm"
139
- isDisabled={!canEdit || state === 'stopped'}
140
- onClick={() => setForceUnlockModalOpen(true)}
141
- title={editActionsTitle}
142
- data-original-title={editActionsTitle}
143
- >
144
- {__('Force Unlock')}
145
- </Button>
146
- </>
146
+ </FlexItem>
147
+ <FlexItem>
148
+ <SimpleDropdown
149
+ ouiaId="task-buttons-overflow-dropdown"
150
+ toggleVariant="plain"
151
+ toggleContent={<EllipsisVIcon aria-hidden="true" />}
152
+ popperProps={{ position: 'right' }}
153
+ initialItems={overflowItems}
154
+ toggleProps={{ 'aria-label': __('Task actions') }}
155
+ />
156
+ </FlexItem>
157
+ </Flex>
147
158
  );
148
159
  };
149
160
 
@@ -0,0 +1,45 @@
1
+ .task-buttons {
2
+ .spin {
3
+ -webkit-animation: spin 1s infinite linear;
4
+ -moz-animation: spin 1s infinite linear;
5
+ -o-animation: spin 1s infinite linear;
6
+ animation: spin 1s infinite linear;
7
+ -webkit-transform-origin: 50% 50%;
8
+ transform-origin: 50% 50%;
9
+ -ms-transform-origin: 50% 50%; /* IE 9 */
10
+ }
11
+
12
+ @-moz-keyframes spin {
13
+ from {
14
+ -moz-transform: rotate(0deg);
15
+ }
16
+
17
+ to {
18
+ -moz-transform: rotate(360deg);
19
+ }
20
+ }
21
+
22
+ @-webkit-keyframes spin {
23
+ from {
24
+ -webkit-transform: rotate(0deg);
25
+ }
26
+
27
+ to {
28
+ -webkit-transform: rotate(360deg);
29
+ }
30
+ }
31
+
32
+ @keyframes spin {
33
+ from {
34
+ transform: rotate(0deg);
35
+ }
36
+
37
+ to {
38
+ transform: rotate(360deg);
39
+ }
40
+ }
41
+
42
+ .dynflow-button > span {
43
+ pointer-events: auto;
44
+ }
45
+ }
@@ -22,3 +22,57 @@ export const durationInWords = (
22
22
 
23
23
  const numberWithDelimiter = x =>
24
24
  x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
25
+
26
+ export const isDelayed = ({ startAt, startedAt }) => {
27
+ if (
28
+ startAt == null ||
29
+ startedAt == null ||
30
+ startAt === '' ||
31
+ startedAt === ''
32
+ ) {
33
+ return false;
34
+ }
35
+
36
+ const a = new Date(startAt);
37
+ const b = new Date(startedAt);
38
+
39
+ if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime())) {
40
+ return false;
41
+ }
42
+
43
+ a.setMilliseconds(0);
44
+ b.setMilliseconds(0);
45
+
46
+ return a.getTime() !== b.getTime();
47
+ };
48
+
49
+ export const parseUsernameLinkHref = usernamePath => {
50
+ if (!usernamePath || typeof usernamePath !== 'string') {
51
+ return null;
52
+ }
53
+
54
+ const match = usernamePath.match(/href=(["'])(.*?)\1/i);
55
+
56
+ return match ? match[2] : null;
57
+ };
58
+
59
+ export const formatTaskDuration = (startedAtStr, endedAtStr, state) => {
60
+ if (state !== 'stopped' || !startedAtStr || !endedAtStr) {
61
+ return __('N/A');
62
+ }
63
+
64
+ const startMs = new Date(startedAtStr).getTime();
65
+ const endMs = new Date(endedAtStr).getTime();
66
+
67
+ if (Number.isNaN(startMs) || Number.isNaN(endMs) || endMs < startMs) {
68
+ return __('N/A');
69
+ }
70
+
71
+ const duration = durationInWords(startedAtStr, endedAtStr);
72
+
73
+ if (typeof duration === 'string') {
74
+ return duration;
75
+ }
76
+
77
+ return duration.text;
78
+ };