foreman_remote_execution 9.0.1 → 10.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/release.yml +14 -0
- data/.tx/config +3 -1
- data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +1 -0
- data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +10 -1
- data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
- data/app/controllers/job_invocations_controller.rb +30 -1
- data/app/controllers/ui_job_wizard_controller.rb +3 -1
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +28 -2
- data/app/models/remote_execution_feature.rb +11 -8
- data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
- data/app/views/job_invocations/_form.html.erb +1 -1
- data/app/views/job_invocations/show.html.erb +1 -1
- data/app/views/job_invocations/welcome.html.erb +1 -1
- data/app/views/templates/script/run_command.erb +1 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +1 -1
- data/db/migrate/20220426145007_add_unique_feature_label_index.rb +14 -0
- data/lib/foreman_remote_execution/engine.rb +9 -9
- data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +12 -3
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/Makefile +6 -3
- data/locale/action_names.rb +1 -1
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +67 -19
- data/locale/en/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en/foreman_remote_execution.po +56 -8
- data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en_GB/foreman_remote_execution.po +58 -10
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +71 -23
- data/locale/foreman_remote_execution.pot +216 -141
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +104 -56
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +72 -24
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +63 -15
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +68 -20
- data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ru/foreman_remote_execution.po +63 -15
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +71 -23
- data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_TW/foreman_remote_execution.po +63 -15
- data/package.json +6 -6
- data/webpack/JobWizard/Footer.js +104 -0
- data/webpack/JobWizard/JobWizard.js +105 -32
- data/webpack/JobWizard/JobWizard.scss +6 -17
- data/webpack/JobWizard/JobWizardConstants.js +1 -1
- data/webpack/JobWizard/JobWizardPageRerun.js +2 -0
- data/webpack/JobWizard/StartsBeforeErrorAlert.js +17 -0
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +8 -0
- data/webpack/JobWizard/__tests__/fixtures.js +13 -1
- data/webpack/JobWizard/__tests__/integration.test.js +15 -0
- data/webpack/JobWizard/__tests__/validation.test.js +36 -0
- data/webpack/JobWizard/autofill.js +1 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +29 -10
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +8 -0
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +5 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +28 -14
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +41 -4
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +16 -10
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +51 -3
- data/webpack/JobWizard/steps/ReviewDetails/ReviewDetails.test.js +117 -0
- data/webpack/JobWizard/steps/ReviewDetails/helpers.js +43 -0
- data/webpack/JobWizard/steps/ReviewDetails/index.js +169 -18
- data/webpack/JobWizard/steps/Schedule/QueryType.js +1 -1
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +0 -1
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +52 -18
- data/webpack/JobWizard/steps/form/DateTimePicker.js +1 -2
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +0 -1
- data/webpack/JobWizard/steps/form/SelectField.js +0 -1
- data/webpack/JobWizard/submit.js +14 -3
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
- data/webpack/react_app/components/RecentJobsCard/constants.js +2 -2
- data/webpack/react_app/components/RegistrationExtension/RexPull.js +0 -2
- metadata +23 -6
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import { Chip, ChipGroup, Button } from '@patternfly/react-core';
|
4
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { hostMethods } from '../../JobWizardConstants';
|
6
6
|
|
7
7
|
const SelectedChip = ({ selected, setSelected, categoryName }) => {
|
@@ -10,19 +10,33 @@ const SelectedChip = ({ selected, setSelected, categoryName }) => {
|
|
10
10
|
oldSelected.filter(({ id }) => id !== itemToRemove)
|
11
11
|
);
|
12
12
|
};
|
13
|
+
const NUM_CHIPS = 3;
|
13
14
|
return (
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
15
|
+
<>
|
16
|
+
<ChipGroup
|
17
|
+
className="hosts-chip-group"
|
18
|
+
categoryName={categoryName}
|
19
|
+
isClosable
|
20
|
+
closeBtnAriaLabel="Remove all"
|
21
|
+
collapsedText={sprintf(__('%s more'), selected.length - NUM_CHIPS)}
|
22
|
+
numChips={NUM_CHIPS}
|
23
|
+
onClick={() => {
|
24
|
+
setSelected(() => []);
|
25
|
+
}}
|
26
|
+
>
|
27
|
+
{selected.map(({ name, id }, index) => (
|
28
|
+
<Chip
|
29
|
+
key={index}
|
30
|
+
id={`${categoryName}-${id}`}
|
31
|
+
onClick={() => deleteItem(id)}
|
32
|
+
closeBtnAriaLabel={`Remove ${name}`}
|
33
|
+
>
|
34
|
+
{name}
|
35
|
+
</Chip>
|
36
|
+
))}
|
37
|
+
</ChipGroup>
|
38
|
+
{selected.length > 0 && <br />}
|
39
|
+
</>
|
26
40
|
);
|
27
41
|
};
|
28
42
|
|
@@ -75,7 +89,7 @@ export const SelectedChips = ({
|
|
75
89
|
/>
|
76
90
|
{showClear && (
|
77
91
|
<Button variant="link" className="clear-chips" onClick={clearAll}>
|
78
|
-
{__('Clear filters')}
|
92
|
+
{__('Clear all filters')}
|
79
93
|
</Button>
|
80
94
|
)}
|
81
95
|
</div>
|
@@ -30,6 +30,27 @@ describe('Hosts', () => {
|
|
30
30
|
const select = name =>
|
31
31
|
screen.getByRole('button', { name: `${name} toggle` });
|
32
32
|
fireEvent.click(select('hosts'));
|
33
|
+
await act(async () => {
|
34
|
+
fireEvent.click(screen.getByText('host1'));
|
35
|
+
fireEvent.click(select('hosts'));
|
36
|
+
});
|
37
|
+
expect(
|
38
|
+
screen.queryAllByText('Please select at least one host')
|
39
|
+
).toHaveLength(0);
|
40
|
+
await act(async () => {
|
41
|
+
fireEvent.click(select('hosts'));
|
42
|
+
});
|
43
|
+
await act(async () => {
|
44
|
+
fireEvent.click(
|
45
|
+
screen.getByText('host1', {
|
46
|
+
selector: '.pf-c-select__menu-item',
|
47
|
+
})
|
48
|
+
);
|
49
|
+
fireEvent.blur(select('hosts'));
|
50
|
+
});
|
51
|
+
expect(
|
52
|
+
screen.queryAllByText('Please select at least one host')
|
53
|
+
).toHaveLength(1);
|
33
54
|
await act(async () => {
|
34
55
|
fireEvent.click(screen.getByText('host1'));
|
35
56
|
fireEvent.click(screen.getByText('host2'));
|
@@ -59,7 +80,7 @@ describe('Hosts', () => {
|
|
59
80
|
expect(screen.queryAllByText('host1')).toHaveLength(1);
|
60
81
|
expect(screen.queryAllByText('host2')).toHaveLength(1);
|
61
82
|
expect(screen.queryAllByText('host3')).toHaveLength(0);
|
62
|
-
const chip1 = screen.getByRole('button', { name: '
|
83
|
+
const chip1 = screen.getByRole('button', { name: 'Remove host1 host1' });
|
63
84
|
await act(async () => {
|
64
85
|
fireEvent.click(chip1);
|
65
86
|
});
|
@@ -70,7 +91,7 @@ describe('Hosts', () => {
|
|
70
91
|
expect(screen.queryAllByText('host_collection1')).toHaveLength(1);
|
71
92
|
|
72
93
|
await act(async () => {
|
73
|
-
fireEvent.click(screen.getByText('Category and
|
94
|
+
fireEvent.click(screen.getByText('Category and template'));
|
74
95
|
});
|
75
96
|
await act(async () => {
|
76
97
|
fireEvent.click(screen.getByText('Target hosts and inputs'));
|
@@ -79,7 +100,7 @@ describe('Hosts', () => {
|
|
79
100
|
expect(screen.queryAllByText('host_group1')).toHaveLength(1);
|
80
101
|
|
81
102
|
await act(async () => {
|
82
|
-
fireEvent.click(screen.getByText('Clear filters'));
|
103
|
+
fireEvent.click(screen.getByText('Clear all filters'));
|
83
104
|
});
|
84
105
|
|
85
106
|
expect(screen.queryAllByText('host2')).toHaveLength(0);
|
@@ -107,6 +128,13 @@ describe('Hosts', () => {
|
|
107
128
|
expect(screen.queryAllByText('Host groups')).toHaveLength(1);
|
108
129
|
expect(screen.queryAllByText('Search query')).toHaveLength(1);
|
109
130
|
expect(screen.queryAllByText('Host collections')).toHaveLength(0);
|
131
|
+
|
132
|
+
await act(async () => {
|
133
|
+
fireEvent.click(
|
134
|
+
// Close the select
|
135
|
+
screen.getByText('Hosts', { selector: '.pf-c-select__toggle-text' })
|
136
|
+
);
|
137
|
+
});
|
110
138
|
});
|
111
139
|
it('Host fill list from url', async () => {
|
112
140
|
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
@@ -151,8 +179,9 @@ describe('Hosts', () => {
|
|
151
179
|
|
152
180
|
it('input fill from url', async () => {
|
153
181
|
const inputText = 'test text';
|
182
|
+
const advancedInputText = 'test adv text';
|
154
183
|
routerSelectors.selectRouterLocation.mockImplementation(() => ({
|
155
|
-
search: `feature=test_feature&inputs[plain hidden]=${inputText}`,
|
184
|
+
search: `host_ids%5B%5D=host1&host_ids%5B%5D=host3&feature=test_feature&inputs[plain hidden]=${inputText}&inputs[adv plain hidden]=${advancedInputText}`,
|
156
185
|
}));
|
157
186
|
render(
|
158
187
|
<MockedProvider mocks={gqlMock} addTypename={false}>
|
@@ -175,5 +204,13 @@ describe('Hosts', () => {
|
|
175
204
|
selector: 'textarea',
|
176
205
|
});
|
177
206
|
expect(textField.value).toBe(inputText);
|
207
|
+
|
208
|
+
await act(async () => {
|
209
|
+
fireEvent.click(screen.getByText('Advanced fields'));
|
210
|
+
});
|
211
|
+
const advancedTextField = screen.getByLabelText('adv plain hidden', {
|
212
|
+
selector: 'textarea',
|
213
|
+
});
|
214
|
+
expect(advancedTextField.value).toBe(advancedInputText);
|
178
215
|
});
|
179
216
|
});
|
@@ -1,18 +1,24 @@
|
|
1
1
|
export const buildHostQuery = (selected, search) => {
|
2
2
|
const { hosts, hostCollections, hostGroups } = selected;
|
3
|
-
const hostsSearch = `
|
4
|
-
const hostCollectionsSearch = `
|
3
|
+
const hostsSearch = `id ^ (${hosts.map(({ id }) => id).join(',')})`;
|
4
|
+
const hostCollectionsSearch = `host_collection_id ^ (${hostCollections
|
5
5
|
.map(({ id }) => id)
|
6
|
-
.join(',')})
|
7
|
-
const hostGroupsSearch = `
|
6
|
+
.join(',')})`;
|
7
|
+
const hostGroupsSearch = `hostgroup_id ^ (${hostGroups
|
8
8
|
.map(({ id }) => id)
|
9
|
-
.join(',')})
|
10
|
-
|
9
|
+
.join(',')})`;
|
10
|
+
const queryParts = [
|
11
11
|
hosts.length ? hostsSearch : false,
|
12
12
|
hostCollections.length ? hostCollectionsSearch : false,
|
13
13
|
hostGroups.length ? hostGroupsSearch : false,
|
14
|
-
search.length ?
|
15
|
-
]
|
16
|
-
|
17
|
-
|
14
|
+
search.length ? search : false,
|
15
|
+
].filter(Boolean);
|
16
|
+
|
17
|
+
if (queryParts.length === 0) {
|
18
|
+
return 'name=a AND name=b';
|
19
|
+
}
|
20
|
+
if (queryParts.length === 1) {
|
21
|
+
return queryParts[0] || 'name=a AND name=b';
|
22
|
+
}
|
23
|
+
return queryParts.map(p => `(${p})`).join(' or ') || 'name=a AND name=b';
|
18
24
|
};
|
@@ -51,6 +51,30 @@ const HostsAndInputs = ({
|
|
51
51
|
const isLoading = useSelector(selectIsLoadingHosts);
|
52
52
|
const templateInputs = useSelector(selectTemplateInputs);
|
53
53
|
const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
|
54
|
+
const [wasFocus, setWasFocus] = useState(false);
|
55
|
+
const [isError, setIsError] = useState(false);
|
56
|
+
useEffect(() => {
|
57
|
+
if (wasFocus) {
|
58
|
+
if (
|
59
|
+
selected.hosts.length === 0 &&
|
60
|
+
selected.hostCollections.length === 0 &&
|
61
|
+
selected.hostGroups.length === 0 &&
|
62
|
+
hostsSearchQuery.length === 0
|
63
|
+
) {
|
64
|
+
setIsError(true);
|
65
|
+
} else {
|
66
|
+
setIsError(false);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}, [
|
70
|
+
hostMethod,
|
71
|
+
hostsSearchQuery.length,
|
72
|
+
selected,
|
73
|
+
selected.hostCollections.length,
|
74
|
+
selected.hostGroups.length,
|
75
|
+
selected.hosts.length,
|
76
|
+
wasFocus,
|
77
|
+
]);
|
54
78
|
useEffect(() => {
|
55
79
|
debounce(() => {
|
56
80
|
dispatch(
|
@@ -99,6 +123,9 @@ const HostsAndInputs = ({
|
|
99
123
|
const clearSearch = () => {
|
100
124
|
setHostsSearchQuery('');
|
101
125
|
};
|
126
|
+
const [errorText, setErrorText] = useState(
|
127
|
+
__('Please select at least one host')
|
128
|
+
);
|
102
129
|
return (
|
103
130
|
<div className="target-hosts-and-inputs">
|
104
131
|
<WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
|
@@ -110,8 +137,13 @@ const HostsAndInputs = ({
|
|
110
137
|
/>
|
111
138
|
)}
|
112
139
|
<Form>
|
113
|
-
<FormGroup
|
114
|
-
|
140
|
+
<FormGroup
|
141
|
+
fieldId="host_selection"
|
142
|
+
id="host-selection"
|
143
|
+
helperTextInvalid={errorText}
|
144
|
+
validated={isError ? 'error' : 'default'}
|
145
|
+
>
|
146
|
+
<InputGroup onBlur={() => setWasFocus(true)}>
|
115
147
|
<SelectField
|
116
148
|
isRequired
|
117
149
|
className="target-method-select"
|
@@ -123,7 +155,23 @@ const HostsAndInputs = ({
|
|
123
155
|
}
|
124
156
|
return true;
|
125
157
|
})}
|
126
|
-
setValue={
|
158
|
+
setValue={val => {
|
159
|
+
setHostMethod(val);
|
160
|
+
if (val === hostMethods.searchQuery) {
|
161
|
+
setErrorText(__('Please enter a search query'));
|
162
|
+
}
|
163
|
+
if (val === hostMethods.hosts) {
|
164
|
+
setErrorText(__('Please select at least one host'));
|
165
|
+
}
|
166
|
+
if (val === hostMethods.hostCollections) {
|
167
|
+
setErrorText(
|
168
|
+
__('Please select at least one host collection')
|
169
|
+
);
|
170
|
+
}
|
171
|
+
if (val === hostMethods.hostGroups) {
|
172
|
+
setErrorText(__('Please select at least one host group'));
|
173
|
+
}
|
174
|
+
}}
|
127
175
|
value={hostMethod}
|
128
176
|
/>
|
129
177
|
{hostMethod === hostMethods.searchQuery && (
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import { render, fireEvent, screen, act } from '@testing-library/react';
|
4
|
+
import { MockedProvider } from '@apollo/client/testing';
|
5
|
+
import * as APIHooks from 'foremanReact/common/hooks/API/APIHooks';
|
6
|
+
import * as api from 'foremanReact/redux/API';
|
7
|
+
|
8
|
+
import JobWizardPageRerun from '../../JobWizardPageRerun';
|
9
|
+
import * as selectors from '../../JobWizardSelectors';
|
10
|
+
import {
|
11
|
+
testSetup,
|
12
|
+
mockApi,
|
13
|
+
gqlMock,
|
14
|
+
jobInvocation,
|
15
|
+
} from '../../__tests__/fixtures';
|
16
|
+
|
17
|
+
const store = testSetup(selectors, api);
|
18
|
+
mockApi(api);
|
19
|
+
jest.spyOn(APIHooks, 'useAPI');
|
20
|
+
APIHooks.useAPI.mockImplementation((action, url) => {
|
21
|
+
if (url === '/ui_job_wizard/job_invocation?id=57') {
|
22
|
+
return { response: jobInvocation, status: 'RESOLVED' };
|
23
|
+
}
|
24
|
+
return {};
|
25
|
+
});
|
26
|
+
jest.useFakeTimers();
|
27
|
+
|
28
|
+
describe('ReviewDetails', () => {
|
29
|
+
it('should call goToStepByName function when StepButton is clicked', async () => {
|
30
|
+
render(
|
31
|
+
<MockedProvider mocks={gqlMock} addTypename={false}>
|
32
|
+
<Provider store={store}>
|
33
|
+
<JobWizardPageRerun
|
34
|
+
match={{
|
35
|
+
params: { id: '57' },
|
36
|
+
}}
|
37
|
+
/>
|
38
|
+
</Provider>
|
39
|
+
</MockedProvider>
|
40
|
+
);
|
41
|
+
|
42
|
+
act(() => {
|
43
|
+
fireEvent.click(screen.getByText('Type of execution'));
|
44
|
+
});
|
45
|
+
act(() => {
|
46
|
+
fireEvent.click(screen.getByText('Future execution'));
|
47
|
+
|
48
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
|
49
|
+
});
|
50
|
+
act(() => {
|
51
|
+
fireEvent.click(screen.getByRole('button', { name: 'Future execution' }));
|
52
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
|
53
|
+
});
|
54
|
+
|
55
|
+
const newStartAtDate = '2030/03/12';
|
56
|
+
const newStartAtTime = '14:27';
|
57
|
+
const startsAtDateField = () =>
|
58
|
+
screen.getByLabelText('starts at datepicker');
|
59
|
+
const startsAtTimeField = () =>
|
60
|
+
screen.getByLabelText('starts at timepicker');
|
61
|
+
|
62
|
+
await act(async () => {
|
63
|
+
await fireEvent.change(startsAtDateField(), {
|
64
|
+
target: { value: newStartAtDate },
|
65
|
+
});
|
66
|
+
fireEvent.change(startsAtTimeField(), {
|
67
|
+
target: { value: newStartAtTime },
|
68
|
+
});
|
69
|
+
jest.advanceTimersByTime(1000);
|
70
|
+
});
|
71
|
+
|
72
|
+
act(() => {
|
73
|
+
fireEvent.click(screen.getByText('Review details'));
|
74
|
+
});
|
75
|
+
expect(screen.getAllByText('Review details')).toHaveLength(3);
|
76
|
+
fireEvent.click(
|
77
|
+
screen.getByText('Job template', {
|
78
|
+
selector: '.pf-c-button',
|
79
|
+
})
|
80
|
+
);
|
81
|
+
expect(screen.getAllByText('Category and template')).toHaveLength(3);
|
82
|
+
|
83
|
+
await act(async () => {
|
84
|
+
fireEvent.click(screen.getByText('Review details'));
|
85
|
+
jest.advanceTimersByTime(1000);
|
86
|
+
});
|
87
|
+
act(() => {
|
88
|
+
fireEvent.click(
|
89
|
+
screen.getByText('Target hosts', {
|
90
|
+
selector: '.pf-c-button',
|
91
|
+
})
|
92
|
+
);
|
93
|
+
jest.advanceTimersByTime(1000); // to handle pf4 date picker popover useTimer
|
94
|
+
});
|
95
|
+
expect(screen.getAllByText('Target hosts and inputs')).toHaveLength(3);
|
96
|
+
act(() => {
|
97
|
+
fireEvent.click(screen.getByText('Review details'));
|
98
|
+
});
|
99
|
+
act(() => {
|
100
|
+
fireEvent.click(
|
101
|
+
screen.getByText('Advanced fields', {
|
102
|
+
selector: '.pf-c-button',
|
103
|
+
})
|
104
|
+
);
|
105
|
+
jest.advanceTimersByTime(1000);
|
106
|
+
});
|
107
|
+
expect(screen.getAllByText('Advanced fields')).toHaveLength(3);
|
108
|
+
|
109
|
+
act(() => {
|
110
|
+
fireEvent.click(screen.getByText('Review details'));
|
111
|
+
});
|
112
|
+
act(() => {
|
113
|
+
fireEvent.click(screen.getByText('Recurrence'));
|
114
|
+
});
|
115
|
+
expect(screen.getAllByText('Schedule')).toHaveLength(3);
|
116
|
+
});
|
117
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
2
|
+
import { getWeekDays } from '../Schedule/RepeatWeek';
|
3
|
+
import { repeatTypes } from '../../JobWizardConstants';
|
4
|
+
|
5
|
+
export const parseEnd = (ends, isNeverEnds, repeatAmount) => {
|
6
|
+
if (isNeverEnds) {
|
7
|
+
return __('Never');
|
8
|
+
}
|
9
|
+
if (ends) {
|
10
|
+
const endsDate = new Date(ends);
|
11
|
+
return endsDate.toString();
|
12
|
+
}
|
13
|
+
return sprintf(__('After %s occurences'), repeatAmount);
|
14
|
+
};
|
15
|
+
|
16
|
+
export const parseRepeat = (repeatType, repeatData) => {
|
17
|
+
switch (repeatType) {
|
18
|
+
case repeatTypes.hourly:
|
19
|
+
return sprintf(__('Every hour at minute %s'), repeatData.minute);
|
20
|
+
case repeatTypes.daily:
|
21
|
+
return sprintf(__('Every day at %s'), repeatData.at);
|
22
|
+
case repeatTypes.weekly: {
|
23
|
+
const daysKeys = Object.keys(repeatData.daysOfWeek).filter(
|
24
|
+
k => repeatData.daysOfWeek[k]
|
25
|
+
);
|
26
|
+
const days = getWeekDays()
|
27
|
+
.filter((d, index) => index in daysKeys)
|
28
|
+
.join(', ');
|
29
|
+
return sprintf(__('Every week on %s at %s'), days, repeatData.at);
|
30
|
+
}
|
31
|
+
case repeatTypes.monthly:
|
32
|
+
return sprintf(
|
33
|
+
__('Every month on %s at %s'),
|
34
|
+
repeatData.days,
|
35
|
+
repeatData.at
|
36
|
+
);
|
37
|
+
case repeatTypes.cronline:
|
38
|
+
return `${__('Cron line')} - ${repeatData.cronline}`;
|
39
|
+
|
40
|
+
default:
|
41
|
+
return '';
|
42
|
+
}
|
43
|
+
};
|