foreman_rh_cloud 13.2.8 → 13.2.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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/insights_cloud/candlepin_proxies_extensions.rb +23 -0
- data/app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb +9 -0
- data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +13 -10
- data/app/controllers/insights_cloud/ui_requests_controller.rb +3 -7
- data/app/models/concerns/rh_cloud_host.rb +4 -2
- data/app/models/insights_client_report_status.rb +9 -1
- data/app/models/inventory_sync/inventory_status.rb +16 -4
- data/lib/foreman_inventory_upload/generators/fact_helpers.rb +26 -4
- data/lib/foreman_inventory_upload.rb +8 -1
- data/lib/foreman_rh_cloud/engine.rb +1 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/foreman_rh_cloud.rb +36 -9
- data/lib/insights_cloud/async/insights_generate_notifications.rb +10 -1
- data/lib/inventory_sync/async/inventory_full_sync.rb +39 -3
- data/lib/inventory_sync/async/inventory_self_host_sync.rb +12 -2
- data/package.json +1 -1
- data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +56 -2
- data/test/controllers/insights_cloud/candlepin_proxies_extensions_test.rb +70 -0
- data/test/controllers/insights_cloud/ui_requests_controller_test.rb +16 -2
- data/test/jobs/insights_client_status_aging_test.rb +40 -0
- data/test/jobs/insights_generate_notifications_test.rb +26 -0
- data/test/jobs/inventory_full_sync_test.rb +212 -0
- data/test/jobs/inventory_self_host_sync_test.rb +9 -0
- data/test/models/insights_client_report_status_test.rb +109 -0
- data/test/models/inventory_sync/inventory_status_test.rb +85 -0
- data/test/unit/foreman_rh_cloud_self_host_test.rb +50 -2
- data/test/unit/metadata_generator_test.rb +24 -1
- data/test/unit/rh_cloud_host_test.rb +60 -0
- data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +24 -2
- data/webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js +73 -10
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js +8 -2
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/__snapshots__/integrations.test.js.snap +1 -0
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/integrations.test.js +1 -0
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/Toast.js +43 -17
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/__tests__/Toast.test.js +82 -0
- data/webpack/ForemanRhCloudHelpers.js +22 -0
- data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +27 -1
- data/webpack/InsightsHostDetailsTab/__tests__/NewHostDetailsTab.test.js +134 -22
- metadata +16 -2
|
@@ -342,4 +342,64 @@ class RhCloudHostTest < ActiveSupport::TestCase
|
|
|
342
342
|
assert_not_includes results, host1
|
|
343
343
|
end
|
|
344
344
|
end
|
|
345
|
+
|
|
346
|
+
test 'scoped search for user_omitted inventory status works' do
|
|
347
|
+
host1 = FactoryBot.create(:host, :managed)
|
|
348
|
+
host2 = FactoryBot.create(:host, :managed)
|
|
349
|
+
host3 = FactoryBot.create(:host, :managed)
|
|
350
|
+
|
|
351
|
+
# Create different inventory statuses
|
|
352
|
+
InventorySync::InventoryStatus.create!(
|
|
353
|
+
host_id: host1.id,
|
|
354
|
+
status: InventorySync::InventoryStatus::SYNC,
|
|
355
|
+
reported_at: Time.zone.now
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
InventorySync::InventoryStatus.create!(
|
|
359
|
+
host_id: host2.id,
|
|
360
|
+
status: InventorySync::InventoryStatus::DISCONNECT,
|
|
361
|
+
reported_at: Time.zone.now
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
InventorySync::InventoryStatus.create!(
|
|
365
|
+
host_id: host3.id,
|
|
366
|
+
status: InventorySync::InventoryStatus::USER_OMITTED,
|
|
367
|
+
reported_at: Time.zone.now
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Search for user_omitted status
|
|
371
|
+
results = Host.search_for('insights_inventory_sync_status = user_omitted')
|
|
372
|
+
result_ids = results.pluck(:id)
|
|
373
|
+
|
|
374
|
+
assert_includes result_ids, host3.id, 'Host with USER_OMITTED status should be in search results'
|
|
375
|
+
assert_not_includes result_ids, host1.id, 'Host with SYNC status should not be in search results'
|
|
376
|
+
assert_not_includes result_ids, host2.id, 'Host with DISCONNECT status should not be in search results'
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
test 'scoped search for user_omitted insights client report status works' do
|
|
380
|
+
host1 = FactoryBot.create(:host, :managed)
|
|
381
|
+
host2 = FactoryBot.create(:host, :managed)
|
|
382
|
+
host3 = FactoryBot.create(:host, :managed)
|
|
383
|
+
|
|
384
|
+
# Create different insights client report statuses
|
|
385
|
+
status1 = host1.get_status(InsightsClientReportStatus)
|
|
386
|
+
status1.status = InsightsClientReportStatus::REPORTING
|
|
387
|
+
status1.save!
|
|
388
|
+
|
|
389
|
+
status2 = host2.get_status(InsightsClientReportStatus)
|
|
390
|
+
status2.status = InsightsClientReportStatus::NO_REPORT
|
|
391
|
+
status2.save!
|
|
392
|
+
|
|
393
|
+
status3 = host3.get_status(InsightsClientReportStatus)
|
|
394
|
+
status3.status = InsightsClientReportStatus::USER_OMITTED
|
|
395
|
+
status3.save!
|
|
396
|
+
|
|
397
|
+
# Search for user_omitted status
|
|
398
|
+
results = Host.search_for('insights_client_report_status = user_omitted')
|
|
399
|
+
result_ids = results.pluck(:id)
|
|
400
|
+
|
|
401
|
+
assert_includes result_ids, host3.id, 'Host with USER_OMITTED status should be in search results'
|
|
402
|
+
assert_not_includes result_ids, host1.id, 'Host with REPORTING status should not be in search results'
|
|
403
|
+
assert_not_includes result_ids, host2.id, 'Host with NO_REPORT status should not be in search results'
|
|
404
|
+
end
|
|
345
405
|
end
|
|
@@ -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
|
|
60
|
+
uuid: PropTypes.string,
|
|
43
61
|
}),
|
|
44
|
-
})
|
|
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
|
-
<
|
|
36
|
-
response={
|
|
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
|
-
<
|
|
51
|
-
response={
|
|
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
|
-
<
|
|
63
|
-
response={
|
|
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
|
});
|
data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButtonActions.js
CHANGED
|
@@ -39,14 +39,20 @@ export const setupInventorySyncTaskPolling = (id, dispatch) =>
|
|
|
39
39
|
key: INVENTORY_SYNC_TASK_UPDATE,
|
|
40
40
|
onTaskSuccess: ({
|
|
41
41
|
output: {
|
|
42
|
-
host_statuses: { sync, disconnect },
|
|
42
|
+
host_statuses: { sync, disconnect, user_omitted: userOmitted },
|
|
43
43
|
},
|
|
44
44
|
}) =>
|
|
45
45
|
dispatch(
|
|
46
46
|
addToast({
|
|
47
47
|
sticky: true,
|
|
48
48
|
type: 'success',
|
|
49
|
-
message:
|
|
49
|
+
message: (
|
|
50
|
+
<Toast
|
|
51
|
+
syncHosts={sync}
|
|
52
|
+
disconnectHosts={disconnect}
|
|
53
|
+
userOmittedHosts={userOmitted}
|
|
54
|
+
/>
|
|
55
|
+
),
|
|
50
56
|
})
|
|
51
57
|
),
|
|
52
58
|
dispatch,
|
data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/components/Toast.js
CHANGED
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
2
3
|
import PropTypes from 'prop-types';
|
|
3
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
|
-
import { foremanUrl } from '../../../../../../ForemanRhCloudHelpers';
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const statusSearchParams = statusName =>
|
|
7
|
+
`/new/hosts?search=insights_inventory_sync_status+%3D+${statusName}&page=1`;
|
|
8
|
+
const DISCONNECT = 'disconnect';
|
|
9
|
+
const SYNC = 'sync';
|
|
10
|
+
const USER_OMITTED = 'user_omitted';
|
|
11
|
+
const HostsWithStatusLink = ({ statusName, children }) => (
|
|
12
|
+
<Link to={statusSearchParams(statusName)}>{children}</Link>
|
|
13
|
+
);
|
|
14
|
+
HostsWithStatusLink.propTypes = {
|
|
15
|
+
statusName: PropTypes.string.isRequired,
|
|
16
|
+
children: PropTypes.node.isRequired,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const Toast = ({ syncHosts, disconnectHosts, userOmittedHosts }) => {
|
|
20
|
+
const totalHosts = syncHosts + disconnectHosts + userOmittedHosts;
|
|
8
21
|
return (
|
|
9
22
|
<span>
|
|
10
23
|
<p>
|
|
11
|
-
{__('
|
|
12
|
-
<
|
|
24
|
+
{__('Registered hosts in organization: ')}
|
|
25
|
+
<Link to="/new/hosts?search=set%3F+subscription_uuid&page=1">
|
|
26
|
+
{totalHosts}
|
|
27
|
+
</Link>
|
|
13
28
|
</p>
|
|
14
29
|
<p>
|
|
15
|
-
{__('
|
|
16
|
-
<
|
|
30
|
+
{__('Uploaded and present on console.redhat.com Inventory service: ')}
|
|
31
|
+
<HostsWithStatusLink statusName={SYNC}>{syncHosts}</HostsWithStatusLink>
|
|
17
32
|
</p>
|
|
18
33
|
<p>
|
|
19
|
-
{__('
|
|
20
|
-
<
|
|
34
|
+
{__('Not present on console.redhat.com Inventory service: ')}
|
|
35
|
+
<HostsWithStatusLink statusName={DISCONNECT}>
|
|
36
|
+
{disconnectHosts}
|
|
37
|
+
</HostsWithStatusLink>
|
|
21
38
|
</p>
|
|
39
|
+
{!!userOmittedHosts && (
|
|
40
|
+
<p>
|
|
41
|
+
{__(
|
|
42
|
+
'Excluded from upload to console.redhat.com Inventory service because host_registration_insights_inventory parameter value is false: '
|
|
43
|
+
)}
|
|
44
|
+
<HostsWithStatusLink statusName={USER_OMITTED}>
|
|
45
|
+
{userOmittedHosts}
|
|
46
|
+
</HostsWithStatusLink>
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
22
49
|
<p>
|
|
23
|
-
{__(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
target="_blank"
|
|
27
|
-
rel="noopener noreferrer"
|
|
28
|
-
>
|
|
29
|
-
{__('hosts page')}
|
|
30
|
-
</a>
|
|
50
|
+
{__(
|
|
51
|
+
'You can review this information later by looking at the Inventory status of each host.'
|
|
52
|
+
)}
|
|
31
53
|
</p>
|
|
32
54
|
</span>
|
|
33
55
|
);
|
|
@@ -36,6 +58,10 @@ const Toast = ({ syncHosts, disconnectHosts }) => {
|
|
|
36
58
|
Toast.propTypes = {
|
|
37
59
|
syncHosts: PropTypes.number.isRequired,
|
|
38
60
|
disconnectHosts: PropTypes.number.isRequired,
|
|
61
|
+
userOmittedHosts: PropTypes.number,
|
|
62
|
+
};
|
|
63
|
+
Toast.defaultProps = {
|
|
64
|
+
userOmittedHosts: 0,
|
|
39
65
|
};
|
|
40
66
|
|
|
41
67
|
export default Toast;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from '@theforeman/test';
|
|
3
|
+
import Toast from '../Toast';
|
|
4
|
+
|
|
5
|
+
describe('Toast', () => {
|
|
6
|
+
it('renders with all three status counts including user_omitted', () => {
|
|
7
|
+
const wrapper = shallow(
|
|
8
|
+
<Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={2} />
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
12
|
+
expect(links).toHaveLength(3);
|
|
13
|
+
|
|
14
|
+
// Check the children (numbers) of each link
|
|
15
|
+
expect(
|
|
16
|
+
links
|
|
17
|
+
.at(0)
|
|
18
|
+
.children()
|
|
19
|
+
.text()
|
|
20
|
+
).toBe('5');
|
|
21
|
+
expect(
|
|
22
|
+
links
|
|
23
|
+
.at(1)
|
|
24
|
+
.children()
|
|
25
|
+
.text()
|
|
26
|
+
).toBe('3');
|
|
27
|
+
expect(
|
|
28
|
+
links
|
|
29
|
+
.at(2)
|
|
30
|
+
.children()
|
|
31
|
+
.text()
|
|
32
|
+
).toBe('2');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('does not render user_omitted section when count is 0', () => {
|
|
36
|
+
const wrapper = shallow(
|
|
37
|
+
<Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={0} />
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Should have only 2 HostsWithStatusLink components (sync and disconnect)
|
|
41
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
42
|
+
expect(links).toHaveLength(2);
|
|
43
|
+
|
|
44
|
+
// Should not contain the user_omitted explanation text
|
|
45
|
+
expect(wrapper.text()).not.toContain(
|
|
46
|
+
'host_registration_insights_inventory parameter value is false'
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders without crashing when userOmittedHosts is not provided (default)', () => {
|
|
51
|
+
const wrapper = shallow(<Toast syncHosts={5} disconnectHosts={3} />);
|
|
52
|
+
|
|
53
|
+
// Should use default value of 0, so only 2 links
|
|
54
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
55
|
+
expect(links).toHaveLength(2);
|
|
56
|
+
|
|
57
|
+
// Verify the count values
|
|
58
|
+
expect(
|
|
59
|
+
links
|
|
60
|
+
.at(0)
|
|
61
|
+
.children()
|
|
62
|
+
.text()
|
|
63
|
+
).toBe('5');
|
|
64
|
+
expect(
|
|
65
|
+
links
|
|
66
|
+
.at(1)
|
|
67
|
+
.children()
|
|
68
|
+
.text()
|
|
69
|
+
).toBe('3');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders correct status links for each category', () => {
|
|
73
|
+
const wrapper = shallow(
|
|
74
|
+
<Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={2} />
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const links = wrapper.find('HostsWithStatusLink');
|
|
78
|
+
expect(links.at(0).prop('statusName')).toBe('sync');
|
|
79
|
+
expect(links.at(1).prop('statusName')).toBe('disconnect');
|
|
80
|
+
expect(links.at(2).prop('statusName')).toBe('user_omitted');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -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.
|
|
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;
|