foreman_rh_cloud 13.0.9 → 13.0.10

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/inventory_upload/report_actions.rb +8 -1
  3. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +82 -7
  4. data/app/controllers/foreman_inventory_upload/api/tasks_controller.rb +110 -0
  5. data/app/controllers/foreman_inventory_upload/reports_controller.rb +41 -17
  6. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -9
  7. data/config/routes.rb +4 -2
  8. data/db/migrate/20251209163012_drop_task_output_tables.foreman_rh_cloud.rb +24 -0
  9. data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +1 -1
  10. data/lib/foreman_inventory_upload/async/host_inventory_report_job.rb +39 -0
  11. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +28 -57
  12. data/lib/foreman_rh_cloud/plugin.rb +1 -0
  13. data/lib/foreman_rh_cloud/version.rb +1 -1
  14. data/lib/tasks/rh_cloud_inventory.rake +4 -2
  15. data/package.json +1 -1
  16. data/test/controllers/accounts_controller_test.rb +10 -11
  17. data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +1 -2
  18. data/test/jobs/host_inventory_report_job_test.rb +161 -97
  19. data/test/jobs/single_host_report_job_test.rb +36 -54
  20. data/test/jobs/upload_report_direct_job_test.rb +132 -132
  21. data/test/unit/rh_cloud_permissions_test.rb +2 -0
  22. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +6 -6
  23. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +49 -34
  24. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListActions.js +2 -2
  25. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +2 -2
  26. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +15 -9
  27. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +6 -5
  28. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.fixtures.js +2 -2
  29. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.js +10 -14
  30. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatusHelper.js +9 -4
  31. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +4 -4
  32. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +15 -9
  33. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +7 -7
  34. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +6 -6
  35. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +12 -12
  36. data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +36 -132
  37. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/Dashboard.test.js +60 -17
  38. data/webpack/ForemanInventoryUpload/Components/Dashboard/index.js +1 -34
  39. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -1
  40. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +1 -26
  41. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +2 -2
  42. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +22 -9
  43. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +67 -4
  44. data/webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js +140 -0
  45. data/webpack/ForemanInventoryUpload/Components/TaskHistory/index.js +1 -0
  46. data/webpack/ForemanInventoryUpload/Components/TaskHistory/taskHistory.scss +40 -0
  47. data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +340 -0
  48. data/webpack/ForemanInventoryUpload/Components/TaskProgress/index.js +1 -0
  49. data/webpack/ForemanInventoryUpload/Components/TaskProgress/taskProgress.scss +8 -0
  50. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +2 -2
  51. data/webpack/ForemanInventoryUpload/ForemanInventoryUploadReducers.js +0 -2
  52. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +1 -1
  53. data/webpack/ForemanRhCloudPages.js +0 -1
  54. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +1 -2
  55. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -4
  56. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +14 -0
  57. data/webpack/__tests__/ForemanRhCloudHelpers.test.js +5 -1
  58. metadata +10 -61
  59. data/app/models/task_output_line.rb +0 -2
  60. data/app/models/task_output_status.rb +0 -2
  61. data/lib/foreman_inventory_upload/async/generate_report_job.rb +0 -61
  62. data/lib/foreman_inventory_upload/async/progress_output.rb +0 -38
  63. data/lib/foreman_inventory_upload/async/shell_process.rb +0 -77
  64. data/test/controllers/reports_controller_test.rb +0 -21
  65. data/test/controllers/uploads_controller_test.rb +0 -21
  66. data/test/jobs/generate_report_job_test.rb +0 -146
  67. data/test/unit/shell_process_job_test.rb +0 -29
  68. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardActions.js +0 -88
  69. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardConstants.js +0 -9
  70. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardReducer.js +0 -68
  71. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardSelectors.js +0 -17
  72. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardActions.test.js +0 -51
  73. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardIntegration.test.js +0 -17
  74. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardReducer.test.js +0 -64
  75. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardSelectors.test.js +0 -46
  76. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/Dashboard.test.js.snap +0 -36
  77. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardActions.test.js.snap +0 -76
  78. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardReducer.test.js.snap +0 -44
  79. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardSelectors.test.js.snap +0 -42
  80. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.fixtures.js +0 -0
  81. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.js +0 -55
  82. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModalHelper.js +0 -0
  83. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/FullScreenModal.test.js +0 -13
  84. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/__snapshots__/FullScreenModal.test.js.snap +0 -65
  85. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/fullScreenModal.scss +0 -20
  86. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/index.js +0 -1
  87. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.fixtures.js +0 -18
  88. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.js +0 -65
  89. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerateHelper.js +0 -0
  90. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/ReportGenerate.test.js +0 -14
  91. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/__snapshots__/ReportGenerate.test.js.snap +0 -47
  92. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/index.js +0 -1
  93. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/reportGenerate.scss +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.fixtures.js +0 -18
  95. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.js +0 -46
  96. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUploadHelper.js +0 -0
  97. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/ReportUpload.test.js +0 -14
  98. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/__snapshots__/ReportUpload.test.js.snap +0 -47
  99. data/webpack/ForemanInventoryUpload/Components/ReportUpload/index.js +0 -1
  100. data/webpack/ForemanInventoryUpload/Components/ReportUpload/reportUpload.scss +0 -0
  101. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.fixtures.js +0 -0
  102. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.js +0 -31
  103. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBodyHelper.js +0 -0
  104. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/TabBody.test.js +0 -13
  105. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/__snapshots__/TabBody.test.js.snap +0 -19
  106. data/webpack/ForemanInventoryUpload/Components/TabBody/index.js +0 -1
  107. data/webpack/ForemanInventoryUpload/Components/TabBody/tabBody.scss +0 -5
  108. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.fixtures.js +0 -10
  109. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.js +0 -110
  110. data/webpack/ForemanInventoryUpload/Components/Terminal/TerminalHelper.js +0 -6
  111. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/Terminal.test.js +0 -34
  112. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/__snapshots__/Terminal.test.js.snap +0 -98
  113. data/webpack/ForemanInventoryUpload/Components/Terminal/index.js +0 -1
  114. data/webpack/ForemanInventoryUpload/Components/Terminal/terminal.scss +0 -32
