foreman_remote_execution 7.2.1 → 8.0.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/.github/workflows/ruby_ci.yml +2 -2
- data/app/controllers/ui_job_wizard_controller.rb +15 -0
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/models/job_invocation.rb +2 -4
- data/app/models/job_invocation_composer.rb +5 -2
- data/app/views/templates/script/package_action.erb +8 -3
- data/config/routes.rb +3 -1
- data/lib/foreman_remote_execution/engine.rb +5 -5
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
- data/test/helpers/remote_execution_helper_test.rb +4 -0
- data/test/unit/job_invocation_report_template_test.rb +1 -1
- data/test/unit/job_invocation_test.rb +1 -2
- data/webpack/JobWizard/JobWizard.js +154 -20
- data/webpack/JobWizard/JobWizard.scss +43 -1
- data/webpack/JobWizard/JobWizardConstants.js +11 -1
- data/webpack/JobWizard/JobWizardPageRerun.js +112 -0
- data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +79 -0
- data/webpack/JobWizard/__tests__/fixtures.js +73 -0
- data/webpack/JobWizard/__tests__/integration.test.js +17 -3
- data/webpack/JobWizard/autofill.js +8 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +36 -17
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -3
- data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -3
- data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
- data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
- data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
- data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
- data/webpack/JobWizard/steps/form/Formatter.js +7 -8
- data/webpack/JobWizard/submit.js +8 -3
- data/webpack/Routes/routes.js +8 -2
- data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
- data/webpack/react_app/components/HostKebab/KebabItems.js +0 -1
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +0 -5
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +59 -51
- data/webpack/react_app/extend/Fills.js +4 -4
- metadata +8 -6
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
- data/webpack/JobWizard/steps/Schedule/index.js +0 -178
| @@ -1,28 +1,96 @@ | |
| 1 1 | 
             
            import React from 'react';
         | 
| 2 2 | 
             
            import PropTypes from 'prop-types';
         | 
| 3 | 
            -
            import { FormGroup, Radio } from '@patternfly/react-core';
         | 
| 3 | 
            +
            import { Form, FormGroup, Radio, Divider } from '@patternfly/react-core';
         | 
| 4 4 | 
             
            import { translate as __ } from 'foremanReact/common/I18n';
         | 
| 5 | 
            +
            import {
         | 
| 6 | 
            +
              WIZARD_TITLES,
         | 
| 7 | 
            +
              SCHEDULE_TYPES,
         | 
| 8 | 
            +
              repeatTypes,
         | 
| 9 | 
            +
            } from '../../JobWizardConstants';
         | 
| 10 | 
            +
            import { WizardTitle } from '../form/WizardTitle';
         | 
| 11 | 
            +
            import { QueryType } from './QueryType';
         | 
