foreman_ansible 6.4.1 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/ansible_inventories_controller.rb +1 -1
  3. data/app/graphql/mutations/ansible_variable_overrides/create.rb +26 -0
  4. data/app/graphql/mutations/ansible_variable_overrides/delete.rb +38 -0
  5. data/app/graphql/mutations/ansible_variable_overrides/update.rb +26 -0
  6. data/app/graphql/mutations/hosts/assign_ansible_roles.rb +37 -0
  7. data/app/graphql/presenters/ansible_role_presenter.rb +12 -0
  8. data/app/graphql/presenters/overriden_ansible_variable_presenter.rb +19 -0
  9. data/app/graphql/types/ansible_role.rb +9 -0
  10. data/app/graphql/types/ansible_variable.rb +23 -0
  11. data/app/graphql/types/ansible_variable_override.rb +9 -0
  12. data/app/graphql/types/inherited_ansible_role.rb +13 -0
  13. data/app/graphql/types/overriden_ansible_variable.rb +27 -0
  14. data/app/helpers/foreman_ansible/ansible_roles_data_preparations.rb +22 -22
  15. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
  16. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +2 -1
  17. data/app/models/foreman_ansible/ansible_provider.rb +7 -5
  18. data/app/services/foreman_ansible/ansible_report_importer.rb +2 -2
  19. data/app/services/foreman_ansible/inventory_creator.rb +1 -1
  20. data/app/services/foreman_ansible/override_resolver.rb +22 -0
  21. data/app/views/api/v2/ansible_override_values/index.json.rabl +3 -0
  22. data/app/views/api/v2/ansible_variables/show.json.rabl +1 -1
  23. data/app/views/foreman_ansible/ansible_roles/_hostgroup_ansible_roles_button.erb +3 -0
  24. data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
  25. data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
  26. data/config/routes.rb +3 -0
  27. data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
  28. data/lib/foreman_ansible/engine.rb +0 -17
  29. data/lib/foreman_ansible/register.rb +115 -4
  30. data/lib/foreman_ansible/version.rb +1 -1
  31. data/package.json +4 -2
  32. data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
  33. data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
  34. data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
  35. data/test/unit/ansible_provider_test.rb +3 -6
  36. data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
  37. data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
  38. data/test/unit/hostgroup_ansible_role_test.rb +13 -0
  39. data/test/unit/services/override_resolver_test.rb +34 -0
  40. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +51 -27
  41. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +12 -6
  42. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
  43. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
  44. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
  45. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
  46. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
  47. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
  48. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
  49. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
  50. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
  51. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
  52. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
  53. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
  54. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
  55. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
  56. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
  57. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
  58. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
  59. data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
  60. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
  61. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
  62. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
  63. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
  64. data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
  65. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
  66. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
  67. data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
  68. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
  69. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
  70. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
  71. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
  72. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
  73. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
  74. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
  75. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
  76. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
  77. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
  78. data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
  79. data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
  80. data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
  81. data/webpack/components/AnsibleHostDetail/constants.js +9 -0
  82. data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
  83. data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
  84. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
  85. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
  86. data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
  87. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
  88. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +4 -0
  89. data/webpack/components/DualList/DualList.scss +3 -0
  90. data/webpack/components/DualList/ListControls.js +65 -0
  91. data/webpack/components/DualList/ListHeader.js +16 -0
  92. data/webpack/components/DualList/ListItem.js +69 -0
  93. data/webpack/components/DualList/ListPane.js +95 -0
  94. data/webpack/components/DualList/SelectedStatus.js +21 -0
  95. data/webpack/components/DualList/index.js +103 -0
  96. data/webpack/components/ErrorState.js +16 -0
  97. data/webpack/components/withLoading.js +135 -0
  98. data/webpack/components/withPagination.js +0 -0
  99. data/webpack/formHelper.js +131 -0
  100. data/webpack/globalIdHelper.js +13 -0
  101. data/webpack/global_index.js +7 -1
  102. data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
  103. data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
  104. data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
  105. data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
  106. data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
  107. data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
  108. data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
  109. data/webpack/graphql/queries/ansibleRoles.gql +13 -0
  110. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  111. data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
  112. data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
  113. data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
  114. data/webpack/graphql/queries/recurringJobs.gql +28 -0
  115. data/webpack/helpers/pageParamsHelper.js +40 -0
  116. data/webpack/helpers/paginationHelper.js +9 -0
  117. data/webpack/permissionsHelper.js +58 -0
  118. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
  119. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
  120. data/webpack/routes/HostgroupJobs/index.js +26 -0
  121. data/webpack/routes/routes.js +10 -0
  122. data/webpack/testHelper.js +165 -0
  123. data/webpack/toastHelper.js +4 -0
  124. metadata +127 -54
  125. data/app/assets/images/foreman_ansible/Ansible.png +0 -0
  126. data/app/models/foreman_ansible/fact_name.rb +0 -16
  127. data/app/models/setting/ansible.rb +0 -106
  128. data/app/services/foreman_ansible/fact_importer.rb +0 -99
  129. data/app/services/foreman_ansible/fact_parser.rb +0 -126
  130. data/app/services/foreman_ansible/fact_sparser.rb +0 -37
  131. data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
  132. data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
  133. data/test/unit/services/fact_importer_test.rb +0 -52
  134. data/test/unit/services/fact_parser_test.rb +0 -281
  135. data/test/unit/services/fact_sparser_test.rb +0 -24
  136. data/test/unit/services/structured_fact_importer_test.rb +0 -30
  137. data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -0,0 +1,29 @@
