foreman_ansible 6.3.3 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) 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_reports_helper.rb +35 -54
  15. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
  16. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +1 -0
  17. data/app/models/foreman_ansible/ansible_provider.rb +56 -6
  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/config_reports/_ansible.html.erb +14 -5
  25. data/app/views/foreman_ansible/job_templates/ansible_roles_-_ansible_default.erb +4 -0
  26. data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
  27. data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
  28. data/config/routes.rb +3 -0
  29. data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
  30. data/lib/foreman_ansible/engine.rb +0 -18
  31. data/lib/foreman_ansible/register.rb +114 -2
  32. data/lib/foreman_ansible/version.rb +1 -1
  33. data/package.json +10 -6
  34. data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
  35. data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
  36. data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
  37. data/test/unit/ansible_provider_test.rb +3 -6
  38. data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
  39. data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
  40. data/test/unit/helpers/ansible_reports_helper_test.rb +4 -30
  41. data/test/unit/services/override_resolver_test.rb +34 -0
  42. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +59 -0
  43. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.scss +6 -0
  44. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +20 -0
  45. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
  46. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
  47. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
  48. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
  49. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
  50. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
  51. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
  52. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
  53. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
  54. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
  55. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
  56. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
  57. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
  58. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
  59. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
  60. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
  61. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
  62. data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
  63. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
  64. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
  65. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
  66. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
  67. data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
  68. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
  69. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
  70. data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
  71. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
  72. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
  73. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
  74. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
  75. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
  76. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
  77. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
  78. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
  79. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
  80. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
  81. data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
  82. data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
  83. data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
  84. data/webpack/components/AnsibleHostDetail/constants.js +9 -0
  85. data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
  86. data/webpack/components/AnsibleHostDetail/index.js +6 -0
  87. data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
  88. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
  89. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
  90. data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
  91. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsiblePermissionDenied.test.js.snap +2 -0
  92. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
  93. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AssignedRolesList.test.js.snap +4 -4
  94. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +9 -0
  95. data/webpack/components/DualList/DualList.scss +3 -0
  96. data/webpack/components/DualList/ListControls.js +65 -0
  97. data/webpack/components/DualList/ListHeader.js +16 -0
  98. data/webpack/components/DualList/ListItem.js +69 -0
  99. data/webpack/components/DualList/ListPane.js +95 -0
  100. data/webpack/components/DualList/SelectedStatus.js +21 -0
  101. data/webpack/components/DualList/index.js +103 -0
  102. data/webpack/components/ErrorState.js +16 -0
  103. data/webpack/components/withLoading.js +135 -0
  104. data/webpack/components/withPagination.js +0 -0
  105. data/webpack/formHelper.js +131 -0
  106. data/webpack/globalIdHelper.js +13 -0
  107. data/webpack/global_index.js +18 -0
  108. data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
  109. data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
  110. data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
  111. data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
  112. data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
  113. data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
  114. data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
  115. data/webpack/graphql/queries/ansibleRoles.gql +13 -0
  116. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  117. data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
  118. data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
  119. data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
  120. data/webpack/graphql/queries/recurringJobs.gql +28 -0
  121. data/webpack/helpers/pageParamsHelper.js +40 -0
  122. data/webpack/helpers/paginationHelper.js +9 -0
  123. data/webpack/permissionsHelper.js +58 -0
  124. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
  125. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
  126. data/webpack/routes/HostgroupJobs/index.js +26 -0
  127. data/webpack/routes/routes.js +10 -0
  128. data/webpack/testHelper.js +165 -0
  129. data/webpack/toastHelper.js +4 -0
  130. metadata +130 -78
  131. data/app/assets/images/foreman_ansible/Ansible.png +0 -0
  132. data/app/models/foreman_ansible/fact_name.rb +0 -16
  133. data/app/models/setting/ansible.rb +0 -106
  134. data/app/services/foreman_ansible/fact_importer.rb +0 -99
  135. data/app/services/foreman_ansible/fact_parser.rb +0 -126
  136. data/app/services/foreman_ansible/fact_sparser.rb +0 -37
  137. data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
  138. data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
  139. data/test/unit/lib/foreman_ansible_core/ansible_runner_test.rb +0 -51
  140. data/test/unit/lib/foreman_ansible_core/command_creator_test.rb +0 -64
  141. data/test/unit/lib/foreman_ansible_core/playbook_runner_test.rb +0 -110
  142. data/test/unit/services/fact_importer_test.rb +0 -52
  143. data/test/unit/services/fact_parser_test.rb +0 -281
  144. data/test/unit/services/fact_sparser_test.rb +0 -24
  145. data/test/unit/services/structured_fact_importer_test.rb +0 -30
  146. data/webpack/__mocks__/foremanReact/common/I18n.js +0 -1
  147. data/webpack/__mocks__/foremanReact/common/helpers.js +0 -13
  148. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +0 -2
  149. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +0 -5
  150. data/webpack/__mocks__/foremanReact/components/common/forms/OrderableSelect/helpers.js +0 -5
  151. data/webpack/__mocks__/foremanReact/redux/API.js +0 -7
  152. data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -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);
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import AnsibleHostDetail from './AnsibleHostDetail';
3
+
4
+ const WrappedAnsibleHostDetail = props => <AnsibleHostDetail {...props} />;
5
+
6
+ export default WrappedAnsibleHostDetail;
@@ -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>
@@ -2,6 +2,7 @@
2
2
 
