foreman_rh_cloud 14.1.0 → 14.1.1

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: ecc86813dfba949a2d7125c96245b767f5ecc4edee058828bac97042ce5d4ee0
4
- data.tar.gz: e6d920065bbe1a0a560c49050614abbad1c72d4f4c5e1494edf390db0495833e
3
+ metadata.gz: c171d38bbf5ab4518afbd0bfa19123c63c3d711bcf4a64070f215890b18cd5fe
4
+ data.tar.gz: 82b8598bf7fd4fec088e01f9c94986e1f73975bca9818fa386bbc82792ebdd93
5
5
  SHA512:
6
- metadata.gz: e672658acda2399401392268c238361a81720e58d6d5693e70b1195c8aaef4d22ee35918b1ee329fae40d03efb09ad77c5562e9d81a905bd9e3b86945ed5a7f7
7
- data.tar.gz: 1ff3460a85b2a26b64de9a472dcaf864bcb9cdd71043736a94f7f0784eb48ed91dcee94c84ae7ad4ae3c10bd31df8addd89175cf7c9ff6b221b8fadbe5035391
6
+ metadata.gz: 331610ee54d503abfa397fd3a4b599ab864dedee5ba0cf6e268d1577db10d0f2187e46829ed35a1133f95398843d9d967b74da6dbee8b99e66a43ee69e6853ff
7
+ data.tar.gz: f495666febadc11ae9924350a7cbb3ec11a5e07945a42e06aad8c923bccd751632b5d87d171d0eb1f248fbbf8f40bd225d13d8f4f9ae1ff9e2b9e6785f34260a
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '14.1.0'.freeze
2
+ VERSION = '14.1.1'.freeze
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "14.1.0",
3
+ "version": "14.1.1",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -3,6 +3,10 @@ import PropTypes from 'prop-types';
3
3
  import { ScalprumComponent, ScalprumProvider } from '@scalprum/react-core';
4
4
  import { createProviderOptions } from '../common/ScalprumModule/ScalprumContext';
5
5
  import { useInsightsPermissions } from '../common/Hooks/PermissionsHooks';
6
+ import {
7
+ vulnerabilityDisabled,
8
+ useTabRedirect,
9
+ } from '../ForemanRhCloudHelpers';
6
10
  import './CVEsHostDetailsTab.scss';
7
11
 
