foreman_remote_execution 9.0.1 → 10.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
};
|