foreman_remote_execution 13.1.0 → 13.2.0
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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +1000 -973
- data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +553 -526
- data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +584 -557
- data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +1043 -1016
- data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +1047 -1020
- data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +1041 -1014
- data/app/assets/javascripts/foreman_remote_execution/locale/ka/foreman_remote_execution.js +1015 -988
- data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +886 -859
- data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +1047 -1020
- data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +901 -874
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1041 -1014
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +886 -859
- data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
- data/app/models/job_invocation.rb +23 -0
- data/app/models/job_invocation_composer.rb +2 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +3 -2
- data/app/views/templates/script/package_action.erb +5 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/de/foreman_remote_execution.po +27 -0
- data/locale/en/foreman_remote_execution.po +27 -0
- data/locale/en_GB/foreman_remote_execution.po +27 -0
- data/locale/es/foreman_remote_execution.po +27 -0
- data/locale/foreman_remote_execution.pot +177 -141
- data/locale/fr/foreman_remote_execution.po +27 -0
- data/locale/ja/foreman_remote_execution.po +27 -0
- data/locale/ka/foreman_remote_execution.po +27 -0
- data/locale/ko/foreman_remote_execution.po +27 -0
- data/locale/pt_BR/foreman_remote_execution.po +27 -0
- data/locale/ru/foreman_remote_execution.po +27 -0
- data/locale/zh_CN/foreman_remote_execution.po +27 -0
- data/locale/zh_TW/foreman_remote_execution.po +27 -0
- data/package.json +2 -1
- data/webpack/JobInvocationDetail/JobInvocationActions.js +134 -3
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +14 -0
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +5 -2
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +5 -0
- data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +13 -9
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +268 -0
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +259 -0
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +117 -0
- data/webpack/JobInvocationDetail/index.js +58 -38
- data/webpack/JobWizard/JobWizardPageRerun.js +16 -11
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +8 -6
- data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
- data/webpack/__mocks__/foremanReact/components/BreadcrumbBar/index.js +4 -0
- data/webpack/__mocks__/foremanReact/components/Head/index.js +10 -0
- data/webpack/__mocks__/foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState.js +8 -0
- data/webpack/__mocks__/foremanReact/components/ToastsList/index.js +3 -0
- data/webpack/__mocks__/foremanReact/redux/API/APIActions.js +21 -0
- data/webpack/__mocks__/foremanReact/redux/API/APIConstants.js +7 -0
- data/webpack/__mocks__/foremanReact/redux/API/index.js +14 -0
- data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/index.js +9 -0
- 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  | 
| 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 | 
            -
               | 
| 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 || | 
| 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 | 
            -
                  < | 
| 74 | 
            -
                     | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 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 | 
            -
                      < | 
| 80 | 
            -
                         | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 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 | 
             
            };
         | 
| @@ -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 | 
| 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');
         | 
| @@ -53,7 +58,7 @@ const JobWizardPageRerun = ({ | |
| 53 58 | 
             
                      component="a"
         | 
| 54 59 | 
             
                      href={`/old/job_invocations/${id}/rerun${search}`}
         | 
| 55 60 | 
             
                    >
         | 
| 56 | 
            -
                      {__('Use  | 
| 61 | 
            +
                      {__('Use legacy form')}
         | 
| 57 62 | 
             
                    </Button>
         | 
| 58 63 | 
             
                  }
         | 
| 59 64 | 
             
                >
         | 
| @@ -4,16 +4,18 @@ import { useSelector } from 'react-redux'; | |
| 4 4 | 
             
            import URI from 'urijs';
         | 
| 5 5 | 
             
            import { List, ListItem, Modal, Button } from '@patternfly/react-core';
         | 
| 6 6 | 
             
            import { translate as __, sprintf } from 'foremanReact/common/I18n';
         | 
| 7 | 
            -
            import { | 
| 8 | 
            -
             | 
| 7 | 
            +
            import {
         | 
| 8 | 
            +
              useForemanHostsPageUrl,
         | 
| 9 | 
            +
              useForemanHostDetailsPageUrl,
         | 
| 10 | 
            +
            } from 'foremanReact/Root/Context/ForemanContext';
         | 
| 9 11 | 
             
            import { selectHosts, selectHostCount } from '../../JobWizardSelectors';
         | 
| 10 12 | 
             
            import { HOSTS_TO_PREVIEW_AMOUNT } from '../../JobWizardConstants';
         | 
| 11 13 |  | 
| 12 14 | 
             
            export const HostPreviewModal = ({ isOpen, setIsOpen, searchQuery }) => {
         | 
| 13 15 | 
             
              const hosts = useSelector(selectHosts);
         | 
| 14 16 | 
             
              const hostsCount = useSelector(selectHostCount);
         | 
| 15 | 
            -
              const  | 
| 16 | 
            -
             | 
| 17 | 
            +
              const hostsUrl = new URI(useForemanHostsPageUrl());
         | 
| 18 | 
            +
              const hostUrl = useForemanHostDetailsPageUrl();
         | 
| 17 19 | 
             
              return (
         | 
| 18 20 | 
             
                <Modal
         | 
| 19 21 | 
             
                  ouiaId="host-preview-modal"
         | 
| @@ -28,7 +30,7 @@ export const HostPreviewModal = ({ isOpen, setIsOpen, searchQuery }) => { | |
| 28 30 | 
             
                        <Button
         | 
| 29 31 | 
             
                          ouiaId={`host-preview-${host}`}
         | 
| 30 32 | 
             
                          component="a"
         | 
| 31 | 
            -
                          href={ | 
| 33 | 
            +
                          href={`${hostUrl}${host.name}`}
         | 
| 32 34 | 
             
                          variant="link"
         | 
| 33 35 | 
             
                          target="_blank"
         | 
| 34 36 | 
             
                          rel="noreferrer"
         | 
| @@ -43,7 +45,7 @@ export const HostPreviewModal = ({ isOpen, setIsOpen, searchQuery }) => { | |
| 43 45 | 
             
                        <Button
         | 
| 44 46 | 
             
                          ouiaId="host-preview-more"
         | 
| 45 47 | 
             
                          component="a"
         | 
| 46 | 
            -
                          href={ | 
| 48 | 
            +
                          href={hostsUrl.addSearch({ search: searchQuery })}
         | 
| 47 49 | 
             
                          variant="link"
         | 
| 48 50 | 
             
                          target="_blank"
         | 
| 49 51 | 
             
                          rel="noreferrer"
         | 
| @@ -1,3 +1,5 @@ | |
| 1 1 | 
             
            export const useForemanOrganization = () => ({ id: 1 });
         | 
| 2 2 | 
             
            export const useForemanLocation = () => ({ id: 2 });
         | 
| 3 3 | 
             
            export const useForemanVersion = () => '3.7';
         | 
| 4 | 
            +
            export const useForemanHostsPageUrl = () => '/hosts';
         | 
| 5 | 
            +
            export const useForemanHostDetailsPageUrl = () => '/hosts/';
         | 
| @@ -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 | 
            +
            };
         |