foreman_rh_cloud 14.3.0 → 14.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +1 -0
  3. data/app/controllers/api/v2/rh_cloud/inventory_controller.rb +11 -5
  4. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +0 -1
  5. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -11
  6. data/app/controllers/foreman_inventory_upload/uploads_settings_controller.rb +0 -8
  7. data/app/services/foreman_rh_cloud/cloud_presence.rb +23 -0
  8. data/config/routes.rb +1 -3
  9. data/lib/foreman_rh_cloud/engine.rb +1 -6
  10. data/lib/foreman_rh_cloud/plugin.rb +3 -2
  11. data/lib/foreman_rh_cloud/version.rb +1 -1
  12. data/lib/insights_cloud/async/cloud_connector_announce_task.rb +76 -19
  13. data/package.json +1 -1
  14. data/test/controllers/inventory_upload/api/inventory_controller_test.rb +10 -8
  15. data/test/jobs/cloud_connector_announce_task_test.rb +116 -106
  16. data/webpack/ForemanInventoryUpload/Components/InventorySettings/MinimalInventoryDropdown.js +1 -0
  17. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageTitle.js +1 -0
  18. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +4 -12
  19. data/webpack/ForemanRhCloudPages.js +6 -0
  20. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsLabel.js +8 -7
  21. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +2 -2
  22. data/webpack/InsightsCloudSync/Components/InsightsTable/Pagination.js +1 -0
  23. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +2 -2
  24. data/webpack/InsightsCloudSync/Components/ToolbarDropdown.js +1 -0
  25. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +7 -5
  26. data/webpack/InsightsHostDetailsTab/InsightsTab.js +53 -39
  27. data/webpack/InsightsHostDetailsTab/InsightsTab.scss +7 -73
  28. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.fixtures.js +7 -0
  29. data/webpack/InsightsHostDetailsTab/__tests__/InsightsTab.test.js +52 -30
  30. data/webpack/InsightsHostDetailsTab/components/ListItem/ListItem.js +43 -35
  31. data/webpack/InsightsHostDetailsTab/components/ListItem/__tests__/ListItem.test.js +111 -0
  32. data/webpack/InsightsVulnerabilityActionsBar/__tests__/InsightsVulnerabilityActionsBar.test.js +1 -5
  33. data/webpack/IopPathwayDetails/IopPathwayDetails.js +40 -0
  34. data/webpack/IopRecommendationDetails/IopRecommendationDetails.js +0 -18
  35. data/webpack/common/DropdownToggle.js +1 -0
  36. data/webpack/common/Switcher/HelpLabel.js +7 -5
  37. data/webpack/test_setup.js +1 -0
  38. metadata +4 -8
  39. data/app/services/foreman_rh_cloud/cloud_connector.rb +0 -74
  40. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext.js +0 -6
  41. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -6
  42. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -14
  43. data/webpack/__mocks__/foremanReact/constants.js +0 -24
  44. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +0 -24
  45. data/webpack/__mocks__/foremanReact/redux/API/index.js +0 -12
@@ -3,24 +3,16 @@ import { render, screen, fireEvent } from '@testing-library/react';
3
3
  import { Provider } from 'react-redux';
4
4
  import configureMockStore from 'redux-mock-store';
5
5
  import thunk from 'redux-thunk';
6
- import * as API from 'foremanReact/redux/API';
7
6
  import ConnectedSyncButton from '../index';
8
- import { successResponse } from './SyncButtonFixtures';
9
- import { INVENTORY_SYNC } from '../SyncButtonConstants';
10
-
11
- jest.spyOn(API, 'post');
12
7
 
13
8
  const mockStore = configureMockStore([thunk]);
14
9
 
