foreman_ansible 7.0.0 → 7.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/graphql/resolvers/ansible_role/path.rb +11 -0
  3. data/app/graphql/resolvers/ansible_variable/path.rb +11 -0
  4. data/app/graphql/types/ansible_role.rb +1 -0
  5. data/app/graphql/types/ansible_variable.rb +1 -0
  6. data/app/models/foreman_ansible/ansible_provider.rb +3 -2
  7. data/app/services/foreman_ansible/ansible_report_importer.rb +0 -4
  8. data/lib/foreman_ansible/engine.rb +1 -0
  9. data/lib/foreman_ansible/version.rb +1 -1
  10. data/test/unit/ansible_provider_test.rb +12 -0
  11. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +1 -0
  12. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.scss +4 -0
  13. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +5 -3
  14. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +9 -0
  15. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +2 -2
  16. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +4 -1
  17. data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +49 -7
  18. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +4 -1
  19. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js.orig +151 -0
  20. data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +19 -3
  21. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +3 -0
  22. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +8 -3
  23. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTabHelper.test.js +11 -0
  24. data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +8 -1
  25. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +8 -14
  26. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +5 -2
  27. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +4 -2
  28. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +2 -2
  29. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +11 -0
  30. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +7 -3
  31. data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +38 -14
  32. data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +1 -0
  33. data/webpack/components/DualList/index.js +2 -2
  34. data/webpack/formHelper.js +1 -1
  35. data/webpack/graphql/queries/hostAnsibleRoles.gql +1 -0
  36. data/webpack/graphql/queries/hostVariableOverrides.gql +1 -0
  37. data/webpack/graphql/queries/recurringJobs.gql +1 -0
  38. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +5 -2
  39. metadata +30 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76cdda389998bc85d45534a8feeb54fde36c671401665e5b337101b952efeae4
4
- data.tar.gz: df707b59de63cd0cf66f553765a9e9a8e659cf2bdb4049198ce12eaff58f12fd
3
+ metadata.gz: 7a8c4f263992d803c31386bc301ce1e032c680ada845db8bf07e71162c003384
4
+ data.tar.gz: 628c7b6952499ef5dfe912370bec8d3e936926a004acf5b7693d7706fe461ea9
5
5
  SHA512:
6
- metadata.gz: d54c5b84429025c6f2f8be0914ab870d8f5945ea97430cdb7129dbff4ceaffc3812608c6a020a0284b454c54542dd61d4682bcdab1532bc6a012b30ef0c48cbe
7
- data.tar.gz: 16628e25e09230f8deeabf013bc0cc4277c271f8efde4d5691fab8a925ba1bac3c94b6b79c929e13dedded52bcd7a6406d474db10f5dd39619991227d9fd00c8
6
+ metadata.gz: '09f3fd7b961edcb7abee1f6dd6de4fbf0bb80130668c39724d6c8658c2ca1570109ab6b7d172d2f9b963c6e4e5aebaa3280ebf199f9fe5ff86580ab1d8f06c7e'
7
+ data.tar.gz: 924b85ee81562a848c07417fd0788520c000292901f63ef45f8adbfc99ec32dae21858589c83688f461d19e49a4c6491801d53f611032a1c291ba941e9068133
@@ -0,0 +1,11 @@
1
+ module Resolvers
2
+ module AnsibleRole
3
+ class Path < Resolvers::BaseResolver
4
+ type String, null: false
5
+
6
+ def resolve
7
+ Rails.application.routes.url_helpers.ansible_roles_path(search: "name = #{object.name}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Resolvers
2
+ module AnsibleVariable
3
+ class Path < Resolvers::BaseResolver
4
+ type String, null: false
5
+
6
+ def resolve
7
+ Rails.application.routes.url_helpers.edit_ansible_variable_path(object.ansible_variable)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -5,5 +5,6 @@ module Types
5
5
  global_id_field :id
6
6
 
7
7
  field :name, String, :null => false
8
+ field :path, resolver: Resolvers::AnsibleRole::Path
8
9
  end
9
10
  end
@@ -5,6 +5,7 @@ module Types
5
5
  global_id_field :id
6
6
 
7
7
  field :key, String
8
+ field :path, resolver: Resolvers::AnsibleVariable::Path
8
9
  field :override, Boolean
9
10
  field :description, String
10
11
  field :hidden_value, Boolean
@@ -88,7 +88,7 @@ if defined? ForemanRemoteExecution
88
88
  :children => [
89
89
  {
90
90
  :name => :tags,
91
- :type => Array,
91
+ :type => String,
92
92
  :opts => { :required => false, :desc => N_('A comma separated list of tags to use for Ansible run') }
93
93
  },
94
94
  {
@@ -115,7 +115,8 @@ if defined? ForemanRemoteExecution
115
115
  end
116
116
 
117
117
  def proxy_batch_size
118
- Setting['foreman_ansible_proxy_batch_size']
118
+ value = Setting['foreman_ansible_proxy_batch_size']
119
+ value.presence && value.to_i
119
120
  end
120
121
 
121
122
  private
@@ -18,10 +18,6 @@ module ForemanAnsible
18
18
  partial_hostname_match(hostname)
19
19
  end
20
20
 
21
- def self.authorized_smart_proxy_features
22
- super + ['Ansible']
23
- end
24
-
25
21
  def partial_hostname_match(hostname)
26
22
  return @host unless @host.new_record?
27
23
  hosts = Host.where(Host.arel_table[:name].matches("#{hostname}.%"))
@@ -75,6 +75,7 @@ module ForemanAnsible
75
75
  ::Api::V2::HostgroupsController.include ForemanAnsible::Api::V2::HostgroupsControllerExtensions
76
76
  ::Api::V2::HostgroupsController.include ForemanAnsible::Api::V2::HostgroupsParamGroupExtensions
77
77
  ::ConfigReportImporter.include ForemanAnsible::AnsibleReportImporter
78
+ ReportImporter.register_smart_proxy_feature('Ansible')
78
79
  rescue StandardError => e
79
80
  Rails.logger.warn "Foreman Ansible: skipping engine hook (#{e})"
80
81
  end
@@ -4,5 +4,5 @@
4
4
  # This way other parts of Foreman can just call ForemanAnsible::VERSION
5
5
  # and detect what version the plugin is running.
6
6
  module ForemanAnsible
7
- VERSION = '7.0.0'
7
+ VERSION = '7.0.3'
8
8
  end
@@ -47,4 +47,16 @@ class AnsibleProviderTest < ActiveSupport::TestCase
47
47
  proxy_command_options(template_invocation, dummyhost)
48
48
  end
49
49
  end
50
+
51
+ describe '#proxy_batch_size' do
52
+ it 'returns integer if setting is string' do
53
+ Setting.expects(:[]).with('foreman_ansible_proxy_batch_size').returns('10')
54
+ _(ForemanAnsible::AnsibleProvider.proxy_batch_size).must_equal(10)
55
+ end
56
+
57
+ it 'returns nil if setting is empty' do
58
+ Setting.expects(:[]).with('foreman_ansible_proxy_batch_size').returns('')
59
+ _(ForemanAnsible::AnsibleProvider.proxy_batch_size).must_equal(nil)
60
+ end
61
+ end
50
62
  end
@@ -21,6 +21,7 @@ const AnsibleHostDetail = ({
21
21
  {response?.id && (
22
22
  <>
23
23
  <Tabs
24
+ className="ansible-host-details-tabs"
24
25
  onSelect={(evt, subTab) => hashHistory.push(subTab)}
25
26
  activeKey={pathname?.split('/')[2]}
26
27
  isSecondary
@@ -4,3 +4,7 @@
4
4
  background-color: $pf-color-white;
5
5
  padding: 1.8rem;
6
6
  }
7
+
8
+ .ansible-host-details-tabs {
9
+ margin: 0 24px;
10
+ }
@@ -63,7 +63,7 @@ const AnsibleVariableOverridesTable = ({
63
63
  }) => {
64
64
  const columns = [
65
65
  __('Name'),
66
- __('Ansible Role'),
66
+ __('Ansible role'),
67
67
  __('Type'),
68
68
  __('Value'),
69
69
  __('Source attribute'),
@@ -131,7 +131,7 @@ const AnsibleVariableOverridesTable = ({
131
131
  onClick: () => {
132
132
  dispatch(
133
133
  openConfirmModal({
134
- title: __('Delete Ansible Variable Override'),
134
+ title: __('Delete Ansible variable override'),
135
135
  message:
136
136
  variable &&
137
137
  sprintf(
@@ -187,7 +187,9 @@ const AnsibleVariableOverridesTable = ({
187
187
  <Tbody>
188
188
  {variables.map((variable, idx) => (
189
189
  <Tr key={idx}>
190
- <Td>{variable.key}</Td>
190
+ <Td>
191
+ <a href={variable.path}>{variable.key}</a>
192
+ </Td>
191
193
  <Td>{variable.ansibleRoleName}</Td>
192
194
  <Td>{variable.parameterType}</Td>
193
195
  <Td>
@@ -29,6 +29,7 @@ const withFqdnOverride = canEdit => ({
29
29
  },
30
30
  id: ansibleVariableId,
31
31
  key: 'rectangle',
32
+ path: '/ansible/ansible_variables/1/edit',
32
33
  defaultValue: 17,
33
34
  parameterType: 'integer',
34
35
  ansibleRoleName: 'test.role',
@@ -62,6 +63,7 @@ const withDomainOverride = canEdit => ({
62
63
  },
63
64
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTc4',
64
65
  key: 'circle',
66
+ path: '/ansible/ansible_variables/2/edit',
65
67
  defaultValue: 'd',
66
68
  parameterType: 'string',
67
69
  ansibleRoleName: 'test.role',
@@ -133,6 +135,7 @@ export const mocks = [
133
135
  },
134
136
  id: barVariableGlobalId,
135
137
  key: 'bar',
138
+ path: '/ansible/ansible_variables/11/edit',
136
139
  defaultValue: 'a',
137
140
  parameterType: 'string',
138
141
  ansibleRoleName: 'test.role',
@@ -160,6 +163,7 @@ export const mocks = [
160
163
  },
161
164
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY1',
162
165
  key: 'square',
166
+ path: '/ansible/ansible_variables/12/edit',
163
167
  defaultValue: true,
164
168
  parameterType: 'boolean',
165
169
  ansibleRoleName: 'test.role',
@@ -179,6 +183,7 @@ export const mocks = [
179
183
  },
180
184
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTc4',
181
185
  key: 'circle',
186
+ path: '/ansible/ansible_variables/13/edit',
182
187
  defaultValue: 'd',
183
188
  parameterType: 'string',
184
189
  ansibleRoleName: 'test.role',
@@ -203,6 +208,7 @@ export const mocks = [
203
208
  },
204
209
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTc5',
205
210
  key: 'ellipse',
211
+ path: '/ansible/ansible_variables/14/edit',
206
212
  defaultValue: ['seven', 'eight'],
207
213
  parameterType: 'array',
208
214
  ansibleRoleName: 'test.role',
@@ -227,6 +233,7 @@ export const mocks = [
227
233
  },
228
234
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY2Ng==',
229
235
  key: 'spiral',
236
+ path: '/ansible/ansible_variables/15/edit',
230
237
  defaultValue: { one: 'one', two: 'two' },
231
238
  parameterType: 'hash',
232
239
  ansibleRoleName: 'test.role',
@@ -246,6 +253,7 @@ export const mocks = [
246
253
  },
247
254
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY3Mg==',
248
255
  key: 'sun',
256
+ path: '/ansible/ansible_variables/16/edit',
249
257
  defaultValue: "{ one: 'one', two: 'two' }",
250
258
  parameterType: 'json',
251
259
  ansibleRoleName: 'test.role',
@@ -265,6 +273,7 @@ export const mocks = [
265
273
  },
266
274
  id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY3Mw==',
267
275
  key: 'moon',
276
+ path: '/ansible/ansible_variables/17/edit',
268
277
  defaultValue: [
269
278
  { hosts: 'all', become: 'true', roles: ['foo'] },
270
279
  ],
@@ -37,12 +37,12 @@ describe('AnsibleVariableOverrides', () => {
37
37
  userEvent.click(screen.getByText('Delete'));
38
38
  await waitFor(tick);
39
39
  expect(
40
- screen.getByText('Delete Ansible Variable Override')
40
+ screen.getByText('Delete Ansible variable override')
41
41
  ).toBeInTheDocument();
42
42
  userEvent.click(screen.getByText('Cancel'));
43
43
  await waitFor(tick);
44
44
  expect(
45
- screen.queryByText('Delete Ansible Variable Override')
45
+ screen.queryByText('Delete Ansible variable override')
46
46
  ).not.toBeInTheDocument();
47
47
  });
48
48
  it('should delete override', async () => {
@@ -41,7 +41,10 @@ const AnsibleVariableOverrides = ({ hostId, hostAttrs, history }) => {
41
41
  renameData={renameData}
42
42
  fetchFn={useFetchFn}
43
43
  renamedDataPath="variables"
44
- emptyStateTitle={__('No Ansible Variables found for Host')}
44
+ emptyStateProps={{
45
+ header: __('No Ansible variables found for Host'),
46
+ description: __('Only variables marked to Override are shown here.'),
47
+ }}
45
48
  permissions={['view_ansible_variables']}
46
49
  pagination={pagination}
47
50
  history={history}
@@ -7,21 +7,36 @@ import { showToast } from '../../../../toastHelper';
7
7
  export const ansiblePurpose = (resourceName, resourceId) =>
8
8
  `ansible-${resourceName}-${resourceId}`;
9
9
 
10
- const jobSearch = (resourceName, resourceId, statusSearch) =>
11
- `recurring = true && pattern_template_name = "Ansible Roles - Ansible Default" && ${statusSearch} && recurring_logic.purpose = ${ansiblePurpose(
10
+ const jobSearch = (resourceName, resourceId, status, hostGroupId) => {
11
+ const search = `recurring = true && pattern_template_name = "Ansible Roles - Ansible Default"`;
12
+ const searchStatus = ` && ${status}`;
13
+ const searchHost = ` && recurring_logic.purpose = ${ansiblePurpose(
12
14
  resourceName,
13
15
  resourceId
14
16
  )}`;
17
+ const searchHostGroup = hostGroupId
18
+ ? ` or recurring_logic.purpose = ${ansiblePurpose(
19
+ 'hostgroup',
20
+ hostGroupId
21
+ )}`
22
+ : '';
15
23
 
16
- export const scheduledJobsSearch = (resourceName, resourceId) =>
17
- jobSearch(resourceName, resourceId, 'status = queued');
18
- export const previousJobsSearch = (resourceName, resourceId) =>
19
- jobSearch(resourceName, resourceId, 'status != queued');
24
+ return search + searchStatus + searchHost + searchHostGroup;
25
+ };
26
+
27
+ export const scheduledJobsSearch = (resourceName, resourceId, hostGroupId) =>
28
+ jobSearch(resourceName, resourceId, 'status = queued', hostGroupId);
29
+ export const previousJobsSearch = (resourceName, resourceId, hostGroupId) =>
30
+ jobSearch(resourceName, resourceId, 'status != queued', hostGroupId);
20
31
 
21
32
  const fetchJobsFn = (searchFn, pagination = {}) => componentProps =>
22
33
  useQuery(jobsQuery, {
23
34
  variables: {
24
- search: searchFn(componentProps.resourceName, componentProps.resourceId),
35
+ search: searchFn(
36
+ componentProps.resourceName,
37
+ componentProps.resourceId,
38
+ componentProps.hostGroupId
39
+ ),
25
40
  ...pagination,
26
41
  },
27
42
  });
@@ -77,3 +92,30 @@ export const useCancelMutation = (resourceName, resourceId) =>
77
92
  },
78
93
  ],
79
94
  });
95
+
96
+ export const readableCron = (cron = '') => {
97
+ if (cron.match(/(\d+ \* \* \* \*)/)) {
98
+ return 'hourly';
99
+ }
100
+
101
+ if (cron.match(/(\d+ \d+ \* \* \*)/)) {
102
+ return 'daily';
103
+ }
104
+
105
+ if (cron.match(/(\d+ \d+ \* \* \d+)/)) {
106
+ return 'weekly';
107
+ }
108
+
109
+ if (cron.match(/(\d+ \d+ \d+ \* \*)/)) {
110
+ return 'monthly';
111
+ }
112
+
113
+ return 'custom';
114
+ };
115
+
116
+ export const readablePurpose = (purpose = '') => {
117
+ if (window.location.href.match(/ansible\/hostgroup/)) {
118
+ return '';
119
+ }
120
+ return purpose.match(/hostgroup/) ? __('(from host group)') : '';
121
+ };
@@ -17,6 +17,7 @@ import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
17
17
 
18
18
  import { decodeId } from '../../../../globalIdHelper';
19
19
  import withLoading from '../../../withLoading';
20
+ import { readableCron, readablePurpose } from './JobsTabHelper';
20
21
  import {
21
22
  preparePerPageOptions,
22
23
  refreshPage,
@@ -78,13 +79,15 @@ const PreviousJobsTable = ({ history, totalCount, jobs, pagination }) => {
78
79
  >
79
80
  {job.description}
80
81
  </a>
82
+ &nbsp;
83
+ {readablePurpose(job.recurringLogic.purpose)}
81
84
  </Td>
82
85
  <Td>{job.task.result}</Td>
83
86
  <Td>{job.task.state}</Td>
84
87
  <Td>
85
88
  <RelativeDateTime date={job.startAt} />
86
89
  </Td>
87
- <Td>{job.recurringLogic.cronLine}</Td>
90
+ <Td>{readableCron(job.recurringLogic.cronLine)}</Td>
88
91
  </Tr>
89
92
  ))}
90
93
  </Tbody>
@@ -0,0 +1,151 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
5
+
6
+ import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
7
+
8
+ import {
9
+ TableComposable,
10
+ Thead,
11
+ Tbody,
12
+ Tr,
13
+ Th,
14
+ Td,
15
+ } from '@patternfly/react-table';
16
+ import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
17
+
18
+ import { decodeId } from '../../../../globalIdHelper';
19
+ import withLoading from '../../../withLoading';
20
+ <<<<<<< HEAD
21
+ import { readableCron } from './JobsTabHelper';
22
+ import {
23
+ preparePerPageOptions,
24
+ refreshPage,
25
+ } from '../../../../helpers/paginationHelper';
26
+ =======
27
+ import { readableCron, readablePurpose } from './JobsTabHelper';
28
+ >>>>>>> 5b01704 (Fixes #34458 - Show Hostgroup jobs on the Host Detail page)
29
+
30
+ const PreviousJobsTable = ({ history, totalCount, jobs, pagination }) => {
31
+ const columns = [
32
+ __('Description'),
33
+ __('Result'),
34
+ __('State'),
35
+ __('Executed at'),
36
+ __('Schedule'),
37
+ ];
38
+
39
+ const handlePerPageSelected = (event, perPage) => {
40
+ refreshPage(history, { page: 1, perPage });
41
+ };
42
+
43
+ const handlePageSelected = (event, page) => {
44
+ refreshPage(history, { ...pagination, page });
45
+ };
46
+
47
+ const perPageOptions = preparePerPageOptions(usePaginationOptions());
48
+
49
+ return (
50
+ <React.Fragment>
51
+ <h3>{__('Previously executed jobs')}</h3>
52
+ <<<<<<< HEAD
53
+ <Flex className="pf-u-pt-md">
54
+ =======
55
+ <Flex direction={{ default: 'column' }} className="pf-u-pt-md">
56
+ <FlexItem align={{ default: 'alignRight' }}>
57
+ <Pagination updateParamsByUrl itemCount={totalCount} variant="top" />
58
+ </FlexItem>
59
+ <FlexItem>
60
+ <TableComposable variant="compact">
61
+ <Thead>
62
+ <Tr>
63
+ {columns.map(col => (
64
+ <Th key={col}>{col}</Th>
65
+ ))}
66
+ </Tr>
67
+ </Thead>
68
+ <Tbody>
69
+ {jobs.map(job => (
70
+ <Tr key={job.id}>
71
+ <Td>
72
+ <a
73
+ onClick={() =>
74
+ window.tfm.nav.pushUrl(
75
+ `/job_invocations/${decodeId(job.id)}`
76
+ )
77
+ }
78
+ >
79
+ {job.description}
80
+ </a>
81
+ &nbsp;
82
+ {readablePurpose(job.recurringLogic.purpose)}
83
+ </Td>
84
+ <Td>{job.task.result}</Td>
85
+ <Td>{job.task.state}</Td>
86
+ <Td>
87
+ <RelativeDateTime date={job.startAt} />
88
+ </Td>
89
+ <Td>{readableCron(job.recurringLogic.cronLine)}</Td>
90
+ </Tr>
91
+ ))}
92
+ </Tbody>
93
+ </TableComposable>
94
+ </FlexItem>
95
+ >>>>>>> 5b01704 (Fixes #34458 - Show Hostgroup jobs on the Host Detail page)
96
+ <FlexItem align={{ default: 'alignRight' }}>
97
+ <Pagination
98
+ itemCount={totalCount}
99
+ page={pagination.page}
100
+ perPage={pagination.perPage}
101
+ onSetPage={handlePageSelected}
102
+ onPerPageSelect={handlePerPageSelected}
103
+ perPageOptions={perPageOptions}
104
+ variant="top"
105
+ />
106
+ </FlexItem>
107
+ </Flex>
108
+ <TableComposable variant="compact">
109
+ <Thead>
110
+ <Tr>
111
+ {columns.map(col => (
112
+ <Th key={col}>{col}</Th>
113
+ ))}
114
+ </Tr>
115
+ </Thead>
116
+ <Tbody>
117
+ {jobs.map(job => (
118
+ <Tr key={job.id}>
119
+ <Td>
120
+ <a
121
+ onClick={() =>
122
+ window.tfm.nav.pushUrl(
123
+ `/job_invocations/${decodeId(job.id)}`
124
+ )
125
+ }
126
+ >
127
+ {job.description}
128
+ </a>
129
+ </Td>
130
+ <Td>{job.task.result}</Td>
131
+ <Td>{job.task.state}</Td>
132
+ <Td>
133
+ <RelativeDateTime date={job.startAt} />
134
+ </Td>
135
+ <Td>{readableCron(job.recurringLogic.cronLine)}</Td>
136
+ </Tr>
137
+ ))}
138
+ </Tbody>
139
+ </TableComposable>
140
+ </React.Fragment>
141
+ );
142
+ };
143
+
144
+ PreviousJobsTable.propTypes = {
145
+ jobs: PropTypes.array.isRequired,
146
+ history: PropTypes.object.isRequired,
147
+ totalCount: PropTypes.number.isRequired,
148
+ pagination: PropTypes.object.isRequired,
149
+ };
150
+
151
+ export default withLoading(PreviousJobsTable);
@@ -14,11 +14,20 @@ import {
14
14
  Td,
15
15
  } from '@patternfly/react-table';
16
16
 
17
- import { useCancelMutation } from './JobsTabHelper';
17
+ import {
18
+ useCancelMutation,
19
+ readableCron,
20
+ readablePurpose,
21
+ } from './JobsTabHelper';
18
22
  import withLoading from '../../../withLoading';
19
23
  import { decodeId } from '../../../../globalIdHelper';
20
24
 
21
- const RecurringJobsTable = ({ jobs, resourceName, resourceId }) => {
25
+ const RecurringJobsTable = ({
26
+ jobs,
27
+ resourceName,
28
+ resourceId,
29
+ hostGroupId,
30
+ }) => {
22
31
  const columns = [__('Description'), __('Schedule'), __('Next Run')];
23
32
  const dispatch = useDispatch();
24
33
 
@@ -73,8 +82,10 @@ const RecurringJobsTable = ({ jobs, resourceName, resourceId }) => {
73
82
  >
74
83
  {job.description}
75
84
  </a>
85
+ &nbsp;
86
+ {readablePurpose(job.recurringLogic.purpose)}
76
87
  </Td>
77
- <Td>{job.recurringLogic.cronLine}</Td>
88
+ <Td>{readableCron(job.recurringLogic.cronLine)}</Td>
78
89
  <Td>
79
90
  <RelativeDateTime date={job.startAt} />
80
91
  </Td>
@@ -91,6 +102,11 @@ RecurringJobsTable.propTypes = {
91
102
  jobs: PropTypes.array.isRequired,
92
103
  resourceId: PropTypes.number.isRequired,
93
104
  resourceName: PropTypes.string.isRequired,
105
+ hostGroupId: PropTypes.number,
106
+ };
107
+
108
+ RecurringJobsTable.defaultProps = {
109
+ hostGroupId: undefined,
94
110
  };
95
111
 
96
112
  export default withLoading(RecurringJobsTable);
@@ -23,10 +23,12 @@ const viewer = userFactory('viewer', [
23
23
 
24
24
  const firstRecurringLogicGlobalId =
25
25
  'MDE6Rm9yZW1hblRhc2tzOjpSZWN1cnJpbmdMb2dpYy0x';
26
+
26
27
  const firstRecurringLogic = {
27
28
  __typename: 'ForemanTasks::RecurringLogic',
28
29
  id: firstRecurringLogicGlobalId,
29
30
  cronLine: toCron(futureDate, 'weekly'),
31
+ purpose: '',
30
32
  meta: {
31
33
  canEdit: true,
32
34
  },
@@ -66,6 +68,7 @@ export const secondJob = {
66
68
  __typename: 'ForemanTasks::RecurringLogic',
67
69
  id: 'MDE6Rm9yZW1hblRhc2tzOjpSZWN1cnJpbmdMb2dpYy0yMw==',
68
70
  cronLine: '54 10 15 * *',
71
+ purpose: '',
69
72
  meta: {
70
73
  canEdit: true,
71
74
  },
@@ -18,6 +18,7 @@ import {
18
18
  import * as toasts from '../../../../../toastHelper';
19
19
 
20
20
  import { toCron } from '../NewRecurringJobHelper';
21
+ import { readableCron } from '../JobsTabHelper';
21
22
 
22
23
  import {
23
24
  tick,
@@ -49,8 +50,10 @@ describe('JobsTab', () => {
49
50
  .map(element => expect(element).toBeInTheDocument());
50
51
  expect(screen.getByText('Scheduled recurring jobs')).toBeInTheDocument();
51
52
  expect(screen.getByText('Previously executed jobs')).toBeInTheDocument();
52
- expect(screen.getByText(toCron(futureDate, 'weekly'))).toBeInTheDocument();
53
- expect(screen.getByText('54 10 15 * *')).toBeInTheDocument();
53
+ expect(
54
+ screen.getByText(readableCron(toCron(futureDate, 'weekly')))
55
+ ).toBeInTheDocument();
56
+ expect(screen.getByText('monthly')).toBeInTheDocument();
54
57
  });
55
58
  it('should show empty state', async () => {
56
59
  render(
@@ -129,7 +132,9 @@ describe('JobsTab', () => {
129
132
  message: 'Ansible job was successfully created.',
130
133
  });
131
134
  await waitFor(tick);
132
- expect(screen.getByText(toCron(futureDate, 'weekly'))).toBeInTheDocument();
135
+ expect(
136
+ screen.getByText(readableCron(toCron(futureDate, 'weekly')))
137
+ ).toBeInTheDocument();
133
138
  expect(screen.getByText('in 3 days')).toBeInTheDocument();
134
139
  expect(
135
140
  screen.queryByText('No config job for Ansible roles scheduled')
@@ -0,0 +1,11 @@
1
+ import { readableCron } from '../JobsTabHelper';
2
+
3
+ describe('JobTabsHelper', () => {
4
+ it('readableCron', () => {
5
+ expect(readableCron('01 * * * *')).toBe('hourly');
6
+ expect(readableCron('01 01 * * *')).toBe('daily');
7
+ expect(readableCron('01 01 * * 01')).toBe('weekly');
8
+ expect(readableCron('01 01 01 * *')).toBe('monthly');
9
+ expect(readableCron()).toBe('custom');
10
+ });
11
+ });
@@ -15,7 +15,7 @@ import RecurringJobsTable from './RecurringJobsTable';
15
15
  import PreviousJobsTable from './PreviousJobsTable';
16
16
  import NewRecurringJobModal from './NewRecurringJobModal';
17
17
 
18
- const JobsTab = ({ resourceName, resourceId, history }) => {
18
+ const JobsTab = ({ resourceName, resourceId, hostGroupId, history }) => {
19
19
  const [modalOpen, setModalOpen] = useState(false);
20
20
  const toggleModal = () => setModalOpen(!modalOpen);
21
21
 
@@ -44,6 +44,7 @@ const JobsTab = ({ resourceName, resourceId, history }) => {
44
44
  <RecurringJobsTable
45
45
  resourceId={resourceId}
46
46
  resourceName={resourceName}
47
+ hostGroupId={hostGroupId}
47
48
  fetchFn={fetchRecurringFn}
48
49
  renameData={renameData}
49
50
  renamedDataPath="jobs"
@@ -59,6 +60,7 @@ const JobsTab = ({ resourceName, resourceId, history }) => {
59
60
  <PreviousJobsTable
60
61
  resourceId={resourceId}
61
62
  resourceName={resourceName}
63
+ hostGroupId={hostGroupId}
62
64
  fetchFn={fetchPreviousFn(useParamsToVars(history))}
63
65
  renameData={renameData}
64
66
  emptyWrapper={() => null}
@@ -81,8 +83,13 @@ const JobsTab = ({ resourceName, resourceId, history }) => {
81
83
 
82
84
  JobsTab.propTypes = {
83
85
  resourceName: PropTypes.string.isRequired,
86
+ hostGroupId: PropTypes.number,
84
87
  resourceId: PropTypes.number.isRequired,
85
88
  history: PropTypes.object.isRequired,
86
89
  };
87
90
 
91
+ JobsTab.defaultProps = {
92
+ hostGroupId: undefined,
93
+ };
94
+
88
95
  export default JobsTab;
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import { useQuery } from '@apollo/client';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
5
 
6
- import { Modal, Button, ModalVariant } from '@patternfly/react-core';
6
+ import { Modal, ModalVariant } from '@patternfly/react-core';
7
7
 
8
8
  import allAnsibleRolesQuery from '../../../../../graphql/queries/allAnsibleRoles.gql';
9
9
  import AllRolesTable from './AllRolesTable';
@@ -17,25 +17,19 @@ const AllRolesModal = ({ hostGlobalId, onClose, history }) => {
17
17
  const baseModalProps = {
18
18
  variant: ModalVariant.large,
19
19
  isOpen: true,
20
+ onClose,
20
21
  className: 'foreman-modal',
21
- showClose: false,
22
- title: __('All Ansible Roles'),
22
+ showClose: true,
23
+ title: __('All assigned Ansible roles'),
23
24
  disableFocusTrap: true,
25
+ description: __(
26
+ 'This list consists of host assigned roles and group assigned roles. Group assigned roles will always be executed prior to host assigned roles'
27
+ ),
24
28
  };
25
29
 
26
30
  const paginationKeys = { page: 'allPage', perPage: 'allPerPage' };
27
31
 
28
- const actions = [
29
- <Button variant="link" onClick={onClose} key="close">
30
- {__('Close')}
31
- </Button>,
32
- ];
33
-
34
- const wrapper = child => (
35
- <Modal {...baseModalProps} actions={actions}>
36
- {child}
37
- </Modal>
38
- );
32
+ const wrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
39
33
 
40
34
  const loadingWrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
41
35
 
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { translate as __ } from 'foremanReact/common/I18n';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
- import { Modal, Button, ModalVariant } from '@patternfly/react-core';
5
+ import { Modal, Button } from '@patternfly/react-core';
6
6
  import { useQuery } from '@apollo/client';
7
7
 
8
8
  import EditRolesForm from './EditRolesForm';
@@ -20,12 +20,15 @@ const EditRolesModal = ({
20
20
  canEditHost,
21
21
  }) => {
22
22
  const baseModalProps = {
23
- variant: ModalVariant.large,
23
+ width: '70%',
24
24
  isOpen,
25
25
  className: 'foreman-modal',
26
26
  showClose: false,
27
27
  title: __('Edit Ansible Roles'),
28
28
  disableFocusTrap: true,
29
+ description: __(
30
+ 'Add, remove or reorder host assigned Ansible roles. This host has also group assigned roles that are not displayed here and will always be executed prior to host assigned roles'
31
+ ),
29
32
  };
30
33
 
31
34
  const actions = [
@@ -48,7 +48,7 @@ const RolesTable = ({
48
48
  <FlexItem>
49
49
  <Link to="/Ansible/roles/edit">
50
50
  <Button aria-label="edit ansible roles">
51
- {__('Edit Ansible Roles')}
51
+ {__('Edit Ansible roles')}
52
52
  </Button>
53
53
  </Link>
54
54
  </FlexItem>
@@ -90,7 +90,9 @@ const RolesTable = ({
90
90
  <Tbody>
91
91
  {ansibleRoles.map(role => (
92
92
  <Tr key={role.id}>
93
- <Td>{role.name}</Td>
93
+ <Td>
94
+ <a href={role.path}>{role.name}</a>
95
+ </Td>
94
96
  </Tr>
95
97
  ))}
96
98
  </Tbody>
@@ -39,7 +39,7 @@ describe('assigning Ansible roles', () => {
39
39
  userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
40
40
  await waitFor(tick);
41
41
  await waitFor(tick);
42
- expect(screen.getByText('Available options')).toBeInTheDocument();
42
+ expect(screen.getByText('Available Ansible roles')).toBeInTheDocument();
43
43
  userEvent.click(screen.getAllByText('another.role')[1]);
44
44
  userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
45
45
  userEvent.click(screen.getByText('geerlingguy.ceylon'));
@@ -67,7 +67,7 @@ describe('assigning Ansible roles', () => {
67
67
  await waitFor(tick);
68
68
  userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
69
69
  await waitFor(tick);
70
- expect(screen.getByText('Available options')).toBeInTheDocument();
70
+ expect(screen.getByText('Available Ansible roles')).toBeInTheDocument();
71
71
  userEvent.click(screen.getAllByText('another.role')[1]);
72
72
  userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
73
73
  userEvent.click(screen.getByText('geerlingguy.ceylon'));
@@ -34,24 +34,28 @@ const role1 = {
34
34
  __typename: 'AnsibleRole',
35
35
  id: 'MDE6QW5zaWJsZVJvbGUtMw==',
36
36
  name: 'aardvaark.cube',
37
+ path: '/ansible/ansible_roles/search="name = aardvaark.cube"',
37
38
  };
38
39
 
39
40
  const role2 = {
40
41
  __typename: 'AnsibleRole',
41
42
  id: 'MDE6QW5zaWJsZVJvbGUtNQ==',
42
43
  name: 'aardvaark.sphere',
44
+ path: '/ansible/ansible_roles/search="name = aardvaark.sphere"',
43
45
  };
44
46
 
45
47
  const role3 = {
46
48
  __typename: 'AnsibleRole',
47
49
  id: 'MDE6QW5zaWJsZVJvbGUtMzA=',
48
50
  name: 'another.role',
51
+ path: '/ansible/ansible_roles/search="name = another.role"',
49
52
  };
50
53
 
51
54
  const role4 = {
52
55
  __typename: 'AnsibleRole',
53
56
  id: 'MDE6QW5zaWJsZVJvbGUtMzk=',
54
57
  name: 'geerlingguy.ceylon',
58
+ path: '/ansible/ansible_roles/search="name = geerlingguy.ceylon"',
55
59
  };
56
60
 
57
61
  const ansibleRolesMock = {
@@ -71,26 +75,32 @@ const availableRoles = {
71
75
  __typename: 'AnsibleRole',
72
76
  id: 'MDE6QW5zaWJsZVJvbGUtMQ==',
73
77
  name: 'theforeman.foreman_scap_client',
78
+ path:
79
+ '/ansible/ansible_roles/search="name = theforeman.foreman_scap_client"',
74
80
  },
75
81
  {
76
82
  __typename: 'AnsibleRole',
77
83
  id: 'MDE6QW5zaWJsZVJvbGUtMg==',
78
84
  name: 'adriagalin.motd',
85
+ path: '/ansible/ansible_roles/search="name = adriagalin.motd"',
79
86
  },
80
87
  {
81
88
  __typename: 'AnsibleRole',
82
89
  id: 'MDE6QW5zaWJsZVJvbGUtMjI=',
83
90
  name: 'geerlingguy.php',
91
+ path: '/ansible/ansible_roles/search="name = geerlingguy.php"',
84
92
  },
85
93
  {
86
94
  __typename: 'AnsibleRole',
87
95
  id: 'MDE6QW5zaWJsZVJvbGUtNTc=',
88
96
  name: 'robertdebock.epel',
97
+ path: '/ansible/ansible_roles/search="name = robertdebock.epel"',
89
98
  },
90
99
  {
91
100
  __typename: 'AnsibleRole',
92
101
  id: 'MDE6QW5zaWJsZVJvbGUtNTg=',
93
102
  name: 'geerlingguy.nfs',
103
+ path: '/ansible/ansible_roles/search="name = geerlingguy.nfs"',
94
104
  },
95
105
  ],
96
106
  };
@@ -106,6 +116,7 @@ export const allRolesMocks = allAnsibleRolesMockFactory(
106
116
  {
107
117
  id: 'MDE6QW5zaWJsZVJvbGUtMg==',
108
118
  name: 'adriagalin.motd',
119
+ path: '/ansible/ansible_roles/search="name = adriagalin.motd"',
109
120
  inherited: true,
110
121
  },
111
122
  { ...role1, inherited: false },
@@ -38,14 +38,18 @@ describe('RolesTab', () => {
38
38
  );
39
39
  await waitFor(tick);
40
40
  expect(screen.getByText('view all assigned roles')).toBeInTheDocument();
41
- expect(screen.queryByText('All Ansible Roles')).not.toBeInTheDocument();
41
+ expect(
42
+ screen.queryByText('All assigned Ansible roles')
43
+ ).not.toBeInTheDocument();
42
44
  userEvent.click(screen.getByText('view all assigned roles'));
43
45
  await waitFor(tick);
44
- expect(screen.getByText('All Ansible Roles')).toBeInTheDocument();
46
+ expect(screen.getByText('All assigned Ansible roles')).toBeInTheDocument();
45
47
  expect(screen.getByText('Inherited from Hostgroup')).toBeInTheDocument();
46
48
  userEvent.click(screen.getByRole('button', { name: 'Close' }));
47
49
  await waitFor(tick);
48
- expect(screen.queryByText('All Ansible Roles')).not.toBeInTheDocument();
50
+ expect(
51
+ screen.queryByText('All assigned Ansible roles')
52
+ ).not.toBeInTheDocument();
49
53
  });
50
54
  it('should load Ansible Roles as viewer', async () => {
51
55
  render(
@@ -1,6 +1,7 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useQuery } from '@apollo/client';
4
+ import { Button } from '@patternfly/react-core';
4
5
  import { translate as __ } from 'foremanReact/common/I18n';
5
6
 
6
7
  import ansibleRolesQuery from '../../../../graphql/queries/hostAnsibleRoles.gql';
@@ -10,11 +11,12 @@ import {
10
11
  useParamsToVars,
11
12
  useCurrentPagination,
12
13
  } from '../../../../helpers/pageParamsHelper';
14
+ import EditRolesModal from './EditRolesModal';
13
15
 
14
16
  const RolesTab = ({ hostId, history, canEditHost }) => {
15
17
  const hostGlobalId = encodeId('Host', hostId);
16
18
  const pagination = useCurrentPagination(history);
17
-
19
+ const [assignModal, setAssignModal] = useState(false);
18
20
  const renameData = data => ({
19
21
  ansibleRoles: data.host.ownAnsibleRoles.nodes,
20
22
  totalCount: data.host.ownAnsibleRoles.totalCount,
@@ -26,19 +28,41 @@ const RolesTab = ({ hostId, history, canEditHost }) => {
26
28
  fetchPolicy: 'network-only',
27
29
  });
28
30
 
31
+ const editBtn = canEditHost ? (
32
+ <Button
33
+ onClick={() => setAssignModal(true)}
34
+ aria-label="edit ansible roles"
35
+ >
36
+ {__('Assign Ansible roles')}
37
+ </Button>
38
+ ) : null;
29
39
  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
- />
40
+ <>
41
+ <RolesTable
42
+ fetchFn={useFetchFn}
43
+ renamedDataPath="ansibleRoles"
44
+ renameData={renameData}
45
+ permissions={['view_ansible_roles']}
46
+ history={history}
47
+ hostGlobalId={hostGlobalId}
48
+ emptyStateProps={{
49
+ header: __('No Ansible roles assigned'),
50
+ action: editBtn,
51
+ }}
52
+ pagination={pagination}
53
+ canEditHost={canEditHost}
54
+ hostId={hostId}
55
+ />
56
+ {assignModal && (
57
+ <EditRolesModal
58
+ closeModal={() => setAssignModal(false)}
59
+ isOpen={assignModal}
60
+ assignedRoles={[]}
61
+ hostId={hostId}
62
+ canEditHost={canEditHost}
63
+ />
64
+ )}
65
+ </>
42
66
  );
43
67
  };
44
68
 
@@ -44,6 +44,7 @@ const SecondaryTabRoutes = ({ response, router, history }) => (
44
44
  <JobsTab
45
45
  resourceId={response.id}
46
46
  resourceName="host"
47
+ hostGroupId={response.hostgroup_id}
47
48
  history={history}
48
49
  />
49
50
  </TabLayout>
@@ -63,7 +63,7 @@ const DualList = props => {
63
63
  return (
64
64
  <div className="pf-c-dual-list-selector">
65
65
  <ListPane
66
- title={__('Available options')}
66
+ title={__('Available Ansible roles')}
67
67
  items={props.availableOptions}
68
68
  paneClass="pf-m-available"
69
69
  onItemClick={onItemClick('availableSelected')}
@@ -81,7 +81,7 @@ const DualList = props => {
81
81
  removeSelectedDisabled={selectState.chosenSelected.length === 0}
82
82
  />
83
83
  <ListPane
84
- title={__('Chosen options')}
84
+ title={__('Host assigned Ansible roles')}
85
85
  items={props.chosenOptions}
86
86
  paneClass="pf-m-chosen"
87
87
  draggable
@@ -7,7 +7,7 @@ import {
7
7
  DatePicker,
8
8
  TimePicker,
9
9
  } from '@patternfly/react-core';
10
- import ExclamationCircleIcon from '@patternfly/react-icons/dist/js/icons/exclamation-circle-icon';
10
+ import { ExclamationCircleIcon } from '@patternfly/react-icons';
11
11
 
12
12
  const wrapFieldProps = fieldProps => {
13
13
  const { onChange } = fieldProps;
@@ -8,6 +8,7 @@ query($id: String!, $first: Int, $last: Int) {
8
8
  nodes {
9
9
  id
10
10
  name
11
+ path
11
12
  }
12
13
  }
13
14
  }
@@ -8,6 +8,7 @@ query($id: String!, $match: String, $first: Int, $last: Int) {
8
8
  nodes {
9
9
  id
10
10
  key
11
+ path
11
12
  meta {
12
13
  canEdit
13
14
  }
@@ -14,6 +14,7 @@ query($search: String, $first: Int, $last: Int) {
14
14
  meta {
15
15
  canEdit
16
16
  }
17
+ purpose
17
18
  }
18
19
  task {
19
20
  id
@@ -24,6 +24,7 @@ import {
24
24
  } from '../../../testHelper';
25
25
 
26
26
  import { toCron } from '../../../components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper';
27
+ import { readableCron } from '../../../components/AnsibleHostDetail/components/JobsTab/JobsTabHelper';
27
28
 
28
29
  const TestComponent = withRedux(withRouter(withMockedProvider(HostgroupJobs)));
29
30
 
@@ -46,7 +47,7 @@ describe('HostgroupJobs', () => {
46
47
  .map(element => expect(element).toBeInTheDocument());
47
48
  expect(screen.getByText('Scheduled recurring jobs')).toBeInTheDocument();
48
49
  expect(screen.getByText('Previously executed jobs')).toBeInTheDocument();
49
- expect(screen.getByText('54 10 15 * *')).toBeInTheDocument();
50
+ expect(screen.getByText(readableCron('54 10 15 * *'))).toBeInTheDocument();
50
51
  });
51
52
  it('should show empty state', async () => {
52
53
  render(
@@ -103,7 +104,9 @@ describe('HostgroupJobs', () => {
103
104
  type: 'success',
104
105
  message: 'Ansible job was successfully created.',
105
106
  });
106
- expect(screen.getByText(toCron(futureDate, 'weekly'))).toBeInTheDocument();
107
+ expect(
108
+ screen.getByText(readableCron(toCron(futureDate, 'weekly')))
109
+ ).toBeInTheDocument();
107
110
  expect(screen.getByText('in 3 days')).toBeInTheDocument();
108
111
  expect(
109
112
  screen.queryByText('No config job for Ansible roles scheduled')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_ansible
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Lobato Garcia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-07 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts_as_list
@@ -101,6 +101,8 @@ files:
101
101
  - app/graphql/mutations/hosts/assign_ansible_roles.rb
102
102
  - app/graphql/presenters/ansible_role_presenter.rb
103
103
  - app/graphql/presenters/overriden_ansible_variable_presenter.rb
104
+ - app/graphql/resolvers/ansible_role/path.rb
105
+ - app/graphql/resolvers/ansible_variable/path.rb
104
106
  - app/graphql/types/ansible_role.rb
105
107
  - app/graphql/types/ansible_variable.rb
106
108
  - app/graphql/types/ansible_variable_override.rb
@@ -325,9 +327,11 @@ files:
325
327
  - webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js
326
328
  - webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss
327
329
  - webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js
330
+ - webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js.orig
328
331
  - webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js
329
332
  - webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js
330
333
  - webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js
334
+ - webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTabHelper.test.js
331
335
  - webpack/components/AnsibleHostDetail/components/JobsTab/index.js
332
336
  - webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js
333
337
  - webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js
@@ -445,50 +449,50 @@ required_rubygems_version: !ruby/object:Gem::Requirement
445
449
  - !ruby/object:Gem::Version
446
450
  version: '0'
447
451
  requirements: []
448
- rubygems_version: 3.1.6
452
+ rubygems_version: 3.3.7
449
453
  signing_key:
450
454
  specification_version: 4
451
455
  summary: Ansible integration with Foreman (theforeman.org)
452
456
  test_files:
453
- - test/functional/ansible_variables_controller_test.rb
454
- - test/functional/ansible_roles_controller_test.rb
455
- - test/functional/api/v2/ansible_variables_controller_test.rb
457
+ - test/functional/api/v2/hosts_controller_test.rb
456
458
  - test/functional/api/v2/ansible_roles_controller_test.rb
457
459
  - test/functional/api/v2/hostgroups_controller_test.rb
460
+ - test/functional/api/v2/ansible_variables_controller_test.rb
458
461
  - test/functional/api/v2/ansible_inventories_controller_test.rb
459
- - test/functional/api/v2/hosts_controller_test.rb
460
- - test/functional/ui_ansible_roles_controller_test.rb
461
462
  - test/functional/hosts_controller_test.rb
462
- - test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb
463
- - test/graphql/queries/ansible_roles_query_test.rb
464
- - test/foreman_ansible/helpers/ansible_roles_helper_test.rb
465
- - test/unit/actions/run_ansible_job_test.rb
466
- - test/unit/actions/run_proxy_ansible_command_test.rb
467
- - test/unit/services/api_roles_importer_test.rb
468
- - test/unit/services/ui_roles_importer_test.rb
463
+ - test/functional/ansible_roles_controller_test.rb
464
+ - test/functional/ansible_variables_controller_test.rb
465
+ - test/functional/ui_ansible_roles_controller_test.rb
466
+ - test/unit/services/inventory_creator_test.rb
469
467
  - test/unit/services/ansible_report_importer_test.rb
470
- - test/unit/services/insights_plan_runner_test.rb
471
- - test/unit/services/override_resolver_test.rb
468
+ - test/unit/services/ui_roles_importer_test.rb
472
469
  - test/unit/services/roles_importer_test.rb
473
- - test/unit/services/inventory_creator_test.rb
474
470
  - test/unit/services/ansible_variables_importer_test.rb
475
- - test/unit/lib/proxy_api/ansible_test.rb
471
+ - test/unit/services/override_resolver_test.rb
472
+ - test/unit/services/api_roles_importer_test.rb
473
+ - test/unit/services/insights_plan_runner_test.rb
476
474
  - test/unit/ansible_role_test.rb
477
- - test/unit/hostgroup_ansible_role_test.rb
478
- - test/unit/ansible_variable_test.rb
479
475
  - test/unit/host_ansible_role_test.rb
476
+ - test/unit/hostgroup_ansible_role_test.rb
480
477
  - test/unit/helpers/ansible_reports_helper_test.rb
481
478
  - test/unit/ansible_provider_test.rb
482
- - test/unit/ignore_roles_test.rb
483
- - test/unit/import_roles_and_variables.rb
484
- - test/unit/concerns/config_reports_extensions_test.rb
485
479
  - test/unit/concerns/host_managed_extensions_test.rb
480
+ - test/unit/concerns/config_reports_extensions_test.rb
486
481
  - test/unit/concerns/hostgroup_extensions_test.rb
482
+ - test/unit/ignore_roles_test.rb
483
+ - test/unit/ansible_variable_test.rb
484
+ - test/unit/import_roles_and_variables.rb
485
+ - test/unit/lib/proxy_api/ansible_test.rb
486
+ - test/unit/actions/run_ansible_job_test.rb
487
+ - test/unit/actions/run_proxy_ansible_command_test.rb
488
+ - test/foreman_ansible/helpers/ansible_roles_helper_test.rb
489
+ - test/test_plugin_helper.rb
487
490
  - test/factories/ansible_proxy.rb
488
- - test/factories/host_ansible_enhancements.rb
489
491
  - test/factories/ansible_variables.rb
492
+ - test/factories/host_ansible_enhancements.rb
490
493
  - test/factories/ansible_roles.rb
491
494
  - test/fixtures/report.json
492
495
  - test/fixtures/sample_facts.json
493
496
  - test/fixtures/insights_playbook.yaml
494
- - test/test_plugin_helper.rb
497
+ - test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb
498
+ - test/graphql/queries/ansible_roles_query_test.rb