8
12
  const CVEsHostDetailsTab = ({ systemId }) => {
@@ -26,6 +30,15 @@ CVEsHostDetailsTab.propTypes = {
26
30
 
27
31
  const CVEsHostDetailsTabWrapper = ({ response }) => {
28
32
  const permissions = useInsightsPermissions();
33
+ const isHostDataLoaded = Boolean(response?.id);
34
+ const shouldHideTab = useTabRedirect(
35
+ isHostDataLoaded && vulnerabilityDisabled({ hostDetails: response })
36
+ );
37
+
38
+ if (shouldHideTab) {
39
+ return null;
40
+ }
41
+
29
42
  return (
30
43
  <ScalprumProvider {...createProviderOptions(permissions)}>
31
44
  <CVEsHostDetailsTab
@@ -38,10 +51,19 @@ const CVEsHostDetailsTabWrapper = ({ response }) => {
38
51
 
39
52
  CVEsHostDetailsTabWrapper.propTypes = {
40
53
  response: PropTypes.shape({
54
+ id: PropTypes.number,
55
+ operatingsystem_name: PropTypes.string,
56
+ vulnerability: PropTypes.shape({
57
+ enabled: PropTypes.bool,
58
+ }),
41
59
  subscription_facet_attributes: PropTypes.shape({
42
- uuid: PropTypes.string.isRequired,
60
+ uuid: PropTypes.string,
43
61
  }),
44
- }).isRequired,
62
+ }),
63
+ };
64
+
65
+ CVEsHostDetailsTabWrapper.defaultProps = {
66
+ response: {},
45
67
  };
46
68
 
47
69
  export default CVEsHostDetailsTabWrapper;
@@ -1,6 +1,17 @@
1
1
  import React from 'react';
2
2
  import { render } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
3
4
  import CVEsHostDetailsTabWrapper from '../CVEsHostDetailsTab';
5
+ import { OVERVIEW_TAB_PATH } from '../../ForemanRhCloudHelpers';
6
+
7
+ const mockHistoryReplace = jest.fn();
8
+
9
+ jest.mock('react-router-dom', () => ({
10
+ ...jest.requireActual('react-router-dom'),
11
+ useHistory: () => ({
12
+ replace: mockHistoryReplace,
13
+ }),
14
+ }));
4
15
 
5
16
  jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
6
17
  useForemanContext: () => ({
@@ -25,31 +36,44 @@ jest.mock('@scalprum/react-core', () => {
25
36
  };
26
37
  });
27
38
 
39
+ const defaultResponse = {
40
+ id: 1,
41
+ operatingsystem_name: 'Red Hat Enterprise Linux 8',
42
+ vulnerability: { enabled: true },
43
+ subscription_facet_attributes: { uuid: '1-2-3' },
44
+ };
45
+
28
46
  describe('CVEsHostDetailsTabWrapper', () => {
29
47
  beforeEach(() => {
30
48
  jest.clearAllMocks();
31
49
  });
32
50
 
33
- it('renders without crashing', () => {
51
+ it('renders without crashing and does not redirect for valid host', () => {
34
52
  const { container } = render(
35
- <CVEsHostDetailsTabWrapper
36
- response={{ subscription_facet_attributes: { uuid: '1-2-3' } }}
37
- />
53
+ <MemoryRouter>
54
+ <CVEsHostDetailsTabWrapper response={defaultResponse} />
55
+ </MemoryRouter>
38
56
  );
39
57
  expect(
40
58
  container.querySelector(
41
59
  '.rh-cloud-insights-vulnerability-host-details-component'
42
60
  )
43
61
  ).toBeTruthy();
62
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
44
63
  });
45
64
 
46
65
  it('remounts ScalprumComponent when systemId changes', () => {
47
66
  const { ScalprumComponent } = require('@scalprum/react-core');
48
67
 
68
+ const responseHostA = {
69
+ ...defaultResponse,
70
+ subscription_facet_attributes: { uuid: 'uuid-host-A' },
71
+ };
72
+
49
73
  const { rerender } = render(
50
- <CVEsHostDetailsTabWrapper
51
- response={{ subscription_facet_attributes: { uuid: 'uuid-host-A' } }}
52
- />
74
+ <MemoryRouter>
75
+ <CVEsHostDetailsTabWrapper response={responseHostA} />
76
+ </MemoryRouter>
53
77
  );
54
78
 
55
79
  expect(mockUnmountTracker).not.toHaveBeenCalled();
@@ -58,10 +82,15 @@ describe('CVEsHostDetailsTabWrapper', () => {
58
82
  expect.anything()
59
83
  );
60
84
 
85
+ const responseHostB = {
86
+ ...defaultResponse,
87
+ subscription_facet_attributes: { uuid: 'uuid-host-B' },
88
+ };
89
+
61
90
  rerender(
62
- <CVEsHostDetailsTabWrapper
63
- response={{ subscription_facet_attributes: { uuid: 'uuid-host-B' } }}
64
- />
91
+ <MemoryRouter>
92
+ <CVEsHostDetailsTabWrapper response={responseHostB} />
93
+ </MemoryRouter>
65
94
  );
66
95
 
67
96
  expect(mockUnmountTracker).toHaveBeenCalledTimes(1);
@@ -70,4 +99,38 @@ describe('CVEsHostDetailsTabWrapper', () => {
70
99
  expect.anything()
71
100
  );
72
101
  });
102
+
103
+ it('redirects to Overview when tab should be hidden', () => {
104
+ const nonRhelResponse = {
105
+ id: 2,
106
+ operatingsystem_name: 'Ubuntu 20.04',
107
+ vulnerability: { enabled: false },
108
+ subscription_facet_attributes: { uuid: '1-2-3' },
109
+ };
110
+
111
+ const { container } = render(
112
+ <MemoryRouter>
113
+ <CVEsHostDetailsTabWrapper response={nonRhelResponse} />
114
+ </MemoryRouter>
115
+ );
116
+
117
+ expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
118
+ expect(
119
+ container.querySelector(
120
+ '.rh-cloud-insights-vulnerability-host-details-component'
121
+ )
122
+ ).toBeNull();
123
+ });
124
+
125
+ it('does not redirect when host data is not yet loaded', () => {
126
+ const emptyResponse = { subscription_facet_attributes: { uuid: '1-2-3' } };
127
+
128
+ render(
129
+ <MemoryRouter>
130
+ <CVEsHostDetailsTabWrapper response={emptyResponse} />
131
+ </MemoryRouter>
132
+ );
133
+
134
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
135
+ });
73
136
  });
@@ -1,3 +1,6 @@
1
+ import { useEffect } from 'react';
2
+ import { useHistory } from 'react-router-dom';
3
+
1
4
  /**
2
5
  * copied from core, since it's not in the ReactApp folder,
3
6
  * it's complicated to import it and mock it in tests.
@@ -5,6 +8,25 @@
5
8
  */
6
9
  export const foremanUrl = path => `${window.URL_PREFIX}${path}`;
7
10
 
11
+ export const OVERVIEW_TAB_PATH = '/Overview';
12
+
13
+ /**
14
+ * Redirects to Overview tab when the current tab should be hidden
15
+ * @param {boolean} shouldRedirect - Whether to redirect (e.g., host loaded AND tab should hide)
16
+ * @returns {boolean} - Returns shouldRedirect for convenience
17
+ */
18
+ export const useTabRedirect = shouldRedirect => {
19
+ const history = useHistory();
20
+
21
+ useEffect(() => {
22
+ if (shouldRedirect && history) {
23
+ history.replace(OVERVIEW_TAB_PATH);
24
+ }
25
+ }, [shouldRedirect, history]);
26
+
27
+ return shouldRedirect;
28
+ };
29
+
8
30
  export const isNotRhelHost = ({ hostDetails }) =>
9
31
  // This regex tries matches sane variations of "RedHat", "RHEL" and "RHCOS"
10
32
  !new RegExp('red[\\s\\-]?hat|rh[\\s\\-]?el|rhc[\\s\\-]?os', 'i').test(
@@ -25,6 +25,11 @@ import { useIopConfig } from '../common/Hooks/ConfigHooks';
25
25
  import { generateRuleUrl } from '../InsightsCloudSync/InsightsCloudSync';
26
26
  import { createProviderOptions } from '../common/ScalprumModule/ScalprumContext';
27
27
  import { useInsightsPermissions } from '../common/Hooks/PermissionsHooks';
28
+ import {
29
+ isNotRhelHost,
30
+ hasNoInsightsFacet,
31
+ useTabRedirect,
32
+ } from '../ForemanRhCloudHelpers';
28
33
 
29
34
  // Hosted Insights advisor
30
35
  const NewHostDetailsTab = ({ hostName, router }) => {
@@ -165,7 +170,18 @@ const IopInsightsTabWrapped = props => {
165
170
  };
166
171
 
167
172
  const InsightsTab = props => {
173
+ const { response } = props;
168
174
  const isIop = useIopConfig();
175
+ const isHostDataLoaded = Boolean(response?.id);
176
+ const shouldHideTab = useTabRedirect(
177
+ isHostDataLoaded &&
178
+ (isNotRhelHost({ hostDetails: response }) ||
179
+ hasNoInsightsFacet({ response, hostDetails: response }))
180
+ );
181
+
182
+ if (shouldHideTab) {
183
+ return null;
184
+ }
169
185
 
170
186
  return isIop ? (
171
187
  <IopInsightsTabWrapped {...props} />
@@ -174,6 +190,16 @@ const InsightsTab = props => {
174
190
  );
175
191
  };
176
192
 
177
- InsightsTab.defaultProps = {};
193
+ InsightsTab.propTypes = {
194
+ response: PropTypes.shape({
195
+ id: PropTypes.number,
196
+ operatingsystem_name: PropTypes.string,
197
+ insights_attributes: PropTypes.object,
198
+ }),
199
+ };
200
+
201
+ InsightsTab.defaultProps = {
202
+ response: {},
203
+ };
178
204
 
179
205
  export default InsightsTab;
@@ -2,9 +2,20 @@ import React from 'react';
2
2
  import { render } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom';
4
4
  import { Provider } from 'react-redux';
5
+ import { MemoryRouter } from 'react-router-dom';
5
6
  import configureMockStore from 'redux-mock-store';
6
7
  import thunk from 'redux-thunk';
7
8
  import NewHostDetailsTab from '../NewHostDetailsTab';
9
+ import { OVERVIEW_TAB_PATH } from '../../ForemanRhCloudHelpers';
10
+
11
+ const mockHistoryReplace = jest.fn();
12
+
13
+ jest.mock('react-router-dom', () => ({
14
+ ...jest.requireActual('react-router-dom'),
15
+ useHistory: () => ({
16
+ replace: mockHistoryReplace,
17
+ }),
18
+ }));
8
19
 
9
20
  jest.mock('../../common/Hooks/ConfigHooks', () => ({
10
21
  useIopConfig: jest.fn(() => false),
@@ -14,8 +25,33 @@ jest.mock('foremanReact/common/I18n', () => ({
14
25
  translate: jest.fn(str => str),
15
26
  }));
16
27
 
28
+ jest.mock(
29
+ 'foremanReact/components/SearchBar',
30
+ () => () => <div>SearchBar</div>,
31
+ { virtual: true }
32
+ );
33
+
34
+ jest.mock('../../InsightsCloudSync/Components/InsightsTable', () => () => (
35
+ <div>InsightsTable</div>
36
+ ));
37
+
38
+ jest.mock('../../InsightsCloudSync/Components/RemediationModal', () => () => (
39
+ <div>RemediationModal</div>
40
+ ));
41
+
42
+ jest.mock(
43
+ '../../InsightsCloudSync/Components/InsightsTable/Pagination',
44
+ () => () => <div>Pagination</div>
45
+ );
46
+
17
47
  const mockStore = configureMockStore([thunk]);
18
48
 
49
+ const defaultResponse = {
50
+ id: 1,
51
+ operatingsystem_name: 'Red Hat Enterprise Linux 8',
52
+ insights_attributes: { uuid: 'test-uuid' },
53
+ };
54
+
19
55
  describe('NewHostDetailsTab', () => {
20
56
  let store;
21
57
  let mockRouter;
@@ -68,17 +104,18 @@ describe('NewHostDetailsTab', () => {
68
104
  it('should preserve hash when clearing search params on unmount', () => {
69
105
  const { unmount } = render(
70
106
  <Provider store={store}>
71
- <NewHostDetailsTab
72
- hostName="test-host.example.com"
73
- router={mockRouter}
74
- />
107
+ <MemoryRouter>
108
+ <NewHostDetailsTab
109
+ hostName="test-host.example.com"
110
+ router={mockRouter}
111
+ response={defaultResponse}
112
+ />
113
+ </MemoryRouter>
75
114
  </Provider>
76
115
  );
77
116
 
78
- // Unmount the component to trigger cleanup
79
117
  unmount();
80
118
 
81
- // Verify router.replace was called with both search: null AND the existing hash
82
119
  expect(mockRouter.replace).toHaveBeenCalledWith({
83
120
  search: null,
84
121
  hash: '#/Insights',
@@ -90,16 +127,18 @@ describe('NewHostDetailsTab', () => {
90
127
 
91
128
  const { unmount } = render(
92
129
  <Provider store={store}>
93
- <NewHostDetailsTab
94
- hostName="test-host.example.com"
95
- router={mockRouter}
96
- />
130
+ <MemoryRouter>
131
+ <NewHostDetailsTab
132
+ hostName="test-host.example.com"
133
+ router={mockRouter}
134
+ response={defaultResponse}
135
+ />
136
+ </MemoryRouter>
97
137
  </Provider>
98
138
  );
99
139
 
100
140
  unmount();
101
141
 
102
- // When there's no hash, should only pass search: null
103
142
  expect(mockRouter.replace).toHaveBeenCalledWith({
104
143
  search: null,
105
144
  });
@@ -114,16 +153,18 @@ describe('NewHostDetailsTab', () => {
114
153
 
115
154
  const { unmount } = render(
116
155
  <Provider store={store}>
117
- <NewHostDetailsTab
118
- hostName="test-host.example.com"
119
- router={routerWithoutLocation}
120
- />
156
+ <MemoryRouter>
157
+ <NewHostDetailsTab
158
+ hostName="test-host.example.com"
159
+ router={routerWithoutLocation}
160
+ response={defaultResponse}
161
+ />
162
+ </MemoryRouter>
121
163
  </Provider>
122
164
  );
123
165
 
124
166
  unmount();
125
167
 
126
- // Should still call replace with search: null even if location is undefined
127
168
  expect(routerWithoutLocation.replace).toHaveBeenCalledWith({
128
169
  search: null,
129
170
  });
@@ -132,23 +173,94 @@ describe('NewHostDetailsTab', () => {
132
173
  it('should use the latest hash value at unmount time, not a stale captured value', () => {
133
174
  const { unmount } = render(
134
175
  <Provider store={store}>
135
- <NewHostDetailsTab
136
- hostName="test-host.example.com"
137
- router={mockRouter}
138
- />
176
+ <MemoryRouter>
177
+ <NewHostDetailsTab
178
+ hostName="test-host.example.com"
179
+ router={mockRouter}
180
+ response={defaultResponse}
181
+ />
182
+ </MemoryRouter>
139
183
  </Provider>
140
184
  );
141
185
 
142
- // Change the hash after mount, before unmount
143
186
  mockRouter.location.hash = '#/Overview';
144
187
 
145
188
  unmount();
146
189
 
147
- // Verify router.replace was called with the UPDATED hash, not the initial '#/Insights'
148
190
  expect(mockRouter.replace).toHaveBeenCalledWith({
149
191
  search: null,
150
192
  hash: '#/Overview',
151
193
  });
152
194
  });
153
195
  });
196
+
197
+ describe('tab visibility', () => {
198
+ it('should redirect to Overview when host is not RHEL', () => {
199
+ const nonRhelResponse = {
200
+ id: 2,
201
+ operatingsystem_name: 'Ubuntu 20.04',
202
+ insights_attributes: { uuid: 'test-uuid' },
203
+ };
204
+
205
+ render(
206
+ <Provider store={store}>
207
+ <MemoryRouter>
208
+ <NewHostDetailsTab
209
+ hostName="test-host.example.com"
210
+ response={nonRhelResponse}
211
+ />
212
+ </MemoryRouter>
213
+ </Provider>
214
+ );
215
+
216
+ expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
217
+ });
218
+
219
+ it('should redirect to Overview when insights facet is missing', () => {
220
+ const responseWithoutInsights = {
221
+ id: 3,
222
+ operatingsystem_name: 'Red Hat Enterprise Linux 8',
223
+ };
224
+
225
+ render(
226
+ <Provider store={store}>
227
+ <MemoryRouter>
228
+ <NewHostDetailsTab
229
+ hostName="test-host.example.com"
230
+ response={responseWithoutInsights}
231
+ />
232
+ </MemoryRouter>
233
+ </Provider>
234
+ );
235
+
236
+ expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
237
+ });
238
+
239
+ it('should not redirect when host is valid RHEL with insights facet', () => {
240
+ render(
241
+ <Provider store={store}>
242
+ <MemoryRouter>
243
+ <NewHostDetailsTab
244
+ hostName="test-host.example.com"
245
+ response={defaultResponse}
246
+ />
247
+ </MemoryRouter>
248
+ </Provider>
249
+ );
250
+
251
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
252
+ });
253
+
254
+ it('should not redirect when host data is not yet loaded', () => {
255
+ render(
256
+ <Provider store={store}>
257
+ <MemoryRouter>
258
+ <NewHostDetailsTab hostName="test-host.example.com" response={{}} />
259
+ </MemoryRouter>
260
+ </Provider>
261
+ );
262
+
263
+ expect(mockHistoryReplace).not.toHaveBeenCalled();
264
+ });
265
+ });
154
266
  });
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_rh_cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.1.0
4
+ version: 14.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team