foreman_remote_execution 13.1.1 → 13.2.1

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
  3. data/app/models/job_invocation.rb +23 -0
  4. data/app/models/job_invocation_composer.rb +2 -0
  5. data/app/views/api/v2/job_invocations/base.json.rabl +3 -2
  6. data/app/views/templates/script/package_action.erb +18 -10
  7. data/lib/foreman_remote_execution/version.rb +1 -1
  8. data/package.json +2 -1
  9. data/webpack/JobInvocationDetail/JobInvocationActions.js +134 -3
  10. data/webpack/JobInvocationDetail/JobInvocationConstants.js +14 -0
  11. data/webpack/JobInvocationDetail/JobInvocationDetail.scss +5 -2
  12. data/webpack/JobInvocationDetail/JobInvocationSelectors.js +5 -0
  13. data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +13 -9
  14. data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +268 -0
  15. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +259 -0
  16. data/webpack/JobInvocationDetail/__tests__/fixtures.js +117 -0
  17. data/webpack/JobInvocationDetail/index.js +58 -38
  18. data/webpack/JobWizard/JobWizard.scss +14 -5
  19. data/webpack/JobWizard/JobWizardPageRerun.js +15 -10
  20. data/webpack/JobWizard/steps/HostsAndInputs/index.js +4 -1
  21. data/webpack/__mocks__/foremanReact/components/BreadcrumbBar/index.js +4 -0
  22. data/webpack/__mocks__/foremanReact/components/Head/index.js +10 -0
  23. data/webpack/__mocks__/foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState.js +8 -0
  24. data/webpack/__mocks__/foremanReact/components/ToastsList/index.js +3 -0
  25. data/webpack/__mocks__/foremanReact/redux/API/APIActions.js +21 -0
  26. data/webpack/__mocks__/foremanReact/redux/API/APIConstants.js +7 -0
  27. data/webpack/__mocks__/foremanReact/redux/API/index.js +14 -0
  28. data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/index.js +9 -0
  29. metadata +12 -2
