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,180 @@
1
+ import {
2
+ mockFactory,
3
+ advancedMockFactory,
4
+ admin,
5
+ intruder,
6
+ userFactory,
7
+ } from '../../../../../testHelper';
8
+ import ansibleRolesQuery from '../../../../../graphql/queries/hostAnsibleRoles.gql';
9
+ import allAnsibleRolesQuery from '../../../../../graphql/queries/allAnsibleRoles.gql';
10
+ import availableAnsibleRolesQuery from '../../../../../graphql/queries/hostAvailableAnsibleRoles.gql';
11
+ import assignAnsibleRolesMutation from '../../../../../graphql/mutations/assignAnsibleRoles.gql';
12
+ import { decodeModelId } from '../../../../../globalIdHelper';
13
+
14
+ export const hostId = 3;
15
+ const hostGlobalId = 'MDE6SG9zdC0z';
16
+
17
+ const ansibleRolesMockFactory = mockFactory('host', ansibleRolesQuery);
18
+ const allAnsibleRolesMockFactory = mockFactory('host', allAnsibleRolesQuery);
19
+ const assignRolesMockFactory = mockFactory(
20
+ 'assignAnsibleRoles',
21
+ assignAnsibleRolesMutation
22
+ );
23
+ const editModalDataFactory = advancedMockFactory(availableAnsibleRolesQuery);
24
+
25
+ const viewer = userFactory('roles_viewer', [
26
+ {
27
+ __typename: 'Permission',
28
+ id: 'MDE6UGVybWlzc2lvbi0x',
29
+ name: 'view_ansible_roles',
30
+ },
31
+ ]);
32
+
33
+ const role1 = {
34
+ __typename: 'AnsibleRole',
35
+ id: 'MDE6QW5zaWJsZVJvbGUtMw==',
36
+ name: 'aardvaark.cube',
37
+ };
38
+
39
+ const role2 = {
40
+ __typename: 'AnsibleRole',
41
+ id: 'MDE6QW5zaWJsZVJvbGUtNQ==',
42
+ name: 'aardvaark.sphere',
43
+ };
44
+
45
+ const role3 = {
46
+ __typename: 'AnsibleRole',
47
+ id: 'MDE6QW5zaWJsZVJvbGUtMzA=',
48
+ name: 'another.role',
49
+ };
50
+
51
+ const role4 = {
52
+ __typename: 'AnsibleRole',
53
+ id: 'MDE6QW5zaWJsZVJvbGUtMzk=',
54
+ name: 'geerlingguy.ceylon',
55
+ };
56
+
57
+ const ansibleRolesMock = {
58
+ totalCount: 3,
59
+ nodes: [role1, role2, role3],
60
+ };
61
+
62
+ const ansibleRolesUpdatedMock = {
63
+ totalCount: 3,
64
+ nodes: [role1, role2, role4],
65
+ };
66
+
67
+ const availableRoles = {
68
+ nodes: [
69
+ role4,
70
+ {
71
+ __typename: 'AnsibleRole',
72
+ id: 'MDE6QW5zaWJsZVJvbGUtMQ==',
73
+ name: 'theforeman.foreman_scap_client',
74
+ },
75
+ {
76
+ __typename: 'AnsibleRole',
77
+ id: 'MDE6QW5zaWJsZVJvbGUtMg==',
78
+ name: 'adriagalin.motd',
79
+ },
80
+ {
81
+ __typename: 'AnsibleRole',
82
+ id: 'MDE6QW5zaWJsZVJvbGUtMjI=',
83
+ name: 'geerlingguy.php',
84
+ },
85
+ {
86
+ __typename: 'AnsibleRole',
87
+ id: 'MDE6QW5zaWJsZVJvbGUtNTc=',
88
+ name: 'robertdebock.epel',
89
+ },
90
+ {
91
+ __typename: 'AnsibleRole',
92
+ id: 'MDE6QW5zaWJsZVJvbGUtNTg=',
93
+ name: 'geerlingguy.nfs',
94
+ },
95
+ ],
96
+ };
97
+
98
+ export const allRolesMocks = allAnsibleRolesMockFactory(
99
+ { id: hostGlobalId, first: 20, last: 20 },
100
+ {
101
+ __typename: 'Host',
102
+ id: hostGlobalId,
103
+ allAnsibleRoles: {
104
+ totalCount: 4,
105
+ nodes: [
106
+ {
107
+ id: 'MDE6QW5zaWJsZVJvbGUtMg==',
108
+ name: 'adriagalin.motd',
109
+ inherited: true,
110
+ },
111
+ { ...role1, inherited: false },
112
+ { ...role2, inherited: false },
113
+ { ...role3, inherited: false },
114
+ ],
115
+ },
116
+ }
117
+ );
118
+
119
+ const editModalData = {
120
+ host: {
121
+ __typename: 'Host',
122
+ id: hostGlobalId,
123
+ availableAnsibleRoles: availableRoles,
124
+ },
125
+ };
126
+
127
+ export const mocks = ansibleRolesMockFactory(
128
+ { id: hostGlobalId, first: 20, last: 20 },
129
+ { __typename: 'Host', id: hostGlobalId, ownAnsibleRoles: ansibleRolesMock },
130
+ { currentUser: admin }
131
+ );
132
+
133
+ export const unauthorizedMocks = ansibleRolesMockFactory(
134
+ { id: hostGlobalId, first: 20, last: 20 },
135
+ { __typename: 'Host', id: hostGlobalId, ownAnsibleRoles: ansibleRolesMock },
136
+ { currentUser: intruder }
137
+ );
138
+
139
+ export const authorizedMocks = ansibleRolesMockFactory(
140
+ { id: hostGlobalId, first: 20, last: 20 },
141
+ { __typename: 'Host', id: hostGlobalId, ownAnsibleRoles: ansibleRolesMock },
142
+ { currentUser: viewer }
143
+ );
144
+
145
+ export const editModalOpenMocks = editModalDataFactory(
146
+ {
147
+ id: hostGlobalId,
148
+ },
149
+ editModalData
150
+ );
151
+
152
+ export const assignRolesSuccessMock = assignRolesMockFactory(
153
+ {
154
+ id: hostGlobalId,
155
+ ansibleRoleIds: [role1, role2, role4].map(decodeModelId),
156
+ },
157
+ {
158
+ host: {
159
+ __typename: 'Host',
160
+ id: hostGlobalId,
161
+ ownAnsibleRoles: ansibleRolesUpdatedMock,
162
+ },
163
+ errors: [],
164
+ }
165
+ );
166
+
167
+ export const assignRolesErrorMock = assignRolesMockFactory(
168
+ {
169
+ id: hostGlobalId,
170
+ ansibleRoleIds: [role1, role2, role4].map(decodeModelId),
171
+ },
172
+ {
173
+ host: {
174
+ __typename: 'Host',
175
+ id: hostGlobalId,
176
+ ownAnsibleRoles: ansibleRolesMock,
177
+ },
178
+ errors: [{ path: ['attributes', 'base'], message: 'is invalid' }],
179
+ }
180
+ );
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import userEvent from '@testing-library/user-event';
5
+ import {
6
+ tick,
7
+ withMockedProvider,
8
+ withReactRouter,
9
+ } from '../../../../../testHelper';
10
+
11
+ import {
12
+ mocks,
13
+ hostId,
14
+ allRolesMocks,
15
+ unauthorizedMocks,
16
+ authorizedMocks,
17
+ } from './RolesTab.fixtures';
18
+
19
+ import RolesTab from '../';
20
+
21
+ const TestComponent = withReactRouter(withMockedProvider(RolesTab));
22
+
23
+ describe('RolesTab', () => {
24
+ it('should load Ansible Roles as admin', async () => {
25
+ render(<TestComponent hostId={hostId} mocks={mocks} canEditHost />);
26
+ await waitFor(tick);
27
+ expect(screen.getByText('aardvaark.cube')).toBeInTheDocument();
28
+ expect(screen.getByText('aardvaark.sphere')).toBeInTheDocument();
29
+ expect(screen.getByText('another.role')).toBeInTheDocument();
30
+ });
31
+ it('should show all Ansible roles modal', async () => {
32
+ render(
33
+ <TestComponent
34
+ hostId={hostId}
35
+ mocks={mocks.concat(allRolesMocks)}
36
+ canEditHost
37
+ />
38
+ );
39
+ await waitFor(tick);
40
+ expect(screen.getByText('view all assigned roles')).toBeInTheDocument();
41
+ expect(screen.queryByText('All Ansible Roles')).not.toBeInTheDocument();
42
+ userEvent.click(screen.getByText('view all assigned roles'));
43
+ await waitFor(tick);
44
+ expect(screen.getByText('All Ansible Roles')).toBeInTheDocument();
45
+ expect(screen.getByText('Inherited from Hostgroup')).toBeInTheDocument();
46
+ userEvent.click(screen.getByRole('button', { name: 'Close' }));
47
+ await waitFor(tick);
48
+ expect(screen.queryByText('All Ansible Roles')).not.toBeInTheDocument();
49
+ });
50
+ it('should load Ansible Roles as viewer', async () => {
51
+ render(
52
+ <TestComponent
53
+ hostId={hostId}
54
+ mocks={authorizedMocks}
55
+ canEditHost={false}
56
+ />
57
+ );
58
+ await waitFor(tick);
59
+ expect(screen.getByText('aardvaark.cube')).toBeInTheDocument();
60
+ expect(screen.queryByText('Edit Ansible Roles')).not.toBeInTheDocument();
61
+ });
62
+ it('should not load Ansible Roles for unauthorized user', async () => {
63
+ render(
64
+ <TestComponent hostId={hostId} mocks={unauthorizedMocks} canEditHost />
65
+ );
66
+ await waitFor(tick);
67
+ expect(screen.queryByText('aardvaark.cube')).not.toBeInTheDocument();
68
+ expect(screen.getByText('Permission denied')).toBeInTheDocument();
69
+ expect(
70
+ screen.getByText(
71
+ 'You are not authorized to view the page. Request the following permissions from administrator: view_ansible_roles.'
72
+ )
73
+ ).toBeInTheDocument();
74
+ });
75
+ });
@@ -0,0 +1,51 @@
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 ansibleRolesQuery from '../../../../graphql/queries/hostAnsibleRoles.gql';
7
+ import { encodeId } from '../../../../globalIdHelper';
8
+ import RolesTable from './RolesTable';
9
+ import {
10
+ useParamsToVars,
11
+ useCurrentPagination,
12
+ } from '../../../../helpers/pageParamsHelper';
13
+
14
+ const RolesTab = ({ hostId, history, canEditHost }) => {
15
+ const hostGlobalId = encodeId('Host', hostId);
16
+ const pagination = useCurrentPagination(history);
17
+
18
+ const renameData = data => ({
19
+ ansibleRoles: data.host.ownAnsibleRoles.nodes,
20
+ totalCount: data.host.ownAnsibleRoles.totalCount,
21
+ });
22
+
23
+ const useFetchFn = () =>
24
+ useQuery(ansibleRolesQuery, {
25
+ variables: { id: hostGlobalId, ...useParamsToVars(history) },
26
+ fetchPolicy: 'network-only',
27
+ });
28
+
29
+ return (
30
+ <RolesTable
31
+ fetchFn={useFetchFn}
32
+ renamedDataPath="ansibleRoles"
33
+ renameData={renameData}
34
+ permissions={['view_ansible_roles']}
35
+ history={history}
36
+ hostGlobalId={hostGlobalId}
37
+ emptyStateProps={{ title: __('No Ansible roles assigned') }}
38
+ pagination={pagination}
39
+ canEditHost={canEditHost}
40
+ hostId={hostId}
41
+ />
42
+ );
43
+ };
44
+
45
+ RolesTab.propTypes = {
46
+ hostId: PropTypes.number.isRequired,
47
+ history: PropTypes.object.isRequired,
48
+ canEditHost: PropTypes.bool.isRequired,
49
+ };
50
+
51
+ export default RolesTab;
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Route, Switch, Redirect } from 'react-router-dom';
4
+
5
+ import AnsibleVariableOverrides from './AnsibleVariableOverrides';
6
+ import RolesTab from './RolesTab';
7
+ import JobsTab from './JobsTab';
8
+ import TabLayout from './TabLayout';
9
+
10
+ import WrappedAnsibleHostInventory from './AnsibleHostInventory';
11
+ import { ANSIBLE_KEY } from '../constants';
12
+ import { route } from '../helpers';
13
+
14
+ const SecondaryTabRoutes = ({ response, router, history }) => (
15
+ <Switch>
16
+ <Route exact path={`/${ANSIBLE_KEY}`}>
17
+ <Redirect to={route('roles')} />
18
+ </Route>
19
+ <Route path={route('roles')}>
20
+ <TabLayout>
21
+ <RolesTab
22
+ hostId={response.id}
23
+ history={history}
24
+ canEditHost={response.permissions.edit_hosts}
25
+ />
26
+ </TabLayout>
27
+ </Route>
28
+ <Route path={route('variables')}>
29
+ <TabLayout>
30
+ <AnsibleVariableOverrides
31
+ hostId={response.id}
32
+ hostAttrs={response}
33
+ history={history}
34
+ />
35
+ </TabLayout>
36
+ </Route>
37
+ <Route path={route('inventory')}>
38
+ <TabLayout>
39
+ <WrappedAnsibleHostInventory hostId={response.id} />
40
+ </TabLayout>
41
+ </Route>
42
+ <Route path={route('jobs')}>
43
+ <TabLayout>
44
+ <JobsTab
45
+ resourceId={response.id}
46
+ resourceName="host"
47
+ history={history}
48
+ />
49
+ </TabLayout>
50
+ </Route>
51
+ </Switch>
52
+ );
53
+
54
+ SecondaryTabRoutes.propTypes = {
55
+ response: PropTypes.object.isRequired,
56
+ router: PropTypes.object.isRequired,
57
+ history: PropTypes.object.isRequired,
58
+ };
59
+
60
+ export default SecondaryTabRoutes;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const TabLayout = props => (
5
+ <div className="ansible-host-detail">{props.children}</div>
6
+ );
7
+
8
+ TabLayout.propTypes = {
9
+ children: PropTypes.object.isRequired,
10
+ };
11
+
12
+ export default TabLayout;
@@ -0,0 +1,9 @@
1
+ import { translate as __ } from 'foremanReact/common/I18n';
2
+
3
+ export const ANSIBLE_KEY = 'Ansible';
4
+ export const SECONDARY_TABS = [
5
+ { key: 'roles', title: __('Roles') },
6
+ { key: 'variables', title: __('Variables') },
7
+ { key: 'inventory', title: __('Inventory') },
8
+ { key: 'jobs', title: __('Jobs') },
9
+ ];
@@ -0,0 +1,4 @@
1
+ import { ANSIBLE_KEY } from './constants';
2
+
3
+ export const hashRoute = subpath => `#/${ANSIBLE_KEY}/${subpath}`;
4
+ export const route = subpath => hashRoute(subpath).substring(1);
@@ -1,4 +1,6 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
2
4
  import ImportRolesAndVariablesTable from '../AnsibleRolesAndVariables';
