foreman_rh_cloud 14.7.1 → 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/app/services/foreman_rh_cloud/tags_auth.rb +28 -13
- data/config/routes.rb +10 -0
- data/lib/foreman_rh_cloud/plugin.rb +14 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/package.json +1 -1
- data/test/unit/services/foreman_rh_cloud/tags_auth_test.rb +26 -0
- 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/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 +27 -1
|
@@ -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;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useInsightsPermissions } from '../Hooks/PermissionsHooks';
|
|
4
|
+
import { useIopIframeChrome } from './useIopIframeChrome';
|
|
5
|
+
import { useIopIframeHeight } from './useIopIframeHeight';
|
|
6
|
+
import './IopHostDetailsIframe.scss';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Host-details tab iframe for an IoP app (chrome post + READY re-sync).
|
|
10
|
+
* App-specific wrappers supply src, appRoute, title, and styles.
|
|
11
|
+
*/
|
|
12
|
+
const IopHostDetailsIframe = ({
|
|
13
|
+
inventoryId,
|
|
14
|
+
iframeSrc,
|
|
15
|
+
appRoute,
|
|
16
|
+
title,
|
|
17
|
+
readyMessageType,
|
|
18
|
+
className = '',
|
|
19
|
+
iframeClassName = 'rh-cloud-iop-host-details-iframe',
|
|
20
|
+
testId = 'iop-host-details-iframe',
|
|
21
|
+
}) => {
|
|
22
|
+
const iframeRef = useRef(null);
|
|
23
|
+
const permissions = useInsightsPermissions();
|
|
24
|
+
const sendChromeContext = useIopIframeChrome({
|
|
25
|
+
iframeRef,
|
|
26
|
+
permissions,
|
|
27
|
+
appRoute,
|
|
28
|
+
embedded: 'host-tab',
|
|
29
|
+
readyMessageType,
|
|
30
|
+
});
|
|
31
|
+
useIopIframeHeight({ iframeRef });
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={className}>
|
|
35
|
+
<iframe
|
|
36
|
+
key={inventoryId}
|
|
37
|
+
ref={iframeRef}
|
|
38
|
+
className={iframeClassName}
|
|
39
|
+
data-testid={testId}
|
|
40
|
+
src={iframeSrc}
|
|
41
|
+
title={title}
|
|
42
|
+
aria-label={title}
|
|
43
|
+
onLoad={sendChromeContext}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
IopHostDetailsIframe.propTypes = {
|
|
50
|
+
inventoryId: PropTypes.string.isRequired,
|
|
51
|
+
iframeSrc: PropTypes.string.isRequired,
|
|
52
|
+
appRoute: PropTypes.string.isRequired,
|
|
53
|
+
title: PropTypes.string.isRequired,
|
|
54
|
+
readyMessageType: PropTypes.string.isRequired,
|
|
55
|
+
className: PropTypes.string,
|
|
56
|
+
iframeClassName: PropTypes.string,
|
|
57
|
+
testId: PropTypes.string,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
IopHostDetailsIframe.defaultProps = {
|
|
61
|
+
className: '',
|
|
62
|
+
iframeClassName: 'rh-cloud-iop-host-details-iframe',
|
|
63
|
+
testId: 'iop-host-details-iframe',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default IopHostDetailsIframe;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import {
|
|
4
|
+
isNotRhelHost,
|
|
5
|
+
hasNoInsightsFacet,
|
|
6
|
+
useTabRedirect,
|
|
7
|
+
} from '../../ForemanRhCloudHelpers';
|
|
8
|
+
import IopHostDetailsIframe from './IopHostDetailsIframe';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Host-details fill wrapper: hide/redirect for non-RHEL / no Insights facet,
|
|
12
|
+
* then render an IoP app iframe for the subscription UUID.
|
|
13
|
+
*/
|
|
14
|
+
const IopHostDetailsIframeTab = ({
|
|
15
|
+
response = {},
|
|
16
|
+
iframeSrc,
|
|
17
|
+
buildAppRoute,
|
|
18
|
+
title,
|
|
19
|
+
readyMessageType,
|
|
20
|
+
className = '',
|
|
21
|
+
iframeClassName = 'rh-cloud-iop-host-details-iframe',
|
|
22
|
+
testId = 'iop-host-details-iframe',
|
|
23
|
+
}) => {
|
|
24
|
+
const isHostDataLoaded = Boolean(response?.id);
|
|
25
|
+
const shouldHideTab = useTabRedirect(
|
|
26
|
+
isHostDataLoaded &&
|
|
27
|
+
(isNotRhelHost({ hostDetails: response }) ||
|
|
28
|
+
hasNoInsightsFacet({ response, hostDetails: response }))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (shouldHideTab) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line camelcase
|
|
36
|
+
const inventoryId = response?.subscription_facet_attributes?.uuid;
|
|
37
|
+
|
|
38
|
+
if (!inventoryId) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<IopHostDetailsIframe
|
|
44
|
+
inventoryId={inventoryId}
|
|
45
|
+
iframeSrc={iframeSrc}
|
|
46
|
+
appRoute={buildAppRoute(inventoryId)}
|
|
47
|
+
title={title}
|
|
48
|
+
readyMessageType={readyMessageType}
|
|
49
|
+
className={className}
|
|
50
|
+
iframeClassName={iframeClassName}
|
|
51
|
+
testId={testId}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
IopHostDetailsIframeTab.propTypes = {
|
|
57
|
+
response: PropTypes.shape({
|
|
58
|
+
id: PropTypes.number,
|
|
59
|
+
operatingsystem_name: PropTypes.string,
|
|
60
|
+
insights_attributes: PropTypes.object,
|
|
61
|
+
subscription_facet_attributes: PropTypes.shape({
|
|
62
|
+
uuid: PropTypes.string,
|
|
63
|
+
}),
|
|
64
|
+
}),
|
|
65
|
+
iframeSrc: PropTypes.string.isRequired,
|
|
66
|
+
buildAppRoute: PropTypes.func.isRequired,
|
|
67
|
+
title: PropTypes.string.isRequired,
|
|
68
|
+
readyMessageType: PropTypes.string.isRequired,
|
|
69
|
+
className: PropTypes.string,
|
|
70
|
+
iframeClassName: PropTypes.string,
|
|
71
|
+
testId: PropTypes.string,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
IopHostDetailsIframeTab.defaultProps = {
|
|
75
|
+
response: {},
|
|
76
|
+
className: '',
|
|
77
|
+
iframeClassName: 'rh-cloud-iop-host-details-iframe',
|
|
78
|
+
testId: 'iop-host-details-iframe',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default IopHostDetailsIframeTab;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter, Route } from 'react-router-dom';
|
|
4
|
+
import IopAppIframe from '../IopAppIframe';
|
|
5
|
+
|
|
6
|
+
const mockHistoryPush = jest.fn();
|
|
7
|
+
|
|
8
|
+
jest.mock('react-router-dom', () => ({
|
|
9
|
+
...jest.requireActual('react-router-dom'),
|
|
10
|
+
useHistory: () => ({
|
|
11
|
+
push: mockHistoryPush,
|
|
12
|
+
}),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
16
|
+
useForemanContext: () => ({
|
|
17
|
+
metadata: {
|
|
18
|
+
permissions: new Set(['view_hosts']),
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
useForemanPermissions: () => new Set(['view_hosts']),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const mockPostChrome = jest.fn();
|
|
25
|
+
jest.mock('../postIopChromeToIframe', () => ({
|
|
26
|
+
postIopChromeToIframe: (...args) => mockPostChrome(...args),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const routePrefix = '/foreman_rh_cloud/insights_example';
|
|
30
|
+
|
|
31
|
+
const defaultProps = {
|
|
32
|
+
appName: 'Example',
|
|
33
|
+
iframeSrc: 'https://example.test/assets/apps/example/index.html',
|
|
34
|
+
getAppRoute: pathname =>
|
|
35
|
+
pathname.startsWith(routePrefix)
|
|
36
|
+
? pathname.slice(routePrefix.length).replace(/^\//, '') || 'dashboard'
|
|
37
|
+
: 'dashboard',
|
|
38
|
+
getForemanPath: appRoute => `${routePrefix}/${appRoute}`,
|
|
39
|
+
defaultAppRoute: 'dashboard',
|
|
40
|
+
readyMessageType: 'IOP_EXAMPLE_READY',
|
|
41
|
+
navigateMessageType: 'IOP_EXAMPLE_NAVIGATE',
|
|
42
|
+
className: 'example-iframe',
|
|
43
|
+
testId: 'example-iframe',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe('IopAppIframe', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
jest.clearAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('posts chrome context derived from the Foreman pathname', () => {
|
|
52
|
+
const { getByTestId } = render(
|
|
53
|
+
<MemoryRouter initialEntries={[`${routePrefix}/cves`]}>
|
|
54
|
+
<Route path="*">
|
|
55
|
+
<IopAppIframe {...defaultProps} />
|
|
56
|
+
</Route>
|
|
57
|
+
</MemoryRouter>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
fireEvent.load(getByTestId('example-iframe'));
|
|
61
|
+
|
|
62
|
+
expect(mockPostChrome).toHaveBeenCalledWith(
|
|
63
|
+
expect.anything(),
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
appRoute: 'cves',
|
|
66
|
+
pathname: `${routePrefix}/cves`,
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('pushes Foreman path when the iframe sends NAVIGATE', () => {
|
|
72
|
+
render(
|
|
73
|
+
<MemoryRouter initialEntries={[`${routePrefix}/dashboard`]}>
|
|
74
|
+
<Route path="*">
|
|
75
|
+
<IopAppIframe {...defaultProps} />
|
|
76
|
+
</Route>
|
|
77
|
+
</MemoryRouter>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
window.dispatchEvent(
|
|
81
|
+
new MessageEvent('message', {
|
|
82
|
+
origin: window.location.origin,
|
|
83
|
+
data: {
|
|
84
|
+
type: 'IOP_EXAMPLE_NAVIGATE',
|
|
85
|
+
payload: { appRoute: 'cves/cve-1' },
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(mockHistoryPush).toHaveBeenCalledWith(`${routePrefix}/cves/cve-1`);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
+
import IopHostDetailsIframeTab from '../IopHostDetailsIframeTab';
|
|
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
|
+
const mockPostChrome = jest.fn();
|
|
26
|
+
jest.mock('../postIopChromeToIframe', () => ({
|
|
27
|
+
postIopChromeToIframe: (...args) => mockPostChrome(...args),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const defaultResponse = {
|
|
31
|
+
id: 1,
|
|
32
|
+
operatingsystem_name: 'Red Hat Enterprise Linux 8',
|
|
33
|
+
insights_attributes: { uuid: 'insights-1' },
|
|
34
|
+
subscription_facet_attributes: { uuid: 'inv-123' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const defaultProps = {
|
|
38
|
+
response: defaultResponse,
|
|
39
|
+
iframeSrc: 'https://example.test/assets/apps/example/index.html',
|
|
40
|
+
buildAppRoute: inventoryId => `systems/${inventoryId}`,
|
|
41
|
+
title: 'Example',
|
|
42
|
+
readyMessageType: 'IOP_EXAMPLE_READY',
|
|
43
|
+
className: 'example-host-tab',
|
|
44
|
+
iframeClassName: 'example-host-iframe',
|
|
45
|
+
testId: 'example-host-iframe',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe('IopHostDetailsIframeTab', () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders iframe and posts host-tab chrome on load', () => {
|
|
54
|
+
const { getByTestId, container } = render(
|
|
55
|
+
<MemoryRouter>
|
|
56
|
+
<IopHostDetailsIframeTab {...defaultProps} />
|
|
57
|
+
</MemoryRouter>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(container.querySelector('.example-host-tab')).toBeTruthy();
|
|
61
|
+
const iframe = getByTestId('example-host-iframe');
|
|
62
|
+
expect(iframe.getAttribute('src')).toBe(defaultProps.iframeSrc);
|
|
63
|
+
|
|
64
|
+
fireEvent.load(iframe);
|
|
65
|
+
|
|
66
|
+
expect(mockPostChrome).toHaveBeenCalledWith(
|
|
67
|
+
expect.anything(),
|
|
68
|
+
expect.objectContaining({
|
|
69
|
+
appRoute: 'systems/inv-123',
|
|
70
|
+
embedded: 'host-tab',
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('redirects to Overview when tab should be hidden', () => {
|
|
76
|
+
const { container } = render(
|
|
77
|
+
<MemoryRouter>
|
|
78
|
+
<IopHostDetailsIframeTab
|
|
79
|
+
{...defaultProps}
|
|
80
|
+
response={{
|
|
81
|
+
id: 2,
|
|
82
|
+
operatingsystem_name: 'Ubuntu 20.04',
|
|
83
|
+
insights_attributes: { uuid: 'insights-1' },
|
|
84
|
+
subscription_facet_attributes: { uuid: 'inv-123' },
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
</MemoryRouter>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
|
|
91
|
+
expect(container.querySelector('.example-host-tab')).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getIopAppRoute,
|
|
3
|
+
getIopForemanPath,
|
|
4
|
+
getIopIframeSrc,
|
|
5
|
+
} from '../iopIframeHelpers';
|
|
6
|
+
|
|
7
|
+
describe('iopIframeHelpers', () => {
|
|
8
|
+
const options = {
|
|
9
|
+
routePrefix: '/foreman_rh_cloud/insights_example',
|
|
10
|
+
fallbackRoute: 'dashboard',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('getIopAppRoute', () => {
|
|
14
|
+
it('maps foreman paths under the prefix to app routes', () => {
|
|
15
|
+
expect(
|
|
16
|
+
getIopAppRoute('/foreman_rh_cloud/insights_example/dashboard', options)
|
|
17
|
+
).toBe('dashboard');
|
|
18
|
+
expect(
|
|
19
|
+
getIopAppRoute('/foreman_rh_cloud/insights_example/cves/cve-1', options)
|
|
20
|
+
).toBe('cves/cve-1');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns fallbackRoute for paths outside the prefix', () => {
|
|
24
|
+
expect(getIopAppRoute('/foreman_rh_cloud/other', options)).toBe(
|
|
25
|
+
'dashboard'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns fallbackRoute when pathname is only the prefix', () => {
|
|
30
|
+
expect(
|
|
31
|
+
getIopAppRoute('/foreman_rh_cloud/insights_example', options)
|
|
32
|
+
).toBe('dashboard');
|
|
33
|
+
expect(
|
|
34
|
+
getIopAppRoute('/foreman_rh_cloud/insights_example/', options)
|
|
35
|
+
).toBe('dashboard');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('getIopForemanPath', () => {
|
|
40
|
+
it('maps app routes to foreman paths', () => {
|
|
41
|
+
expect(getIopForemanPath('dashboard', options)).toBe(
|
|
42
|
+
'/foreman_rh_cloud/insights_example/dashboard'
|
|
43
|
+
);
|
|
44
|
+
expect(getIopForemanPath('cves/cve-1', options)).toBe(
|
|
45
|
+
'/foreman_rh_cloud/insights_example/cves/cve-1'
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('uses fallbackRoute when appRoute is empty', () => {
|
|
50
|
+
expect(getIopForemanPath('', options)).toBe(
|
|
51
|
+
'/foreman_rh_cloud/insights_example/dashboard'
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('getIopIframeSrc', () => {
|
|
57
|
+
it('returns origin + static app path', () => {
|
|
58
|
+
expect(getIopIframeSrc('/assets/apps/example/index.html')).toBe(
|
|
59
|
+
`${window.location.origin}/assets/apps/example/index.html`
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { postIopChromeToIframe } from '../postIopChromeToIframe';
|
|
2
|
+
import { IOP_CHROME_INIT } from '../iopIframeConstants';
|
|
3
|
+
|
|
4
|
+
describe('postIopChromeToIframe', () => {
|
|
5
|
+
it('posts appRoute and embedded mode', () => {
|
|
6
|
+
const postMessage = jest.fn();
|
|
7
|
+
const iframe = { contentWindow: { postMessage } };
|
|
8
|
+
|
|
9
|
+
postIopChromeToIframe(iframe, {
|
|
10
|
+
permissions: [{ permission: 'compliance:policies:read' }],
|
|
11
|
+
pathname: '/new/hosts/demo.example.com',
|
|
12
|
+
appRoute: 'systems/uuid-1',
|
|
13
|
+
embedded: 'host-tab',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(postMessage).toHaveBeenCalledWith(
|
|
17
|
+
{
|
|
18
|
+
type: IOP_CHROME_INIT,
|
|
19
|
+
payload: {
|
|
20
|
+
user: expect.any(Object),
|
|
21
|
+
permissions: [{ permission: 'compliance:policies:read' }],
|
|
22
|
+
appRoute: 'systems/uuid-1',
|
|
23
|
+
pathname: '/new/hosts/demo.example.com',
|
|
24
|
+
embedded: 'host-tab',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
window.location.origin
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('omits embedded from payload when not provided', () => {
|
|
32
|
+
const postMessage = jest.fn();
|
|
33
|
+
const iframe = { contentWindow: { postMessage } };
|
|
34
|
+
|
|
35
|
+
postIopChromeToIframe(iframe, {
|
|
36
|
+
permissions: [],
|
|
37
|
+
pathname: '/foreman_rh_cloud/insights_compliance/scappolicies',
|
|
38
|
+
appRoute: 'scappolicies',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(postMessage).toHaveBeenCalledWith(
|
|
42
|
+
{
|
|
43
|
+
type: IOP_CHROME_INIT,
|
|
44
|
+
payload: {
|
|
45
|
+
user: expect.any(Object),
|
|
46
|
+
permissions: [],
|
|
47
|
+
appRoute: 'scappolicies',
|
|
48
|
+
pathname: '/foreman_rh_cloud/insights_compliance/scappolicies',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
window.location.origin
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('no-ops when iframe has no contentWindow', () => {
|
|
56
|
+
expect(() =>
|
|
57
|
+
postIopChromeToIframe(null, { appRoute: 'reports' })
|
|
58
|
+
).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { useIopIframeHeight } from '../useIopIframeHeight';
|
|
4
|
+
import { IOP_IFRAME_HEIGHT } from '../iopIframeConstants';
|
|
5
|
+
|
|
6
|
+
const HostTabIframe = () => {
|
|
7
|
+
const iframeRef = useRef(null);
|
|
8
|
+
useIopIframeHeight({ iframeRef });
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<iframe
|
|
12
|
+
ref={iframeRef}
|
|
13
|
+
data-testid="height-iframe"
|
|
14
|
+
title="height-iframe"
|
|
15
|
+
src="about:blank"
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('useIopIframeHeight', () => {
|
|
21
|
+
it('sizes the iframe from a HEIGHT message', () => {
|
|
22
|
+
const { getByTestId } = render(<HostTabIframe />);
|
|
23
|
+
const iframe = getByTestId('height-iframe');
|
|
24
|
+
|
|
25
|
+
window.dispatchEvent(
|
|
26
|
+
new MessageEvent('message', {
|
|
27
|
+
origin: window.location.origin,
|
|
28
|
+
data: {
|
|
29
|
+
type: IOP_IFRAME_HEIGHT,
|
|
30
|
+
payload: { height: 2100 },
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(iframe.style.height).toBe('2100px');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foreman browser path for an IoP app sub-route.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} appRoute Path inside the iframe application page
|
|
5
|
+
* @param {{ routePrefix: string, fallbackRoute: string }} options
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export const getIopForemanPath = (
|
|
9
|
+
appRoute = '',
|
|
10
|
+
{ routePrefix, fallbackRoute } = {}
|
|
11
|
+
) => {
|
|
12
|
+
const subRoute = String(appRoute).replace(/^\/+/, '') || fallbackRoute;
|
|
13
|
+
|
|
14
|
+
return `${routePrefix}/${subRoute}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Path inside the iframe application page derived from a Foreman pathname.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} pathname Foreman location pathname
|
|
21
|
+
* @param {{ routePrefix: string, fallbackRoute: string }} options
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export const getIopAppRoute = (
|
|
25
|
+
pathname = '',
|
|
26
|
+
{ routePrefix, fallbackRoute } = {}
|
|
27
|
+
) => {
|
|
28
|
+
if (!pathname.startsWith(routePrefix)) {
|
|
29
|
+
return fallbackRoute;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const subRoute = pathname.slice(routePrefix.length).replace(/^\//, '');
|
|
33
|
+
|
|
34
|
+
return subRoute || fallbackRoute;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Absolute URL for a static IoP app entry under Satellite assets.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} staticAppPath e.g. /assets/apps/compliance/index.html
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
export const getIopIframeSrc = staticAppPath => {
|
|
44
|
+
if (typeof window === 'undefined') {
|
|
45
|
+
return staticAppPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `${window.location.origin}${staticAppPath}`;
|
|
49
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { mockUser } from '../ScalprumModule/ScalprumContext';
|
|
2
|
+
import { IOP_CHROME_INIT } from './iopIframeConstants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sends chrome/RBAC context into an IoP app iframe after it loads.
|
|
6
|
+
*
|
|
7
|
+
* @param {HTMLIFrameElement} iframe
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {Array} options.permissions Insights-format permissions from useInsightsPermissions
|
|
10
|
+
* @param {string} options.appRoute Path inside the iframe application page (e.g. reports, systems/<uuid>)
|
|
11
|
+
* @param {string} [options.pathname] Foreman pathname (informational; passed through to the child)
|
|
12
|
+
* @param {string} [options.embedded] Embedding mode for the iframe (e.g. 'host-tab')
|
|
13
|
+
*/
|
|
14
|
+
export const postIopChromeToIframe = (
|
|
15
|
+
iframe,
|
|
16
|
+
{ permissions = [], pathname = '', appRoute = '', embedded } = {}
|
|
17
|
+
) => {
|
|
18
|
+
if (!iframe?.contentWindow || typeof window === 'undefined') {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
iframe.contentWindow.postMessage(
|
|
23
|
+
{
|
|
24
|
+
type: IOP_CHROME_INIT,
|
|
25
|
+
payload: {
|
|
26
|
+
user: mockUser,
|
|
27
|
+
permissions,
|
|
28
|
+
appRoute,
|
|
29
|
+
pathname,
|
|
30
|
+
...(embedded ? { embedded } : {}),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
window.location.origin
|
|
34
|
+
);
|
|
35
|
+
};
|