| 5 12 |  | 
| 6 | 
            -
            export const ScheduleType = ({ | 
| 7 | 
            -
               | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                 | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
                   | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 13 | 
            +
            export const ScheduleType = ({
         | 
| 14 | 
            +
              scheduleType,
         | 
| 15 | 
            +
              isTypeStatic,
         | 
| 16 | 
            +
              setScheduleValue,
         | 
| 17 | 
            +
              setValid,
         | 
| 18 | 
            +
            }) => (
         | 
| 19 | 
            +
              <div className="schedule-tab">
         | 
| 20 | 
            +
                <WizardTitle title={WIZARD_TITLES.schedule} />
         | 
| 21 | 
            +
                <Form>
         | 
| 22 | 
            +
                  <FormGroup
         | 
| 23 | 
            +
                    fieldId="schedule-type-now"
         | 
| 24 | 
            +
                    label={__('Select the type of execution')}
         | 
| 25 | 
            +
                  >
         | 
| 26 | 
            +
                    <Radio
         | 
| 27 | 
            +
                      isChecked={scheduleType === SCHEDULE_TYPES.NOW}
         | 
| 28 | 
            +
                      name="schedule-type-now"
         | 
| 29 | 
            +
                      id="schedule-type-now"
         | 
| 30 | 
            +
                      onChange={() => {
         | 
| 31 | 
            +
                        setScheduleValue(current => ({
         | 
| 32 | 
            +
                          ...current,
         | 
| 33 | 
            +
                          scheduleType: SCHEDULE_TYPES.NOW,
         | 
| 34 | 
            +
                          repeatType: repeatTypes.noRepeat,
         | 
| 35 | 
            +
                          startsAt: null,
         | 
| 36 | 
            +
                          startsBefore: null,
         | 
| 37 | 
            +
                        }));
         | 
| 38 | 
            +
                        setValid(true);
         | 
| 39 | 
            +
                      }}
         | 
| 40 | 
            +
                      label={__('Immediate execution')}
         | 
| 41 | 
            +
                      body={__('Execute the job now.')}
         | 
| 42 | 
            +
                    />
         | 
| 43 | 
            +
                  </FormGroup>
         | 
| 44 | 
            +
                  <FormGroup fieldId="schedule-type-future">
         | 
| 45 | 
            +
                    <Radio
         | 
| 46 | 
            +
                      isChecked={scheduleType === SCHEDULE_TYPES.FUTURE}
         | 
| 47 | 
            +
                      onChange={() => {
         | 
| 48 | 
            +
                        setScheduleValue(current => ({
         | 
| 49 | 
            +
                          ...current,
         | 
| 50 | 
            +
                          startsAt: new Date().toISOString(),
         | 
| 51 | 
            +
                          scheduleType: SCHEDULE_TYPES.FUTURE,
         | 
| 52 | 
            +
                          repeatType: repeatTypes.noRepeat,
         | 
| 53 | 
            +
                        }));
         | 
| 54 | 
            +
                        setValid(true);
         | 
| 55 | 
            +
                      }}
         | 
| 56 | 
            +
                      name="schedule-type-future"
         | 
| 57 | 
            +
                      id="schedule-type-future"
         | 
| 58 | 
            +
                      label={__('Future execution')}
         | 
| 59 | 
            +
                      body={__('Execute the job later, at a scheduled time.')}
         | 
| 60 | 
            +
                    />
         | 
| 61 | 
            +
                  </FormGroup>
         | 
| 62 | 
            +
                  <FormGroup fieldId="schedule-type-recurring">
         | 
| 63 | 
            +
                    <Radio
         | 
| 64 | 
            +
                      isChecked={scheduleType === SCHEDULE_TYPES.RECURRING}
         | 
| 65 | 
            +
                      onChange={() => {
         | 
| 66 | 
            +
                        setScheduleValue(current => ({
         | 
| 67 | 
            +
                          ...current,
         | 
| 68 | 
            +
                          scheduleType: SCHEDULE_TYPES.RECURRING,
         | 
| 69 | 
            +
                          repeatType: repeatTypes.daily,
         | 
| 70 | 
            +
                          repeatData: { at: '12:00' },
         | 
| 71 | 
            +
                        }));
         | 
| 72 | 
            +
                        setValid(true);
         | 
| 73 | 
            +
                      }}
         | 
| 74 | 
            +
                      name="schedule-type-recurring"
         | 
| 75 | 
            +
                      id="schedule-type-recurring"
         | 
| 76 | 
            +
                      label={__('Recurring execution')}
         | 
| 77 | 
            +
                      body={__('Execute the job on a repeating schedule.')}
         | 
| 78 | 
            +
                    />
         | 
| 79 | 
            +
                  </FormGroup>
         | 
| 80 | 
            +
                  <Divider component="div" />
         | 
| 81 | 
            +
                  <QueryType
         | 
| 82 | 
            +
                    isTypeStatic={isTypeStatic}
         | 
| 83 | 
            +
                    setIsTypeStatic={newValue => {
         | 
| 84 | 
            +
                      setScheduleValue(current => ({ ...current, isTypeStatic: newValue }));
         | 
| 85 | 
            +
                    }}
         | 
| 86 | 
            +
                  />
         | 
| 87 | 
            +
                </Form>
         | 
| 88 | 
            +
              </div>
         | 
| 23 89 | 
             
            );
         | 
| 24 90 |  | 
| 25 91 | 
             
            ScheduleType.propTypes = {
         | 
| 26 | 
            -
               | 
| 27 | 
            -
               | 
| 92 | 
            +
              isTypeStatic: PropTypes.bool.isRequired,
         | 
| 93 | 
            +
              scheduleType: PropTypes.string.isRequired,
         | 
| 94 | 
            +
              setScheduleValue: PropTypes.func.isRequired,
         | 
| 95 | 
            +
              setValid: PropTypes.func.isRequired,
         | 
| 28 96 | 
             
            };
         | 
| @@ -3,6 +3,7 @@ import React from 'react'; | |
| 3 3 | 
             
            import { Provider } from 'react-redux';
         | 
| 4 4 | 
             
            import configureMockStore from 'redux-mock-store';
         | 
| 5 5 | 
             
            import { fireEvent, screen, render, act } from '@testing-library/react';
         | 
| 6 | 
            +
            import '@testing-library/jest-dom';
         | 
| 6 7 | 
             
            import * as api from 'foremanReact/redux/API';
         | 
| 7 8 | 
             
            import { JobWizard } from '../../../JobWizard';
         | 
| 8 9 | 
             
            import * as selectors from '../../../JobWizardSelectors';
         | 
| @@ -47,166 +48,232 @@ const store = mockStore({}); | |
| 47 48 | 
             
            jest.useFakeTimers();
         | 
| 48 49 |  | 
| 49 50 | 
             
            describe('Schedule', () => {
         | 
| 50 | 
            -
              it(' | 
| 51 | 
            +
              it('sub steps appear', () => {
         | 
| 51 52 | 
             
                render(
         | 
| 52 53 | 
             
                  <Provider store={store}>
         | 
| 53 54 | 
             
                    <JobWizard />
         | 
| 54 55 | 
             
                  </Provider>
         | 
| 55 56 | 
             
                );
         | 
| 56 | 
            -
                 | 
| 57 | 
            -
                  fireEvent.click(screen.getByText(' | 
| 58 | 
            -
                });
         | 
| 59 | 
            -
                const newStartDate = '2020/03/12';
         | 
| 60 | 
            -
                const newStartTime = '12:03';
         | 
| 61 | 
            -
                const newEndsDate = '2030/03/12';
         | 
| 62 | 
            -
                const newEndsTime = '17:34';
         | 
| 63 | 
            -
                const startsDateField = screen.getByLabelText('starts at datepicker');
         | 
| 64 | 
            -
                const endsDateField = screen.getByLabelText('ends datepicker');
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                const startsTimeField = screen.getByLabelText('starts at timepicker');
         | 
| 67 | 
            -
                const endsTimeField = screen.getByLabelText('ends timepicker');
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                const staticQuery = screen.getByLabelText('Static query');
         | 
| 70 | 
            -
                const dynamicQuery = screen.getByLabelText('Dynamic query');
         | 
| 71 | 
            -
                const purpose = screen.getByLabelText('purpose');
         | 
| 72 | 
            -
                const newPurposeLabel = 'some fun text';
         | 
| 73 | 
            -
                expect(staticQuery.checked).toBeTruthy();
         | 
| 74 | 
            -
                await act(async () => {
         | 
| 75 | 
            -
                  await fireEvent.change(startsDateField, {
         | 
| 76 | 
            -
                    target: { value: newStartDate },
         | 
| 77 | 
            -
                  });
         | 
| 78 | 
            -
                  await fireEvent.change(startsTimeField, {
         | 
| 79 | 
            -
                    target: { value: newStartTime },
         | 
| 80 | 
            -
                  });
         | 
| 81 | 
            -
                  await fireEvent.change(purpose, {
         | 
| 82 | 
            -
                    target: { value: newPurposeLabel },
         | 
| 83 | 
            -
                  });
         | 
| 84 | 
            -
                  await fireEvent.change(endsDateField, { target: { value: newEndsDate } });
         | 
| 85 | 
            -
                  await fireEvent.change(endsTimeField, { target: { value: newEndsTime } });
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                  await fireEvent.click(dynamicQuery);
         | 
| 88 | 
            -
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 57 | 
            +
                act(() => {
         | 
| 58 | 
            +
                  fireEvent.click(screen.getByText('Type of execution'));
         | 
| 89 59 | 
             
                });
         | 
| 90 | 
            -
                 | 
| 91 | 
            -
             | 
| 92 | 
            -
                });
         | 
| 93 | 
            -
                expect(screen.getAllByText('Category and Template')).toHaveLength(3);
         | 
| 60 | 
            +
                expect(screen.getAllByText('Future execution')).toHaveLength(1);
         | 
| 61 | 
            +
                expect(screen.getAllByText('Recurring execution')).toHaveLength(1);
         | 
| 94 62 |  | 
| 95 | 
            -
                 | 
| 96 | 
            -
                  fireEvent.click(screen.getByText(' | 
| 97 | 
            -
                  jest.runAllTimers();
         | 
| 63 | 
            +
                act(() => {
         | 
| 64 | 
            +
                  fireEvent.click(screen.getByText('Future execution'));
         | 
| 98 65 | 
             
                });
         | 
| 99 | 
            -
                expect( | 
| 100 | 
            -
                expect( | 
| 101 | 
            -
                 | 
| 102 | 
            -
             | 
| 103 | 
            -
                 | 
| 104 | 
            -
                expect( | 
| 66 | 
            +
                expect(screen.getAllByText('Future execution')).toHaveLength(2);
         | 
| 67 | 
            +
                expect(screen.getAllByText('Recurring execution')).toHaveLength(1);
         | 
| 68 | 
            +
                act(() => {
         | 
| 69 | 
            +
                  fireEvent.click(screen.getByText('Recurring execution'));
         | 
| 70 | 
            +
                });
         | 
| 71 | 
            +
                expect(screen.getAllByText('Future execution')).toHaveLength(1);
         | 
| 72 | 
            +
                expect(screen.getAllByText('Recurring execution')).toHaveLength(2);
         | 
| 105 73 | 
             
              });
         | 
| 106 | 
            -
              it(' | 
| 74 | 
            +
              it('Future execution', async () => {
         | 
| 107 75 | 
             
                render(
         | 
| 108 76 | 
             
                  <Provider store={store}>
         | 
| 109 77 | 
             
                    <JobWizard />
         | 
| 110 78 | 
             
                  </Provider>
         | 
| 111 79 | 
             
                );
         | 
| 112 | 
            -
                 | 
| 113 | 
            -
                  fireEvent.click(screen.getByText(' | 
| 80 | 
            +
                act(() => {
         | 
| 81 | 
            +
                  fireEvent.click(screen.getByText('Type of execution'));
         | 
| 114 82 | 
             
                });
         | 
| 115 | 
            -
                 | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
                ) | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
                 | 
| 122 | 
            -
             | 
| 123 | 
            -
                const  | 
| 83 | 
            +
                act(() => {
         | 
| 84 | 
            +
                  fireEvent.click(screen.getByText('Future execution'));
         | 
| 85 | 
            +
                });
         | 
| 86 | 
            +
                act(() => {
         | 
| 87 | 
            +
                  fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
         | 
| 88 | 
            +
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 89 | 
            +
                });
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                const newStartAtDate = '2030/03/12';
         | 
| 92 | 
            +
                const newStartBeforeDate = '2030/05/22';
         | 
| 93 | 
            +
                const newStartAtTime = '12:46';
         | 
| 94 | 
            +
                const newStartBeforeTime = '14:27';
         | 
| 95 | 
            +
                const startsAtDateField = () =>
         | 
| 96 | 
            +
                  screen.getByLabelText('starts at datepicker');
         | 
| 97 | 
            +
                const startsAtTimeField = () =>
         | 
| 98 | 
            +
                  screen.getByLabelText('starts at timepicker');
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                const startsBeforeDateField = () =>
         | 
| 101 | 
            +
                  screen.getByLabelText('starts before datepicker');
         | 
| 102 | 
            +
                const startsBeforeTimeField = () =>
         | 
| 103 | 
            +
                  screen.getByLabelText('starts before timepicker');
         | 
| 104 | 
            +
             | 
| 124 105 | 
             
                await act(async () => {
         | 
| 125 | 
            -
                  await fireEvent.change( | 
| 126 | 
            -
                    target: { value:  | 
| 106 | 
            +
                  await fireEvent.change(startsAtDateField(), {
         | 
| 107 | 
            +
                    target: { value: newStartAtDate },
         | 
| 127 108 | 
             
                  });
         | 
| 128 | 
            -
                   | 
| 129 | 
            -
                    target: { value:  | 
| 109 | 
            +
                  fireEvent.change(startsAtTimeField(), {
         | 
| 110 | 
            +
                    target: { value: newStartAtTime },
         | 
| 111 | 
            +
                  });
         | 
| 112 | 
            +
                  await fireEvent.change(startsBeforeDateField(), {
         | 
| 113 | 
            +
                    target: { value: newStartBeforeDate },
         | 
| 114 | 
            +
                  });
         | 
| 115 | 
            +
                  fireEvent.change(startsBeforeTimeField(), {
         | 
| 116 | 
            +
                    target: { value: newStartBeforeTime },
         | 
| 117 | 
            +
                  });
         | 
| 118 | 
            +
                  jest.runOnlyPendingTimers();
         | 
| 119 | 
            +
                });
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                act(() => {
         | 
| 122 | 
            +
                  fireEvent.click(screen.getByText('Category and Template'));
         | 
| 123 | 
            +
                });
         | 
| 124 | 
            +
                act(() => {
         | 
| 125 | 
            +
                  fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
         | 
| 126 | 
            +
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 127 | 
            +
                });
         | 
| 128 | 
            +
                expect(startsAtDateField().value).toBe(newStartAtDate);
         | 
| 129 | 
            +
                expect(startsAtTimeField().value).toBe(newStartAtTime);
         | 
| 130 | 
            +
                expect(startsBeforeDateField().value).toBe(newStartBeforeDate);
         | 
| 131 | 
            +
                expect(startsBeforeTimeField().value).toBe(newStartBeforeTime);
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                expect(
         | 
| 134 | 
            +
                  screen.queryAllByText(
         | 
| 135 | 
            +
                    "'Starts before' date must be after 'Starts at' date"
         | 
| 136 | 
            +
                  )
         | 
| 137 | 
            +
                ).toHaveLength(0);
         | 
| 138 | 
            +
                await act(async () => {
         | 
| 139 | 
            +
                  await fireEvent.change(startsBeforeDateField(), {
         | 
| 140 | 
            +
                    target: { value: '2030/03/11' },
         | 
| 130 141 | 
             
                  });
         | 
| 142 | 
            +
                  await fireEvent.click(startsBeforeTimeField());
         | 
| 131 143 | 
             
                  await jest.runOnlyPendingTimers();
         | 
| 132 144 | 
             
                });
         | 
| 133 | 
            -
                expect( | 
| 134 | 
            -
                expect( | 
| 135 | 
            -
             | 
| 145 | 
            +
                expect(startsBeforeDateField().value).toBe('2030/03/11');
         | 
| 146 | 
            +
                expect(
         | 
| 147 | 
            +
                  screen.getAllByText("'Starts before' date must be after 'Starts at' date")
         | 
| 148 | 
            +
                ).toHaveLength(1);
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                expect(
         | 
| 151 | 
            +
                  screen.queryAllByText("'Starts before' date must in the future")
         | 
| 152 | 
            +
                ).toHaveLength(0);
         | 
| 136 153 | 
             
                await act(async () => {
         | 
| 137 | 
            -
                  await fireEvent. | 
| 138 | 
            -
             | 
| 154 | 
            +
                  await fireEvent.change(startsBeforeDateField(), {
         | 
| 155 | 
            +
                    target: { value: '2019/03/11' },
         | 
| 156 | 
            +
                  });
         | 
| 157 | 
            +
                  await fireEvent.change(startsAtDateField(), {
         | 
| 158 | 
            +
                    target: { value: '' },
         | 
| 159 | 
            +
                  });
         | 
| 160 | 
            +
                  jest.runOnlyPendingTimers();
         | 
| 139 161 | 
             
                });
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                expect( | 
| 142 | 
            -
                expect( | 
| 162 | 
            +
             | 
| 163 | 
            +
                expect(startsBeforeDateField().value).toBe('2019/03/11');
         | 
| 164 | 
            +
                expect(
         | 
| 165 | 
            +
                  screen.getAllByText("'Starts before' date must in the future")
         | 
| 166 | 
            +
                ).toHaveLength(1);
         | 
| 143 167 | 
             
              });
         | 
| 144 168 |  | 
| 145 | 
            -
              it(' | 
| 169 | 
            +
              it('Recurring execution - date pickers', async () => {
         | 
| 146 170 | 
             
                render(
         | 
| 147 171 | 
             
                  <Provider store={store}>
         | 
| 148 172 | 
             
                    <JobWizard />
         | 
| 149 173 | 
             
                  </Provider>
         | 
| 150 174 | 
             
                );
         | 
| 151 | 
            -
                 | 
| 152 | 
            -
                   | 
| 153 | 
            -
             | 
| 175 | 
            +
                act(() => {
         | 
| 176 | 
            +
                  fireEvent.click(screen.getByText('Type of execution'));
         | 
| 177 | 
            +
                });
         | 
| 178 | 
            +
                act(() => {
         | 
| 179 | 
            +
                  fireEvent.click(screen.getByText('Recurring execution'));
         | 
| 180 | 
            +
                });
         | 
| 181 | 
            +
                act(() => {
         | 
| 182 | 
            +
                  fireEvent.click(
         | 
| 183 | 
            +
                    screen.getByRole('button', { name: 'Recurring execution' })
         | 
| 184 | 
            +
                  );
         | 
| 185 | 
            +
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 154 186 | 
             
                });
         | 
| 155 | 
            -
                const neverEnds = screen.getByLabelText('Never ends');
         | 
| 156 | 
            -
                expect(neverEnds.checked).toBeFalsy();
         | 
| 157 187 |  | 
| 158 | 
            -
                const  | 
| 159 | 
            -
                const  | 
| 160 | 
            -
                 | 
| 161 | 
            -
                  screen.getByLabelText(' | 
| 162 | 
            -
                ) | 
| 188 | 
            +
                const newStartAtDate = '2030/03/12';
         | 
| 189 | 
            +
                const newStartAtTime = '12:46';
         | 
| 190 | 
            +
                const startsAtDateField = () =>
         | 
| 191 | 
            +
                  screen.getByLabelText('starts at datepicker');
         | 
| 192 | 
            +
                const startsAtTimeField = () =>
         | 
| 193 | 
            +
                  screen.getByLabelText('starts at timepicker');
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                const endsAtDateField = () => screen.getByLabelText('ends on datepicker');
         | 
| 196 | 
            +
                const endsAtTimeField = () => screen.getByLabelText('ends on timepicker');
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                expect(startsAtDateField().disabled).toBeTruthy();
         | 
| 199 | 
            +
                act(() => {
         | 
| 200 | 
            +
                  fireEvent.click(screen.getAllByText('At')[0]);
         | 
| 201 | 
            +
                });
         | 
| 202 | 
            +
                expect(startsAtDateField().disabled).toBeFalsy();
         | 
| 163 203 | 
             
                await act(async () => {
         | 
| 164 | 
            -
                  fireEvent. | 
| 204 | 
            +
                  await fireEvent.change(startsAtDateField(), {
         | 
| 205 | 
            +
                    target: { value: newStartAtDate },
         | 
| 206 | 
            +
                  });
         | 
| 207 | 
            +
                  fireEvent.change(startsAtTimeField(), {
         | 
| 208 | 
            +
                    target: { value: newStartAtTime },
         | 
| 209 | 
            +
                  });
         | 
| 210 | 
            +
                  jest.runOnlyPendingTimers();
         | 
| 211 | 
            +
                });
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                expect(endsAtDateField().disabled).toBeTruthy();
         | 
| 214 | 
            +
                act(() => {
         | 
| 215 | 
            +
                  fireEvent.click(screen.getByText('On'));
         | 
| 165 216 | 
             
                });
         | 
| 166 | 
            -
                expect( | 
| 167 | 
            -
                expect(endsTimeField.disabled).toBeFalsy();
         | 
| 217 | 
            +
                expect(endsAtDateField().disabled).toBeFalsy();
         | 
| 168 218 | 
             
                await act(async () => {
         | 
| 169 | 
            -
                  fireEvent. | 
| 219 | 
            +
                  await fireEvent.change(endsAtDateField(), {
         | 
| 220 | 
            +
                    target: { value: newStartAtDate },
         | 
| 221 | 
            +
                  });
         | 
| 222 | 
            +
                  fireEvent.change(endsAtTimeField(), {
         | 
| 223 | 
            +
                    target: { value: newStartAtTime },
         | 
| 224 | 
            +
                  });
         | 
| 225 | 
            +
                  jest.runOnlyPendingTimers();
         | 
| 170 226 | 
             
                });
         | 
| 171 | 
            -
                expect(neverEnds.checked).toBeTruthy();
         | 
| 172 | 
            -
                expect(endsDateField.disabled).toBeTruthy();
         | 
| 173 | 
            -
                expect(endsTimeField.disabled).toBeTruthy();
         | 
| 174 | 
            -
              });
         | 
| 175 227 |  | 
| 176 | 
            -
             | 
| 228 | 
            +
                act(() => {
         | 
| 229 | 
            +
                  fireEvent.click(screen.getByText('Category and Template'));
         | 
| 230 | 
            +
                });
         | 
| 231 | 
            +
                act(() => {
         | 
| 232 | 
            +
                  fireEvent.click(
         | 
| 233 | 
            +
                    screen.getByRole('button', { name: 'Recurring execution' })
         | 
| 234 | 
            +
                  );
         | 
| 235 | 
            +
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 236 | 
            +
                });
         | 
| 237 | 
            +
                expect(startsAtDateField().value).toBe(newStartAtDate);
         | 
| 238 | 
            +
                expect(startsAtTimeField().value).toBe(newStartAtTime);
         | 
| 239 | 
            +
                expect(endsAtDateField().value).toBe(newStartAtDate);
         | 
| 240 | 
            +
                expect(endsAtTimeField().value).toBe(newStartAtTime);
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                act(() => {
         | 
| 243 | 
            +
                  fireEvent.click(screen.getByText('Now'));
         | 
| 244 | 
            +
                  fireEvent.click(screen.getByText('After'));
         | 
| 245 | 
            +
                });
         | 
| 246 | 
            +
                expect(startsAtDateField().disabled).toBeTruthy();
         | 
| 247 | 
            +
                expect(endsAtDateField().disabled).toBeTruthy();
         | 
| 248 | 
            +
                expect(startsAtDateField().value).toBe('');
         | 
| 249 | 
            +
                expect(startsAtTimeField().value).toBe('');
         | 
| 250 | 
            +
                expect(endsAtDateField().value).toBe('');
         | 
| 251 | 
            +
                expect(endsAtTimeField().value).toBe('');
         | 
| 252 | 
            +
              });
         | 
| 253 | 
            +
              it('Recurring execution - repeat', async () => {
         | 
| 177 254 | 
             
                render(
         | 
| 178 255 | 
             
                  <Provider store={store}>
         | 
| 179 256 | 
             
                    <JobWizard />
         | 
| 180 257 | 
             
                  </Provider>
         | 
| 181 258 | 
             
                );
         | 
| 182 | 
            -
                 | 
| 183 | 
            -
                  fireEvent.click(screen.getByText(' | 
| 184 | 
            -
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 259 | 
            +
                act(() => {
         | 
| 260 | 
            +
                  fireEvent.click(screen.getByText('Type of execution'));
         | 
| 185 261 | 
             
                });
         | 
| 186 | 
            -
                 | 
| 187 | 
            -
                  screen. | 
| 188 | 
            -
                ) | 
| 189 | 
            -
                 | 
| 190 | 
            -
                await act(async () => {
         | 
| 262 | 
            +
                act(() => {
         | 
| 263 | 
            +
                  fireEvent.click(screen.getByText('Recurring execution'));
         | 
| 264 | 
            +
                });
         | 
| 265 | 
            +
                act(() => {
         | 
| 191 266 | 
             
                  fireEvent.click(
         | 
| 192 | 
            -
                    screen. | 
| 267 | 
            +
                    screen.getByRole('button', { name: 'Recurring execution' })
         | 
| 193 268 | 
             
                  );
         | 
| 269 | 
            +
                  jest.runAllTimers(); // to handle pf4 date picker popover useTimer
         | 
| 194 270 | 
             
                });
         | 
| 195 | 
            -
             | 
| 196 271 | 
             
                await act(async () => {
         | 
| 197 | 
            -
                  fireEvent.click(screen. | 
| 272 | 
            +
                  fireEvent.click(screen.getByLabelText('Daily', { selector: 'button' }));
         | 
| 198 273 | 
             
                });
         | 
| 199 | 
            -
                expect(screen.getByText('Review details').disabled).toBeTruthy();
         | 
| 200 | 
            -
                const newRepeatTimes = '3';
         | 
| 201 | 
            -
                const repeatNTimes = screen.getByPlaceholderText('Repeat N times');
         | 
| 202 | 
            -
                expect(repeatNTimes.value).toBe('');
         | 
| 203 274 | 
             
                await act(async () => {
         | 
| 204 | 
            -
                  fireEvent. | 
| 205 | 
            -
                    target: { value: newRepeatTimes },
         | 
| 206 | 
            -
                  });
         | 
| 275 | 
            +
                  fireEvent.click(screen.getByText('Cronline'));
         | 
| 207 276 | 
             
                });
         | 
| 208 | 
            -
                expect(repeatNTimes.value).toBe(newRepeatTimes);
         | 
| 209 | 
            -
             | 
| 210 277 | 
             
                const newCronline = '1 2';
         | 
| 211 278 | 
             
                const cronline = screen.getByLabelText('cronline');
         | 
| 212 279 | 
             
                expect(cronline.value).toBe('');
         | 
| @@ -224,11 +291,12 @@ describe('Schedule', () => { | |
| 224 291 | 
             
                expect(screen.getAllByText('Category and Template')).toHaveLength(3);
         | 
| 225 292 |  | 
| 226 293 | 
             
                await act(async () => {
         | 
| 227 | 
            -
                  fireEvent.click( | 
| 294 | 
            +
                  fireEvent.click(
         | 
| 295 | 
            +
                    screen.getByRole('button', { name: 'Recurring execution' })
         | 
| 296 | 
            +
                  );
         | 
| 228 297 | 
             
                  jest.runAllTimers();
         | 
| 229 298 | 
             
                });
         | 
| 230 | 
            -
                expect(screen.queryAllByText(' | 
| 231 | 
            -
                expect(repeatNTimes.value).toBe(newRepeatTimes);
         | 
| 299 | 
            +
                expect(screen.queryAllByText('Recurring execution')).toHaveLength(3);
         | 
| 232 300 | 
             
                expect(cronline.value).toBe(newCronline);
         | 
| 233 301 |  | 
| 234 302 | 
             
                fireEvent.click(screen.getByText('Cronline'));
         | 
| @@ -244,7 +312,6 @@ describe('Schedule', () => { | |
| 244 312 | 
             
                  fireEvent.change(days, {
         | 
| 245 313 | 
             
                    target: { value: newDays },
         | 
| 246 314 | 
             
                  });
         | 
| 247 | 
            -
                  fireEvent.click(repeatNTimes);
         | 
| 248 315 | 
             
                });
         | 
| 249 316 | 
             
                expect(days.value).toBe(newDays);
         | 
| 250 317 |  | 
| @@ -295,12 +362,12 @@ describe('Schedule', () => { | |
| 295 362 |  | 
| 296 363 | 
             
                expect(screen.getByText('Review details').disabled).toBeFalsy();
         | 
| 297 364 | 
             
                fireEvent.click(screen.getByText('Weekly'));
         | 
| 298 | 
            -
                 | 
| 365 | 
            +
                act(() => {
         | 
| 299 366 | 
             
                  fireEvent.click(screen.getByText('Daily'));
         | 
| 300 367 | 
             
                });
         | 
| 301 368 |  | 
| 302 369 | 
             
                expect(screen.getByText('Review details').disabled).toBeFalsy();
         | 
| 303 | 
            -
                 | 
| 370 | 
            +
                act(() => {
         | 
| 304 371 | 
             
                  fireEvent.change(at(), {
         | 
| 305 372 | 
             
                    target: { value: '' },
         | 
| 306 373 | 
             
                  });
         | 
| @@ -308,7 +375,7 @@ describe('Schedule', () => { | |
| 308 375 | 
             
                expect(screen.getByText('Review details').disabled).toBeTruthy();
         | 
| 309 376 | 
             
                const newAtDaily = '17:07';
         | 
| 310 377 | 
             
                expect(at().value).toBe('');
         | 
| 311 | 
            -
                 | 
| 378 | 
            +
                act(() => {
         | 
| 312 379 | 
             
                  fireEvent.change(at(), {
         | 
| 313 380 | 
             
                    target: { value: newAtDaily },
         | 
| 314 381 | 
             
                  });
         | 
| @@ -317,86 +384,39 @@ describe('Schedule', () => { | |
| 317 384 | 
             
                expect(screen.getByText('Review details').disabled).toBeFalsy();
         | 
| 318 385 |  | 
| 319 386 | 
             
                fireEvent.click(screen.getByText('Daily'));
         | 
| 320 | 
            -
                 | 
| 387 | 
            +
                act(() => {
         | 
| 321 388 | 
             
                  fireEvent.click(screen.getByText('Hourly'));
         | 
| 322 389 | 
             
                });
         | 
| 323 390 |  | 
| 324 | 
            -
                expect(screen.getByText('Review details').disabled).toBeTruthy();
         | 
| 325 | 
            -
                const newMinutes = '6';
         | 
| 326 | 
            -
                const atHourly = screen.getByLabelText('repeat-at-minute-typeahead');
         | 
| 327 | 
            -
                expect(atHourly.value).toBe('');
         | 
| 328 | 
            -
                await act(async () => {
         | 
| 329 | 
            -
                  fireEvent.click(screen.getByLabelText('select minute toggle'));
         | 
| 330 | 
            -
                });
         | 
| 331 | 
            -
                await act(async () => {
         | 
| 332 | 
            -
                  fireEvent.click(screen.getByText(newMinutes));
         | 
| 333 | 
            -
                });
         | 
| 334 391 | 
             
                expect(screen.getByText('Review details').disabled).toBeFalsy();
         | 
| 335 | 
            -
                 | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 338 | 
            -
                 | 
| 339 | 
            -
                   | 
| 340 | 
            -
                     | 
| 341 | 
            -
                   | 
| 342 | 
            -
                );
         | 
| 343 | 
            -
                await act(async () => {
         | 
| 344 | 
            -
                  await fireEvent.click(screen.getByText('Schedule'));
         | 
| 345 | 
            -
                  jest.runAllTimers();
         | 
| 392 | 
            +
                const newMinutes = '6';
         | 
| 393 | 
            +
                const atHourly = () => screen.getByLabelText('repeat-at-minute-typeahead');
         | 
| 394 | 
            +
                expect(atHourly().value).toBe('0');
         | 
| 395 | 
            +
                act(() => {
         | 
| 396 | 
            +
                  fireEvent.change(atHourly(), {
         | 
| 397 | 
            +
                    target: { value: '62' },
         | 
| 398 | 
            +
                  });
         | 
| 346 399 | 
             
                });
         | 
| 347 | 
            -
                const neverEnds = screen.getByLabelText('Never ends');
         | 
| 348 | 
            -
                expect(neverEnds.checked).toBeFalsy();
         | 
| 349 | 
            -
             | 
| 350 | 
            -
                const startsDateField = screen.getByLabelText('starts at datepicker');
         | 
| 351 | 
            -
                const endsDateField = screen.getByLabelText('ends datepicker');
         | 
| 352 | 
            -
             | 
| 353 | 
            -
                expect(
         | 
| 354 | 
            -
                  screen.queryAllByText('End time needs to be after start time')
         | 
| 355 | 
            -
                ).toHaveLength(0);
         | 
| 356 | 
            -
                expect(screen.getByText('Review details').disabled).toBeFalsy();
         | 
| 357 400 | 
             
                await act(async () => {
         | 
| 358 | 
            -
                  await fireEvent. | 
| 359 | 
            -
                    target: { value: '2020/10/15' },
         | 
| 360 | 
            -
                  });
         | 
| 361 | 
            -
                  await fireEvent.change(endsDateField, {
         | 
| 362 | 
            -
                    target: { value: '2020/10/14' },
         | 
| 363 | 
            -
                  });
         | 
| 364 | 
            -
                  await jest.runOnlyPendingTimers();
         | 
| 401 | 
            +
                  await fireEvent.click(screen.getByText('Create "62"'));
         | 
| 365 402 | 
             
                });
         | 
| 366 | 
            -
             | 
| 403 | 
            +
                expect(atHourly().value).toBe('0');
         | 
| 367 404 | 
             
                expect(
         | 
| 368 | 
            -
                  screen.queryAllByText(' | 
| 405 | 
            +
                  screen.queryAllByText('Minute can only be a number between 0-59')
         | 
| 369 406 | 
             
                ).toHaveLength(1);
         | 
| 370 407 |  | 
| 371 | 
            -
                 | 
| 372 | 
            -
             | 
| 373 | 
            -
             | 
| 374 | 
            -
             | 
| 375 | 
            -
                  <Provider store={store}>
         | 
| 376 | 
            -
                    <JobWizard />
         | 
| 377 | 
            -
                  </Provider>
         | 
| 378 | 
            -
                );
         | 
| 379 | 
            -
                await act(async () => {
         | 
| 380 | 
            -
                  await fireEvent.click(screen.getByText('Schedule'));
         | 
| 381 | 
            -
                  jest.runAllTimers();
         | 
| 382 | 
            -
                });
         | 
| 383 | 
            -
             | 
| 384 | 
            -
                const endsDateField = screen.getByLabelText('ends datepicker');
         | 
| 385 | 
            -
                const endsTimeField = screen.getByLabelText('ends timepicker');
         | 
| 386 | 
            -
                const purpose = screen.getByLabelText('purpose');
         | 
| 387 | 
            -
                expect(endsDateField.disabled).toBeTruthy();
         | 
| 388 | 
            -
                expect(endsTimeField.disabled).toBeTruthy();
         | 
| 389 | 
            -
                expect(purpose.disabled).toBeTruthy();
         | 
| 390 | 
            -
                await act(async () => {
         | 
| 391 | 
            -
                  fireEvent.click(
         | 
| 392 | 
            -
                    screen.getByLabelText('Does not repeat', { selector: 'button' })
         | 
| 393 | 
            -
                  );
         | 
| 408 | 
            +
                act(() => {
         | 
| 409 | 
            +
                  fireEvent.change(atHourly(), {
         | 
| 410 | 
            +
                    target: { value: newMinutes },
         | 
| 411 | 
            +
                  });
         | 
| 394 412 | 
             
                });
         | 
| 395 413 | 
             
                await act(async () => {
         | 
| 396 | 
            -
                  fireEvent.click(screen.getByText( | 
| 414 | 
            +
                  await fireEvent.click(screen.getByText(`Create "${newMinutes}"`));
         | 
| 397 415 | 
             
                });
         | 
| 398 | 
            -
                expect( | 
| 399 | 
            -
             | 
| 400 | 
            -
                 | 
| 416 | 
            +
                expect(
         | 
| 417 | 
            +
                  screen.queryAllByText('Minute can only be a number between 0-59')
         | 
| 418 | 
            +
                ).toHaveLength(0);
         | 
| 419 | 
            +
                expect(screen.getByText('Review details').disabled).toBeFalsy();
         | 
| 420 | 
            +
                expect(atHourly().value).toBe(newMinutes);
         | 
| 401 421 | 
             
              });
         | 
| 402 422 | 
             
            });
         |