foreman_rh_cloud 14.6.2 → 15.0.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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/app/assets/javascripts/foreman_rh_cloud/locale/fr/foreman_rh_cloud.js +76 -88
- data/app/assets/javascripts/foreman_rh_cloud/locale/ja/foreman_rh_cloud.js +87 -99
- data/app/assets/javascripts/foreman_rh_cloud/locale/ka/foreman_rh_cloud.js +139 -151
- data/app/assets/javascripts/foreman_rh_cloud/locale/ko/foreman_rh_cloud.js +71 -83
- data/app/assets/javascripts/foreman_rh_cloud/locale/zh_CN/foreman_rh_cloud.js +70 -82
- data/app/services/foreman_rh_cloud/insights_api_forwarder.rb +16 -0
- data/app/services/foreman_rh_cloud/tags_auth.rb +28 -13
- data/config/routes.rb +10 -0
- data/lib/foreman_inventory_upload/generators/queries.rb +1 -1
- data/lib/foreman_inventory_upload/generators/slice.rb +14 -2
- data/lib/foreman_inventory_upload.rb +1 -7
- data/lib/foreman_rh_cloud/plugin.rb +14 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/locale/foreman_rh_cloud.pot +246 -268
- data/locale/fr/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/fr/foreman_rh_cloud.po +78 -91
- data/locale/ja/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/ja/foreman_rh_cloud.po +87 -100
- data/locale/ka/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/ka/foreman_rh_cloud.po +139 -152
- data/locale/ko/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/ko/foreman_rh_cloud.po +71 -84
- data/locale/zh_CN/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
- data/locale/zh_CN/foreman_rh_cloud.po +71 -84
- data/package.json +1 -1
- data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +0 -1
- data/test/test_plugin_helper.rb +0 -8
- data/test/unit/services/foreman_rh_cloud/cloud_request_forwarder_test.rb +0 -1
- data/test/unit/services/foreman_rh_cloud/insights_api_forwarder_test.rb +22 -0
- data/test/unit/services/foreman_rh_cloud/tags_auth_test.rb +26 -0
- data/test/unit/slice_generator_test.rb +29 -0
- data/test/unit/tags_generator_test.rb +0 -1
- data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.js +36 -0
- data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.scss +3 -0
- data/webpack/ComplianceHostDetailsTab/__tests__/ComplianceHostDetailsTab.test.js +159 -0
- data/webpack/ComplianceHostDetailsTab/index.js +3 -0
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/Dashboard.test.js +79 -49
- data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +1 -5
- data/webpack/ForemanRhCloudFills.js +11 -0
- data/webpack/ForemanRhCloudPages.js +7 -0
- data/webpack/InsightsCompliance/ComplianceIframe.js +34 -0
- data/webpack/InsightsCompliance/ComplianceIframe.scss +12 -0
- data/webpack/InsightsCompliance/InsightsCompliancePage.js +11 -0
- data/webpack/InsightsCompliance/InsightsCompliancePage.test.js +35 -0
- data/webpack/InsightsCompliance/complianceIframeConstants.js +7 -0
- data/webpack/InsightsCompliance/complianceIframeHelpers.js +23 -0
- data/webpack/InsightsCompliance/complianceIframeHelpers.test.js +50 -0
- data/webpack/common/Hooks/PermissionsHooks.js +10 -0
- data/webpack/common/IopIframe/IopAppIframe.js +74 -0
- data/webpack/common/IopIframe/IopHostDetailsIframe.js +66 -0
- data/webpack/common/IopIframe/IopHostDetailsIframe.scss +6 -0
- data/webpack/common/IopIframe/IopHostDetailsIframeTab.js +81 -0
- data/webpack/common/IopIframe/__tests__/IopAppIframe.test.js +92 -0
- data/webpack/common/IopIframe/__tests__/IopHostDetailsIframeTab.test.js +93 -0
- data/webpack/common/IopIframe/__tests__/iopIframeHelpers.test.js +63 -0
- data/webpack/common/IopIframe/__tests__/postIopChromeToIframe.test.js +60 -0
- data/webpack/common/IopIframe/__tests__/useIopIframeHeight.test.js +37 -0
- data/webpack/common/IopIframe/iopIframeConstants.js +5 -0
- data/webpack/common/IopIframe/iopIframeHelpers.js +49 -0
- data/webpack/common/IopIframe/postIopChromeToIframe.js +35 -0
- data/webpack/common/IopIframe/useIopIframeChrome.js +54 -0
- data/webpack/common/IopIframe/useIopIframeHeight.js +35 -0
- data/webpack/common/IopIframe/useIopIframeNavigate.js +40 -0
- metadata +28 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
+
import ComplianceHostDetailsTabWrapper from '../ComplianceHostDetailsTab';
|
|
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
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
17
|
+
useForemanContext: () => ({
|
|
18
|
+
metadata: {
|
|
19
|
+
permissions: new Set(['view_hosts']),
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
useForemanPermissions: () => new Set(['view_hosts']),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.mock('foremanReact/common/I18n', () => ({
|
|
26
|
+
translate: str => str,
|
|
27
|
+
sprintf: str => str,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
jest.mock('../../InsightsCompliance/complianceIframeHelpers', () => ({
|
|
31
|
+
getComplianceIframeSrc: () =>
|
|
32
|
+
'https://example.test/assets/apps/compliance/index.html',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const mockPostChrome = jest.fn();
|
|
36
|
+
jest.mock('../../common/IopIframe/postIopChromeToIframe', () => ({
|
|
37
|
+
postIopChromeToIframe: (...args) => mockPostChrome(...args),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const defaultResponse = {
|
|
41
|
+
id: 1,
|
|
42
|
+
operatingsystem_name: 'Red Hat Enterprise Linux 8',
|
|
43
|
+
insights_attributes: { uuid: 'insights-1' },
|
|
44
|
+
subscription_facet_attributes: { uuid: '1-2-3' },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe('ComplianceHostDetailsTabWrapper', () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders compliance iframe for a valid host without redirecting', () => {
|
|
53
|
+
const { getByTestId, container } = render(
|
|
54
|
+
<MemoryRouter>
|
|
55
|
+
<ComplianceHostDetailsTabWrapper response={defaultResponse} />
|
|
56
|
+
</MemoryRouter>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(
|
|
60
|
+
container.querySelector(
|
|
61
|
+
'.rh-cloud-insights-compliance-host-details-component'
|
|
62
|
+
)
|
|
63
|
+
).toBeTruthy();
|
|
64
|
+
|
|
65
|
+
const iframe = getByTestId('compliance-host-details-iframe');
|
|
66
|
+
expect(iframe.getAttribute('src')).toBe(
|
|
67
|
+
'https://example.test/assets/apps/compliance/index.html'
|
|
68
|
+
);
|
|
69
|
+
expect(mockHistoryReplace).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('posts systems/<uuid> appRoute for the host-tab iframe', () => {
|
|
73
|
+
const { getByTestId } = render(
|
|
74
|
+
<MemoryRouter>
|
|
75
|
+
<ComplianceHostDetailsTabWrapper response={defaultResponse} />
|
|
76
|
+
</MemoryRouter>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
fireEvent.load(getByTestId('compliance-host-details-iframe'));
|
|
80
|
+
|
|
81
|
+
expect(mockPostChrome).toHaveBeenCalledWith(
|
|
82
|
+
expect.anything(),
|
|
83
|
+
expect.objectContaining({
|
|
84
|
+
appRoute: 'systems/1-2-3',
|
|
85
|
+
embedded: 'host-tab',
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('posts updated appRoute when inventoryId changes', () => {
|
|
91
|
+
const responseHostA = {
|
|
92
|
+
...defaultResponse,
|
|
93
|
+
subscription_facet_attributes: { uuid: 'uuid-host-A' },
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const { rerender, getByTestId } = render(
|
|
97
|
+
<MemoryRouter>
|
|
98
|
+
<ComplianceHostDetailsTabWrapper response={responseHostA} />
|
|
99
|
+
</MemoryRouter>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(getByTestId('compliance-host-details-iframe')).toBeTruthy();
|
|
103
|
+
|
|
104
|
+
const responseHostB = {
|
|
105
|
+
...defaultResponse,
|
|
106
|
+
subscription_facet_attributes: { uuid: 'uuid-host-B' },
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
rerender(
|
|
110
|
+
<MemoryRouter>
|
|
111
|
+
<ComplianceHostDetailsTabWrapper response={responseHostB} />
|
|
112
|
+
</MemoryRouter>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
fireEvent.load(getByTestId('compliance-host-details-iframe'));
|
|
116
|
+
|
|
117
|
+
expect(mockPostChrome).toHaveBeenCalledWith(
|
|
118
|
+
expect.anything(),
|
|
119
|
+
expect.objectContaining({
|
|
120
|
+
appRoute: 'systems/uuid-host-B',
|
|
121
|
+
embedded: 'host-tab',
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('redirects to Overview when tab should be hidden', () => {
|
|
127
|
+
const nonRhelResponse = {
|
|
128
|
+
id: 2,
|
|
129
|
+
operatingsystem_name: 'Ubuntu 20.04',
|
|
130
|
+
insights_attributes: { uuid: 'insights-1' },
|
|
131
|
+
subscription_facet_attributes: { uuid: '1-2-3' },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const { container } = render(
|
|
135
|
+
<MemoryRouter>
|
|
136
|
+
<ComplianceHostDetailsTabWrapper response={nonRhelResponse} />
|
|
137
|
+
</MemoryRouter>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
|
|
141
|
+
expect(
|
|
142
|
+
container.querySelector(
|
|
143
|
+
'.rh-cloud-insights-compliance-host-details-component'
|
|
144
|
+
)
|
|
145
|
+
).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('does not redirect when host data is not yet loaded', () => {
|
|
149
|
+
const emptyResponse = { subscription_facet_attributes: { uuid: '1-2-3' } };
|
|
150
|
+
|
|
151
|
+
render(
|
|
152
|
+
<MemoryRouter>
|
|
153
|
+
<ComplianceHostDetailsTabWrapper response={emptyResponse} />
|
|
154
|
+
</MemoryRouter>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(mockHistoryReplace).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -1,67 +1,97 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { rtlHelpers } from 'foremanReact/common/rtlTestHelpers';
|
|
3
5
|
import Dashboard from '../Dashboard';
|
|
4
6
|
|
|
7
|
+
const { renderWithStoreAndI18n } = rtlHelpers;
|
|
8
|
+
|
|
9
|
+
jest.mock('../../../../common/Hooks/ConfigHooks', () => ({
|
|
10
|
+
useIopConfig: jest.fn(() => false),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const runningTask = {
|
|
14
|
+
id: 'task-1',
|
|
15
|
+
state: 'running',
|
|
16
|
+
result: null,
|
|
17
|
+
progress: 50,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const inventoryStore = {
|
|
21
|
+
API: {
|
|
22
|
+
INVENTORY_SETTINGS: {
|
|
23
|
+
response: { subscriptionConnectionEnabled: true },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const renderDashboard = (props = {}) =>
|
|
29
|
+
renderWithStoreAndI18n(<Dashboard {...props} />, inventoryStore);
|
|
30
|
+
|
|
5
31
|
describe('Dashboard', () => {
|
|
6
|
-
it('
|
|
7
|
-
const account = {
|
|
8
|
-
id: 1,
|
|
9
|
-
generate_task: {
|
|
10
|
-
id: 'task-1',
|
|
11
|
-
state: 'running',
|
|
12
|
-
result: null,
|
|
13
|
-
progress: 50,
|
|
14
|
-
},
|
|
15
|
-
};
|
|
32
|
+
it('renders report generation progress for a running task', async () => {
|
|
16
33
|
const onTaskStart = jest.fn();
|
|
17
34
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
renderDashboard({
|
|
36
|
+
account: { id: 1, generate_task: runningTask },
|
|
37
|
+
onTaskStart,
|
|
38
|
+
});
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
25
|
-
expect(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
expect(
|
|
40
|
+
expect(await screen.findByText('Report Generation')).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByLabelText('task-progress')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText('Running')).toBeInTheDocument();
|
|
43
|
+
expect(
|
|
44
|
+
screen.getByRole('link', { name: 'View task details' })
|
|
45
|
+
).toHaveAttribute('href', '/foreman_tasks/tasks/task-1');
|
|
46
|
+
expect(
|
|
47
|
+
screen.getByRole('button', { name: 'Generate and upload report' })
|
|
48
|
+
).toBeDisabled();
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByRole('button', { name: 'Generate report' })
|
|
51
|
+
).toBeDisabled();
|
|
29
52
|
});
|
|
30
53
|
|
|
31
|
-
it('
|
|
32
|
-
|
|
54
|
+
it('shows an empty state when account data is missing', async () => {
|
|
55
|
+
renderDashboard({ account: null, onTaskStart: null });
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
expect(
|
|
36
|
-
expect(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'No account data available.'
|
|
40
|
-
);
|
|
57
|
+
expect(await screen.findByText('No recent tasks')).toBeInTheDocument();
|
|
58
|
+
expect(screen.getByText('No account data available.')).toBeInTheDocument();
|
|
59
|
+
expect(
|
|
60
|
+
screen.queryByRole('button', { name: 'Generate report' })
|
|
61
|
+
).not.toBeInTheDocument();
|
|
41
62
|
});
|
|
42
63
|
|
|
43
|
-
it('
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
it('shows generate actions when the account has no generate task', async () => {
|
|
65
|
+
renderDashboard({
|
|
66
|
+
account: { id: 1, generate_task: null },
|
|
67
|
+
onTaskStart: null,
|
|
68
|
+
});
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect(
|
|
70
|
+
expect(
|
|
71
|
+
await screen.findByText('No report generation tasks have been run yet.')
|
|
72
|
+
).toBeInTheDocument();
|
|
73
|
+
expect(
|
|
74
|
+
screen.getByRole('button', { name: 'Generate and upload report' })
|
|
75
|
+
).toBeInTheDocument();
|
|
76
|
+
expect(
|
|
77
|
+
screen.getByRole('button', { name: 'Generate report' })
|
|
78
|
+
).toBeInTheDocument();
|
|
51
79
|
});
|
|
52
80
|
|
|
53
|
-
it('
|
|
54
|
-
|
|
55
|
-
generate_task:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
const wrapper = shallow(<Dashboard account={account} onTaskStart={null} />);
|
|
81
|
+
it('renders the task without generate actions when account id is missing', async () => {
|
|
82
|
+
renderDashboard({
|
|
83
|
+
account: { generate_task: runningTask },
|
|
84
|
+
onTaskStart: null,
|
|
85
|
+
});
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(
|
|
87
|
+
expect(
|
|
88
|
+
await screen.findByRole('link', { name: 'View task details' })
|
|
89
|
+
).toHaveAttribute('href', '/foreman_tasks/tasks/task-1');
|
|
90
|
+
expect(
|
|
91
|
+
screen.queryByRole('button', { name: 'Generate report' })
|
|
92
|
+
).not.toBeInTheDocument();
|
|
93
|
+
expect(
|
|
94
|
+
screen.queryByRole('button', { name: 'Generate and upload report' })
|
|
95
|
+
).not.toBeInTheDocument();
|
|
66
96
|
});
|
|
67
97
|
});
|
|
@@ -255,11 +255,7 @@ const TaskProgress = ({
|
|
|
255
255
|
measureLocation="outside"
|
|
256
256
|
aria-label="task-progress"
|
|
257
257
|
/>
|
|
258
|
-
<DescriptionList
|
|
259
|
-
isHorizontal
|
|
260
|
-
className="task-progress-details"
|
|
261
|
-
ouiaId="task-progress-details"
|
|
262
|
-
>
|
|
258
|
+
<DescriptionList isHorizontal className="task-progress-details">
|
|
263
259
|
<DescriptionListGroup>
|
|
264
260
|
<DescriptionListTerm>{__('Started')}</DescriptionListTerm>
|
|
265
261
|
<DescriptionListDescription>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
hasNoInsightsFacet,
|
|
11
11
|
} from './ForemanRhCloudHelpers';
|
|
12
12
|
import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab/CVEsHostDetailsTab';
|
|
13
|
+
import ComplianceHostDetailsTabWrapper from './ComplianceHostDetailsTab/ComplianceHostDetailsTab';
|
|
13
14
|
import InsightsVulnerabilityActionsBar from './InsightsVulnerabilityActionsBar';
|
|
14
15
|
|
|
15
16
|
const fills = [
|
|
@@ -51,6 +52,16 @@ const fills = [
|
|
|
51
52
|
title: __('Vulnerabilities'),
|
|
52
53
|
},
|
|
53
54
|
},
|
|
55
|
+
{
|
|
56
|
+
slot: 'host-details-page-tabs',
|
|
57
|
+
name: 'Compliance',
|
|
58
|
+
component: props => <ComplianceHostDetailsTabWrapper {...props} />,
|
|
59
|
+
weight: 350,
|
|
60
|
+
metadata: {
|
|
61
|
+
hideTab: props => isNotRhelHost(props) || hasNoInsightsFacet(props),
|
|
62
|
+
title: __('Compliance'),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
54
65
|
];
|
|
55
66
|
|
|
56
67
|
export const registerFills = () => {
|
|
@@ -8,6 +8,7 @@ import IopRecommendationDetails from './IopRecommendationDetails/IopRecommendati
|
|
|
8
8
|
import IopPathwayDetails from './IopPathwayDetails/IopPathwayDetails';
|
|
9
9
|
import InsightsHostDetailsTab from './InsightsHostDetailsTab';
|
|
10
10
|
import CveDetailsPage from './CveDetailsPage';
|
|
11
|
+
import InsightsCompliancePage from './InsightsCompliance/InsightsCompliancePage';
|
|
11
12
|
import './common/styles.scss';
|
|
12
13
|
|
|
13
14
|
const pages = [
|
|
@@ -20,6 +21,7 @@ const pages = [
|
|
|
20
21
|
type: InsightsVulnerabilityListPage,
|
|
21
22
|
},
|
|
22
23
|
{ name: 'CveDetailsPage', type: CveDetailsPage },
|
|
24
|
+
{ name: 'InsightsCompliancePage', type: InsightsCompliancePage },
|
|
23
25
|
];
|
|
24
26
|
|
|
25
27
|
export const registerPages = () => {
|
|
@@ -57,6 +59,11 @@ export const routes = [
|
|
|
57
59
|
exact: true,
|
|
58
60
|
render: props => <CveDetailsPage {...props} />,
|
|
59
61
|
},
|
|
62
|
+
{
|
|
63
|
+
path: '/foreman_rh_cloud/insights_compliance',
|
|
64
|
+
exact: false,
|
|
65
|
+
render: props => <InsightsCompliancePage {...props} />,
|
|
66
|
+
},
|
|
60
67
|
];
|
|
61
68
|
|
|
62
69
|
export const registerRoutes = () => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import IopAppIframe from '../common/IopIframe/IopAppIframe';
|
|
4
|
+
import {
|
|
5
|
+
getComplianceAppRoute,
|
|
6
|
+
getComplianceForemanPath,
|
|
7
|
+
getComplianceIframeSrc,
|
|
8
|
+
} from './complianceIframeHelpers';
|
|
9
|
+
import {
|
|
10
|
+
IOP_COMPLIANCE_NAVIGATE,
|
|
11
|
+
IOP_COMPLIANCE_READY,
|
|
12
|
+
} from './complianceIframeConstants';
|
|
13
|
+
|
|
14
|
+
import './ComplianceIframe.scss';
|
|
15
|
+
|
|
16
|
+
const ComplianceIframe = ({ appName }) => (
|
|
17
|
+
<IopAppIframe
|
|
18
|
+
appName={appName}
|
|
19
|
+
iframeSrc={getComplianceIframeSrc()}
|
|
20
|
+
getAppRoute={getComplianceAppRoute}
|
|
21
|
+
getForemanPath={getComplianceForemanPath}
|
|
22
|
+
defaultAppRoute="reports"
|
|
23
|
+
readyMessageType={IOP_COMPLIANCE_READY}
|
|
24
|
+
navigateMessageType={IOP_COMPLIANCE_NAVIGATE}
|
|
25
|
+
className="rh-cloud-insights-compliance-iframe"
|
|
26
|
+
testId="compliance-iframe"
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
ComplianceIframe.propTypes = {
|
|
31
|
+
appName: PropTypes.string.isRequired,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default ComplianceIframe;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.rh-cloud-insights-compliance-page {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
min-height: calc(100vh - var(--pf-v5-global--spacer--2xl, 3rem));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.rh-cloud-insights-compliance-iframe {
|
|
8
|
+
border: 0;
|
|
9
|
+
flex: 1 1 auto;
|
|
10
|
+
min-height: 70vh;
|
|
11
|
+
width: 100%;
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import ComplianceIframe from './ComplianceIframe';
|
|
4
|
+
|
|
5
|
+
const InsightsCompliancePage = () => (
|
|
6
|
+
<div className="rh-cloud-insights-compliance-page compliance">
|
|
7
|
+
<ComplianceIframe appName="Compliance" />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export default InsightsCompliancePage;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
|
|
6
|
+
import InsightsCompliancePage from './InsightsCompliancePage';
|
|
7
|
+
|
|
8
|
+
jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
9
|
+
useForemanContext: () => ({
|
|
10
|
+
metadata: {
|
|
11
|
+
permissions: new Set(['view_compliance']),
|
|
12
|
+
},
|
|
13
|
+
}),
|
|
14
|
+
useForemanPermissions: () => new Set(['view_compliance']),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('InsightsCompliancePage', () => {
|
|
18
|
+
it('renders the compliance iframe container', () => {
|
|
19
|
+
render(
|
|
20
|
+
<MemoryRouter
|
|
21
|
+
initialEntries={['/foreman_rh_cloud/insights_compliance/reports']}
|
|
22
|
+
>
|
|
23
|
+
<InsightsCompliancePage />
|
|
24
|
+
</MemoryRouter>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
document.querySelector('.rh-cloud-insights-compliance-page')
|
|
29
|
+
).toBeTruthy();
|
|
30
|
+
expect(screen.getByTestId('compliance-iframe')).toHaveAttribute(
|
|
31
|
+
'src',
|
|
32
|
+
`${window.location.origin}/assets/apps/compliance/index.html`
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const COMPLIANCE_STATIC_APP_PATH = '/assets/apps/compliance/index.html';
|
|
2
|
+
|
|
3
|
+
export const IOP_COMPLIANCE_READY = 'IOP_COMPLIANCE_READY';
|
|
4
|
+
|
|
5
|
+
export const IOP_COMPLIANCE_NAVIGATE = 'IOP_COMPLIANCE_NAVIGATE';
|
|
6
|
+
|
|
7
|
+
export const COMPLIANCE_ROUTE_PREFIX = '/foreman_rh_cloud/insights_compliance';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPLIANCE_ROUTE_PREFIX,
|
|
3
|
+
COMPLIANCE_STATIC_APP_PATH,
|
|
4
|
+
} from './complianceIframeConstants';
|
|
5
|
+
import {
|
|
6
|
+
getIopAppRoute,
|
|
7
|
+
getIopForemanPath,
|
|
8
|
+
getIopIframeSrc,
|
|
9
|
+
} from '../common/IopIframe/iopIframeHelpers';
|
|
10
|
+
|
|
11
|
+
const COMPLIANCE_IOP_PATH = {
|
|
12
|
+
routePrefix: COMPLIANCE_ROUTE_PREFIX,
|
|
13
|
+
fallbackRoute: 'reports',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getComplianceForemanPath = (appRoute = 'reports') =>
|
|
17
|
+
getIopForemanPath(appRoute, COMPLIANCE_IOP_PATH);
|
|
18
|
+
|
|
19
|
+
export const getComplianceAppRoute = (pathname = '') =>
|
|
20
|
+
getIopAppRoute(pathname, COMPLIANCE_IOP_PATH);
|
|
21
|
+
|
|
22
|
+
export const getComplianceIframeSrc = () =>
|
|
23
|
+
getIopIframeSrc(COMPLIANCE_STATIC_APP_PATH);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getComplianceAppRoute,
|
|
3
|
+
getComplianceForemanPath,
|
|
4
|
+
getComplianceIframeSrc,
|
|
5
|
+
} from './complianceIframeHelpers';
|
|
6
|
+
|
|
7
|
+
describe('complianceIframeHelpers', () => {
|
|
8
|
+
describe('getComplianceAppRoute', () => {
|
|
9
|
+
it('maps foreman compliance paths to app routes', () => {
|
|
10
|
+
expect(
|
|
11
|
+
getComplianceAppRoute('/foreman_rh_cloud/insights_compliance/reports')
|
|
12
|
+
).toBe('reports');
|
|
13
|
+
expect(
|
|
14
|
+
getComplianceAppRoute(
|
|
15
|
+
'/foreman_rh_cloud/insights_compliance/reports/report-1'
|
|
16
|
+
)
|
|
17
|
+
).toBe('reports/report-1');
|
|
18
|
+
expect(
|
|
19
|
+
getComplianceAppRoute(
|
|
20
|
+
'/foreman_rh_cloud/insights_compliance/scappolicies/new'
|
|
21
|
+
)
|
|
22
|
+
).toBe('scappolicies/new');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('defaults to reports for unknown paths', () => {
|
|
26
|
+
expect(getComplianceAppRoute('/foreman_rh_cloud/insights_cloud')).toBe(
|
|
27
|
+
'reports'
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('getComplianceForemanPath', () => {
|
|
33
|
+
it('maps app routes to foreman compliance paths', () => {
|
|
34
|
+
expect(getComplianceForemanPath('reports')).toBe(
|
|
35
|
+
'/foreman_rh_cloud/insights_compliance/reports'
|
|
36
|
+
);
|
|
37
|
+
expect(getComplianceForemanPath('scappolicies/new')).toBe(
|
|
38
|
+
'/foreman_rh_cloud/insights_compliance/scappolicies/new'
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getComplianceIframeSrc', () => {
|
|
44
|
+
it('returns the static compliance app entry', () => {
|
|
45
|
+
expect(getComplianceIframeSrc()).toBe(
|
|
46
|
+
`${window.location.origin}/assets/apps/compliance/index.html`
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -25,6 +25,16 @@ const PERMISSION_MAPPING = {
|
|
|
25
25
|
],
|
|
26
26
|
view_advisor: ['advisor:recommendation-results:read', 'advisor:exports:read'],
|
|
27
27
|
edit_advisor: ['advisor:disable-recommendations:write'],
|
|
28
|
+
view_compliance: [
|
|
29
|
+
'inventory:hosts:read',
|
|
30
|
+
'compliance:report:read',
|
|
31
|
+
'compliance:policy:read',
|
|
32
|
+
],
|
|
33
|
+
edit_compliance: [
|
|
34
|
+
'compliance:policy:write',
|
|
35
|
+
'compliance:policy:create',
|
|
36
|
+
'compliance:policy:delete',
|
|
37
|
+
],
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
/**
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useHistory, useLocation } from 'react-router-dom';
|
|
4
|
+
import { useInsightsPermissions } from '../Hooks/PermissionsHooks';
|
|
5
|
+
import { useIopIframeChrome } from './useIopIframeChrome';
|
|
6
|
+
import { useIopIframeNavigate } from './useIopIframeNavigate';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Full-page IoP app iframe: chrome sync + Foreman URL updates from NAVIGATE.
|
|
10
|
+
* App shells supply src, route mappers, and message types.
|
|
11
|
+
*/
|
|
12
|
+
const IopAppIframe = ({
|
|
13
|
+
appName,
|
|
14
|
+
iframeSrc,
|
|
15
|
+
getAppRoute,
|
|
16
|
+
getForemanPath,
|
|
17
|
+
defaultAppRoute,
|
|
18
|
+
readyMessageType,
|
|
19
|
+
navigateMessageType,
|
|
20
|
+
className = '',
|
|
21
|
+
testId = 'iop-app-iframe',
|
|
22
|
+
}) => {
|
|
23
|
+
const iframeRef = useRef(null);
|
|
24
|
+
const history = useHistory();
|
|
25
|
+
const { pathname } = useLocation();
|
|
26
|
+
const permissions = useInsightsPermissions();
|
|
27
|
+
const appRoute = getAppRoute(pathname);
|
|
28
|
+
|
|
29
|
+
const sendChromeContext = useIopIframeChrome({
|
|
30
|
+
iframeRef,
|
|
31
|
+
permissions,
|
|
32
|
+
appRoute,
|
|
33
|
+
pathname,
|
|
34
|
+
readyMessageType,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
useIopIframeNavigate({
|
|
38
|
+
history,
|
|
39
|
+
navigateMessageType,
|
|
40
|
+
getForemanPath,
|
|
41
|
+
defaultAppRoute,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
// eslint-disable-next-line jsx-a11y/iframe-has-title
|
|
46
|
+
<iframe
|
|
47
|
+
ref={iframeRef}
|
|
48
|
+
className={className}
|
|
49
|
+
data-testid={testId}
|
|
50
|
+
src={iframeSrc}
|
|
51
|
+
aria-label={appName}
|
|
52
|
+
onLoad={sendChromeContext}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
IopAppIframe.propTypes = {
|
|
58
|
+
appName: PropTypes.string.isRequired,
|
|
59
|
+
iframeSrc: PropTypes.string.isRequired,
|
|
60
|
+
getAppRoute: PropTypes.func.isRequired,
|
|
61
|
+
getForemanPath: PropTypes.func.isRequired,
|
|
62
|
+
defaultAppRoute: PropTypes.string.isRequired,
|
|
63
|
+
readyMessageType: PropTypes.string.isRequired,
|
|
64
|
+
navigateMessageType: PropTypes.string.isRequired,
|
|
65
|
+
className: PropTypes.string,
|
|
66
|
+
testId: PropTypes.string,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
IopAppIframe.defaultProps = {
|
|
70
|
+
className: '',
|
|
71
|
+
testId: 'iop-app-iframe',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default IopAppIframe;
|