Skip to content

Commit 2310a73

Browse files
MariaAgaadamruzicka
authored andcommitted
Fixes #38609 - fix job details rerun all button
1 parent a0e14cf commit 2310a73

File tree

7 files changed

+80
-51
lines changed

7 files changed

+80
-51
lines changed

webpack/JobInvocationDetail/CheckboxesActions.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable camelcase */
12
import {
23
Button,
34
Dropdown,
@@ -21,16 +22,28 @@ import {
2122
MAX_HOSTS_API_SIZE,
2223
DIRECT_OPEN_HOST_LIMIT,
2324
} from './JobInvocationConstants';
24-
import { selectTemplateInvocation } from './JobInvocationSelectors';
25+
import { selectHasPermission, selectItems } from './JobInvocationSelectors';
2526
import OpenAllInvocationsModal, { PopupAlert } from './OpenAllInvocationsModal';
2627

27-
export const CheckboxesActions = ({ selectedIds, failedCount, jobID }) => {
28+
export const CheckboxesActions = ({
29+
selectedIds,
30+
failedCount,
31+
jobID,
32+
filter,
33+
bulkParams,
34+
}) => {
2835
const [isModalOpen, setIsModalOpen] = useState(false);
2936
const [showAlert, setShowAlert] = useState(false);
3037
const [isOpenFailed, setIsOpenFailed] = useState(false);
31-
const permissions = useSelector(
32-
state => selectTemplateInvocation(state)?.permissions
38+
const hasPermission = useSelector(
39+
selectHasPermission('create_job_invocations')
3340
);
41+
const jobSearchQuery = useSelector(selectItems)?.targeting?.search_query;
42+
const filterQuery =
43+
filter && filter !== 'all_statuses'
44+
? ` and job_invocation.result = ${filter}`
45+
: '';
46+
const combinedQuery = `${bulkParams}${filterQuery}`;
3447

3548
const { response: failedHostsData } = useAPI(
3649
'get',
@@ -131,19 +144,16 @@ export const CheckboxesActions = ({ selectedIds, failedCount, jobID }) => {
131144
</Button>
132145
);
133146

134-
const searchParams = new URLSearchParams();
135-
selectedIds.forEach(id => searchParams.append('host_ids[]', id));
136-
137147
const RerunSelectedButton = () => (
138148
<Button
139149
aria-label="rerun selected template invocations"
140150
className="rerun-selected-button"
141151
component="a"
142152
href={foremanUrl(
143-
`/job_invocations/${jobID}/rerun?${searchParams.toString()}`
153+
`/job_invocations/${jobID}/rerun?search=(${jobSearchQuery}) AND (${combinedQuery})`
144154
)}
145155
// eslint-disable-next-line camelcase
146-
isDisabled={selectedIds.length === 0 || !permissions?.execute_jobs}
156+
isDisabled={selectedIds.length === 0 || !hasPermission}
147157
isInline
148158
ouiaId="template-invocation-rerun-selected-button"
149159
variant="secondary"
@@ -176,4 +186,11 @@ CheckboxesActions.propTypes = {
176186
selectedIds: PropTypes.array.isRequired,
177187
failedCount: PropTypes.number.isRequired,
178188
jobID: PropTypes.string.isRequired,
189+
bulkParams: PropTypes.string,
190+
filter: PropTypes.string,
191+
};
192+
193+
CheckboxesActions.defaultProps = {
194+
bulkParams: '',
195+
filter: '',
179196
};

webpack/JobInvocationDetail/JobInvocationConstants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS';
1919
export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
2020
export const MAX_HOSTS_API_SIZE = 100;
2121
export const DIRECT_OPEN_HOST_LIMIT = 3;
22+
export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS';
2223
export const currentPermissionsUrl = foremanUrl(
2324
'/api/v2/permissions/current_permissions'
2425
);

webpack/JobInvocationDetail/JobInvocationHostTable.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Columns, {
3434
MAX_HOSTS_API_SIZE,
3535
STATUS_UPPERCASE,
3636
LIST_TEMPLATE_INVOCATIONS,
37+
ALL_JOB_HOSTS,
3738
} from './JobInvocationConstants';
3839
import { PopupAlert } from './OpenAllInvocationsModal';
3940
import { TemplateInvocation } from './TemplateInvocation';
@@ -117,6 +118,7 @@ const JobInvocationHostTable = ({
117118
`/api/job_invocations/${id}/hosts`,
118119
{
119120
params: apiAllParams,
121+
key: ALL_JOB_HOSTS,
120122
}
121123
);
122124

@@ -138,6 +140,7 @@ const JobInvocationHostTable = ({
138140

139141
const {
140142
updateSearchQuery: updateSearchQueryBulk,
143+
fetchBulkParams,
141144
inclusionSet,
142145
exclusionSet,
143146
...selectAllOptions
@@ -329,10 +332,12 @@ const JobInvocationHostTable = ({
329332
setDropdownFilter={wrapSetSelectedFilter}
330333
/>,
331334
<CheckboxesActions
335+
bulkParams={selectedCount > 0 ? fetchBulkParams() : null}
332336
selectedIds={selectedIds}
333337
failedCount={failedCount}
334338
jobID={id}
335339
key="checkboxes-actions"
340+
filter={selectedFilter}
336341
/>,
337342
]}
338343
selectionToolbar={selectionToolbar}

webpack/JobInvocationDetail/JobInvocationSelectors.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
selectAPIResponse,
44
selectAPIStatus,
55
} from 'foremanReact/redux/API/APISelectors';
6+
import { STATUS as APIStatus } from 'foremanReact/constants';
67
import {
78
JOB_INVOCATION_KEY,
89
GET_TASK,
910
GET_TEMPLATE_INVOCATION,
1011
LIST_TEMPLATE_INVOCATIONS,
12+
CURRENT_PERMISSIONS,
1113
} from './JobInvocationConstants';
1214

1315
export const selectItems = state =>
@@ -27,3 +29,16 @@ export const selectTemplateInvocationStatus = hostID => state =>
2729
export const selectTemplateInvocationList = state =>
2830
selectAPIResponse(state, LIST_TEMPLATE_INVOCATIONS)
2931
?.template_invocations_task_by_hosts;
32+
33+
export const selectCurrentPermisions = state =>
34+
selectAPIResponse(state, CURRENT_PERMISSIONS);
35+
36+
export const selectHasPermission = permissionRequired => state => {
37+
const status = selectAPIStatus(state, CURRENT_PERMISSIONS);
38+
const selectCurrentPermissions = selectCurrentPermisions(state)?.results;
39+
return status === APIStatus.RESOLVED
40+
? selectCurrentPermissions?.some(
41+
permission => permission.name === permissionRequired
42+
)
43+
: false;
44+
};

webpack/JobInvocationDetail/JobInvocationToolbarButtons.js

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
} from '@patternfly/react-core/deprecated';
1212
import { translate as __ } from 'foremanReact/common/I18n';
1313
import { foremanUrl } from 'foremanReact/common/helpers';
14-
import { STATUS as APIStatus } from 'foremanReact/constants';
1514
import { get } from 'foremanReact/redux/API';
1615
import {
1716
cancelJob,
@@ -23,14 +22,12 @@ import {
2322
GET_REPORT_TEMPLATES,
2423
GET_REPORT_TEMPLATE_INPUTS,
2524
} from './JobInvocationConstants';
26-
import { selectTaskCancelable } from './JobInvocationSelectors';
25+
import {
26+
selectTaskCancelable,
27+
selectHasPermission,
28+
} from './JobInvocationSelectors';
2729

28-
const JobInvocationToolbarButtons = ({
29-
jobId,
30-
data,
31-
currentPermissions,
32-
permissionsStatus,
33-
}) => {
30+
const JobInvocationToolbarButtons = ({ jobId, data }) => {
3431
const { succeeded, failed, task, recurrence, permissions } = data;
3532
const recurringEnabled = recurrence?.state === 'active';
3633
const canViewForemanTasks = permissions
@@ -40,6 +37,8 @@ const JobInvocationToolbarButtons = ({
4037
? permissions.edit_recurring_logics
4138
: false;
4239
const isTaskCancelable = useSelector(selectTaskCancelable);
40+
const useHasPermission = permissionRequired =>
41+
useSelector(selectHasPermission(permissionRequired));
4342
const [isActionOpen, setIsActionOpen] = useState(false);
4443
const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
4544
const [templateInputId, setTemplateInputId] = useState(undefined);
@@ -58,12 +57,6 @@ const JobInvocationToolbarButtons = ({
5857
setIsActionOpen(false);
5958
onActionFocus();
6059
};
61-
const hasPermission = permissionRequired =>
62-
permissionsStatus === APIStatus.RESOLVED
63-
? currentPermissions?.some(
64-
permission => permission.name === permissionRequired
65-
)
66-
: false;
6760

6861
useEffect(() => {
6962
dispatch(
@@ -142,7 +135,9 @@ const JobInvocationToolbarButtons = ({
142135
ouiaId="rerun-succeeded-dropdown-item"
143136
href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
144137
key="rerun-succeeded"
145-
isDisabled={!(succeeded > 0) || !hasPermission('create_job_invocations')}
138+
isDisabled={
139+
!useHasPermission('create_job_invocations') || !(succeeded > 0)
140+
}
146141
description="Rerun job on successful hosts"
147142
>
148143
{__('Rerun successful')}
@@ -151,7 +146,7 @@ const JobInvocationToolbarButtons = ({
151146
ouiaId="rerun-failed-dropdown-item"
152147
href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
153148
key="rerun-failed"
154-
isDisabled={!(failed > 0) || !hasPermission('create_job_invocations')}
149+
isDisabled={!useHasPermission('create_job_invocations') || !(failed > 0)}
155150
description="Rerun job on failed hosts"
156151
>
157152
{__('Rerun failed')}
@@ -171,7 +166,9 @@ const JobInvocationToolbarButtons = ({
171166
onClick={() => dispatch(cancelJob(jobId, false))}
172167
key="cancel"
173168
component="button"
174-
isDisabled={!isTaskCancelable || !hasPermission('cancel_job_invocations')}
169+
isDisabled={
170+
!useHasPermission('cancel_job_invocations') || !isTaskCancelable
171+
}
175172
description="Cancel job gracefully"
176173
>
177174
{__('Cancel')}
@@ -181,7 +178,9 @@ const JobInvocationToolbarButtons = ({
181178
onClick={() => dispatch(cancelJob(jobId, true))}
182179
key="abort"
183180
component="button"
184-
isDisabled={!isTaskCancelable || !hasPermission('cancel_job_invocations')}
181+
isDisabled={
182+
!useHasPermission('cancel_job_invocations') || !isTaskCancelable
183+
}
185184
description="Cancel job immediately"
186185
>
187186
{__('Abort')}
@@ -210,9 +209,9 @@ const JobInvocationToolbarButtons = ({
210209
)}
211210
variant="secondary"
212211
isDisabled={
212+
!useHasPermission('generate_report_templates') ||
213213
task?.state === STATUS.PENDING ||
214-
templateInputId === undefined ||
215-
!hasPermission('generate_report_templates')
214+
templateInputId === undefined
216215
}
217216
>
218217
{__(`Create report`)}
@@ -234,7 +233,7 @@ const JobInvocationToolbarButtons = ({
234233
key="rerun"
235234
href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
236235
variant="control"
237-
isDisabled={!hasPermission('create_job_invocations')}
236+
isDisabled={!useHasPermission('create_job_invocations')}
238237
>
239238
{__(`Rerun all`)}
240239
</Button>,
@@ -255,12 +254,6 @@ const JobInvocationToolbarButtons = ({
255254
JobInvocationToolbarButtons.propTypes = {
256255
jobId: PropTypes.string.isRequired,
257256
data: PropTypes.object.isRequired,
258-
currentPermissions: PropTypes.array,
259-
permissionsStatus: PropTypes.string,
260-
};
261-
JobInvocationToolbarButtons.defaultProps = {
262-
currentPermissions: undefined,
263-
permissionsStatus: undefined,
264257
};
265258

266259
export default JobInvocationToolbarButtons;

webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
55
import '@testing-library/jest-dom/extend-expect';
66
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
77
import { CheckboxesActions } from '../CheckboxesActions';
8+
import * as selectors from '../JobInvocationSelectors';
89
import { PopupAlert } from '../OpenAllInvocationsModal';
910

1011
jest.mock('foremanReact/common/hooks/API/APIHooks');
12+
13+
jest.mock('../JobInvocationSelectors');
1114
jest.mock('../JobInvocationConstants', () => ({
1215
...jest.requireActual('../JobInvocationConstants'),
1316
templateInvocationPageUrl: jest.fn(
1417
(hostId, jobId) => `url/${hostId}/${jobId}`
1518
),
1619
DIRECT_OPEN_HOST_LIMIT: 3,
1720
}));
18-
21+
selectors.selectItems.mockImplementation(() => ({
22+
targeting: { search_query: 'name~*' },
23+
}));
24+
selectors.selectHasPermission.mockImplementation(() => () => () => true);
1925
const mockStore = configureStore([]);
2026
const store = mockStore({
2127
templateInvocation: {
@@ -178,6 +184,7 @@ describe('TableToolbarActions', () => {
178184
render(
179185
<Provider store={store}>
180186
<CheckboxesActions
187+
bulkParams="(id ^ (101, 102, 103))"
181188
selectedIds={selectedIds}
182189
failedCount={1}
183190
jobID={jobID}
@@ -188,7 +195,7 @@ describe('TableToolbarActions', () => {
188195
expect(rerunLink).toBeEnabled();
189196
const expectedSearchParams = new URLSearchParams();
190197
selectedIds.forEach(id => expectedSearchParams.append('host_ids[]', id));
191-
const expectedHref = `foreman/job_invocations/${jobID}/rerun?${expectedSearchParams.toString()}`;
198+
const expectedHref = `foreman/job_invocations/42/rerun?search=(name~*) AND ((id ^ (101, 102, 103)))`;
192199
expect(rerunLink).toHaveAttribute('href', expectedHref);
193200
});
194201
});

webpack/JobInvocationDetail/index.js

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,9 @@ const JobInvocationDetailPage = ({
5252
statusLabel === STATUS.SUCCEEDED ||
5353
statusLabel === STATUS.CANCELLED;
5454
const autoRefresh = task?.state === STATUS.PENDING || false;
55-
const { response, status: permissionsApiStatus } = useAPI(
56-
'get',
57-
currentPermissionsUrl,
58-
CURRENT_PERMISSIONS
59-
);
55+
useAPI('get', currentPermissionsUrl, {
56+
key: CURRENT_PERMISSIONS,
57+
});
6058
const [selectedFilter, setSelectedFilter] = useState('');
6159

6260
const handleFilterChange = newFilter => {
@@ -117,14 +115,7 @@ const JobInvocationDetailPage = ({
117115
<PageLayout
118116
header={description}
119117
breadcrumbOptions={breadcrumbOptions}
120-
toolbarButtons={
121-
<JobInvocationToolbarButtons
122-
jobId={id}
123-
data={items}
124-
currentPermissions={response.results}
125-
permissionsStatus={permissionsApiStatus}
126-
/>
127-
}
118+
toolbarButtons={<JobInvocationToolbarButtons jobId={id} data={items} />}
128119
searchable={false}
129120
>
130121
<Flex

0 commit comments

Comments
 (0)