@@ -8,19 +8,12 @@ import {
8
8
  TabContent,
9
9
  TabPane,
10
10
  Icon,
11
- noop,
12
11
  } from 'patternfly-react';
13
12
  import { translate as __ } from 'foremanReact/common/I18n';
14
13
  import './navContainer.scss';
15
- import FullScreenModal from '../FullScreenModal';
16
14
  import { selectSubscriptionConnectionEnabled } from '../InventorySettings/InventorySettingsSelectors';
17
15
 
18
- const NavContainer = ({
19
- items,
20
- showFullScreen,
21
- toggleFullScreen,
22
- terminalProps,
23
- }) => {
16
+ const NavContainer = ({ items }) => {
24
17
  const subscriptionConnectionEnabled = useSelector(
25
18
  selectSubscriptionConnectionEnabled
26
19
  );
@@ -50,11 +43,6 @@ const NavContainer = ({
50
43
  <div className="dashboard">
51
44
  <Nav bsClass="nav nav-tabs nav-tabs-pf">{navItems}</Nav>
52
45
  <TabContent animation>{tabComponents}</TabContent>
53
- <FullScreenModal
54
- showFullScreen={showFullScreen}
55
- toggleFullScreen={toggleFullScreen}
56
- terminalProps={terminalProps}
57
- />
58
46
  </div>
59
47
  </TabContainer>
60
48
  );
@@ -69,23 +57,10 @@ NavContainer.propTypes = {
69
57
  onClick: PropTypes.func,
70
58
  })
71
59
  ),
72
- showFullScreen: PropTypes.bool,
73
- toggleFullScreen: PropTypes.func,
74
- terminalProps: PropTypes.shape({
75
- exitCode: PropTypes.string,
76
- logs: PropTypes.oneOfType([
77
- PropTypes.arrayOf(PropTypes.string),
78
- PropTypes.string,
79
- ]),
80
- error: PropTypes.string,
81
- }),
82
60
  };
