foreman_openscap 4.1.1 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (194) 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/api/v2/compliance/scap_contents_controller.rb +2 -0
  7. data/app/controllers/concerns/foreman/controller/parameters/oval_content.rb +22 -0
  8. data/app/controllers/concerns/foreman/controller/parameters/oval_policy.rb +22 -0
  9. data/app/controllers/concerns/foreman_openscap/hosts_controller_extensions.rb +1 -1
  10. data/app/graphql/types/cve.rb +17 -0
  11. data/app/graphql/types/oval_content.rb +17 -0
  12. data/app/graphql/types/oval_policy.rb +21 -0
  13. data/app/helpers/arf_reports_helper.rb +7 -24
  14. data/app/helpers/policies_helper.rb +4 -17
  15. data/app/mailers/foreman_openscap/policy_mailer.rb +2 -2
  16. data/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb +1 -1
  17. data/app/models/concerns/foreman_openscap/data_stream_content.rb +0 -17
  18. data/app/models/concerns/foreman_openscap/host_extensions.rb +11 -11
  19. data/app/models/concerns/foreman_openscap/hostgroup_extensions.rb +3 -5
  20. data/app/models/concerns/foreman_openscap/inherited_policies.rb +11 -0
  21. data/app/models/concerns/foreman_openscap/oval_facet_host_extensions.rb +38 -0
  22. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
  23. data/app/models/concerns/foreman_openscap/policy_common.rb +75 -0
  24. data/app/models/concerns/foreman_openscap/scap_file_content.rb +24 -0
  25. data/app/models/foreman_openscap/arf_report.rb +2 -2
  26. data/app/models/foreman_openscap/cve.rb +23 -0
  27. data/app/models/foreman_openscap/host/oval_facet.rb +14 -0
  28. data/app/models/foreman_openscap/host_cve.rb +7 -0
  29. data/app/models/foreman_openscap/hostgroup/oval_facet.rb +14 -0
  30. data/app/models/foreman_openscap/hostgroup_oval_facet_oval_policy.rb +6 -0
  31. data/app/models/foreman_openscap/oval_content.rb +26 -0
  32. data/app/models/foreman_openscap/oval_facet_oval_policy.rb +6 -0
  33. data/app/models/foreman_openscap/oval_policy.rb +54 -0
  34. data/app/models/foreman_openscap/oval_status.rb +45 -0
  35. data/app/models/foreman_openscap/policy.rb +10 -73
  36. data/app/models/foreman_openscap/scap_content.rb +1 -0
  37. data/app/models/foreman_openscap/tailoring_file.rb +1 -0
  38. data/app/services/foreman_openscap/client_config/ansible.rb +39 -6
  39. data/app/services/foreman_openscap/client_config/base.rb +5 -1
  40. data/app/services/foreman_openscap/client_config/puppet.rb +2 -1
  41. data/app/services/foreman_openscap/config_name_service.rb +1 -1
  42. data/app/services/foreman_openscap/hostgroup_overrider.rb +2 -24
  43. data/app/services/foreman_openscap/hostgroup_overrider_common.rb +28 -0
  44. data/app/services/foreman_openscap/lookup_key_overrider.rb +30 -62
  45. data/app/services/foreman_openscap/lookup_key_overrides_common.rb +63 -0
  46. data/app/services/foreman_openscap/oval/check_collection.rb +45 -0
  47. data/app/services/foreman_openscap/oval/configure.rb +80 -0
  48. data/app/services/foreman_openscap/oval/cves.rb +41 -0
  49. data/app/services/foreman_openscap/oval/setup.rb +93 -0
  50. data/app/services/foreman_openscap/oval/setup_check.rb +55 -0
  51. data/app/services/foreman_openscap/oval/sync_oval_contents.rb +42 -0
  52. data/app/views/api/v2/compliance/oval_contents/base.json.rabl +6 -0
  53. data/app/views/api/v2/compliance/oval_contents/create.json.rabl +3 -0
  54. data/app/views/api/v2/compliance/oval_contents/index.json.rabl +3 -0
  55. data/app/views/api/v2/compliance/oval_contents/show.json.rabl +3 -0
  56. data/app/views/api/v2/compliance/oval_contents/sync.json.rabl +3 -0
  57. data/app/views/api/v2/compliance/oval_contents/sync_result.json.rabl +11 -0
  58. data/app/views/api/v2/compliance/oval_contents/update.json.rabl +3 -0
  59. data/app/views/api/v2/compliance/oval_policies/create.json.rabl +3 -0
  60. data/app/views/api/v2/compliance/oval_policies/index.json.rabl +3 -0
  61. data/app/views/api/v2/compliance/oval_policies/main.json.rabl +15 -0
  62. data/app/views/api/v2/compliance/oval_policies/show.json.rabl +3 -0
  63. data/app/views/api/v2/compliance/policies/base.json.rabl +2 -2
  64. data/app/views/api/v2/compliance/policies_common/_attrs.json.rabl +2 -0
  65. data/app/views/arf_reports/_output.html.erb +9 -1
  66. data/app/views/arf_reports/show.html.erb +1 -1
  67. data/app/views/arf_reports/show_html.html.erb +1 -0
  68. data/app/views/compliance_hosts/show.html.erb +1 -8
  69. data/app/views/job_templates/run_oval_scans.erb +24 -0
  70. data/app/views/policies/edit.html.erb +3 -2
  71. data/app/views/policies/show.html.erb +3 -1
  72. data/app/views/policies/steps/_deployment_options_form.html.erb +2 -2
  73. data/app/views/scap_contents/edit.html.erb +2 -12
  74. data/app/views/tailoring_files/edit.html.erb +2 -10
  75. data/config/initializers/inflections.rb +12 -0
  76. data/config/routes.rb +19 -0
  77. data/db/migrate/20201019074925_create_oval_policy.rb +13 -0
  78. data/db/migrate/20201020113801_create_oval_facet.rb +14 -0
  79. data/db/migrate/20201021084109_create_hostgroup_oval_facet.rb +14 -0
  80. data/db/migrate/20201106080924_create_oval_content.rb +12 -0
  81. data/db/migrate/20201116110256_add_oval_content_to_oval_policy.rb +5 -0
  82. data/db/migrate/20201120080329_create_cves.rb +13 -0
  83. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -6
  84. data/db/migrate/20201217130800_add_has_errata_to_cve.rb +8 -0
  85. data/db/migrate/20201217161511_add_url_to_oval_content.rb +5 -0
  86. data/db/migrate/20210409095625_add_oval_policy_reference_to_cve.rb +7 -0
  87. data/db/seeds.d/75-job_templates.rb +3 -2
  88. data/lib/foreman_openscap/bulk_upload.rb +2 -2
  89. data/lib/foreman_openscap/engine.rb +67 -9
  90. data/lib/foreman_openscap/version.rb +1 -1
  91. data/lib/tasks/foreman_openscap_tasks.rake +14 -9
  92. data/locale/de/LC_MESSAGES/foreman_openscap.mo +0 -0
  93. data/locale/de/foreman_openscap.edit.po +0 -0
  94. data/locale/de/foreman_openscap.po +215 -17
  95. data/locale/en_GB/LC_MESSAGES/foreman_openscap.mo +0 -0
  96. data/locale/en_GB/foreman_openscap.edit.po +0 -0
  97. data/locale/en_GB/foreman_openscap.po +213 -15
  98. data/locale/es/LC_MESSAGES/foreman_openscap.mo +0 -0
  99. data/locale/es/foreman_openscap.edit.po +0 -0
  100. data/locale/es/foreman_openscap.po +239 -41
  101. data/locale/foreman_openscap.pot +395 -112
  102. data/locale/fr/LC_MESSAGES/foreman_openscap.mo +0 -0
  103. data/locale/fr/foreman_openscap.edit.po +0 -0
  104. data/locale/fr/foreman_openscap.po +243 -45
  105. data/locale/gl/LC_MESSAGES/foreman_openscap.mo +0 -0
  106. data/locale/gl/foreman_openscap.edit.po +0 -0
  107. data/locale/gl/foreman_openscap.po +213 -15
  108. data/locale/it/LC_MESSAGES/foreman_openscap.mo +0 -0
  109. data/locale/it/foreman_openscap.edit.po +0 -0
  110. data/locale/it/foreman_openscap.po +213 -15
  111. data/locale/ja/LC_MESSAGES/foreman_openscap.mo +0 -0
  112. data/locale/ja/foreman_openscap.edit.po +0 -0
  113. data/locale/ja/foreman_openscap.po +262 -66
  114. data/locale/ko/LC_MESSAGES/foreman_openscap.mo +0 -0
  115. data/locale/ko/foreman_openscap.edit.po +0 -0
  116. data/locale/ko/foreman_openscap.po +214 -16
  117. data/locale/pt_BR/LC_MESSAGES/foreman_openscap.mo +0 -0
  118. data/locale/pt_BR/foreman_openscap.edit.po +0 -0
  119. data/locale/pt_BR/foreman_openscap.po +252 -54
  120. data/locale/ru/LC_MESSAGES/foreman_openscap.mo +0 -0
  121. data/locale/ru/foreman_openscap.edit.po +0 -0
  122. data/locale/ru/foreman_openscap.po +214 -16
  123. data/locale/sv_SE/LC_MESSAGES/foreman_openscap.mo +0 -0
  124. data/locale/sv_SE/foreman_openscap.edit.po +0 -0
  125. data/locale/sv_SE/foreman_openscap.po +213 -15
  126. data/locale/zh_CN/LC_MESSAGES/foreman_openscap.mo +0 -0
  127. data/locale/zh_CN/foreman_openscap.edit.po +0 -0
  128. data/locale/zh_CN/foreman_openscap.po +369 -169
  129. data/locale/zh_TW/LC_MESSAGES/foreman_openscap.mo +0 -0
  130. data/locale/zh_TW/foreman_openscap.edit.po +0 -0
  131. data/locale/zh_TW/foreman_openscap.po +214 -16
  132. data/package.json +48 -0
  133. data/test/factories/compliance_host_factory.rb +12 -0
  134. data/test/factories/oval_content_factory.rb +7 -0
  135. data/test/factories/oval_policy_factory.rb +9 -0
  136. data/test/files/oval_contents/ansible-2.9.oval.xml.bz2 +0 -0
  137. data/test/fixtures/cve_fixtures.rb +104 -0
  138. data/test/functional/api/v2/compliance/oval_contents_controller_test.rb +39 -0
  139. data/test/functional/api/v2/compliance/oval_policies_controller_test.rb +141 -0
  140. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +32 -0
  141. data/test/graphql/queries/oval_contents_query_test.rb +35 -0
  142. data/test/graphql/queries/oval_policies_query_test.rb +35 -0
  143. data/test/test_plugin_helper.rb +4 -0
  144. data/test/unit/oval_host_test.rb +45 -0
  145. data/test/unit/oval_policy_test.rb +133 -0
  146. data/test/unit/oval_status_test.rb +47 -0
  147. data/test/unit/services/oval/cves_test.rb +81 -0
  148. data/test/unit/services/oval/setup_test.rb +87 -0
  149. data/webpack/components/EmptyState.js +67 -0
  150. data/webpack/components/IndexLayout.js +35 -0
  151. data/webpack/components/IndexLayout.scss +3 -0
  152. data/webpack/components/IndexTable/IndexTableHelper.js +9 -0
  153. data/webpack/components/IndexTable/index.js +66 -0
  154. data/webpack/components/RuleSeverity/RuleSeverity.scss +3 -0
  155. data/webpack/components/RuleSeverity/RuleSeverity.test.js +13 -0
  156. data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +41 -0
  157. data/webpack/components/RuleSeverity/i_severity-critical.svg +61 -0
  158. data/webpack/components/RuleSeverity/i_severity-high.svg +61 -0
  159. data/webpack/components/RuleSeverity/i_severity-low.svg +62 -0
  160. data/webpack/components/RuleSeverity/i_severity-med.svg +62 -0
  161. data/webpack/components/RuleSeverity/i_unknown.svg +33 -0
  162. data/webpack/components/RuleSeverity/index.js +33 -0
  163. data/webpack/components/withLoading.js +68 -0
  164. data/webpack/global_index.js +5 -0
  165. data/webpack/graphql/queries/cves.gql +18 -0
  166. data/webpack/graphql/queries/ovalContents.gql +11 -0
  167. data/webpack/graphql/queries/ovalPolicies.gql +12 -0
  168. data/webpack/graphql/queries/ovalPolicy.gql +21 -0
  169. data/webpack/helpers/commonHelper.js +1 -0
  170. data/webpack/helpers/globalIdHelper.js +13 -0
  171. data/webpack/helpers/pageParamsHelper.js +31 -0
  172. data/webpack/helpers/pathsHelper.js +22 -0
  173. data/webpack/helpers/tableHelper.js +9 -0
  174. data/webpack/index.js +8 -0
  175. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +45 -0
  176. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +38 -0
  177. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +106 -0
  178. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +75 -0
  179. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -0
  180. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +46 -0
  181. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +44 -0
  182. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +61 -0
  183. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +78 -0
  184. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -0
  185. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +48 -0
  186. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +63 -0
  187. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +79 -0
  188. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +39 -0
  189. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +78 -0
  190. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +112 -0
  191. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +35 -0
  192. data/webpack/routes/routes.js +28 -0
  193. data/webpack/testHelper.js +64 -0
  194. 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,79 @@
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
+ import '@patternfly/patternfly/patternfly-addons.scss';
18
+
19
+ import withLoading from '../../../components/withLoading';
20
+
21
+ import CvesTab from './CvesTab';
22
+
23
+ import { policySchedule, newJobFormPath } from './OvalPoliciesShowHelper';
24
+ import { resolvePath } from '../../../helpers/pathsHelper';
25
+
26
+ const OvalPoliciesShow = props => {
27
+ const { policy, match, history } = props;
28
+ const activeTab = match.params.tab ? match.params.tab : 'details';
29
+
30
+ const handleTabSelect = (event, value) => {
31
+ history.push(
32
+ resolvePath(match.path, { ':id': match.params.id, ':tab?': value })
33
+ );
34
+ };
35
+
36
+ return (
37
+ <React.Fragment>
38
+ <Helmet>
39
+ <title>{`${policy.name} | OVAL Policy`}</title>
40
+ </Helmet>
41
+ <Grid className="scap-page-grid">
42
+ <GridItem span={10}>
43
+ <Text component={TextVariants.h1}>{policy.name}</Text>
44
+ </GridItem>
45
+ <GridItem span={2}>
46
+ <Link to={newJobFormPath(policy, match.params.id)}>
47
+ <Button variant="secondary">{__('Scan All Hostgroups')}</Button>
48
+ </Link>
49
+ </GridItem>
50
+ <GridItem span={12}>
51
+ <Tabs mountOnEnter activeKey={activeTab} onSelect={handleTabSelect}>
52
+ <Tab
53
+ eventKey="details"
54
+ title={<TabTitleText>Details</TabTitleText>}
55
+ >
56
+ <TextContent className="pf-u-pt-md">
57
+ <Text component={TextVariants.h3}>Period</Text>
58
+ <Text component={TextVariants.p}>{policySchedule(policy)}</Text>
59
+ <Text component={TextVariants.h3}>Description</Text>
60
+ <Text component={TextVariants.p}>{policy.description}</Text>
61
+ </TextContent>
62
+ </Tab>
63
+ <Tab eventKey="cves" title={<TabTitleText>CVEs</TabTitleText>}>
64
+ <CvesTab {...props} />
65
+ </Tab>
66
+ </Tabs>
67
+ </GridItem>
68
+ </Grid>
69
+ </React.Fragment>
70
+ );
71
+ };
72
+
73
+ OvalPoliciesShow.propTypes = {
74
+ match: PropTypes.object.isRequired,
75
+ history: PropTypes.object.isRequired,
76
+ policy: PropTypes.object.isRequired,
77
+ };
78
+
79
+ export default withLoading(OvalPoliciesShow);