3
3
  exports[`AnsiblePermissionDenied should render 1`] = `
4
4
  <EmptyStatePattern
5
+ action={null}
5
6
  description={
6
7
  <span>
7
8
  You are not authorized to perform this action.
@@ -22,5 +23,6 @@ exports[`AnsiblePermissionDenied should render 1`] = `
22
23
  header="Permission Denied"
23
24
  icon="lock"
24
25
  iconType="fa"
26
+ secondaryActions={Array []}
25
27
  />
26
28
  `;
@@ -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}
@@ -1,13 +1,13 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`AssignedRolesList should render 1`] = `
4
- <DndProvider
4
+ <Memo(DndProvider)
5
5
  backend={[Function]}
6
6
  >
7
7
  <ListView
8
8
  className=""
9
9
  >
10
- <Component
10
+ <DropTarget(DragSource(Orderable(AnsibleRole)))
11
11
  icon="fa fa-minus-circle"
12
12
  index={0}
13
13
  key="1"
@@ -21,7 +21,7 @@ exports[`AssignedRolesList should render 1`] = `
21
21
  }
22
22
  }
23
23
  />
24
- <Component
24
+ <DropTarget(DragSource(Orderable(AnsibleRole)))
25
25
  icon="fa fa-minus-circle"
26
26
  index={1}
27
27
  key="2"
@@ -60,5 +60,5 @@ exports[`AssignedRolesList should render 1`] = `
60
60
  }
61
61
  />
62
62
  </div>
63
- </DndProvider>
63
+ </Memo(DndProvider)>
64
64
  `;
@@ -8,9 +8,14 @@ exports[`AvailableRolesList should render 1`] = `
8
8
  className="sticky-pagination"
9
9
  >
10
10
  <PaginationWrapper
11
+ className=""
12
+ disableNext={false}
13
+ disablePrev={false}
11
14
  dropdownButtonId="available-ansible-roles-pagination-row-dropdown"
12
15
  itemCount={2}
13
16
  onChange={[Function]}
17
+ onPageSet={[Function]}
18
+ onPerPageSelect={[Function]}
14
19
  pagination={
15
20
  Object {
16
21
  "page": 1,
@@ -29,8 +34,10 @@ exports[`AvailableRolesList should render 1`] = `
29
34
  >
30
35
  <AnsibleRole
31
36
  icon="fa fa-plus-circle"
37
+ index={0}
32
38
  key="1"
33
39
  onClick={[Function]}
40
+ resourceName="host"
34
41
  role={
35
42
  Object {
36
43
  "id": 1,
@@ -40,8 +47,10 @@ exports[`AvailableRolesList should render 1`] = `
40
47
  />
41
48
  <AnsibleRole
42
49
  icon="fa fa-plus-circle"
50
+ index={1}
43
51
  key="2"
44
52
  onClick={[Function]}
53
+ resourceName="host"
45
54
  role={
46
55
  Object {
47
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;
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import PropTypes from 'prop-types';
4
+
5
+ const ListItem = props => {
6
+ const draggableBtn = (
7
+ <div className="pf-c-dual-list-selector__draggable">
8
+ <button
9
+ className="pf-c-button pf-m-plain"
10
+ type="button"
11
+ aria-pressed="false"
12
+ aria-label="Reorder"
13
+ id="draggable-list-item-2-draggable-button"
14
+ aria-labelledby="draggable-list-item-2-draggable-button draggable-list-item-2-item-text"
15
+ aria-describedby="draggable-help"
16
+ >
17
+ <i className="fas fa-grip-vertical" aria-hidden="true" />
18
+ </button>
19
+ </div>
20
+ );
21
+
22
+ const orderBtn = (
23
+ <span className="foreman-dual-list-order">{`${props.index + 1}.`}</span>
24
+ );
25
+
26
+ return (
27
+ <li className="pf-c-dual-list-selector__list-item">
28
+ <div
29
+ className={classNames('pf-c-dual-list-selector__list-item-row ', {
30
+ 'pf-m-selected': props.selected,
31
+ 'pf-m-ghost-row': props.dragging,
32
+ })}
33
+ >
34
+ {props.draggable && draggableBtn}
35
+ <button
36
+ className="pf-c-dual-list-selector__item"
37
+ type="button"
38
+ onClick={props.onClick}
39
+ >
40
+ <span className="pf-c-dual-list-selector__item-main">
41
+ <span className="pf-c-dual-list-selector__item-text">
42
+ {props.draggable && orderBtn}
43
+ <span>{props.name}</span>
44
+ </span>
45
+ </span>
46
+ <span className="pf-c-dual-list-selector__item-count">
47
+ <span className="pf-c-badge pf-m-read" />
48
+ </span>
49
+ </button>
50
+ </div>
51
+ </li>
52
+ );
53
+ };
54
+
55
+ ListItem.propTypes = {
56
+ selected: PropTypes.bool.isRequired,
57
+ dragging: PropTypes.bool,
58
+ draggable: PropTypes.bool,
59
+ onClick: PropTypes.func.isRequired,
60
+ name: PropTypes.string.isRequired,
61
+ index: PropTypes.number.isRequired,
62
+ };
63
+
64
+ ListItem.defaultProps = {
65
+ draggable: false,
66
+ dragging: false,
67
+ };
68
+
69
+ export default ListItem;