@@ -0,0 +1,259 @@
1
+ import React from 'react';
2
+ import configureMockStore from 'redux-mock-store';
3
+ import { fireEvent, render, screen, act } from '@testing-library/react';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import { Provider } from 'react-redux';
6
+ import thunk from 'redux-thunk';
7
+ import { foremanUrl } from 'foremanReact/common/helpers';
8
+ import * as api from 'foremanReact/redux/API';
9
+ import JobInvocationDetailPage from '../index';
10
+ import {
11
+ jobInvocationData,
12
+ jobInvocationDataScheduled,
13
+ jobInvocationDataRecurring,
14
+ mockPermissionsData,
15
+ mockReportTemplatesResponse,
16
+ mockReportTemplateInputsResponse,
17
+ } from './fixtures';
18
+ import {
19
+ cancelJob,
20
+ enableRecurringLogic,
21
+ cancelRecurringLogic,
22
+ } from '../JobInvocationActions';
23
+ import {
24
+ CANCEL_JOB,
25
+ CANCEL_RECURRING_LOGIC,
26
+ CHANGE_ENABLED_RECURRING_LOGIC,
27
+ GET_REPORT_TEMPLATES,
28
+ GET_REPORT_TEMPLATE_INPUTS,
29
+ JOB_INVOCATION_KEY,
30
+ } from '../JobInvocationConstants';
31
+
32
+ jest.spyOn(api, 'get');
33
+
34
+ jest.mock('foremanReact/common/hooks/API/APIHooks', () => ({
35
+ useAPI: jest.fn(() => ({
36
+ response: mockPermissionsData,
37
+ })),
38
+ }));
39
+
40
+ jest.mock('foremanReact/routes/common/PageLayout/PageLayout', () =>
41
+ jest.fn(props => (
42
+ <div>
43
+ {props.header && <h1>{props.header}</h1>}
44
+ {props.toolbarButtons && <div>{props.toolbarButtons}</div>}
45
+ {props.children}
46
+ </div>
47
+ ))
48
+ );
49
+
50
+ const initialState = {
51
+ JOB_INVOCATION_KEY: {
52
+ response: jobInvocationData,
53
+ },
54
+ GET_REPORT_TEMPLATES: mockReportTemplatesResponse,
55
+ };
56
+
57
+ const initialStateScheduled = {
58
+ JOB_INVOCATION_KEY: {
59
+ response: jobInvocationDataScheduled,
60
+ },
61
+ };
62
+
63
+ api.get.mockImplementation(({ handleSuccess, ...action }) => {
64
+ if (action.key === 'GET_REPORT_TEMPLATES') {
65
+ handleSuccess &&
66
+ handleSuccess({
67
+ data: mockReportTemplatesResponse,
68
+ });
69
+ } else if (action.key === 'GET_REPORT_TEMPLATE_INPUTS') {
70
+ handleSuccess &&
71
+ handleSuccess({
72
+ data: mockReportTemplateInputsResponse,
73
+ });
74
+ }
75
+
76
+ return { type: 'get', ...action };
77
+ });
78
+
79
+ const reportTemplateJobId = mockReportTemplatesResponse.results[0].id;
80
+
81
+ const mockStore = configureMockStore([thunk]);
82
+
83
+ describe('JobInvocationDetailPage', () => {
84
+ it('renders main information', async () => {
85
+ const jobId = jobInvocationData.id;
86
+ const store = mockStore(initialState);
87
+
88
+ const { container } = render(
89
+ <Provider store={store}>
90
+ <JobInvocationDetailPage match={{ params: { id: `${jobId}` } }} />
91
+ </Provider>
92
+ );
93
+
94
+ expect(screen.getByText('Description')).toBeInTheDocument();
95
+ expect(
96
+ container.querySelector('.chart-donut .pf-c-chart')
97
+ ).toBeInTheDocument();
98
+ expect(screen.getByText('2/6')).toBeInTheDocument();
99
+ expect(screen.getByText('Systems')).toBeInTheDocument();
100
+ expect(screen.getByText('System status')).toBeInTheDocument();
101
+ expect(screen.getByText('Succeeded: 2')).toBeInTheDocument();
102
+ expect(screen.getByText('Failed: 4')).toBeInTheDocument();
103
+ expect(screen.getByText('In Progress: 0')).toBeInTheDocument();
104
+ expect(screen.getByText('Canceled: 0')).toBeInTheDocument();
105
+
106
+ const informationToCheck = {
107
+ 'Effective user:': jobInvocationData.effective_user,
108
+ 'Started at:': 'Jan 1, 2024, 11:34 UTC',
109
+ 'SSH user:': 'Not available',
110
+ 'Template:': jobInvocationData.template_name,
111
+ };
112
+
113
+ Object.entries(informationToCheck).forEach(([term, expectedText]) => {
114
+ const termContainers = container.querySelectorAll(
115
+ '.pf-c-description-list__term .pf-c-description-list__text'
116
+ );
117
+ termContainers.forEach(termContainer => {
118
+ if (termContainer.textContent.includes(term)) {
119
+ let descriptionContainer;
120
+ if (term === 'SSH user:') {
121
+ descriptionContainer = termContainer
122
+ .closest('.pf-c-description-list__group')
123
+ .querySelector(
124
+ '.pf-c-description-list__description .pf-c-description-list__text .disabled-text'
125
+ );
126
+ } else {
127
+ descriptionContainer = termContainer
128
+ .closest('.pf-c-description-list__group')
129
+ .querySelector(
130
+ '.pf-c-description-list__description .pf-c-description-list__text'
131
+ );
132
+ }
133
+ expect(descriptionContainer.textContent).toContain(expectedText);
134
+ }
135
+ });
136
+ });
137
+
138
+ // checks the global actions and if they link to the correct url
139
+ expect(screen.getByText('Create report').getAttribute('href')).toEqual(
140
+ foremanUrl(
141
+ `/templates/report_templates/${mockReportTemplatesResponse.results[0].id}/generate?report_template_report%5Binput_values%5D%5B${mockReportTemplateInputsResponse.results[0].id}%5D%5Bvalue%5D=${jobId}`
142
+ )
143
+ );
144
+ expect(screen.getByText('Rerun all').getAttribute('href')).toEqual(
145
+ foremanUrl(`/job_invocations/${jobId}/rerun`)
146
+ );
147
+ act(() => {
148
+ fireEvent.click(screen.getByRole('button', { name: 'Select' }));
149
+ });
150
+ expect(
151
+ screen
152
+ .getByText('Rerun successful')
153
+ .closest('a')
154
+ .getAttribute('href')
155
+ ).toEqual(foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`));
156
+ expect(
157
+ screen
158
+ .getByText('Rerun failed')
159
+ .closest('a')
160
+ .getAttribute('href')
161
+ ).toEqual(foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`));
162
+ expect(
163
+ screen
164
+ .getByText('View task')
165
+ .closest('a')
166
+ .getAttribute('href')
167
+ ).toEqual(foremanUrl(`/foreman_tasks/tasks/${jobInvocationData.task.id}`));
168
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
169
+ expect(screen.getByText('Abort')).toBeInTheDocument();
170
+ expect(screen.queryByText('Enable recurring')).not.toBeInTheDocument();
171
+ expect(screen.queryByText('Cancel recurring')).not.toBeInTheDocument();
172
+ expect(
173
+ screen
174
+ .getByText('Legacy UI')
175
+ .closest('a')
176
+ .getAttribute('href')
177
+ ).toEqual(`/job_invocations/${jobId}`);
178
+ });
179
+
180
+ it('shows scheduled date', async () => {
181
+ const store = mockStore(initialStateScheduled);
182
+ render(
183
+ <Provider store={store}>
184
+ <JobInvocationDetailPage
185
+ match={{ params: { id: `${jobInvocationDataScheduled.id}` } }}
186
+ />
187
+ </Provider>
188
+ );
189
+
190
+ expect(screen.getByText('Scheduled at:')).toBeInTheDocument();
191
+ expect(screen.getByText('Jan 1, 3000, 11:34 UTC')).toBeInTheDocument();
192
+ expect(screen.getByText('Not yet')).toBeInTheDocument();
193
+ });
194
+
195
+ it('should dispatch global actions', async () => {
196
+ // recurring in the future
197
+ const jobId = jobInvocationDataRecurring.id;
198
+ const recurrenceId = jobInvocationDataRecurring.recurrence.id;
199
+ const store = mockStore(jobInvocationDataRecurring);
200
+ render(
201
+ <Provider store={store}>
202
+ <JobInvocationDetailPage match={{ params: { id: `${jobId}` } }} />
203
+ </Provider>
204
+ );
205
+
206
+ const expectedActions = [
207
+ { key: GET_REPORT_TEMPLATES, url: '/api/report_templates' },
208
+ {
209
+ key: JOB_INVOCATION_KEY,
210
+ url: `/api/job_invocations/${jobId}`,
211
+ },
212
+ {
213
+ key: GET_REPORT_TEMPLATE_INPUTS,
214
+ url: `/api/templates/${reportTemplateJobId}/template_inputs`,
215
+ },
216
+ {
217
+ key: CANCEL_JOB,
218
+ url: `/job_invocations/${jobId}/cancel`,
219
+ },
220
+ {
221
+ key: CANCEL_JOB,
222
+ url: `/job_invocations/${jobId}/cancel?force=true`,
223
+ },
224
+ {
225
+ key: CHANGE_ENABLED_RECURRING_LOGIC,
226
+ url: `/foreman_tasks/api/recurring_logics/${recurrenceId}`,
227
+ },
228
+ {
229
+ key: CHANGE_ENABLED_RECURRING_LOGIC,
230
+ url: `/foreman_tasks/api/recurring_logics/${recurrenceId}`,
231
+ },
232
+ {
233
+ key: CANCEL_RECURRING_LOGIC,
234
+ url: `/foreman_tasks/recurring_logics/${recurrenceId}/cancel`,
235
+ },
236
+ ];
237
+
238
+ store.dispatch(cancelJob(jobId, false));
239
+ store.dispatch(cancelJob(jobId, true));
240
+ store.dispatch(enableRecurringLogic(recurrenceId, true, jobId));
241
+ store.dispatch(enableRecurringLogic(recurrenceId, false, jobId));
242
+ store.dispatch(cancelRecurringLogic(recurrenceId, jobId));
243
+
244
+ const actualActions = store.getActions();
245
+ expect(actualActions).toHaveLength(expectedActions.length);
246
+
247
+ expectedActions.forEach((expectedAction, index) => {
248
+ if (actualActions[index].type === 'WITH_INTERVAL') {
249
+ expect(actualActions[index].key.key).toEqual(expectedAction.key);
250
+ expect(actualActions[index].key.url).toEqual(expectedAction.url);
251
+ } else {
252
+ expect(actualActions[index].key).toEqual(expectedAction.key);
253
+ if (expectedAction.url) {
254
+ expect(actualActions[index].url).toEqual(expectedAction.url);
255
+ }
256
+ }
257
+ });
258
+ });
259
+ });
@@ -0,0 +1,117 @@
1
+ export const jobInvocationData = {
2
+ id: 123,
3
+ description: 'Description',
4
+ job_category: 'Commands',
5
+ targeting_id: 123,
6
+ status: 1,
7
+ start_at: '2024-01-01 12:34:56 +0100',
8
+ status_label: 'failed',
9
+ ssh_user: null,
10
+ time_to_pickup: null,
11
+ template_id: 321,
12
+ template_name: 'Run Command - Script Default',
13
+ effective_user: 'root',
14
+ succeeded: 2,
15
+ failed: 4,
16
+ pending: 0,
17
+ cancelled: 0,
18
+ total: 6,
19
+ missing: 5,
20
+ total_hosts: 6,
21
+ task: {
22
+ id: '37ad5ead-51de-4798-bc73-a17687c4d5aa',
23
+ state: 'stopped',
24
+ },
25
+ template_invocations: [
26
+ {
27
+ template_id: 321,
28
+ template_name: 'Run Command - Script Default',
29
+ host_id: 1,
30
+ template_invocation_input_values: [
31
+ {
32
+ template_input_name: 'command',
33
+ template_input_id: 59,
34
+ value:
35
+ 'echo start; for i in $(seq 1 120); do echo $i; sleep 1; done; echo done',
36
+ },
37
+ ],
38
+ },
39
+ ],
40
+ };
41
+
42
+ export const jobInvocationDataScheduled = {
43
+ id: 456,
44
+ description: 'Description',
45
+ job_category: 'Commands',
46
+ targeting_id: 456,
47
+ status: 1,
48
+ start_at: '3000-01-01 12:34:56 +0100',
49
+ status_label: 'failed',
50
+ ssh_user: null,
51
+ time_to_pickup: null,
52
+ template_id: 321,
53
+ template_name: 'Run Command - Script Default',
54
+ effective_user: 'root',
55
+ succeeded: 2,
56
+ failed: 4,
57
+ pending: 0,
58
+ cancelled: 0,
59
+ total: 6,
60
+ missing: 5,
61
+ total_hosts: 6,
62
+ };
63
+
64
+ export const jobInvocationDataRecurring = {
65
+ id: 789,
66
+ description: 'Description',
67
+ job_category: 'Commands',
68
+ targeting_id: 456,
69
+ status: 2,
70
+ start_at: '3000-01-01 12:00:00 +0100',
71
+ status_label: 'queued',
72
+ ssh_user: null,
73
+ time_to_pickup: null,
74
+ template_id: 321,
75
+ template_name: 'Run Command - Script Default',
76
+ effective_user: 'root',
77
+ succeeded: 0,
78
+ failed: 0,
79
+ pending: 0,
80
+ cancelled: 0,
81
+ total: 'N/A',
82
+ missing: 0,
83
+ total_hosts: 1,
84
+ task: {
85
+ id: '37ad5ead-51de-4798-bc73-a17687c4d5aa',
86
+ state: 'scheduled',
87
+ },
88
+ mode: 'recurring',
89
+ recurrence: {
90
+ id: 1,
91
+ cron_line: '00 12 * * *',
92
+ end_time: null,
93
+ iteration: 1,
94
+ task_group_id: 12,
95
+ state: 'active',
96
+ max_iteration: null,
97
+ purpose: null,
98
+ task_count: 1,
99
+ action: 'Run hosts job:',
100
+ last_occurence: null,
101
+ next_occurence: '3000-01-01 12:00:00 +0100',
102
+ },
103
+ };
104
+
105
+ export const mockPermissionsData = {
106
+ edit_job_templates: true,
107
+ view_foreman_tasks: true,
108
+ edit_recurring_logics: true,
109
+ };
110
+
111
+ export const mockReportTemplatesResponse = {
112
+ results: [{ id: '12', name: 'Job - Invocation Report' }],
113
+ };
114
+
115
+ export const mockReportTemplateInputsResponse = {
116
+ results: [{ id: '34', name: 'job_id' }],
117
+ };
@@ -1,20 +1,24 @@
1
- import React, { useEffect } from 'react';
2
- import { useSelector, useDispatch } from 'react-redux';
3
1
  import PropTypes from 'prop-types';