10
+ afterEach(() => {
11
+ jest.clearAllMocks();
12
+ });
13
+
15
14
  describe('SyncButton integration test', () => {
16
15
  it('dispatches sync action when button is clicked', () => {
17
- API.post.mockImplementation(({ handleSuccess, key, ...action }) => {
18
- if (key === INVENTORY_SYNC && handleSuccess) {
19
- handleSuccess(successResponse);
20
- }
21
- return { type: 'API_POST', ...action };
22
- });
23
-
24
16
  const store = mockStore({
25
17
  API: {},
26
18
  });
@@ -5,6 +5,7 @@ import ForemanInventoryUpload from './ForemanInventoryUpload';
5
5
  import InsightsVulnerabilityListPage from './InsightsVulnerability/InsightsVulnerabilityListPage';
6
6
  import InsightsCloudSync from './InsightsCloudSync';
7
7
  import IopRecommendationDetails from './IopRecommendationDetails/IopRecommendationDetails';
8
+ import IopPathwayDetails from './IopPathwayDetails/IopPathwayDetails';
8
9
  import InsightsHostDetailsTab from './InsightsHostDetailsTab';
9
10
  import CveDetailsPage from './CveDetailsPage';
10
11
  import './common/styles.scss';
@@ -31,6 +32,11 @@ export const routes = [
31
32
  exact: true,
32
33
  render: props => <InsightsCloudSync {...props} />,
33
34
  },
35
+ {
36
+ path: '/foreman_rh_cloud/recommendations/pathways/:slug',
37
+ exact: false,
38
+ render: props => <IopPathwayDetails {...props} />,
39
+ },
34
40
  {
35
41
  path: '/foreman_rh_cloud/recommendations',
36
42
  exact: false,
@@ -7,21 +7,22 @@ import {
7
7
  } from '@patternfly/react-icons';
8
8
  import { Label } from '@patternfly/react-core';
9
9
  import PropTypes from 'prop-types';
10
+ import { translate as __ } from 'foremanReact/common/I18n';
10
11
 
11
12
  const VALUE_TO_STATE = {
12
- 1: { icon: <AngleDoubleDownIcon />, text: 'Low', color: 'blue' },
13
- 2: { icon: <EqualsIcon />, text: 'Moderate', color: 'yellow' },
14
- 3: { icon: <AngleDoubleUpIcon />, text: 'Important', color: 'orange' },
15
- 4: { icon: <CriticalRiskIcon />, text: 'Critical', color: 'red' },
13
+ 1: { icon: <AngleDoubleDownIcon />, text: __('Low'), color: 'blue' },
14
+ 2: { icon: <EqualsIcon />, text: __('Moderate'), color: 'yellow' },
15
+ 3: { icon: <AngleDoubleUpIcon />, text: __('Important'), color: 'orange' },
16
+ 4: { icon: <CriticalRiskIcon />, text: __('Critical'), color: 'red' },
16
17
  };
17
18
 
18
19
  const InsightsLabel = ({ value = 1, text, hideIcon, ...props }) => (
19
20
  <Label
20
21
  {...props}
21
- color={VALUE_TO_STATE[value].color}
22
- icon={!hideIcon && VALUE_TO_STATE[value].icon}
22
+ color={VALUE_TO_STATE[value]?.color}
23
+ icon={!hideIcon && VALUE_TO_STATE[value]?.icon}
23
24
  >
24
- {text || VALUE_TO_STATE[value].text}
25
+ {text || VALUE_TO_STATE[value]?.text}
25
26
  </Label>
26
27
  );
27
28
 
@@ -31,7 +31,7 @@ export const actionsFormatter = rowData => {
31
31
  recommendationUrl &&
32
32
  !isLocalAdvisorEngine &&
33
33
  dropdownItems.push(
34
- <DropdownItem key="recommendation-url">
34
+ <DropdownItem key="recommendation-url" ouiaId="insights-dropdown-item-recommendation-url">
35
35
  <a href={recommendationUrl} target="_blank" rel="noopener noreferrer">
36
36
  {__('View in Red Hat Insights')} <ExternalLinkAltIcon />
37
37
  </a>
@@ -40,7 +40,7 @@ export const actionsFormatter = rowData => {
40
40
 
41
41
  accessRHUrl &&
42
42
  dropdownItems.push(
43
- <DropdownItem key="access-url">
43
+ <DropdownItem key="access-url" ouiaId="insights-dropdown-item-knowledgebase">
44
44
  <a href={accessRHUrl} target="_blank" rel="noopener noreferrer">
45
45
  {__('Knowledgebase article')} <ExternalLinkAltIcon />
46
46
  </a>
@@ -42,6 +42,7 @@ const Pagination = ({ variant, ...props }) => {
42
42
 
43
43
  return (
44
44
  <PfPagination
45
+ ouiaId={`recommendations-pagination-${variant}`}
45
46
  itemCount={itemCount}
46
47
  widgetId={`recommendation-pagination-${variant}`}
47
48
  perPage={perPage}
@@ -117,10 +117,10 @@ describe('InsightsTable actions', () => {
117
117
  expect(push).toHaveBeenCalled();
118
118
 
119
119
  const apiAction = dispatch.mock.calls.find(
120
- call => call[0] && call[0].key === INSIGHTS_HITS_API_KEY
120
+ call => call[0] && call[0].payload?.key === INSIGHTS_HITS_API_KEY
121
121
  );
122
122
  expect(apiAction).toBeTruthy();
123
- const getArg = apiAction[0];
123
+ const getArg = apiAction[0].payload;
124
124
  expect(getArg.url).toBe(INSIGHTS_HITS_PATH);
125
125
  expect(getArg.params.page).toBe(2);
126
126
  expect(getArg.params.per_page).toBe(7);
@@ -53,6 +53,7 @@ const ToolbarDropdown = ({ onRecommendationSync }) => {
53
53
  aria-label={__('Recommendations actions')}
54
54
  onClick={() => setIsDropdownOpen(prev => !prev)}
55
55
  isExpanded={isDropdownOpen}
56
+ ouiaId="recommendations-dropdown-toggle"
56
57
  >
57
58
  <EllipsisVIcon />
58
59
  </MenuToggle>
@@ -1,3 +1,4 @@
1
+ import { API_OPERATIONS } from 'foremanReact/redux/API';
1
2
  import { syncInsights } from '../InsightsCloudSyncActions';
2
3
  import { INSIGHTS_CLOUD_SYNC } from '../InsightsCloudSyncConstants';
3
4
 
@@ -17,17 +18,18 @@ describe('InsightsCloudSync actions', () => {
17
18
 
18
19
  expect(dispatch).toHaveBeenCalledTimes(1);
19
20
  const dispatched = dispatch.mock.calls[0][0];
20
- expect(dispatched.key).toBe(INSIGHTS_CLOUD_SYNC);
21
- expect(dispatched.url).toBe('/insights_cloud/tasks');
22
- expect(typeof dispatched.handleSuccess).toBe('function');
23
- expect(typeof dispatched.errorToast).toBe('function');
21
+ expect(dispatched.type).toBe(API_OPERATIONS.POST);
22
+ expect(dispatched.payload.key).toBe(INSIGHTS_CLOUD_SYNC);
23
+ expect(dispatched.payload.url).toBe('/insights_cloud/tasks');
24
+ expect(typeof dispatched.payload.handleSuccess).toBe('function');
25
+ expect(typeof dispatched.payload.errorToast).toBe('function');
24
26
  });
25
27
 
26
28
  it('errorToast returns failure message with error details', () => {
27
29
  syncInsights(jest.fn(), '')(dispatch);
28
30
  const dispatched = dispatch.mock.calls[0][0];
29
31
 
30
- const result = dispatched.errorToast('some error');
32
+ const result = dispatched.payload.errorToast('some error');
31
33
  expect(result).toContain('some error');
32
34
  });
33
35
  });
@@ -1,56 +1,70 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { orderBy } from 'lodash';
4
- import { Grid, ListView } from 'patternfly-react';
4
+ import { Accordion, Grid, GridItem, Title } from '@patternfly/react-core';
5
5
  import { noop } from 'foremanReact/common/helpers';
6
6
  import { translate as __ } from 'foremanReact/common/I18n';
7
7
  import ListItem from './components/ListItem';
8
8
  import './InsightsTab.scss';
9
9
 
10
- class InsightsHostDetailsTab extends React.Component {
11
- componentDidMount() {
12
- const { fetchHits, hostID } = this.props;
10
+ const InsightsHostDetailsTab = ({ fetchHits, hostID, hits }) => {
11
+ useEffect(() => {
13
12
  fetchHits(hostID);
14
- }
15
-
16
- render() {
17
- const { hits } = this.props;
13
+ }, [hostID]);
18
14
 
19
- if (!hits.length) {
20
- return <h2>{__('No recommendations were found for this host!')}</h2>;
21
- }
22
- const hitsSorted = orderBy(hits, ['total_risk'], ['desc']);
23
- const items = hitsSorted.map(
24
- (
25
- {
26
- title,
27
- total_risk: totalRisk,
28
- results_url: resultsUrl,
29
- solution_url: solutionUrl,
30
- },
31
- index
32
- ) => (
33
- <ListItem
34
- key={index}
35
- title={title}
36
- totalRisk={totalRisk}
37
- resultsUrl={resultsUrl}
38
- solutionUrl={solutionUrl}
39
- />
40
- )
41
- );
15
+ if (!hits.length) {
42
16
  return (
43
17
  <div id="host_details_insights_tab">
44
- <Grid.Row>
45
- <Grid.Col xs={12}>
46
- <h2>{__('Recommendations')}</h2>
47
- <ListView id="hits_list">{items}</ListView>
48
- </Grid.Col>
49
- </Grid.Row>
18
+ <Title
19
+ headingLevel="h2"
20
+ size="md"
21
+ ouiaId="insights-recommendations-empty-title"
22
+ >
23
+ {__('No recommendations were found for this host!')}
24
+ </Title>
50
25
  </div>
51
26
  );
52
27
  }
53
- }
28
+
29
+ const hitsSorted = orderBy(hits, ['total_risk'], ['desc']);
30
+
31
+ return (
32
+ <div id="host_details_insights_tab">
33
+ <Grid hasGutter>
34
+ <GridItem span={12}>
35
+ <Title
36
+ headingLevel="h2"
37
+ size="md"
38
+ ouiaId="insights-recommendations-title"
39
+ >
40
+ {__('Recommendations')}
41
+ </Title>
42
+ <div id="hits_list" data-testid="hits-list">
43
+ <Accordion>
44
+ {hitsSorted.map(
45
+ ({
46
+ id,
47
+ title,
48
+ total_risk: totalRisk,
49
+ results_url: resultsUrl,
50
+ solution_url: solutionUrl,
51
+ }) => (
52
+ <ListItem
53
+ key={id}
54
+ title={title}
55
+ totalRisk={totalRisk}
56
+ resultsUrl={resultsUrl}
57
+ solutionUrl={solutionUrl}
58
+ />
59
+ )
60
+ )}
61
+ </Accordion>
62
+ </div>
63
+ </GridItem>
64
+ </Grid>
65
+ </div>
66
+ );
67
+ };
54
68
 
55
69
  InsightsHostDetailsTab.propTypes = {
56
70
  hostID: PropTypes.number.isRequired,
@@ -1,84 +1,18 @@
1
1
  #host_details_insights_tab {
2
- #btn_toolbar {
3
- float: right;
4
- }
5
-
6
- #btn_fullscreen {
7
- margin-left: 5px;
8
-
9
- .fa-arrows-alt {
10
- margin-left: 5px;
11
- }
12
- }
13
-
14
- h2 {
15
- margin-top: 5px;
16
- }
17
-
18
2
  #hits_list {
19
3
  max-height: 650px;
20
4
  overflow-y: scroll;
21
5
  overflow-x: hidden;
22
- margin-top: 15px;
23
-
24
- .list-view-pf-expand {
25
- padding: 0;
26
- }
27
-
28
- .list-group-item-header {
29
- .list-view-pf-expand .fa-angle-right {
30
- margin-top: 6px;
31
- }
32
-
33
- .list-view-pf-main-info .list-view-pf-description {
34
- width: 83%;
35
-
36
- .list-group-item-heading {
37
- width: 375px;
38
- }
39
-
40
- p {
41
- margin: 0;
42
- font-size: 14px;
43
- }
44
- }
45
-
46
- .list-view-pf-additional-info {
47
- .risk-label {
48
- padding: 5px 8px;
49
- border-radius: 12px;
50
-
51
- &.risk-1 {
52
- background-color: #e7f1fa;
53
- }
54
-
55
- &.risk-2 {
56
- background-color: #fdf7e7;
57
- }
58
-
59
- &.risk-3 {
60
- background-color: #f9dddd;
61
- }
62
-
63
- &.risk-4 {
64
- background-color: #ffecec;
65
- }
66
-
67
- p {
68
- margin: 0;
69
- }
70
- }
71
- }
72
- }
6
+ margin-top: var(--pf-v5-global--spacer--md);
73
7
 
74
8
  &::-webkit-scrollbar {
75
- width: 12px;
76
- height: 12px;
9
+ width: var(--pf-v5-global--spacer--sm);
10
+ height: var(--pf-v5-global--spacer--sm);
77
11
  }
78
12
 
79
13
  &::-webkit-scrollbar-thumb {
80
- background: #222;
81
- border-radius: 6px;
14
+ background: var(--pf-v5-global--palette--black-900);
15
+ border-radius: var(--pf-v5-global--BorderRadius--sm);
82
16
  border: 3px solid transparent;
83
17
  background-clip: content-box;
84
18
  }
@@ -86,9 +20,9 @@
86
20
  }
87
21
 
88
22
  #new_host_details_insights_tab {
89
- padding: 24px;
23
+ padding: var(--pf-v5-global--spacer--lg);
90
24
  }
91
25
 
92
26
  #total-risks-dropdown-container ul.pf-v5-c-menu__list {
93
- margin-bottom: 0px;
27
+ margin-bottom: 0;
94
28
  }
@@ -2,6 +2,7 @@ export const hostID = 1234;
2
2
 
3
3
  export const hits = [
4
4
  {
5
+ id: 1,
5
6
  hostname: 'my-host.example.com',
6
7
  rhel_version: '7.8',
7
8
  uuid: '4739b323-a343-4e89-b71b-81991b8dc656',
@@ -17,6 +18,12 @@ export const hits = [
17
18
  },
18
19
  ];
19
20
 
21
+ export const multipleHits = [
22
+ { ...hits[0], id: 1, title: 'Low risk', total_risk: 1 },
23
+ { ...hits[0], id: 2, title: 'High risk', total_risk: 4 },
24
+ { ...hits[0], id: 3, title: 'Medium risk', total_risk: 2 },
25
+ ];
26
+
20
27
  export const props = {
21
28
  hostID,
22
29
  hits,
@@ -1,46 +1,68 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { screen, waitFor, within } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { rtlHelpers } from 'foremanReact/common/rtlTestHelpers';
3
5
  import InsightsTab from '../InsightsTab';
4
- import { hits, hostID } from './InsightsTab.fixtures';
6
+ import { hits, hostID, multipleHits } from './InsightsTab.fixtures';
7
+
8
+ const { renderWithI18n } = rtlHelpers;
9
+
10
+ const renderInsightsTab = (props = {}) =>
11
+ renderWithI18n(
12
+ <InsightsTab hostID={hostID} hits={[]} fetchHits={jest.fn()} {...props} />
13
+ );
5
14
 
6
15
  describe('InsightsTab', () => {
7
- it('renders "No recommendations" message when hits is empty', () => {
8
- render(<InsightsTab hostID={hostID} hits={[]} />);
16
+ it('renders empty state heading when there are no recommendations', async () => {
17
+ renderInsightsTab({ hits: [] });
18
+
19
+ expect(
20
+ await screen.findByRole('heading', {
21
+ name: 'No recommendations were found for this host!',
22
+ })
23
+ ).toBeInTheDocument();
24
+
9
25
  expect(
10
- screen.getByText('No recommendations were found for this host!')
11
- ).toBeTruthy();
26
+ screen.queryByRole('heading', { name: 'Recommendations' })
27
+ ).not.toBeInTheDocument();
12
28
  });
13
29
 
14
- it('renders Recommendations heading when hits exist', () => {
15
- render(<InsightsTab hostID={hostID} hits={hits} />);
16
- expect(screen.getByText('Recommendations')).toBeTruthy();
30
+ it('renders recommendations heading when hits exist', async () => {
31
+ renderInsightsTab({ hits });
32
+
33
+ expect(
34
+ await screen.findByRole('heading', { name: 'Recommendations' })
35
+ ).toBeInTheDocument();
17
36
  });
18
37
 
19
- it('displays hit titles', () => {
20
- render(<InsightsTab hostID={hostID} hits={hits} />);
21
- hits.forEach(hit => {
22
- expect(screen.getByText(hit.title)).toBeTruthy();
23
- });
38
+ it('displays recommendation titles in the list', async () => {
39
+ renderInsightsTab({ hits });
40
+
41
+ expect(
42
+ await screen.findByText(
43
+ 'New Ansible Engine packages are inaccessible when dedicated Ansible repo is not enabled'
44
+ )
45
+ ).toBeInTheDocument();
24
46
  });
25
47
 
26
- it('calls fetchHits on mount', () => {
48
+ it('requests recommendations for the host on mount', async () => {
27
49
  const fetchHits = jest.fn();
28
- render(<InsightsTab hostID={hostID} hits={[]} fetchHits={fetchHits} />);
29
- expect(fetchHits).toHaveBeenCalledWith(hostID);
50
+
51
+ renderInsightsTab({ hits: [], fetchHits });
52
+
53
+ await waitFor(() => {
54
+ expect(fetchHits).toHaveBeenCalledWith(hostID);
55
+ });
30
56
  });
31
57
 
32
- it('sorts hits by total_risk descending', () => {
33
- const multipleHits = [
34
- { ...hits[0], title: 'Low risk', total_risk: 1 },
35
- { ...hits[0], title: 'High risk', total_risk: 4 },
36
- { ...hits[0], title: 'Medium risk', total_risk: 2 },
37
- ];
38
- render(<InsightsTab hostID={hostID} hits={multipleHits} />);
39
- const titles = screen.getAllByText(/risk/i).map(el => el.textContent);
40
- const highRiskIndex = titles.findIndex(t => t === 'High risk');
41
- const mediumRiskIndex = titles.findIndex(t => t === 'Medium risk');
42
- const lowRiskIndex = titles.findIndex(t => t === 'Low risk');
43
- expect(highRiskIndex).toBeLessThan(mediumRiskIndex);
44
- expect(mediumRiskIndex).toBeLessThan(lowRiskIndex);
58
+ it('sorts recommendations by total risk descending', async () => {
59
+ renderInsightsTab({ hits: multipleHits });
60
+
61
+ const hitsList = await screen.findByTestId('hits-list');
62
+ const toggles = within(hitsList).getAllByRole('button');
63
+
64
+ expect(toggles[0]).toHaveTextContent('High risk');
65
+ expect(toggles[1]).toHaveTextContent('Medium risk');
66
+ expect(toggles[2]).toHaveTextContent('Low risk');
45
67
  });
46
68
  });
@@ -1,33 +1,24 @@
1
- import React, { Fragment } from 'react';
2
- import { ListView, Icon } from 'patternfly-react';
1
+ import React, { useState } from 'react';
2
+ import {
3
+ AccordionContent,
4
+ AccordionItem,
5
+ AccordionToggle,
6
+ Flex,
7
+ FlexItem,
8
+ Truncate,
9
+ } from '@patternfly/react-core';
10
+ import { ExternalLinkAltIcon } from '@patternfly/react-icons';
3
11
  import PropTypes from 'prop-types';
4
12
  import { translate as __ } from 'foremanReact/common/I18n';
5
-
6
- const labelMapper = {
7
- 1: __('Low'),
8
- 2: __('Moderate'),
9
- 3: __('Important'),
10
- 4: __('Critical'),
11
- };
13
+ import InsightsLabel from '../../../InsightsCloudSync/Components/InsightsTable/InsightsLabel';
12
14
 
13
15
  const ListItem = ({ title, totalRisk, resultsUrl, solutionUrl }) => {
14
- const heading = (
15
- <p className="ellipsis list-item-heading" title={title}>
16
- {title}
17
- </p>
18
- );
19
-
20
- const riskLabel = labelMapper[totalRisk];
21
- const additionalInfo = [
22
- <span key={`risk-info-${title}`} className={`risk-label risk-${totalRisk}`}>
23
- <p>{riskLabel}</p>
24
- </span>,
25
- ];
16
+ const [isExpanded, setIsExpanded] = useState(false);
26
17
 
27
18
  const knowledgebaseLink = solutionUrl && (
28
19
  <p>
29
20
  <a href={solutionUrl} target="_blank" rel="noopener noreferrer">
30
- {__('Knowledgebase article')} <Icon name="external-link" />
21
+ {__('Knowledgebase article')} <ExternalLinkAltIcon />
31
22
  </a>
32
23
  </p>
33
24
  );
@@ -35,24 +26,41 @@ const ListItem = ({ title, totalRisk, resultsUrl, solutionUrl }) => {
35
26
  const insightsCloudLink = resultsUrl && (
36
27
  <p>
37
28
  <a href={resultsUrl} target="_blank" rel="noopener noreferrer">
38
- {__('Read more about it in RH cloud insights')}{' '}
39
- <Icon name="external-link" />
29
+ {__('Read more about it in RH cloud insights')}
30
+ <ExternalLinkAltIcon />
40
31
  </a>
41
32
  </p>
42
33
  );
43
34
 
44
35
  return (
45
- <ListView.Item
46
- heading={heading}
47
- additionalInfo={additionalInfo}
48
- hideCloseIcon
49
- >
50
- <Fragment>
51
- <p>{title}</p>
52
- {knowledgebaseLink}
53
- {insightsCloudLink}
54
- </Fragment>
55
- </ListView.Item>
36
+ <AccordionItem>
37
+ <AccordionToggle
38
+ onClick={() => setIsExpanded(currentValue => !currentValue)}
39
+ isExpanded={isExpanded}
40
+ >
41
+ <Flex
42
+ alignItems={{ default: 'alignItemsCenter' }}
43
+ justifyContent={{ default: 'justifyContentSpaceBetween' }}
44
+ flexWrap={{ default: 'nowrap' }}
45
+ >
46
+ <FlexItem>
47
+ <Truncate content={title} />
48
+ </FlexItem>
49
+ <FlexItem>
50
+ <InsightsLabel value={totalRisk} />
51
+ </FlexItem>
52
+ </Flex>
53
+ </AccordionToggle>
54
+ <AccordionContent isHidden={!isExpanded}>
55
+ {isExpanded && (
56
+ <>
57
+ <p>{title}</p>
58
+ {knowledgebaseLink}
59
+ {insightsCloudLink}
60
+ </>
61
+ )}
62
+ </AccordionContent>
63
+ </AccordionItem>
56
64
  );
57
65
  };
58
66