83
61
 
84
62
  NavContainer.defaultProps = {
85
63
  items: [],
86
- showFullScreen: false,
87
- toggleFullScreen: noop,
88
- terminalProps: {},
89
64
  };
90
65
 
91
66
  export default NavContainer;
@@ -24,7 +24,7 @@ exports[`PageTitle rendering render without Props 1`] = `
24
24
  dropdownItems={
25
25
  Array [
26
26
  <DropdownItem
27
- href="/foreman_tasks/tasks?search=label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AGenerateReportJob+or+label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AGenerateAllReportsJob&page=1"
27
+ href="/foreman_tasks/tasks?search=label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AHostInventoryReportJob+or+label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AGenerateAllReportsJob&page=1"
28
28
  ouiaId="tasks-history-button"
29
29
  rel="noopener noreferrer"
30
30
  target="_blank"
@@ -32,7 +32,7 @@ exports[`PageTitle rendering render without Props 1`] = `
32
32
  Actions history
33
33
  </DropdownItem>,
34
34
  <DropdownItem
35
- href="/links/manual/?root_url=https%3A%2F%2Faccess.redhat.com%2Fdocumentation%2Fen-us%2Fred_hat_insights%2F2023%2Fhtml%2Fred_hat_insights_remediations_guide%2Fhost-communication-with-insights_red-hat-insights-remediation-guide%23uploading-satellite-host-inventory-to-insights_configuring-satellite-cloud-connector"
35
+ href="/links/manual/?root_url=https%3A%2F%2Fdocs.redhat.com%2Fen%2Fdocumentation%2Fred_hat_lightspeed%2F1-latest%2Fhtml-single%2Fred_hat_lightspeed_remediations_guide%2Findex"
36
36
  ouiaId="inventory-documentation-button"
37
37
  rel="noopener noreferrer"
38
38
  target="_blank"
@@ -2,10 +2,12 @@ import React from 'react';
2
2
  import { useSelector } from 'react-redux';
3
3
  import PropTypes from 'prop-types';
4
4
  import { Grid, Button, Icon } from 'patternfly-react';
5
+ import { Tooltip } from '@patternfly/react-core';
5
6
  import { noop } from 'foremanReact/common/helpers';
6
7
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
7
8
  import { selectSubscriptionConnectionEnabled } from '../InventorySettings/InventorySettingsSelectors';
8
9
  import { isExitCodeLoading } from '../../ForemanInventoryHelpers';
10
+ import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
9
11
  import './tabHeader.scss';
10
12
 
