Skip to content

Commit

Permalink
Fixes #38165 - Add checkboxes actions to job invocation page
Browse files Browse the repository at this point in the history
  • Loading branch information
kmalyjur committed Jan 28, 2025
1 parent 45bd8b8 commit fe71baf
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 28 deletions.
2 changes: 1 addition & 1 deletion app/views/api/v2/job_invocations/hosts.json.rabl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
collection @hosts

attribute :name, :operatingsystem_id, :operatingsystem_name, :hostgroup_id, :hostgroup_name, :id
attribute :id, :name, :operatingsystem_id, :operatingsystem_name, :hostgroup_id, :hostgroup_name

node :job_status do |host|
@host_statuses[host.id]
Expand Down
193 changes: 193 additions & 0 deletions webpack/JobInvocationDetail/CheckboxesActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
Dropdown,
DropdownItem,
DropdownSeparator,
KebabToggle,
} from '@patternfly/react-core';
import { useDispatch, useSelector } from 'react-redux';
import { translate as __ } from 'foremanReact/common/I18n';
import { addToast } from 'foremanReact/components/ToastsList';
import { APIActions } from 'foremanReact/redux/API';
import { selectTaskCancelable } from './JobInvocationSelectors';
import { openLink, useModalToggle, OpenAllModal } from './OpenAllHelpers';
import { hasPermission } from './JobInvocationConstants';

export const CheckboxesActions = ({
jobID,
bulkParams,
selectedCount,
currentPermissions,
permissionsStatus,
failedHosts,
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const isTaskCancelable = useSelector(selectTaskCancelable);
const dispatch = useDispatch();
const { isModalOpen, handleModalToggle } = useModalToggle();
const idsArray = bulkParams
?.match(/\(([^)]+)\)/)?.[1]
?.split(',')
.map(id => id.trim());

const onFocus = () => {
const element = document.getElementById('toggle-kebab');
element.focus();
};
const onSelect = () => {
setIsOpen(false);
onFocus();
};

const getRerunUrl = () =>
idsArray?.length
? `/job_invocations/${jobID}/rerun?${idsArray
.map(id => `host_ids[]=${id}`)
.join('&')}`
: null;

const handleTaskAction = action => {
if (idsArray) {
idsArray.forEach(taskID => {
dispatch(
addToast({
key: `${action}-job-info-${taskID}`,
type: 'info',
message: __(`Trying to ${action} the task for the host`),
})
);

dispatch(
APIActions.post({
url: `/foreman_tasks/tasks/${taskID}/${action}`,
key: `${action.toUpperCase()}_TASK_${taskID}`,
errorToast: ({ response }) => response.data.message,
successToast: () =>
__(
`Task for the host ${
action === 'cancel' ? 'cancelled' : 'aborted'
} successfully`
),
})
);
});
}
};

const RerunButton = () => (
<Button
ouiaId="template-invocation-new-tab-button"
href={getRerunUrl()}
variant="secondary"
component="a"
isDisabled={
selectedCount === 0 ||
!hasPermission(
currentPermissions,
permissionsStatus,
'create_job_invocations'
)
}
>
{__('Rerun')}
</Button>
);

const dropdownItems = [
<DropdownItem
ouiaId="cancel-host-dropdown-item"
onClick={() => handleTaskAction('cancel')}
key="cancel"
component="button"
isDisabled={
selectedCount === 0 ||
!isTaskCancelable ||
!hasPermission(
currentPermissions,
permissionsStatus,
'cancel_job_invocations'
)
}
>
{__('Cancel')}
</DropdownItem>,
<DropdownItem
ouiaId="abort-host-dropdown-item"
onClick={() => handleTaskAction('abort')}
key="abort"
component="button"
isDisabled={
selectedCount === 0 ||
!isTaskCancelable ||
!hasPermission(
currentPermissions,
permissionsStatus,
'cancel_job_invocations'
)
}
>
{__('Abort')}
</DropdownItem>,
<DropdownSeparator ouiaId="dropdown-separator" key="separator" />,
<DropdownItem
ouiaId="open-failed-host-dropdown-item"
key="open-failed"
component="button"
isDisabled={failedHosts.length === 0}
onClick={() => {
if (failedHosts.length <= 3) {
failedHosts.forEach(id =>
openLink(`/job_invocations/${id}`, () => {})
);
} else {
handleModalToggle();
}
}}
>
{__('Open all failed runs')}
</DropdownItem>,
];

const ActionsKebab = () => (
<Dropdown
ouiaId="host-actions-dropdown-kebab"
onSelect={onSelect}
toggle={<KebabToggle id="toggle-kebab" onToggle={setIsOpen} />}
isOpen={isOpen}
isPlain
dropdownItems={dropdownItems}
/>
);

return (
<>
<RerunButton />
<ActionsKebab />
<OpenAllModal
isModalOpen={isModalOpen}
handleModalToggle={handleModalToggle}
hosts={failedHosts}
jobID={jobID}
onConfirm={url => openLink(url, () => {})}
/>
</>
);
};

CheckboxesActions.propTypes = {
jobID: PropTypes.string.isRequired,
bulkParams: PropTypes.string,
selectedCount: PropTypes.number.isRequired,
currentPermissions: PropTypes.array,
failedHosts: PropTypes.array,
permissionsStatus: PropTypes.string,
};

CheckboxesActions.defaultProps = {
bulkParams: undefined,
currentPermissions: [],
failedHosts: [],
permissionsStatus: undefined,
};
12 changes: 12 additions & 0 deletions webpack/JobInvocationDetail/JobInvocationConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { foremanUrl } from 'foremanReact/common/helpers';
import { translate as __ } from 'foremanReact/common/I18n';
import { useForemanHostDetailsPageUrl } from 'foremanReact/Root/Context/ForemanContext';
import { STATUS as APIStatus } from 'foremanReact/constants';
import JobStatusIcon from '../react_app/components/RecentJobsCard/JobStatusIcon';

export const JOB_INVOCATION_KEY = 'JOB_INVOCATION_KEY';
Expand Down Expand Up @@ -52,6 +53,17 @@ export const DATE_OPTIONS = {
timeZoneName: 'short',
};

export const hasPermission = (
currentPermissions,
permissionsStatus,
permissionRequired
) =>
permissionsStatus === APIStatus.RESOLVED
? currentPermissions?.some(
permission => permission.name === permissionRequired
)
: false;

const Columns = () => {
const getColumnsStatus = ({ hostJobStatus }) => {
switch (hostJobStatus) {
Expand Down
Loading

0 comments on commit fe71baf

Please sign in to comment.