foreman_remote_execution 16.2.1 → 16.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +10 -10
  3. data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +9 -9
  4. data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +10 -10
  5. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +28 -28
  6. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +32 -32
  7. data/app/assets/javascripts/foreman_remote_execution/locale/ka/foreman_remote_execution.js +10 -10
  8. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +27 -27
  9. data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +9 -9
  10. data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +9 -9
  11. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +27 -27
  12. data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +9 -9
  13. data/lib/foreman_remote_execution/version.rb +1 -1
  14. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  15. data/locale/de/foreman_remote_execution.po +10 -10
  16. data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  17. data/locale/en_GB/foreman_remote_execution.po +9 -9
  18. data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  19. data/locale/es/foreman_remote_execution.po +10 -10
  20. data/locale/foreman_remote_execution.pot +66 -66
  21. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  22. data/locale/fr/foreman_remote_execution.po +28 -28
  23. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  24. data/locale/ja/foreman_remote_execution.po +32 -32
  25. data/locale/ka/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  26. data/locale/ka/foreman_remote_execution.po +10 -10
  27. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  28. data/locale/ko/foreman_remote_execution.po +27 -27
  29. data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  30. data/locale/pt_BR/foreman_remote_execution.po +9 -9
  31. data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  32. data/locale/ru/foreman_remote_execution.po +9 -9
  33. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  34. data/locale/zh_CN/foreman_remote_execution.po +27 -27
  35. data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  36. data/locale/zh_TW/foreman_remote_execution.po +9 -9
  37. data/webpack/JobInvocationDetail/CheckboxesActions.js +39 -30
  38. data/webpack/JobInvocationDetail/JobInvocationConstants.js +0 -1
  39. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +156 -170
  40. data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +27 -32
  41. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +10 -2
  42. data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +46 -30
  43. data/webpack/JobInvocationDetail/index.js +13 -2
  44. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +5 -19
  45. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +19 -3
  46. metadata +2 -3
  47. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +0 -36