11
13
  const TabHeader = ({
@@ -18,10 +20,23 @@ const TabHeader = ({
18
20
  const subscriptionConnectionEnabled = useSelector(
19
21
  selectSubscriptionConnectionEnabled
20
22
  );
23
+ const isIop = Boolean(useIopConfig());
21
24
  const buttonGenerateLabel = subscriptionConnectionEnabled
22
25
  ? __('Generate and upload report')
23
26
  : __('Generate report');
24
27
 
28
+ const isUploadDisabled = !isIop && !subscriptionConnectionEnabled;
29
+ const isButtonDisabled = isExitCodeLoading(exitCode) || isUploadDisabled;
30
+ const tooltipContent = __(
31
+ 'Upload is disabled because subscription connection is not enabled. Enable it in Administer > Settings > Content.'
32
+ );
33
+
34
+ const generateButton = onRestart ? (
35
+ <Button bsStyle="primary" onClick={onRestart} disabled={isButtonDisabled}>
36
+ {buttonGenerateLabel}
37
+ </Button>
38
+ ) : null;
39
+
25
40
  return (
26
41
  <Grid.Row className="tab-header">
27
42
  <Grid.Col sm={6}>
@@ -29,15 +44,13 @@ const TabHeader = ({
29
44
  </Grid.Col>
30
45
  <Grid.Col sm={6}>
31
46
  <div className="tab-action-buttons">
32
- {onRestart ? (
33
- <Button
34
- bsStyle="primary"
35
- onClick={onRestart}
36
- disabled={isExitCodeLoading(exitCode)}
37
- >
38
- {buttonGenerateLabel}
39
- </Button>
40
- ) : null}
47
+ {generateButton && isUploadDisabled ? (
48
+ <Tooltip content={tooltipContent}>
49
+ <div style={{ display: 'inline-block' }}>{generateButton}</div>
50
+ </Tooltip>
51
+ ) : (
52
+ generateButton
53
+ )}
41
54
  {onDownload ? (
42
55
  <Button onClick={onDownload} disabled={downloadButtonDisabled()}>
43
56
  {__('Download Report')} <Icon name="download" />
@@ -4,13 +4,21 @@ import configureMockStore from 'redux-mock-store';
4
4
  import thunk from 'redux-thunk';
5
5
  import { screen, render } from '@testing-library/react';
6
6
  import { noop } from 'foremanReact/common/helpers';
7
+ import * as ConfigHooks from '../../../../common/Hooks/ConfigHooks';
7
8
  import TabHeader from '../TabHeader';
8
9
 
9
10
  const middlewares = [thunk];
10
11
  const mockStore = configureMockStore(middlewares);
11
12
 
13
+ jest.mock('../../../../common/Hooks/ConfigHooks');
14
+
12
15
  describe('TabHeader', () => {
13
- test('when subscription connection is enabled', () => {
16
+ beforeEach(() => {
17
+ ConfigHooks.useIopConfig = jest.fn();
18
+ });
19
+
20
+ test('when subscription connection is enabled and not IoP', () => {
21
+ ConfigHooks.useIopConfig.mockReturnValue(false);
14
22
  const renderOptions = {
15
23
  API: {
16
24
  INVENTORY_SETTINGS: {
@@ -20,16 +28,19 @@ describe('TabHeader', () => {
20
28
  };
21
29
  const store = mockStore(renderOptions);
22
30
 
23
- render(
31
+ const { container } = render(
24
32
  <Provider store={store}>
25
33
  <TabHeader exitCode="exit 0" onRestart={noop} toggleFullScreen={noop} />
26
34
  </Provider>
27
35
  );
28
36
  expect(screen.queryAllByText('Generate and upload report')).toHaveLength(1);
29
37
  expect(screen.queryAllByText('Full Screen')).toHaveLength(1);
38
+ const button = container.querySelector('button[class*="btn-primary"]');
39
+ expect(button.hasAttribute('disabled')).toBe(false);
30
40
  });
31
41
 
32
- test('when subscription connection is not enabled', () => {
42
+ test('when subscription connection is not enabled and not IoP - button is disabled with tooltip', () => {
43
+ ConfigHooks.useIopConfig.mockReturnValue(false);
33
44
  const renderOptions = {
34
45
  API: {
35
46
  INVENTORY_SETTINGS: {
@@ -39,7 +50,7 @@ describe('TabHeader', () => {
39
50
  };
40
51
  const store = mockStore(renderOptions);
41
52
 
42
- render(
53
+ const { container } = render(
43
54
  <Provider store={store}>
44
55
  <TabHeader exitCode="exit 0" onRestart={noop} toggleFullScreen={noop} />
45
56
  </Provider>
@@ -47,5 +58,57 @@ describe('TabHeader', () => {
47
58
 
48
59
  expect(screen.queryAllByText('Generate report')).toHaveLength(1);
49
60
  expect(screen.queryAllByText('Full Screen')).toHaveLength(1);
61
+ const button = container.querySelector('button[class*="btn-primary"]');
62
+ expect(button.hasAttribute('disabled')).toBe(true);
63
+
64
+ // Verify button is wrapped in a div for tooltip (Patternfly requirement)
65
+ // The tooltip content prop is passed but not rendered in the DOM until hover
66
+ const wrapper = button.parentElement;
67
+ expect(wrapper.tagName).toBe('DIV');
68
+ expect(wrapper.style.display).toBe('inline-block');
69
+ });
70
+
71
+ test('when subscription connection is not enabled but IoP is enabled - button is enabled', () => {
72
+ ConfigHooks.useIopConfig.mockReturnValue(true);
73
+ const renderOptions = {
74
+ API: {
75
+ INVENTORY_SETTINGS: {
76
+ response: { subscriptionConnectionEnabled: false },
77
+ },
78
+ },
79
+ };
80
+ const store = mockStore(renderOptions);
81
+
82
+ const { container } = render(
83
+ <Provider store={store}>
84
+ <TabHeader exitCode="exit 0" onRestart={noop} toggleFullScreen={noop} />
85
+ </Provider>
86
+ );
87
+
88
+ expect(screen.queryAllByText('Generate report')).toHaveLength(1);
89
+ const button = container.querySelector('button[class*="btn-primary"]');
90
+ expect(button.hasAttribute('disabled')).toBe(false);
91
+ });
92
+
93
+ test('when IoP is enabled and subscription connection is enabled - button is enabled', () => {
94
+ ConfigHooks.useIopConfig.mockReturnValue(true);
95
+ const renderOptions = {
96
+ API: {
97
+ INVENTORY_SETTINGS: {
98
+ response: { subscriptionConnectionEnabled: true },
99
+ },
100
+ },
101
+ };
102
+ const store = mockStore(renderOptions);
103
+
104
+ const { container } = render(
105
+ <Provider store={store}>
106
+ <TabHeader exitCode="exit 0" onRestart={noop} toggleFullScreen={noop} />
107
+ </Provider>
108
+ );
109
+
110
+ expect(screen.queryAllByText('Generate and upload report')).toHaveLength(1);
111
+ const button = container.querySelector('button[class*="btn-primary"]');
112
+ expect(button.hasAttribute('disabled')).toBe(false);
50
113
  });
51
114
  });
@@ -0,0 +1,140 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ DataList,
5
+ DataListItem,
6
+ DataListItemRow,
7
+ DataListItemCells,
8
+ DataListCell,
9
+ EmptyState,
10
+ EmptyStateIcon,
11
+ EmptyStateBody,
12
+ Title,
13
+ } from '@patternfly/react-core';
14
+ import {
15
+ CheckCircleIcon,
16
+ ExclamationCircleIcon,
17
+ ExclamationTriangleIcon,
18
+ } from '@patternfly/react-icons';
19
+ import { translate as __ } from 'foremanReact/common/I18n';
20
+ import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
21
+ import './taskHistory.scss';
22
+
23
+ const TaskHistory = ({ tasks, title }) => {
24
+ if (!tasks || tasks.length === 0) {
25
+ return (
26
+ <EmptyState>
27
+ <EmptyStateIcon icon={CheckCircleIcon} />
28
+ <Title headingLevel="h4" size="lg" ouiaId="task-history-empty-title">
29
+ {__('No task history')}
30
+ </Title>
31
+ <EmptyStateBody>
32
+ {__('Previous tasks will appear here.')}
33
+ </EmptyStateBody>
34
+ </EmptyState>
35
+ );
36
+ }
37
+
38
+ const getResultIcon = result => {
39
+ if (result === 'success') {
40
+ return <CheckCircleIcon className="task-history-icon-success" />;
41
+ }
42
+ if (result === 'error') {
43
+ return <ExclamationCircleIcon className="task-history-icon-error" />;
44
+ }
45
+ if (result === 'warning') {
46
+ return <ExclamationTriangleIcon className="task-history-icon-warning" />;
47
+ }
48
+ return null;
49
+ };
50
+
51
+ const formatDuration = seconds => {
52
+ if (!Number.isFinite(seconds) || seconds < 0) {
53
+ return __('N/A');
54
+ }
55
+ const mins = Math.floor(seconds / 60);
56
+ const secs = Math.floor(seconds % 60);
57
+ if (mins > 0) {
58
+ return `${mins}m ${secs}s`;
59
+ }
60
+ return `${secs}s`;
61
+ };
62
+
63
+ const getResultLabel = result => {
64
+ if (result === 'success') return __('Success');
65
+ if (result === 'error') return __('Failed');
66
+ if (result === 'warning') return __('Warning');
67
+ return result || __('Unknown');
68
+ };
69
+
70
+ return (
71
+ <div className="task-history-container">
72
+ {title && (
73
+ <Title headingLevel="h3" size="md" ouiaId="task-history-title">
74
+ {title}
75
+ </Title>
76
+ )}
77
+ <DataList aria-label="task history" className="task-history-list">
78
+ {tasks.map(task => (
79
+ <DataListItem key={task.id} aria-labelledby={`task-${task.id}`}>
80
+ <DataListItemRow>
81
+ <DataListItemCells
82
+ dataListCells={[
83
+ <DataListCell key="icon" className="task-history-icon-cell">
84
+ {getResultIcon(task.result)}
85
+ </DataListCell>,
86
+ <DataListCell key="time" className="task-history-time-cell">
87
+ <RelativeDateTime date={task.started_at} />
88
+ </DataListCell>,
89
+ <DataListCell
90
+ key="result"
91
+ className="task-history-result-cell"
92
+ >
93
+ {getResultLabel(task.result)}
94
+ </DataListCell>,
95
+ <DataListCell
96
+ key="duration"
97
+ className="task-history-duration-cell"
98
+ >
99
+ {formatDuration(task.duration)}
100
+ </DataListCell>,
101
+ <DataListCell key="link" className="task-history-link-cell">
102
+ <a href={`/foreman_tasks/tasks/${task.id}`}>
103
+ {__('Details')}
104
+ </a>
105
+ </DataListCell>,
106
+ ]}
107
+ />
108
+ </DataListItemRow>
109
+ </DataListItem>
110
+ ))}
111
+ </DataList>
112
+ </div>
113
+ );
114
+ };
115
+
116
+ TaskHistory.propTypes = {
117
+ tasks: PropTypes.arrayOf(
118
+ PropTypes.shape({
119
+ id: PropTypes.string.isRequired,
120
+ label: PropTypes.string,
121
+ action: PropTypes.string,
122
+ state: PropTypes.string,
123
+ result: PropTypes.string,
124
+ progress: PropTypes.number,
125
+ started_at: PropTypes.string,
126
+ ended_at: PropTypes.string,
127
+ duration: PropTypes.number,
128
+ humanized: PropTypes.object,
129
+ report_file_path: PropTypes.string,
130
+ })
131
+ ),
132
+ title: PropTypes.string,
133
+ };
134
+
135
+ TaskHistory.defaultProps = {
136
+ tasks: [],
137
+ title: null,
138
+ };
139
+
140
+ export default TaskHistory;
@@ -0,0 +1 @@
1
+ export { default } from './TaskHistory';
@@ -0,0 +1,40 @@
1
+ .task-history-container {
2
+ margin-top: 2rem;
3
+
4
+ .task-history-list {
5
+ margin-top: 1rem;
6
+ }
7
+
8
+ .task-history-icon-cell {
9
+ width: 40px;
10
+ }
11
+
12
+ .task-history-time-cell {
13
+ flex: 2;
14
+ }
15
+
16
+ .task-history-result-cell {
17
+ flex: 1;
18
+ }
19
+
20
+ .task-history-duration-cell {
21
+ flex: 1;
22
+ }
23
+
24
+ .task-history-link-cell {
25
+ flex: 1;
26
+ text-align: right;
27
+ }
28
+
29
+ .task-history-icon-success {
30
+ color: var(--pf-global--success-color--100);
31
+ }
32
+
33
+ .task-history-icon-error {
34
+ color: var(--pf-global--danger-color--100);
35
+ }
36
+
37
+ .task-history-icon-warning {
38
+ color: var(--pf-global--warning-color--100);
39
+ }
40
+ }