1
+ mutation UpdateAnsibleVariableOverride(
2
+ $id: ID!,
3
+ $hostId: Int!,
4
+ $ansibleVariableId: Int!,
5
+ $value: RawJson!
6
+ $match: String!) {
7
+ updateAnsibleVariableOverride(input: { id: $id, hostId: $hostId, value: $value, ansibleVariableId: $ansibleVariableId }) {
8
+ overridenAnsibleVariable {
9
+ id
10
+ lookupValues(match: $match) {
11
+ nodes {
12
+ id
13
+ match
14
+ value
15
+ omit
16
+ }
17
+ }
18
+ currentValue {
19
+ element
20
+ value
21
+ elementName
22
+ }
23
+ }
24
+ errors {
25
+ path
26
+ message
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,13 @@
1
+ query($id: String!, $first: Int, $last: Int){
2
+ host(id: $id) {
3
+ id
4
+ allAnsibleRoles(first: $first, last: $last) {
5
+ totalCount
6
+ nodes {
7
+ id
8
+ name
9
+ inherited
10
+ }
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ #import "./currentUserAttributes.gql"
2
+
3
+ query($search: String) {
4
+ ansibleRoles(search: $search) {
5
+ nodes {
6
+ id
7
+ name
8
+ }
9
+ }
10
+ currentUser {
11
+ ...CurrentUserAttributes
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ fragment CurrentUserAttributes on User {
2
+ id
3
+ login
4
+ admin
5
+ permissions {
6
+ nodes {
7
+ id
8
+ name
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,17 @@
1
+ #import "./currentUserAttributes.gql"
2
+
3
+ query($id: String!, $first: Int, $last: Int) {
4
+ host(id: $id) {
5
+ id
6
+ ownAnsibleRoles(first: $first, last: $last) {
7
+ totalCount
8
+ nodes {
9
+ id
10
+ name
11
+ }
12
+ }
13
+ }
14
+ currentUser {
15
+ ...CurrentUserAttributes
16
+ }
17
+ }
@@ -0,0 +1,11 @@
1
+ query($id: String!) {
2
+ host(id: $id) {
3
+ id
4
+ availableAnsibleRoles {
5
+ nodes {
6
+ id
7
+ name
8
+ }
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,39 @@
1
+ #import "./currentUserAttributes.gql"
2
+
3
+ query($id: String!, $match: String, $first: Int, $last: Int) {
4
+ host(id: $id) {
5
+ id
6
+ ansibleVariablesWithOverrides(first: $first, last: $last) {
7
+ totalCount
8
+ nodes {
9
+ id
10
+ key
11
+ meta {
12
+ canEdit
13
+ }
14
+ defaultValue
15
+ parameterType
16
+ ansibleRoleName
17
+ validatorType
18
+ validatorRule
19
+ required
20
+ lookupValues(match: $match) {
21
+ nodes {
22
+ id
23
+ match
24
+ value
25
+ omit
26
+ }
27
+ }
28
+ currentValue {
29
+ value
30
+ element
31
+ elementName
32
+ }
33
+ }
34
+ }
35
+ }
36
+ currentUser {
37
+ ...CurrentUserAttributes
38
+ }
39
+ }
@@ -0,0 +1,28 @@
1
+ #import "./currentUserAttributes.gql"
2
+
3
+ query($search: String, $first: Int, $last: Int) {
4
+ jobInvocations(search: $search, first: $first, last: $last) {
5
+ totalCount
6
+ nodes {
7
+ id
8
+ description
9
+ startAt
10
+ statusLabel
11
+ recurringLogic {
12
+ id
13
+ cronLine
14
+ meta {
15
+ canEdit
16
+ }
17
+ }
18
+ task {
19
+ id
20
+ state
21
+ result
22
+ }
23
+ }
24
+ }
25
+ currentUser {
26
+ ...CurrentUserAttributes
27
+ }
28
+ }
@@ -0,0 +1,40 @@
1
+ import URI from 'urijs';
2
+ import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
3
+
4
+ const parsePageParams = history => URI.parseQuery(history.location.search);
5
+
6
+ export const addSearch = (basePath, params) => {
7
+ let stringyfied = '';
8
+ if (Object.keys(params).length > 0) {
9
+ stringyfied = `?${URI.buildQuery(params)}`;
10
+ }
11
+
12
+ return `${basePath}${stringyfied}`;
13
+ };
14
+
15
+ export const useCurrentPagination = (
16
+ history,
17
+ keys = { page: 'page', perPage: 'perPage' }
18
+ ) => {
19
+ const pageParams = parsePageParams(history);
20
+ const uiSettings = useForemanSettings();
21
+
22
+ return {
23
+ [keys.page]: parseInt(pageParams[keys.page], 10) || 1,
24
+ [keys.perPage]:
25
+ parseInt(pageParams[keys.perPage], 10) || uiSettings.perPage,
26
+ };
27
+ };
28
+
29
+ export const pageToVars = (
30
+ pagination,
31
+ keys = { page: 'page', perPage: 'perPage' }
32
+ ) => ({
33
+ first: pagination[keys.page] * pagination[keys.perPage],
34
+ last: pagination[keys.perPage],
35
+ });
36
+
37
+ export const useParamsToVars = (
38
+ history,
39
+ keys = { page: 'page', perPage: 'perPage' }
40
+ ) => pageToVars(useCurrentPagination(history, keys), keys);
@@ -0,0 +1,9 @@
1
+ import { addSearch } from './pageParamsHelper';
2
+
3
+ export const preparePerPageOptions = opts =>
4
+ opts.map(item => ({ title: item.toString(), value: item }));
5
+
6
+ export const refreshPage = (history, params = {}) => {
7
+ const url = addSearch(history.location.pathname, params);
8
+ history.push(url);
9
+ };
@@ -0,0 +1,58 @@
1
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
+
3
+ export const permissionCheck = (user, permissionsRequired) => {
4
+ if (permissionsRequired.length === 0) {
5
+ return { allowed: true };
6
+ }
7
+
8
+ if (!user) {
9
+ throw new Error(
10
+ 'No user data when loading the page - cannot determine if current user is allowed to view the page.'
11
+ );
12
+ }
13
+
14
+ if (user.admin) {
15
+ return { allowed: true };
16
+ }
17
+
18
+ const permList = permissionsRequired.reduce((memo, item) => {
19
+ const found = user.permissions.nodes.find(
20
+ permission => permission.name === item
21
+ );
22
+ memo.push({ name: item, present: !!found });
23
+ return memo;
24
+ }, []);
25
+
26
+ if (permList.reduce((memo, item) => memo && item.present, true)) {
27
+ return { allowed: true, permissions: permList };
28
+ }
29
+
30
+ return { allowed: false, permissions: permList };
31
+ };
32
+
33
+ export const permissionDeniedMsg = permissions => {
34
+ let msg = __('You are not authorized to view the page. ');
35
+ if (permissions?.length > 0) {
36
+ msg += sprintf(
37
+ __('Request the following permissions from administrator: %s.'),
38
+ permissions.join(', ')
39
+ );
40
+ }
41
+ return msg;
42
+ };
43
+
44
+ export const allowPrimaryAction = (
45
+ emptyStateProps,
46
+ currentUser,
47
+ permissionsRequired
48
+ ) => {
49
+ if (!permissionCheck(currentUser, permissionsRequired).allowed) {
50
+ return Object.keys(emptyStateProps)
51
+ .filter(key => key !== 'action')
52
+ .reduce((memo, key) => {
53
+ memo[key] = emptyStateProps[key];
54
+ return memo;
55
+ }, {});
56
+ }
57
+ return emptyStateProps;
58
+ };
@@ -0,0 +1,63 @@
1
+ import { toVars } from '../../../components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper';
2
+
3
+ import {
4
+ scheduledJobsSearch,
5
+ previousJobsSearch,
6
+ } from '../../../components/AnsibleHostDetail/components/JobsTab/JobsTabHelper';
7
+ import {
8
+ jobInvocationsMockFactory,
9
+ jobCreateMockFactory,
10
+ firstJob,
11
+ secondJob,
12
+ futureDate,
13
+ } from '../../../components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures';
14
+
15
+ import { admin } from '../../../testHelper';
16
+
17
+ export const hgId = 22;
18
+ export const matchMock = {
19
+ params: {
20
+ id: '22',
21
+ },
22
+ };
23
+ export { futureDate };
24
+
25
+ const emptyScheduledJobsMock = jobInvocationsMockFactory(
26
+ { search: scheduledJobsSearch('hostgroup', hgId) },
27
+ { nodes: [], totalCount: 0 },
28
+ { currentUser: admin }
29
+ );
30
+ const emptyScheduledJobsRefetchMock = jobInvocationsMockFactory(
31
+ { search: scheduledJobsSearch('hostgroup', hgId) },
32
+ { nodes: [], totalCount: 0 },
33
+ { refetchData: { nodes: [firstJob], totalCount: 1 }, currentUser: admin }
34
+ );
35
+ const emptyPreviousJobsMock = jobInvocationsMockFactory(
36
+ { search: previousJobsSearch('hostgroup', hgId), first: 20, last: 20 },
37
+ { nodes: [], totalCount: 0 },
38
+ { currentUser: admin }
39
+ );
40
+ const scheduledJobsMocks = jobInvocationsMockFactory(
41
+ { search: scheduledJobsSearch('hostgroup', hgId) },
42
+ { nodes: [firstJob], totalCount: 1 },
43
+ { currentUser: admin }
44
+ );
45
+ const previousJobsMocks = jobInvocationsMockFactory(
46
+ { search: previousJobsSearch('hostgroup', hgId), first: 20, last: 20 },
47
+ { nodes: [secondJob], totalCount: 1 },
48
+ { currentUser: admin }
49
+ );
50
+
51
+ export const emptyMocks = emptyScheduledJobsMock.concat(emptyPreviousJobsMock);
52
+ export const scheduledAndPreviousMocks = scheduledJobsMocks.concat(
53
+ previousJobsMocks
54
+ );
55
+
56
+ const createJobMock = jobCreateMockFactory(
57
+ toVars('hostgroup', hgId, futureDate, 'weekly').variables,
58
+ { jobInvocation: { id: 'MDE6Sm9iSW52b2NhdGlvbi00MTU=' }, errors: [] }
59
+ );
60
+
61
+ export const createMocks = emptyScheduledJobsRefetchMock
62
+ .concat(emptyPreviousJobsMock)
63
+ .concat(createJobMock);
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom';
5
+
6
+ import { i18nProviderWrapperFactory } from 'foremanReact/common/i18nProviderWrapperFactory';
7
+
8
+ import {
9
+ emptyMocks,
10
+ scheduledAndPreviousMocks,
11
+ createMocks,
12
+ matchMock,
13
+ futureDate,
14
+ } from './HostgroupJobs.fixtures';
15
+ import HostgroupJobs from '../';
16
+ import * as toasts from '../../../toastHelper';
17
+
18
+ import {
19
+ tick,
20
+ withRouter,
21
+ withMockedProvider,
22
+ withRedux,
23
+ historyMock,
24
+ } from '../../../testHelper';
25
+
26
+ import { toCron } from '../../../components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper';
27
+
28
+ const TestComponent = withRedux(withRouter(withMockedProvider(HostgroupJobs)));
29
+
30
+ const now = new Date('2021-08-28 00:00:00 -1100');
31
+ const ComponentWithIntl = i18nProviderWrapperFactory(now, 'UTC')(TestComponent);
32
+
33
+ describe('HostgroupJobs', () => {
34
+ it('should load the page', async () => {
35
+ render(
36
+ <ComponentWithIntl
37
+ match={matchMock}
38
+ mocks={scheduledAndPreviousMocks}
39
+ history={historyMock}
40
+ />
41
+ );
42
+ await waitFor(tick);
43
+ await waitFor(tick);
44
+ screen
45
+ .getAllByText('Run Ansible roles')
46
+ .map(element => expect(element).toBeInTheDocument());
47
+ expect(screen.getByText('Scheduled recurring jobs')).toBeInTheDocument();
48
+ expect(screen.getByText('Previously executed jobs')).toBeInTheDocument();
49
+ expect(screen.getByText('54 10 15 * *')).toBeInTheDocument();
50
+ });
51
+ it('should show empty state', async () => {
52
+ render(
53
+ <ComponentWithIntl
54
+ match={matchMock}
55
+ mocks={emptyMocks}
56
+ history={historyMock}
57
+ />
58
+ );
59
+ await waitFor(tick);
60
+ await waitFor(tick);
61
+ expect(
62
+ screen.getByText('No config job for Ansible roles scheduled')
63
+ ).toBeInTheDocument();
64
+ });
65
+ it('should create new recurring job', async () => {
66
+ const showToast = jest.fn();
67
+ jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
68
+
69
+ render(
70
+ <ComponentWithIntl
71
+ match={matchMock}
72
+ mocks={createMocks}
73
+ history={historyMock}
74
+ />
75
+ );
76
+ await waitFor(tick);
77
+ userEvent.click(
78
+ screen.getByRole('button', { name: 'schedule recurring job' })
79
+ );
80
+ await waitFor(tick);
81
+ userEvent.selectOptions(screen.getByLabelText(/repeat/), 'weekly');
82
+ userEvent.type(
83
+ screen.getByLabelText(/startTime/),
84
+ futureDate
85
+ .toISOString()
86
+ .split('T')[1]
87
+ .slice(0, 5)
88
+ );
89
+ userEvent.type(
90
+ screen.getByLabelText(/startDate/),
91
+ futureDate.toISOString().split('T')[0]
92
+ );
93
+ expect(
94
+ screen.getByRole('button', { name: 'submit creating job' })
95
+ ).not.toBeDisabled();
96
+ userEvent.click(
97
+ screen.getByRole('button', { name: 'submit creating job' })
98
+ );
99
+ await waitFor(tick);
100
+ await waitFor(tick);
101
+ await waitFor(tick);
102
+ expect(showToast).toHaveBeenCalledWith({
103
+ type: 'success',
104
+ message: 'Ansible job was successfully created.',
105
+ });
106
+ expect(screen.getByText(toCron(futureDate, 'weekly'))).toBeInTheDocument();
107
+ expect(screen.getByText('in 3 days')).toBeInTheDocument();
108
+ expect(
109
+ screen.queryByText('No config job for Ansible roles scheduled')
110
+ ).not.toBeInTheDocument();
111
+ });
112
+ });
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Helmet } from 'react-helmet';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import JobsPage from '../../components/AnsibleHostDetail/components/JobsTab';
7
+
8
+ const HostgroupJobs = props => (
9
+ <React.Fragment>
10
+ <Helmet>
11
+ <title>{__('Configure Recurring Job')}</title>
12
+ </Helmet>
13
+ <JobsPage
14
+ resourceName="hostgroup"
15
+ resourceId={parseInt(props.match.params.id, 10)}
16
+ history={props.history}
17
+ />
18
+ </React.Fragment>
19
+ );
20
+
21
+ HostgroupJobs.propTypes = {
22
+ match: PropTypes.object.isRequired,
23
+ history: PropTypes.object.isRequired,
24
+ };
25
+
26
+ export default HostgroupJobs;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import HostgroupJobs from './HostgroupJobs';
3
+
4
+ export default [
5
+ {
6
+ path: '/ansible/hostgroups/:id',
7
+ render: props => <HostgroupJobs {...props} />,
8
+ exact: true,
9
+ },
10
+ ];
@@ -0,0 +1,165 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import { MockedProvider } from '@apollo/react-testing';
4
+ import { Router, MemoryRouter } from 'react-router-dom';
5
+ import { createMemoryHistory } from 'history';
6
+
7
+ import store from 'foremanReact/redux';
8
+ import ConfirmModal from 'foremanReact/components/ConfirmModal';
9
+ import { getForemanContext } from 'foremanReact/Root/Context/ForemanContext';
10
+
11
+ export const withRedux = Component => props => (
12
+ <Provider store={store}>
13
+ <Component {...props} />
14
+ <ConfirmModal />
15
+ </Provider>
16
+ );
17
+
18
+ export const withRouter = Component => props => (
19
+ <MemoryRouter>
20
+ <Component {...props} />
21
+ </MemoryRouter>
22
+ );
23
+
24
+ export const withReactRouter = Component => props => {
25
+ // eslint-disable-next-line react/prop-types
26
+ const history = props.history || createMemoryHistory();
27
+
28
+ return (
29
+ <Router history={history}>
30
+ <Component {...props} history={history} />
31
+ </Router>
32
+ );
33
+ };
34
+
35
+ export const withMockedProvider = Component => props => {
36
+ const ForemanContext = getForemanContext(ctx);
37
+ // eslint-disable-next-line react/prop-types
38
+ const { mocks, ...rest } = props;
39
+
40
+ const ctx = {
41
+ metadata: {
42
+ UISettings: {
43
+ perPage: 20,
44
+ },
45
+ },
46
+ };
47
+
48
+ return (
49
+ <ForemanContext.Provider value={ctx}>
50
+ <MockedProvider mocks={mocks}>
51
+ <Component {...rest} />
52
+ </MockedProvider>
53
+ </ForemanContext.Provider>
54
+ );
55
+ };
56
+
57
+ export const userFactory = (login, permissions = []) => ({
58
+ __typename: 'User',
59
+ id: 'MDE6VXNlci01',
60
+ login,
61
+ admin: false,
62
+ permissions: {
63
+ nodes: permissions,
64
+ },
65
+ });
66
+
67
+ export const admin = {
68
+ __typename: 'User',
69
+ id: 'MDE6VXNlci00',
70
+ login: 'admin',
71
+ admin: true,
72
+ permissions: {
73
+ nodes: [],
74
+ },
75
+ };
76
+
77
+ export const intruder = userFactory('intruder', [
78
+ {
79
+ __typename: 'Permission',
80
+ id: 'MDE6UGVybWlzc2lvbi0x',
81
+ name: 'view_architectures',
82
+ },
83
+ ]);
84
+
85
+ // use to resolve async mock requests for apollo MockedProvider
86
+ export const tick = () => new Promise(resolve => setTimeout(resolve, 0));
87
+
88
+ export const historyMock = {
89
+ location: {
90
+ search: '',
91
+ },
92
+ };
93
+
94
+ export const mockFactory = (resultName, query) => (
95
+ variables,
96
+ modelResults,
97
+ { errors = [], currentUser = null, refetchData = null } = {}
98
+ ) => {
99
+ let called = false;
100
+
101
+ const returnData = results => {
102
+ const result = {
103
+ data: {
104
+ [resultName]: results,
105
+ },
106
+ };
107
+
108
+ if (errors.length !== 0) {
109
+ result.errors = errors;
110
+ }
111
+
112
+ if (currentUser) {
113
+ result.data.currentUser = currentUser;
114
+ }
115
+
116
+ return result;
117
+ };
118
+
119
+ const mock = {
120
+ request: {
121
+ query,
122
+ variables,
123
+ },
124
+ newData: () => {
125
+ if (called && refetchData) {
126
+ return returnData(refetchData);
127
+ }
128
+ called = true;
129
+ return returnData(modelResults);
130
+ },
131
+ };
132
+
133
+ return [mock];
134
+ };
135
+
136
+ export const advancedMockFactory = query => (
137
+ variables,
138
+ data,
139
+ { errors = [], currentUser = null, refetchData = null } = {}
140
+ ) => {
141
+ let called = false;
142
+
143
+ const mock = {
144
+ request: {
145
+ query,
146
+ variables,
147
+ },
148
+ newData: () => {
149
+ if (called && refetchData) {
150
+ return { data: refetchData };
151
+ }
152
+ called = true;
153
+ return { data };
154
+ },
155
+ };
156
+
157
+ if (errors.length !== 0) {
158
+ mock.result.errors = errors;
159
+ }
160
+
161
+ if (currentUser) {
162
+ mock.result.data.currentUser = currentUser;
163
+ }
164
+ return [mock];
165
+ };
@@ -0,0 +1,4 @@
1
+ import store from 'foremanReact/redux';
2
+ import { addToast } from 'foremanReact/components/ToastsList';
3
+
4
+ export const showToast = toast => store.dispatch(addToast(toast));