3
5
 
4
6
  const rowsData = [
@@ -28,12 +30,15 @@ const columnsData = [
28
30
  { title: 'Hostgroups count' },
29
31
  ];
30
32
 
31
- const fixtures = {
32
- 'should render': {
33
- rowsData,
34
- columnsData,
35
- },
36
- };
37
-
38
- describe('ImportRolesAndVariablesTable', () =>
39
- testComponentSnapshotsWithFixtures(ImportRolesAndVariablesTable, fixtures));
33
+ describe('ImportRolesAndVariablesTable', () => {
34
+ it('should render', () => {
35
+ render(
36
+ <ImportRolesAndVariablesTable
37
+ rowsData={rowsData}
38
+ columnsData={columnsData}
39
+ />
40
+ );
41
+ expect(screen.getByText('bennojoy.ntp')).toBeInTheDocument();
42
+ expect(screen.getByText('0ta2.git_role')).toBeInTheDocument();
43
+ });
44
+ });
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import PropTypes from 'prop-types';
2
3
  import { ListView, Tooltip, OverlayTrigger } from 'patternfly-react';
3
4
  import classNames from 'classnames';
4
5
  import { translate as __ } from 'foremanReact/common/I18n';
@@ -57,4 +58,32 @@ const AnsibleRole = ({ role, icon, onClick, resourceName, index }) => {
57
58
  return listItem(clickHandler);
58
59
  };
59
60
 
61
+ AnsibleRole.propTypes = {
62
+ icon: PropTypes.string,
63
+ index: PropTypes.number,
64
+ onClick: PropTypes.func,
65
+ resourceName: PropTypes.string,
66
+ role: PropTypes.shape({
67
+ id: PropTypes.number,
68
+ inherited: PropTypes.bool,
69
+ name: PropTypes.string,
70
+ }),
71
+ };
72
+
73
+ AnsibleRole.defaultProps = {
74
+ icon: undefined,
75
+ index: undefined,
76
+ onClick: undefined,
77
+ resourceName: undefined,
78
+ role: {
79
+ id: undefined,
80
+ inherited: false,
81
+ name: '',
82
+ },
83
+ };
84
+
85
+ AnsibleRole.defaultProps = {
86
+ resourceName: 'host',
87
+ };
88
+
60
89
  export default AnsibleRole;
@@ -9,16 +9,19 @@ const fixtures = {
9
9
  role: { name: 'test.role', id: 5 },
10
10
  icon: 'fa fa-plus-circle',
11
11
  onClick: noop,
12
+ index: 0,
12
13
  },
13
14
  'should render a role to remove': {
14
15
  role: { name: 'test.role', id: 5 },
15
16
  icon: 'fa fa-minus-circle',
16
17
  onClick: noop,
18
+ index: 0,
17
19
  },
18
20
  'should render inherited role to remove': {
19
21
  role: { name: 'test.role', id: 5, inherited: true },
20
22
  icon: 'fa fa-minus-circle',
21
23
  onClick: noop,
24
+ index: 0,
22
25
  },
23
26
  };