4
- import { Divider, PageSection, Flex } from '@patternfly/react-core';
2
+ import React, { useEffect } from 'react';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { Divider, Flex } from '@patternfly/react-core';
5
5
  import { translate as __, documentLocale } from 'foremanReact/common/I18n';
6
6
  import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
7
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
7
8
  import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
8
- import { getData } from './JobInvocationActions';
9
- import { selectItems } from './JobInvocationSelectors';
10
- import JobInvocationOverview from './JobInvocationOverview';
11
- import JobInvocationSystemStatusChart from './JobInvocationSystemStatusChart';
9
+ import { getData, getTask } from './JobInvocationActions';
12
10
  import {
11
+ CURRENT_PERMISSIONS,
12
+ DATE_OPTIONS,
13
13
  JOB_INVOCATION_KEY,
14
14
  STATUS,
15
- DATE_OPTIONS,
15
+ currentPermissionsUrl,
16
16
  } from './JobInvocationConstants';
17
17
  import './JobInvocationDetail.scss';
18
+ import JobInvocationOverview from './JobInvocationOverview';
19
+ import { selectItems } from './JobInvocationSelectors';
20
+ import JobInvocationSystemStatusChart from './JobInvocationSystemStatusChart';
21
+ import JobInvocationToolbarButtons from './JobInvocationToolbarButtons';
18
22
 
