foreman_openscap 4.1.2 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/foreman_openscap/policy.css +5 -0
  3. data/app/controllers/api/v2/compliance/oval_contents_controller.rb +72 -0
  4. data/app/controllers/api/v2/compliance/oval_policies_controller.rb +111 -0
  5. data/app/controllers/api/v2/compliance/oval_reports_controller.rb +47 -0
  6. data/app/controllers/concerns/foreman/controller/parameters/oval_content.rb +22 -0
  7. data/app/controllers/concerns/foreman/controller/parameters/oval_policy.rb +22 -0
  8. data/app/controllers/concerns/foreman_openscap/hosts_controller_extensions.rb +1 -1
  9. data/app/graphql/types/cve.rb +17 -0
  10. data/app/graphql/types/oval_content.rb +17 -0
  11. data/app/graphql/types/oval_policy.rb +21 -0
  12. data/app/helpers/arf_reports_helper.rb +7 -24
  13. data/app/helpers/policies_helper.rb +4 -17
  14. data/app/mailers/foreman_openscap/policy_mailer.rb +2 -2
  15. data/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb +1 -1
  16. data/app/models/concerns/foreman_openscap/data_stream_content.rb +0 -17
  17. data/app/models/concerns/foreman_openscap/host_extensions.rb +11 -11
  18. data/app/models/concerns/foreman_openscap/hostgroup_extensions.rb +3 -5
  19. data/app/models/concerns/foreman_openscap/inherited_policies.rb +11 -0
  20. data/app/models/concerns/foreman_openscap/oval_facet_host_extensions.rb +38 -0
  21. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
  22. data/app/models/concerns/foreman_openscap/policy_common.rb +75 -0
  23. data/app/models/concerns/foreman_openscap/scap_file_content.rb +24 -0
  24. data/app/models/foreman_openscap/arf_report.rb +2 -2
  25. data/app/models/foreman_openscap/cve.rb +23 -0
  26. data/app/models/foreman_openscap/host/oval_facet.rb +14 -0
  27. data/app/models/foreman_openscap/host_cve.rb +7 -0
  28. data/app/models/foreman_openscap/hostgroup/oval_facet.rb +14 -0
  29. data/app/models/foreman_openscap/hostgroup_oval_facet_oval_policy.rb +6 -0
  30. data/app/models/foreman_openscap/oval_content.rb +26 -0
  31. data/app/models/foreman_openscap/oval_facet_oval_policy.rb +6 -0
  32. data/app/models/foreman_openscap/oval_policy.rb +54 -0
  33. data/app/models/foreman_openscap/oval_status.rb +45 -0
  34. data/app/models/foreman_openscap/policy.rb +10 -73
  35. data/app/models/foreman_openscap/scap_content.rb +1 -0
  36. data/app/models/foreman_openscap/tailoring_file.rb +1 -0
  37. data/app/services/foreman_openscap/client_config/ansible.rb +39 -6
  38. data/app/services/foreman_openscap/client_config/base.rb +5 -1
  39. data/app/services/foreman_openscap/client_config/puppet.rb +2 -1
  40. data/app/services/foreman_openscap/config_name_service.rb +1 -1
  41. data/app/services/foreman_openscap/hostgroup_overrider.rb +2 -24
  42. data/app/services/foreman_openscap/hostgroup_overrider_common.rb +28 -0
  43. data/app/services/foreman_openscap/lookup_key_overrider.rb +30 -62
  44. data/app/services/foreman_openscap/lookup_key_overrides_common.rb +63 -0
  45. data/app/services/foreman_openscap/oval/check_collection.rb +45 -0
  46. data/app/services/foreman_openscap/oval/configure.rb +80 -0
  47. data/app/services/foreman_openscap/oval/cves.rb +41 -0
  48. data/app/services/foreman_openscap/oval/setup.rb +93 -0
  49. data/app/services/foreman_openscap/oval/setup_check.rb +55 -0
  50. data/app/services/foreman_openscap/oval/sync_oval_contents.rb +42 -0
  51. data/app/views/api/v2/compliance/oval_contents/base.json.rabl +6 -0
  52. data/app/views/api/v2/compliance/oval_contents/create.json.rabl +3 -0
  53. data/app/views/api/v2/compliance/oval_contents/index.json.rabl +3 -0
  54. data/app/views/api/v2/compliance/oval_contents/show.json.rabl +3 -0
  55. data/app/views/api/v2/compliance/oval_contents/sync.json.rabl +3 -0
  56. data/app/views/api/v2/compliance/oval_contents/sync_result.json.rabl +11 -0
  57. data/app/views/api/v2/compliance/oval_contents/update.json.rabl +3 -0
  58. data/app/views/api/v2/compliance/oval_policies/create.json.rabl +3 -0
  59. data/app/views/api/v2/compliance/oval_policies/index.json.rabl +3 -0
  60. data/app/views/api/v2/compliance/oval_policies/main.json.rabl +15 -0
  61. data/app/views/api/v2/compliance/oval_policies/show.json.rabl +3 -0
  62. data/app/views/api/v2/compliance/policies/base.json.rabl +2 -2
  63. data/app/views/api/v2/compliance/policies_common/_attrs.json.rabl +2 -0
  64. data/app/views/arf_reports/_output.html.erb +9 -1
  65. data/app/views/arf_reports/show.html.erb +1 -1
  66. data/app/views/arf_reports/show_html.html.erb +1 -0
  67. data/app/views/compliance_hosts/show.html.erb +1 -8
  68. data/app/views/job_templates/run_oval_scans.erb +24 -0
  69. data/app/views/policies/edit.html.erb +3 -2
  70. data/app/views/policies/show.html.erb +3 -1
  71. data/app/views/policies/steps/_deployment_options_form.html.erb +2 -2
  72. data/app/views/scap_contents/edit.html.erb +2 -12
  73. data/app/views/tailoring_files/edit.html.erb +2 -10
  74. data/config/initializers/inflections.rb +12 -0
  75. data/config/routes.rb +19 -0
  76. data/db/migrate/20201019074925_create_oval_policy.rb +13 -0
  77. data/db/migrate/20201020113801_create_oval_facet.rb +14 -0
  78. data/db/migrate/20201021084109_create_hostgroup_oval_facet.rb +14 -0
  79. data/db/migrate/20201106080924_create_oval_content.rb +12 -0
  80. data/db/migrate/20201116110256_add_oval_content_to_oval_policy.rb +5 -0
  81. data/db/migrate/20201120080329_create_cves.rb +13 -0
  82. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -6
  83. data/db/migrate/20201217130800_add_has_errata_to_cve.rb +8 -0
  84. data/db/migrate/20201217161511_add_url_to_oval_content.rb +5 -0
  85. data/db/migrate/20210409095625_add_oval_policy_reference_to_cve.rb +7 -0
  86. data/db/seeds.d/75-job_templates.rb +3 -2
  87. data/lib/foreman_openscap/bulk_upload.rb +2 -2
  88. data/lib/foreman_openscap/engine.rb +67 -9
  89. data/lib/foreman_openscap/version.rb +1 -1
  90. data/lib/tasks/foreman_openscap_tasks.rake +14 -9
  91. data/locale/de/LC_MESSAGES/foreman_openscap.mo +0 -0
  92. data/locale/de/foreman_openscap.edit.po +0 -0
  93. data/locale/de/foreman_openscap.po +215 -17
  94. data/locale/en_GB/LC_MESSAGES/foreman_openscap.mo +0 -0
  95. data/locale/en_GB/foreman_openscap.edit.po +0 -0
  96. data/locale/en_GB/foreman_openscap.po +213 -15
  97. data/locale/es/LC_MESSAGES/foreman_openscap.mo +0 -0
  98. data/locale/es/foreman_openscap.edit.po +0 -0
  99. data/locale/es/foreman_openscap.po +239 -41
  100. data/locale/foreman_openscap.pot +395 -112
  101. data/locale/fr/LC_MESSAGES/foreman_openscap.mo +0 -0
  102. data/locale/fr/foreman_openscap.edit.po +0 -0
  103. data/locale/fr/foreman_openscap.po +243 -45
  104. data/locale/gl/LC_MESSAGES/foreman_openscap.mo +0 -0
  105. data/locale/gl/foreman_openscap.edit.po +0 -0
  106. data/locale/gl/foreman_openscap.po +213 -15
  107. data/locale/it/LC_MESSAGES/foreman_openscap.mo +0 -0
  108. data/locale/it/foreman_openscap.edit.po +0 -0
  109. data/locale/it/foreman_openscap.po +213 -15
  110. data/locale/ja/LC_MESSAGES/foreman_openscap.mo +0 -0
  111. data/locale/ja/foreman_openscap.edit.po +0 -0
  112. data/locale/ja/foreman_openscap.po +262 -66
  113. data/locale/ko/LC_MESSAGES/foreman_openscap.mo +0 -0
  114. data/locale/ko/foreman_openscap.edit.po +0 -0
  115. data/locale/ko/foreman_openscap.po +214 -16
  116. data/locale/pt_BR/LC_MESSAGES/foreman_openscap.mo +0 -0
  117. data/locale/pt_BR/foreman_openscap.edit.po +0 -0
  118. data/locale/pt_BR/foreman_openscap.po +252 -54
  119. data/locale/ru/LC_MESSAGES/foreman_openscap.mo +0 -0
  120. data/locale/ru/foreman_openscap.edit.po +0 -0
  121. data/locale/ru/foreman_openscap.po +214 -16
  122. data/locale/sv_SE/LC_MESSAGES/foreman_openscap.mo +0 -0
  123. data/locale/sv_SE/foreman_openscap.edit.po +0 -0
  124. data/locale/sv_SE/foreman_openscap.po +213 -15
  125. data/locale/zh_CN/LC_MESSAGES/foreman_openscap.mo +0 -0
  126. data/locale/zh_CN/foreman_openscap.edit.po +0 -0
  127. data/locale/zh_CN/foreman_openscap.po +369 -169
  128. data/locale/zh_TW/LC_MESSAGES/foreman_openscap.mo +0 -0
  129. data/locale/zh_TW/foreman_openscap.edit.po +0 -0
  130. data/locale/zh_TW/foreman_openscap.po +214 -16
  131. data/package.json +48 -0
  132. data/test/factories/compliance_host_factory.rb +12 -0
  133. data/test/factories/oval_content_factory.rb +7 -0
  134. data/test/factories/oval_policy_factory.rb +9 -0
  135. data/test/files/oval_contents/ansible-2.9.oval.xml.bz2 +0 -0
  136. data/test/fixtures/cve_fixtures.rb +104 -0
  137. data/test/functional/api/v2/compliance/oval_contents_controller_test.rb +39 -0
  138. data/test/functional/api/v2/compliance/oval_policies_controller_test.rb +141 -0
  139. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +32 -0
  140. data/test/graphql/queries/oval_contents_query_test.rb +35 -0
  141. data/test/graphql/queries/oval_policies_query_test.rb +35 -0
  142. data/test/test_plugin_helper.rb +4 -0
  143. data/test/unit/oval_host_test.rb +45 -0
  144. data/test/unit/oval_policy_test.rb +133 -0
  145. data/test/unit/oval_status_test.rb +47 -0
  146. data/test/unit/services/oval/cves_test.rb +81 -0
  147. data/test/unit/services/oval/setup_test.rb +87 -0
  148. data/webpack/components/EmptyState.js +67 -0
  149. data/webpack/components/IndexLayout.js +35 -0
  150. data/webpack/components/IndexLayout.scss +3 -0
  151. data/webpack/components/IndexTable/IndexTableHelper.js +9 -0
  152. data/webpack/components/IndexTable/index.js +65 -0
  153. data/webpack/components/RuleSeverity/RuleSeverity.scss +3 -0
  154. data/webpack/components/RuleSeverity/RuleSeverity.test.js +13 -0
  155. data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +41 -0
  156. data/webpack/components/RuleSeverity/i_severity-critical.svg +61 -0
  157. data/webpack/components/RuleSeverity/i_severity-high.svg +61 -0
  158. data/webpack/components/RuleSeverity/i_severity-low.svg +62 -0
  159. data/webpack/components/RuleSeverity/i_severity-med.svg +62 -0
  160. data/webpack/components/RuleSeverity/i_unknown.svg +33 -0
  161. data/webpack/components/RuleSeverity/index.js +33 -0
  162. data/webpack/components/withLoading.js +68 -0
  163. data/webpack/global_index.js +5 -0
  164. data/webpack/graphql/queries/cves.gql +18 -0
  165. data/webpack/graphql/queries/ovalContents.gql +11 -0
  166. data/webpack/graphql/queries/ovalPolicies.gql +12 -0
  167. data/webpack/graphql/queries/ovalPolicy.gql +21 -0
  168. data/webpack/helpers/commonHelper.js +1 -0
  169. data/webpack/helpers/globalIdHelper.js +13 -0
  170. data/webpack/helpers/pageParamsHelper.js +31 -0
  171. data/webpack/helpers/pathsHelper.js +22 -0
  172. data/webpack/helpers/tableHelper.js +9 -0
  173. data/webpack/index.js +8 -0
  174. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +45 -0
  175. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +38 -0
  176. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +106 -0
  177. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +75 -0
  178. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -0
  179. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +46 -0
  180. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +44 -0
  181. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +61 -0
  182. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +78 -0
  183. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -0
  184. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +48 -0
  185. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +63 -0
  186. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +78 -0
  187. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +39 -0
  188. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +78 -0
  189. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +112 -0
  190. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +35 -0
  191. data/webpack/routes/routes.js +28 -0
  192. data/webpack/testHelper.js +64 -0
  193. metadata +143 -3
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import { within } from '@testing-library/dom';
4
+ import userEvent from '@testing-library/user-event';
5
+ import '@testing-library/jest-dom';
6
+
7
+ import OvalContentsIndex from '../OvalContentsIndex';
8
+
9
+ import { withMockedProvider, tick, historyMock } from '../../../../testHelper';
10
+ import { ovalContentsPath } from '../../../../helpers/pathsHelper';
11
+
12
+ import {
13
+ mocks,
14
+ paginatedMocks,
15
+ pushMock,
16
+ pagePaginationHistoryMock,
17
+ emptyMocks,
18
+ errorMocks,
19
+ } from './OvalContentsIndex.fixtures';
20
+
21
+ const TestComponent = withMockedProvider(OvalContentsIndex);
22
+
23
+ describe('OvalContentsIndex', () => {
24
+ it('should load page', async () => {
25
+ const { container } = render(
26
+ <TestComponent history={historyMock} mocks={mocks} />
27
+ );
28
+ expect(screen.getByText('Loading')).toBeInTheDocument();
29
+ await waitFor(tick);
30
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
31
+ expect(screen.getByText('ansible OVAL content')).toBeInTheDocument();
32
+ expect(screen.getByText('openshift OVAL content')).toBeInTheDocument();
33
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
34
+ expect(within(pageItems).getByText(/1 - 4/)).toBeInTheDocument();
35
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
36
+ expect(within(pageItems).getByText('4')).toBeInTheDocument();
37
+ });
38
+ it('should load page with pagination params', async () => {
39
+ const { container } = render(
40
+ <TestComponent
41
+ history={pagePaginationHistoryMock}
42
+ mocks={paginatedMocks}
43
+ />
44
+ );
45
+ await waitFor(tick);
46
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
47
+ expect(within(pageItems).getByText(/6 - 7/)).toBeInTheDocument();
48
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
49
+ expect(within(pageItems).getByText('7')).toBeInTheDocument();
50
+ userEvent.click(
51
+ screen.getByRole('button', { name: 'Go to previous page' })
52
+ );
53
+
54
+ expect(pushMock).toHaveBeenCalledWith(
55
+ `${ovalContentsPath}?page=1&perPage=5`
56
+ );
57
+ });
58
+ it('should show empty state', async () => {
59
+ render(<TestComponent history={historyMock} mocks={emptyMocks} />);
60
+ expect(screen.getByText('Loading')).toBeInTheDocument();
61
+ await waitFor(tick);
62
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
63
+ expect(screen.getByText('No OVAL Contents found.')).toBeInTheDocument();
64
+ });
65
+ it('should show errors', async () => {
66
+ render(<TestComponent history={historyMock} mocks={errorMocks} />);
67
+ expect(screen.getByText('Loading')).toBeInTheDocument();
68
+ await waitFor(tick);
69
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
70
+ expect(
71
+ screen.getByText('Something very bad happened.')
72
+ ).toBeInTheDocument();
73
+ expect(screen.getByText('Error!')).toBeInTheDocument();
74
+ });
75
+ });
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ import OvalContentsIndex from './OvalContentsIndex';
4
+
5
+ const WrappedOvalContentsIndex = props => <OvalContentsIndex {...props} />;
6
+
7
+ export default WrappedOvalContentsIndex;
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useQuery } from '@apollo/client';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import OvalPoliciesTable from './OvalPoliciesTable';
7
+ import IndexLayout from '../../../components/IndexLayout';
8
+
9
+ import {
10
+ useParamsToVars,
11
+ useCurrentPagination,
12
+ } from '../../../helpers/pageParamsHelper';
13
+ import policiesQuery from '../../../graphql/queries/ovalPolicies.gql';
14
+
15
+ const OvalPoliciesIndex = props => {
16
+ const pagination = useCurrentPagination(props.history);
17
+
18
+ const useFetchFn = componentProps =>
19
+ useQuery(policiesQuery, {
20
+ variables: useParamsToVars(componentProps.history),
21
+ });
22
+
23
+ const renameData = data => ({
24
+ policies: data.ovalPolicies.nodes,
25
+ totalCount: data.ovalPolicies.totalCount,
26
+ });
27
+
28
+ return (
29
+ <IndexLayout pageTitle={__('OVAL Policies')}>
30
+ <OvalPoliciesTable
31
+ {...props}
32
+ fetchFn={useFetchFn}
33
+ renameData={renameData}
34
+ resultPath="ovalPolicies.nodes"
35
+ pagination={pagination}
36
+ emptyStateTitle={__('No OVAL Policies found')}
37
+ />
38
+ </IndexLayout>
39
+ );
40
+ };
41
+
42
+ OvalPoliciesIndex.propTypes = {
43
+ history: PropTypes.object.isRequired,
44
+ };
45
+
46
+ export default OvalPoliciesIndex;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import IndexTable from '../../../components/IndexTable';
6
+ import withLoading from '../../../components/withLoading';
7
+
8
+ import { linkCell } from '../../../helpers/tableHelper';
9
+ import { ovalPoliciesPath, modelPath } from '../../../helpers/pathsHelper';
10
+
11
+ const OvalPoliciesTable = props => {
12
+ const columns = [{ title: __('Name') }, { title: __('OVAL Content') }];
13
+
14
+ const rows = props.policies.map(policy => ({
15
+ cells: [
16
+ { title: linkCell(modelPath(ovalPoliciesPath, policy), policy.name) },
17
+ { title: policy.ovalContent.name },
18
+ ],
19
+ policy,
20
+ }));
21
+
22
+ const actions = [];
23
+
24
+ return (
25
+ <IndexTable
26
+ columns={columns}
27
+ rows={rows}
28
+ actions={actions}
29
+ pagination={props.pagination}
30
+ totalCount={props.totalCount}
31
+ history={props.history}
32
+ ariaTableLabel={__('OVAL Policies Table')}
33
+ />
34
+ );
35
+ };
36
+
37
+ OvalPoliciesTable.propTypes = {
38
+ policies: PropTypes.array.isRequired,
39
+ pagination: PropTypes.object.isRequired,
40
+ totalCount: PropTypes.number.isRequired,
41
+ history: PropTypes.object.isRequired,
42
+ };
43
+
44
+ export default withLoading(OvalPoliciesTable);
@@ -0,0 +1,61 @@
1
+ import policiesQuery from '../../../../graphql/queries/ovalPolicies.gql';
2
+ import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
3
+ import { mockFactory } from '../../../../testHelper';
4
+
5
+ const policiesMockFactory = mockFactory('ovalPolicies', policiesQuery);
6
+
7
+ export const pushMock = jest.fn();
8
+
9
+ export const pageParamsHistoryMock = {
10
+ location: {
11
+ search: '?page=2&perPage=5',
12
+ pathname: ovalPoliciesPath,
13
+ },
14
+ push: pushMock,
15
+ };
16
+
17
+ export const mocks = policiesMockFactory(
18
+ { first: 20, last: 20 },
19
+ {
20
+ totalCount: 2,
21
+ nodes: [
22
+ {
23
+ id: 'abc',
24
+ name: 'first policy',
25
+ ovalContent: { name: 'first content' },
26
+ },
27
+ {
28
+ id: 'xyz',
29
+ name: 'second policy',
30
+ ovalContent: { name: 'second content' },
31
+ },
32
+ ],
33
+ }
34
+ );
35
+ export const pageParamsMocks = policiesMockFactory(
36
+ { first: 10, last: 5 },
37
+ {
38
+ totalCount: 7,
39
+ nodes: [
40
+ {
41
+ id: 'xyz',
42
+ name: 'sixth policy',
43
+ ovalContent: { name: 'sixth content' },
44
+ },
45
+ {
46
+ id: 'abc',
47
+ name: 'seventh policy',
48
+ ovalContent: { name: 'seventh content' },
49
+ },
50
+ ],
51
+ }
52
+ );
53
+ export const emptyMocks = policiesMockFactory(
54
+ { first: 20, last: 20 },
55
+ { totalCount: 0, nodes: [] }
56
+ );
57
+ export const errorMocks = policiesMockFactory(
58
+ { first: 20, last: 20 },
59
+ { totalCount: 0, nodes: [] },
60
+ [{ message: 'Something very bad happened.' }]
61
+ );
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { within } from '@testing-library/dom';
5
+ import '@testing-library/jest-dom';
6
+
7
+ import {
8
+ withMockedProvider,
9
+ withRouter,
10
+ tick,
11
+ historyMock,
12
+ } from '../../../../testHelper';
13
+
14
+ import {
15
+ mocks,
16
+ pushMock,
17
+ pageParamsMocks,
18
+ pageParamsHistoryMock,
19
+ emptyMocks,
20
+ errorMocks,
21
+ } from './OvalPoliciesIndex.fixtures';
22
+
23
+ import OvalPoliciesIndex from '../OvalPoliciesIndex';
24
+ import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
25
+
26
+ const TestComponent = withRouter(withMockedProvider(OvalPoliciesIndex));
27
+
28
+ describe('OvalPoliciesIndex', () => {
29
+ it('should load page', async () => {
30
+ const { container } = render(
31
+ <TestComponent history={historyMock} mocks={mocks} />
32
+ );
33
+ expect(screen.getByText('Loading')).toBeInTheDocument();
34
+ await waitFor(tick);
35
+ expect(screen.getByText('first policy')).toBeInTheDocument();
36
+ expect(screen.getByText('second policy')).toBeInTheDocument();
37
+ expect(screen.getByText('first content')).toBeInTheDocument();
38
+ expect(screen.getByText('second content')).toBeInTheDocument();
39
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
40
+ expect(within(pageItems).getByText(/1 - 2/)).toBeInTheDocument();
41
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
42
+ expect(within(pageItems).getByText('2')).toBeInTheDocument();
43
+ });
44
+ it('should load page with page params', async () => {
45
+ const { container } = render(
46
+ <TestComponent history={pageParamsHistoryMock} mocks={pageParamsMocks} />
47
+ );
48
+ await waitFor(tick);
49
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
50
+ expect(within(pageItems).getByText(/6 - 7/)).toBeInTheDocument();
51
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
52
+ expect(within(pageItems).getByText('7')).toBeInTheDocument();
53
+ userEvent.click(
54
+ screen.getByRole('button', { name: 'Go to previous page' })
55
+ );
56
+
57
+ expect(pushMock).toHaveBeenCalledWith(
58
+ `${ovalPoliciesPath}?page=1&perPage=5`
59
+ );
60
+ });
61
+ it('should show empty state', async () => {
62
+ render(<TestComponent history={historyMock} mocks={emptyMocks} />);
63
+ expect(screen.getByText('Loading')).toBeInTheDocument();
64
+ await waitFor(tick);
65
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
66
+ expect(screen.getByText('No OVAL Policies found')).toBeInTheDocument();
67
+ });
68
+ it('should show errors', async () => {
69
+ render(<TestComponent history={historyMock} mocks={errorMocks} />);
70
+ expect(screen.getByText('Loading')).toBeInTheDocument();
71
+ await waitFor(tick);
72
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
73
+ expect(
74
+ screen.getByText('Something very bad happened.')
75
+ ).toBeInTheDocument();
76
+ expect(screen.getByText('Error!')).toBeInTheDocument();
77
+ });
78
+ });
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ import OvalPoliciesIndex from './OvalPoliciesIndex';
4
+
5
+ const WrappedOvalPoliciesIndex = props => <OvalPoliciesIndex {...props} />;
6
+
7
+ export default WrappedOvalPoliciesIndex;
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import { useQuery } from '@apollo/client';
6
+
7
+ import CvesTable from './CvesTable';
8
+
9
+ import cves from '../../../graphql/queries/cves.gql';
10
+ import {
11
+ useParamsToVars,
12
+ useCurrentPagination,
13
+ } from '../../../helpers/pageParamsHelper';
14
+
15
+ const CvesTab = props => {
16
+ const useFetchFn = componentProps =>
17
+ useQuery(cves, {
18
+ variables: {
19
+ search: `oval_policy_id = ${componentProps.match.params.id}`,
20
+ ...useParamsToVars(componentProps.history),
21
+ },
22
+ });
23
+
24
+ const renameData = data => ({
25
+ cves: data.cves.nodes,
26
+ totalCount: data.cves.totalCount,
27
+ });
28
+
29
+ const pagination = useCurrentPagination(props.history);
30
+
31
+ return (
32
+ <CvesTable
33
+ {...props}
34
+ fetchFn={useFetchFn}
35
+ renameData={renameData}
36
+ resultPath="cves.nodes"
37
+ pagination={pagination}
38
+ emptyStateTitle={__('No CVEs found.')}
39
+ />
40
+ );
41
+ };
42
+
43
+ CvesTab.propTypes = {
44
+ match: PropTypes.object.isRequired,
45
+ history: PropTypes.object.isRequired,
46
+ };
47
+
48
+ export default CvesTab;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import { linkCell } from '../../../helpers/tableHelper';
6
+ import { hostsPath } from '../../../helpers/pathsHelper';
7
+ import { decodeId } from '../../../helpers/globalIdHelper';
8
+ import { addSearch } from '../../../helpers/pageParamsHelper';
9
+
10
+ import withLoading from '../../../components/withLoading';
11
+ import IndexTable from '../../../components/IndexTable';
12
+
13
+ const CvesTable = props => {
14
+ const columns = [
15
+ { title: __('Ref Id') },
16
+ { title: __('Has Errata?') },
17
+ { title: __('Hosts Count') },
18
+ ];
19
+
20
+ const cveRefId = cve => (
21
+ <a href={cve.refUrl} rel="noopener noreferrer" target="_blank">
22
+ {cve.refId}
23
+ </a>
24
+ );
25
+
26
+ const hostCount = cve =>
27
+ linkCell(
28
+ addSearch(hostsPath, { search: `cve_id = ${decodeId(cve)}` }),
29
+ cve.hosts.nodes.length
30
+ );
31
+
32
+ const rows = props.cves.map(cve => ({
33
+ cells: [
34
+ { title: cveRefId(cve) },
35
+ { title: cve.hasErrata ? __('Yes') : __('No') },
36
+ { title: hostCount(cve) },
37
+ ],
38
+ cve,
39
+ }));
40
+
41
+ const actions = [];
42
+
43
+ return (
44
+ <IndexTable
45
+ columns={columns}
46
+ rows={rows}
47
+ actions={actions}
48
+ pagination={props.pagination}
49
+ totalCount={props.totalCount}
50
+ history={props.history}
51
+ ariaTableLabel={__('Table of CVEs for OVAL policy')}
52
+ />
53
+ );
54
+ };
55
+
56
+ CvesTable.propTypes = {
57
+ cves: PropTypes.array.isRequired,
58
+ pagination: PropTypes.object.isRequired,
59
+ totalCount: PropTypes.number.isRequired,
60
+ history: PropTypes.object.isRequired,
61
+ };
62
+
63
+ export default withLoading(CvesTable);
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Link } from 'react-router-dom';
4
+ import { Helmet } from 'react-helmet';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import {
7
+ Button,
8
+ Grid,
9
+ GridItem,
10
+ TextContent,
11
+ Text,
12
+ TextVariants,
13
+ Tabs,
14
+ Tab,
15
+ TabTitleText,
16
+ } from '@patternfly/react-core';
17
+
18
+ import withLoading from '../../../components/withLoading';
19
+
20
+ import CvesTab from './CvesTab';
21
+
22
+ import { policySchedule, newJobFormPath } from './OvalPoliciesShowHelper';
23
+ import { resolvePath } from '../../../helpers/pathsHelper';
24
+
25
+ const OvalPoliciesShow = props => {
26
+ const { policy, match, history } = props;
27
+ const activeTab = match.params.tab ? match.params.tab : 'details';
28
+
29
+ const handleTabSelect = (event, value) => {
30
+ history.push(
31
+ resolvePath(match.path, { ':id': match.params.id, ':tab?': value })
32
+ );
33
+ };
34
+
35
+ return (
36
+ <React.Fragment>
37
+ <Helmet>
38
+ <title>{`${policy.name} | OVAL Policy`}</title>
39
+ </Helmet>
40
+ <Grid className="scap-page-grid">
41
+ <GridItem span={10}>
42
+ <Text component={TextVariants.h1}>{policy.name}</Text>
43
+ </GridItem>
44
+ <GridItem span={2}>
45
+ <Link to={newJobFormPath(policy, match.params.id)}>
46
+ <Button variant="secondary">{__('Scan All Hostgroups')}</Button>
47
+ </Link>
48
+ </GridItem>
49
+ <GridItem span={12}>
50
+ <Tabs mountOnEnter activeKey={activeTab} onSelect={handleTabSelect}>
51
+ <Tab
52
+ eventKey="details"
53
+ title={<TabTitleText>Details</TabTitleText>}
54
+ >
55
+ <TextContent className="pf-u-pt-md">
56
+ <Text component={TextVariants.h3}>Period</Text>
57
+ <Text component={TextVariants.p}>{policySchedule(policy)}</Text>
58
+ <Text component={TextVariants.h3}>Description</Text>
59
+ <Text component={TextVariants.p}>{policy.description}</Text>
60
+ </TextContent>
61
+ </Tab>
62
+ <Tab eventKey="cves" title={<TabTitleText>CVEs</TabTitleText>}>
63
+ <CvesTab {...props} />
64
+ </Tab>
65
+ </Tabs>
66
+ </GridItem>
67
+ </Grid>
68
+ </React.Fragment>
69
+ );
70
+ };
71
+
72
+ OvalPoliciesShow.propTypes = {
73
+ match: PropTypes.object.isRequired,
74
+ history: PropTypes.object.isRequired,
75
+ policy: PropTypes.object.isRequired,
76
+ };
77
+
78
+ export default withLoading(OvalPoliciesShow);