@@ -7,10 +7,6 @@ import {
7
7
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
8
8
  import PropTypes from 'prop-types';
9
9
  import React from 'react';
10
- import {
11
- templateInvocationPageUrl,
12
- MAX_HOSTS_API_SIZE,
13
- } from './JobInvocationConstants';
14
10
 
15
11
  export const PopupAlert = ({ setShowAlert }) => (
16
12
  <Alert
@@ -25,21 +21,35 @@ export const PopupAlert = ({ setShowAlert }) => (
25
21
 
26
22
  const OpenAllInvocationsModal = ({
27
23
  failedCount,
28
- failedHosts,
29
24
  isOpen,
30
25
  isOpenFailed,
31
- jobID,
32
26
  onClose,
33
- setShowAlert,
34
27
  selectedIds,
28
+ confirmCallback,
35
29
  }) => {
36
- const modalText = isOpenFailed ? 'failed' : 'selected';
30
+ const modalText = () => {
31
+ if (isOpenFailed) return 'failed';
32
+ if (selectedIds.length > 0) return 'selected';
33
+ return 'current page';
34
+ };
37
35
 
38
- const openLink = url => {
39
- const newWin = window.open(url);
40
- if (!newWin || newWin.closed || typeof newWin.closed === 'undefined') {
41
- setShowAlert(true);
36
+ const selectedText = () => {
37
+ if (isOpenFailed) {
38
+ return (
39
+ <>
40
+ {__('The number of failed invocations is:')} <b>{failedCount}</b>
41
+ </>
42
+ );
42
43
  }
44
+ if (selectedIds.length > 0) {
45
+ return (
46
+ <>
47
+ {__('The number of selected invocations is:')}{' '}
48
+ <b>{selectedIds.length}</b>
49
+ </>
50
+ );
51
+ }
52
+ return <></>;
43
53
  };
44
54
 
45
55
  return (
@@ -48,7 +58,7 @@ const OpenAllInvocationsModal = ({
48
58
  isOpen={isOpen}
49
59
  onClose={onClose}
50
60
  ouiaId="template-invocation-new-tab-modal"
51
- title={sprintf(__('Open all %s invocations in new tabs'), modalText)}
61
+ title={sprintf(__('Open all %s invocations in new tabs'), modalText())}
52
62
  titleIconVariant="warning"
53
63
  width={590}
54
64
  actions={[
@@ -57,16 +67,7 @@ const OpenAllInvocationsModal = ({
57
67
  key="confirm"
58
68
  variant="primary"
59
69
  onClick={() => {
60
- const hostsToOpen = isOpenFailed
61
- ? failedHosts
62
- : selectedIds.map(id => ({ id }));
63
-
64
- hostsToOpen
65
- .slice(0, MAX_HOSTS_API_SIZE)
66
- .forEach(({ id }) =>
67
- openLink(templateInvocationPageUrl(id, jobID), '_blank')
68
- );
69
-
70
+ confirmCallback();
70
71
  onClose();
71
72
  }}
72
73
  >
@@ -84,30 +85,24 @@ const OpenAllInvocationsModal = ({
84
85
  >
85
86
  {sprintf(
86
87
  __('Are you sure you want to open all %s invocations in new tabs?'),
87
- modalText
88
+ modalText()
88
89
  )}
89
90
  <br />
90
- {__('This will open a new tab for each invocation. The maximum is 100.')}
91
- <br />
92
- {sprintf(__('The number of %s invocations is:'), modalText)}{' '}
93
- <b>{isOpenFailed ? failedCount : selectedIds.length}</b>
91
+ {selectedText()}
94
92
  </Modal>
95
93
  );
96
94
  };
97
95
 
98
96
  OpenAllInvocationsModal.propTypes = {
99
97
  failedCount: PropTypes.number.isRequired,
100
- failedHosts: PropTypes.array,
101
98
  isOpen: PropTypes.bool.isRequired,
102
99
  isOpenFailed: PropTypes.bool,
103
- jobID: PropTypes.string.isRequired,
104
100
  onClose: PropTypes.func.isRequired,
105
- setShowAlert: PropTypes.func.isRequired,
106
101
  selectedIds: PropTypes.array.isRequired,
102
+ confirmCallback: PropTypes.func.isRequired,
107
103
  };
108
104
 
109
105
  OpenAllInvocationsModal.defaultProps = {
110
- failedHosts: [],
111
106
  isOpenFailed: false,
112
107
  };
113
108
 
@@ -83,6 +83,7 @@ jest.mock('../JobInvocationHostTable.js', () => () => (
83
83
  const reportTemplateJobId = mockReportTemplatesResponse.results[0].id;
84
84
 
85
85
  const mockStore = configureMockStore([thunk]);
86
+ const props = { history: { push: jest.fn() } };
86
87
 
87
88
  describe('JobInvocationDetailPage', () => {
88
89
  it('renders main information', async () => {
@@ -91,7 +92,10 @@ describe('JobInvocationDetailPage', () => {
91
92
 
92
93
  const { container } = render(
93
94
  <Provider store={store}>
94
- <JobInvocationDetailPage match={{ params: { id: `${jobId}` } }} />
95
+ <JobInvocationDetailPage
96
+ match={{ params: { id: `${jobId}` } }}
97
+ {...props}
98
+ />
95
99
  </Provider>
96
100
  );
97
101
 
@@ -185,6 +189,7 @@ describe('JobInvocationDetailPage', () => {
185
189
  <Provider store={store}>
186
190
  <JobInvocationDetailPage
187
191
  match={{ params: { id: `${jobInvocationDataScheduled.id}` } }}
192
+ {...props}
188
193
  />
189
194
  </Provider>
190
195
  );
@@ -201,7 +206,10 @@ describe('JobInvocationDetailPage', () => {
201
206
  const store = mockStore(jobInvocationDataRecurring);
202
207
  render(
203
208
  <Provider store={store}>
204
- <JobInvocationDetailPage match={{ params: { id: `${jobId}` } }} />
209
+ <JobInvocationDetailPage
210
+ match={{ params: { id: `${jobId}` } }}
211
+ {...props}
212
+ />
205
213
  </Provider>
206
214
  );
207
215
 
@@ -32,6 +32,29 @@ selectors.selectTaskCancelable.mockImplementation(() => true);
32
32
  const mockStore = configureStore([]);
33
33
  const store = mockStore({});
34
34
 
35
+ const allJobs = [
36
+ {
37
+ id: 1,
38
+ job_status: 'error',
39
+ },
40
+ {
41
+ id: 2,
42
+ job_status: 'error',
43
+ },
44
+ {
45
+ id: 3,
46
+ job_status: 'error',
47
+ },
48
+ {
49
+ id: 4,
50
+ job_status: 'error',
51
+ },
52
+ {
53
+ id: 5,
54
+ job_status: 'error',
55
+ },
56
+ ];
57
+
35
58
  describe('TableToolbarActions', () => {
36
59
  const jobID = '42';
37
60
  let openSpy;
@@ -58,8 +81,9 @@ describe('TableToolbarActions', () => {
58
81
  <Provider store={store}>
59
82
  <CheckboxesActions
60
83
  selectedIds={selectedIds}
61
- failedCount={0}
62
84
  jobID={jobID}
85
+ allJobs={allJobs}
86
+ setShowAlert={jest.fn()}
63
87
  />
64
88
  </Provider>
65
89
  );
@@ -76,8 +100,9 @@ describe('TableToolbarActions', () => {
76
100
  <Provider store={store}>
77
101
  <CheckboxesActions
78
102
  selectedIds={selectedIds}
79
- failedCount={0}
80
103
  jobID={jobID}
104
+ allJobs={allJobs}
105
+ setShowAlert={jest.fn()}
81
106
  />
82
107
  </Provider>
83
108
  );
@@ -94,47 +119,52 @@ describe('TableToolbarActions', () => {
94
119
  test('shows alert when popups are blocked', async () => {
95
120
  openSpy.mockReturnValue(null);
96
121
  const selectedIds = [1, 2];
122
+ const setShowMock = jest.fn();
97
123
  render(
98
124
  <Provider store={store}>
99
125
  <CheckboxesActions
100
126
  selectedIds={selectedIds}
101
- failedCount={0}
102
127
  jobID={jobID}
128
+ allJobs={allJobs}
129
+ setShowAlert={setShowMock}
103
130
  />
104
131
  </Provider>
105
132
  );
106
133
  fireEvent.click(
107
134
  screen.getByLabelText(/open all template invocations in new tab/i)
108
135
  );
109
- expect(
110
- await screen.findByText(/Popups are blocked by your browser/)
111
- ).toBeInTheDocument();
136
+ expect(setShowMock).toHaveBeenCalledWith(true);
112
137
  });
113
138
  });
114
139
 
115
140
  describe('Opening failed in new tabs', () => {
116
141
  test('opens links when results length is less than or equal to 3', async () => {
117
- const failedHosts = [{ id: 301 }, { id: 302 }];
118
- useAPI.mockReturnValue({
119
- response: { results: failedHosts },
120
- status: 'success',
121
- });
122
142
  render(
123
143
  <Provider store={store}>
124
- <CheckboxesActions selectedIds={[]} failedCount={2} jobID={jobID} />
144
+ <CheckboxesActions
145
+ selectedIds={[]}
146
+ jobID={jobID}
147
+ allJobs={allJobs.slice(0, 2)}
148
+ setShowAlert={jest.fn()}
149
+ />
125
150
  </Provider>
126
151
  );
127
152
  fireEvent.click(screen.getByLabelText(/actions dropdown toggle/i));
128
153
  fireEvent.click(await screen.findByText(/open all failed runs/i));
129
154
  await waitFor(() => {
130
- expect(openSpy).toHaveBeenCalledTimes(failedHosts.length);
155
+ expect(openSpy).toHaveBeenCalledTimes(2);
131
156
  });
132
157
  });
133
158
 
134
159
  test('shows modal when results length is greater than 3', async () => {
135
160
  render(
136
161
  <Provider store={store}>
137
- <CheckboxesActions selectedIds={[]} failedCount={4} jobID={jobID} />
162
+ <CheckboxesActions
163
+ selectedIds={[]}
164
+ jobID={jobID}
165
+ allJobs={allJobs}
166
+ setShowAlert={jest.fn()}
167
+ />
138
168
  </Provider>
139
169
  );
140
170
  fireEvent.click(screen.getByLabelText(/actions dropdown toggle/i));
@@ -145,21 +175,6 @@ describe('TableToolbarActions', () => {
145
175
  })
146
176
  ).toBeInTheDocument();
147
177
  });
148
-
149
- test('calls useApi with skip: true when failedCount is 0', () => {
150
- render(
151
- <Provider store={store}>
152
- <CheckboxesActions selectedIds={[]} failedCount={0} jobID={jobID} />
153
- </Provider>
154
- );
155
- expect(useAPI).toHaveBeenCalledWith(
156
- 'get',
157
- foremanUrl(`/api/job_invocations/${jobID}/hosts`),
158
- expect.objectContaining({
159
- skip: true,
160
- })
161
- );
162
- });
163
178
  });
164
179
 
165
180
  describe('PopupAlert', () => {
@@ -188,8 +203,9 @@ describe('TableToolbarActions', () => {
188
203
  <CheckboxesActions
189
204
  bulkParams="(id ^ (101, 102, 103))"
190
205
  selectedIds={selectedIds}
191
- failedCount={1}
192
206
  jobID={jobID}
207
+ allJobs={allJobs}
208
+ setShowAlert={jest.fn()}
193
209
  />
194
210
  </Provider>
195
211
  );
@@ -35,12 +35,12 @@ const JobInvocationDetailPage = ({
35
35
  match: {
36
36
  params: { id },
37
37
  },
38
+ history,
38
39
  }) => {
39
40
  const dispatch = useDispatch();
40
41
  const items = useSelector(selectItems);
41
42
  const {
42
43
  description,
43
- failed = 0,
44
44
  status_label: statusLabel,
45
45
  task,
46
46
  start_at: startAt,
@@ -95,6 +95,16 @@ const JobInvocationDetailPage = ({
95
95
  : STATUS_UPPERCASE.RESOLVED;
96
96
 
97
97
  const breadcrumbOptions = {
98
+ isSwitchable: true,
99
+ onSwitcherItemClick: (e, href) => {
100
+ e.preventDefault();
101
+ history.push(href);
102
+ },
103
+ resource: {
104
+ nameField: 'description',
105
+ resourceUrl: '/api/v2/job_invocations',
106
+ switcherItemUrl: '/job_invocations/:id',
107
+ },
98
108
  breadcrumbItems: [
99
109
  { caption: __('Jobs'), url: `/job_invocations` },
100
110
  {
@@ -182,7 +192,7 @@ const JobInvocationDetailPage = ({
182
192
  <JobInvocationHostTable
183
193
  id={id}
184
194
  targeting={targeting}
185
- failedCount={failed}
195
+ finished={finished}
186
196
  autoRefresh={autoRefresh}
187
197
  initialFilter={selectedFilter}
188
198
  statusLabel={statusLabel}
@@ -200,6 +210,7 @@ JobInvocationDetailPage.propTypes = {
200
210
  id: PropTypes.string.isRequired,
201
211
  }),
202
212
  }).isRequired,
213
+ history: PropTypes.object.isRequired,
203
214
  };
204
215
 
205
216
  export default JobInvocationDetailPage;
@@ -2,32 +2,18 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
+ import LabelIcon from 'foremanReact/components/common/LabelIcon';
5
6
 
6
- import { FormGroup, TextInput, Popover, Icon } from '@patternfly/react-core';
7
-
8
- import { HelpIcon } from '@patternfly/react-icons';
7
+ import { FormGroup, TextInput } from '@patternfly/react-core';
9
8
 
10
9
  const RexInterface = ({ isLoading, onChange }) => (
11
10
  <FormGroup
12
11
  label={__('Remote Execution Interface')}
13
12
  fieldId="reg_rex_interface"
14
13
  labelIcon={
15
- <Popover
16
- bodyContent={
17
- <div>
18
- {__('Identifier of the Host interface for Remote execution')}
19
- </div>
20
- }
21
- >
22
- <button
23
- className="pf-v5-cform__group-label-help"
24
- onClick={e => e.preventDefault()}
25
- >
26
- <Icon isInline>
27
- <HelpIcon />
28
- </Icon>
29
- </button>
30
- </Popover>
14
+ <LabelIcon
15
+ text={__('Identifier of the Host interface for Remote execution')}
16
+ />
31
17
  }
32
18
  >
33
19
  <TextInput
@@ -1,9 +1,25 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
1
+ import React from 'react';
2
+ import { screen, fireEvent, render, act } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/extend-expect';
2
4
  import RexInterface from '../RexInterface';
3
5
 
4
6
  const fixtures = {
5
7
  renders: { isLoading: false, onChange: () => {} },
6
8
  };
7
9
 
8
- describe('RexInterface', () =>
9
- testComponentSnapshotsWithFixtures(RexInterface, fixtures));
10
+ describe('RexInterface', () => {
11
+ it('should render label with help icon and popover instructions', async () => {
12
+ jest.useFakeTimers();
13
+ render(<RexInterface {...fixtures.renders} />);
14
+ await act(async () => {
15
+ await fireEvent.click(screen.getByRole('button'));
16
+
17
+ jest.advanceTimersByTime(500);
18
+ });
19
+
20
+ expect(screen.getByText('Remote Execution Interface')).toBeInTheDocument();
21
+ expect(
22
+ screen.getByText('Identifier of the Host interface for Remote execution')
23
+ ).toBeInTheDocument();
24
+ });
25
+ });
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.2.1
4
+ version: 16.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-16 00:00:00.000000000 Z
10
+ date: 2025-10-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: deface
@@ -551,7 +551,6 @@ files:
551
551
  - webpack/react_app/components/RegistrationExtension/RexInterface.js
552
552
  - webpack/react_app/components/RegistrationExtension/RexPull.js
553
553
  - webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js
554
- - webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap
555
554
  - webpack/react_app/components/TargetingHosts/TargetingHosts.js
556
555
  - webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js
557
556
  - webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js
@@ -1,36 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`RexInterface renders 1`] = `
4
- <FormGroup
5
- fieldId="reg_rex_interface"
6
- label="Remote Execution Interface"
7
- labelIcon={
8
- <Popover
9
- bodyContent={
10
- <div>
11
- Identifier of the Host interface for Remote execution
12
- </div>
13
- }
14
- >
15
- <button
16
- className="pf-v5-cform__group-label-help"
17
- onClick={[Function]}
18
- >
19
- <Icon
20
- isInline={true}
21
- >
22
- <HelpIcon />
23
- </Icon>
24
- </button>
25
- </Popover>
26
- }
27
- >
28
- <TextInput
29
- id="reg_rex_interface_input"
30
- isDisabled={false}
31
- onBlur={[Function]}
32
- ouiaId="reg_rex_interface_input"
33
- type="text"
34
- />
35
- </FormGroup>
36
- `;