19
23
  const JobInvocationDetailPage = ({
20
24
  match: {
@@ -30,8 +34,15 @@ const JobInvocationDetailPage = ({
30
34
  start_at: startAt,
31
35
  } = items;
32
36
  const finished =
33
- statusLabel === STATUS.FAILED || statusLabel === STATUS.SUCCEEDED;
37
+ statusLabel === STATUS.FAILED ||
38
+ statusLabel === STATUS.SUCCEEDED ||
39
+ statusLabel === STATUS.CANCELLED;
34
40
  const autoRefresh = task?.state === STATUS.PENDING || false;
41
+ const { response, status } = useAPI(
42
+ 'get',
43
+ currentPermissionsUrl,
44
+ CURRENT_PERMISSIONS
45
+ );
35
46
 
36
47
  let isAlreadyStarted = false;
37
48
  let formattedStartDate;
@@ -56,6 +67,12 @@ const JobInvocationDetailPage = ({
56
67
  // eslint-disable-next-line react-hooks/exhaustive-deps
57
68
  }, [dispatch, id, finished, autoRefresh]);
58
69
 
70
+ useEffect(() => {
71
+ if (task?.id !== undefined) {
72
+ dispatch(getTask(`${task?.id}`));
73
+ }
74
+ }, [dispatch, task]);
75
+
59
76
  const breadcrumbOptions = {
60
77
  breadcrumbItems: [
61
78
  { caption: __('Jobs'), url: `/job_invocations` },
@@ -68,38 +85,41 @@ const JobInvocationDetailPage = ({
68
85
  <PageLayout
69
86
  header={description}
70
87
  breadcrumbOptions={breadcrumbOptions}
88
+ toolbarButtons={
89
+ <JobInvocationToolbarButtons
90
+ jobId={id}
91
+ data={items}
92
+ currentPermissions={response.results}
93
+ permissionsStatus={status}
94
+ />
95
+ }
71
96
  searchable={false}
72
97
  >
73
- <React.Fragment>
74
- <PageSection
75
- className="job-invocation-detail-page-section"
76
- isFilled
77
- variant="light"
98
+ <Flex
99
+ className="job-invocation-detail-flex"
100
+ alignItems={{ default: 'alignItemsFlexStart' }}
101
+ >
102
+ <JobInvocationSystemStatusChart
103
+ data={items}
104
+ isAlreadyStarted={isAlreadyStarted}
105
+ formattedStartDate={formattedStartDate}
106
+ />
107
+ <Divider
108
+ orientation={{
109
+ default: 'vertical',
110
+ }}
111
+ />
112
+ <Flex
113
+ className="job-overview"
114
+ alignItems={{ default: 'alignItemsCenter' }}
78
115
  >
79
- <Flex alignItems={{ default: 'alignItemsFlexStart' }}>
80
- <JobInvocationSystemStatusChart
81
- data={items}
82
- isAlreadyStarted={isAlreadyStarted}
83
- formattedStartDate={formattedStartDate}
84
- />
85
- <Divider
86
- orientation={{
87
- default: 'vertical',
88
- }}
89
- />
90
- <Flex
91
- className="job-overview"
92
- alignItems={{ default: 'alignItemsCenter' }}
93
- >
94
- <JobInvocationOverview
95
- data={items}
96
- isAlreadyStarted={isAlreadyStarted}
97
- formattedStartDate={formattedStartDate}
98
- />
99
- </Flex>
100
- </Flex>
101
- </PageSection>
102
- </React.Fragment>
116
+ <JobInvocationOverview
117
+ data={items}
118
+ isAlreadyStarted={isAlreadyStarted}
119
+ formattedStartDate={formattedStartDate}
120
+ />
121
+ </Flex>
122
+ </Flex>
103
123
  </PageLayout>
104
124
  );
105
125
  };
@@ -16,7 +16,17 @@
16
16
  ); // So the select box can be shown above the wizard footer and navigation toggle
17
17
  }
18
18
  .pf-c-wizard__main-body {
19
- max-width: 500px;
19
+ @media (max-width: 600px) {
20
+ max-width: 100%;
21
+ }
22
+
23
+ @media (min-width: 601px) and (max-width: 1300px) {
24
+ max-width: 65vw;
25
+ }
26
+ @media (min-width: 1301px) {
27
+ max-width: 50vw;
28
+ }
29
+
20
30
  .advanced-fields-title {
21
31
  margin-bottom: 10px;
22
32
  }
@@ -40,6 +50,9 @@
40
50
  flex-wrap: nowrap;
41
51
  }
42
52
  }
53
+ .foreman-search-field {
54
+ width: 100%;
55
+ }
43
56
  }
44
57
  input[type='radio'],
45
58
  input[type='checkbox'] {
@@ -110,10 +123,6 @@
110
123
  min-height: 40px;
111
124
  min-width: 100px;
112
125
  }
113
- #host-selection {
114
- width: 500px;
115
- }
116
-
117
126
  .pf-c-modal-box {
118
127
  width: auto;
119
128
  }
@@ -1,15 +1,15 @@
1
- import React from 'react';
2
1
  import PropTypes from 'prop-types';
2
+ import React from 'react';
3
3
  import URI from 'urijs';
4
- import { Alert, Divider, Skeleton, Button } from '@patternfly/react-core';
5
- import { sprintf, translate as __ } from 'foremanReact/common/I18n';
6
- import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
7
- import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
8
- import { STATUS } from 'foremanReact/constants';
4
+ import { Alert, Button, Divider, Skeleton } from '@patternfly/react-core';
9
5
  import {
10
- useForemanOrganization,
11
6
  useForemanLocation,
7
+ useForemanOrganization,
12
8
  } from 'foremanReact/Root/Context/ForemanContext';
9
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
10
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
11
+ import { STATUS } from 'foremanReact/constants';
12
+ import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
13
13
  import { JobWizard } from './JobWizard';
14
14
  import { JOB_API_KEY } from './JobWizardConstants';
15
15
 
@@ -21,11 +21,16 @@ const JobWizardPageRerun = ({
21
21
  }) => {
22
22
  const uri = new URI(search);
23
23
  const { failed_only: failedOnly } = uri.search(true);
24
+ const { succeeded_only: succeededOnly } = uri.search(true);
25
+ let queryParams = '';
26
+ if (failedOnly) {
27
+ queryParams = '&failed_only=1';
28
+ } else if (succeededOnly) {
29
+ queryParams = '&succeeded_only=1';
30
+ }
24
31
  const { response, status } = useAPI(
25
32
  'get',
26
- `/ui_job_wizard/job_invocation?id=${id}${
27
- failedOnly ? '&failed_only=1' : ''
28
- }`,
33
+ `/ui_job_wizard/job_invocation?id=${id}${queryParams}`,
29
34
  JOB_API_KEY
30
35
  );
31
36
  const title = __('Run job');
@@ -49,7 +49,10 @@ const HostsAndInputs = ({
49
49
  hostsSearchQuery,
50
50
  setHostsSearchQuery,
51
51
  }) => {
52
- const [hostMethod, setHostMethod] = useState(hostMethods.hosts);
52
+ const defaultHostMethod = hostsSearchQuery.length
53
+ ? hostMethods.searchQuery
54
+ : hostMethods.hosts;
55
+ const [hostMethod, setHostMethod] = useState(defaultHostMethod);
53
56
  const isLoading = useSelector(selectIsLoadingHosts);
54
57
  const templateInputs = useSelector(selectTemplateInputs);
55
58
  const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+
3
+ const BreadcrumbBar = () => <div />;
4
+ export default BreadcrumbBar;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const Head = ({ children }) => <div>{children}</div>;
5
+
6
+ Head.propTypes = {
7
+ children: PropTypes.node.isRequired,
8
+ };
9
+
10
+ export default Head;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { translate as __ } from '../../../common/I18n';
3
+
4
+ const DefaultLoaderEmptyState = () => (
5
+ <span className="disabled-text">{__('Not available')}</span>
6
+ );
7
+
8
+ export default DefaultLoaderEmptyState;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+
3
+ export const ToastsList = () => <div />;
@@ -0,0 +1,21 @@
1
+ import { API_OPERATIONS } from 'foremanReact/redux/API/APIConstants';
2
+
3
+ const { GET, POST, PUT, DELETE, PATCH } = API_OPERATIONS;
4
+
5
+ const apiAction = (type, payload) => ({ type, payload });
6
+
7
+ export const get = payload => apiAction(GET, payload);
8
+
9
+ export const post = payload => apiAction(POST, payload);
10
+
11
+ export const put = payload => apiAction(PUT, payload);
12
+
13
+ export const patch = payload => apiAction(PATCH, payload);
14
+
15
+ export const APIActions = {
16
+ get,
17
+ post,
18
+ put,
19
+ patch,
20
+ delete: payload => apiAction(DELETE, payload),
21
+ };
@@ -0,0 +1,7 @@
1
+ export const API_OPERATIONS = {
2
+ GET: 'API_GET',
3
+ POST: 'API_POST',
4
+ PUT: 'API_PUT',
5
+ DELETE: 'API_DELETE',
6
+ PATCH: 'API_PATCH',
7
+ };
@@ -1,5 +1,19 @@
1
1
  export const API = {
2
2
  get: jest.fn(),
3
+ put: jest.fn(),
4
+ post: jest.fn(),
5
+ delete: jest.fn(),
6
+ patch: jest.fn(),
3
7
  };
4
8
 
5
9
  export const get = data => ({ type: 'get-some-type', ...data });
10
+ export const post = data => ({ type: 'post-some-type', ...data });
11
+ export const put = data => ({ type: 'put-some-type', ...data });
12
+ export const patch = data => ({ type: 'patch-some-type', ...data });
13
+
14
+ export const APIActions = {
15
+ get,
16
+ post,
17
+ put,
18
+ patch,
19
+ };