24
27
 
@@ -25,12 +25,13 @@ const AvailableRolesList = ({
25
25
  />
26
26
  </div>
27
27
  <LoadingState loading={loading}>
28
- {unassignedRoles.map(role => (
28
+ {unassignedRoles.map((role, index) => (
29
29
  <AnsibleRole
30
30
  key={role.id}
31
31
  role={role}
32
32
  icon="fa fa-plus-circle"
33
33
  onClick={onAddRole}
34
+ index={index}
34
35
  />
35
36
  ))}
36
37
  </LoadingState>
@@ -19,7 +19,7 @@ exports[`AnsibleRole should render a role to add 1`] = `
19
19
  compoundExpand={false}
20
20
  compoundExpanded={false}
21
21
  description={null}
22
- heading="test.role"
22
+ heading="1. test.role"
23
23
  hideCloseIcon={false}
24
24
  id={5}
25
25
  initExpanded={false}
@@ -51,7 +51,7 @@ exports[`AnsibleRole should render a role to remove 1`] = `
51
51
  compoundExpand={false}
52
52
  compoundExpanded={false}
53
53
  description={null}
54
- heading="test.role"
54
+ heading="1. test.role"
55
55
  hideCloseIcon={false}
56
56
  id={5}
57
57
  initExpanded={false}
@@ -94,7 +94,7 @@ exports[`AnsibleRole should render inherited role to remove 1`] = `
94
94
  compoundExpand={false}
95
95
  compoundExpanded={false}
96
96
  description={null}
97
- heading="test.role"
97
+ heading="1. test.role"
98
98
  hideCloseIcon={false}
99
99
  id={5}
100
100
  initExpanded={false}
@@ -34,8 +34,10 @@ exports[`AvailableRolesList should render 1`] = `
34
34
  >
35
35
  <AnsibleRole
36
36
  icon="fa fa-plus-circle"
37
+ index={0}
37
38
  key="1"
38
39
  onClick={[Function]}
40
+ resourceName="host"
39
41
  role={
40
42
  Object {
41
43
  "id": 1,
@@ -45,8 +47,10 @@ exports[`AvailableRolesList should render 1`] = `
45
47
  />
46
48
  <AnsibleRole
47
49
  icon="fa fa-plus-circle"
50
+ index={1}
48
51
  key="2"
49
52
  onClick={[Function]}
53
+ resourceName="host"
50
54
  role={
51
55
  Object {
52
56
  "id": 2,
@@ -0,0 +1,3 @@
1
+ .foreman-dual-list-order {
2
+ padding-right: var(--pf-global--spacer--sm);
3
+ }
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ const ListControls = props => (
6
+ <div className="pf-c-dual-list-selector__controls">
7
+ <div className="pf-c-dual-list-selector__controls-item">
8
+ <button
9
+ className="pf-c-button pf-m-plain"
10
+ type="button"
11
+ disabled={props.addSelectedDisabled}
12
+ onClick={props.onAddSelected}
13
+ aria-label={__('Add selected')}
14
+ >
15
+ <i className="fas fa-fw fa-angle-right" />
16
+ </button>
17
+ </div>
18
+ <div className="pf-c-dual-list-selector__controls-item">
19
+ <button
20
+ className="pf-c-button pf-m-plain"
21
+ type="button"
22
+ disabled={props.addAllDisabled}
23
+ onClick={props.onAddAll}
24
+ aria-label={__('Add all')}
25
+ >
26
+ <i className="fas fa-fw fa-angle-double-right" />
27
+ </button>
28
+ </div>
29
+ <div className="pf-c-dual-list-selector__controls-item">
30
+ <button
31
+ className="pf-c-button pf-m-plain"
32
+ type="button"
33
+ disabled={props.removeAllDisabled}
34
+ onClick={props.onRemoveAll}
35
+ aria-label={__('Remove all')}
36
+ >
37
+ <i className="fas fa-fw fa-angle-double-left" />
38
+ </button>
39
+ </div>
40
+ <div className="pf-c-dual-list-selector__controls-item">
41
+ <button
42
+ className="pf-c-button pf-m-plain"
43
+ type="button"
44
+ disabled={props.removeSelectedDisabled}
45
+ onClick={props.onRemoveSelected}
46
+ aria-label={__('Remove selected')}
47
+ >
48
+ <i className="fas fa-fw fa-angle-left" />
49
+ </button>
50
+ </div>
51
+ </div>
52
+ );
53
+
54
+ ListControls.propTypes = {
55
+ addSelectedDisabled: PropTypes.bool.isRequired,
56
+ onAddSelected: PropTypes.func.isRequired,
57
+ addAllDisabled: PropTypes.bool.isRequired,
58
+ onAddAll: PropTypes.func.isRequired,
59
+ removeAllDisabled: PropTypes.bool.isRequired,
60
+ onRemoveAll: PropTypes.func.isRequired,
61
+ removeSelectedDisabled: PropTypes.bool.isRequired,
62
+ onRemoveSelected: PropTypes.func.isRequired,
63
+ };
64
+
65
+ export default ListControls;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const ListHeader = props => (
5
+ <div className="pf-c-dual-list-selector__header">
6
+ <div className="pf-c-dual-list-selector__title">
7
+ <div className="pf-c-dual-list-selector__title-text">{props.title}</div>
8
+ </div>
9
+ </div>
10
+ );
11
+
12
+ ListHeader.propTypes = {
13
+ title: PropTypes.string.isRequired,
14
+ };
15
+
16
+ export default ListHeader;