mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
Re-add workflow approval bulk actions to workflow approvals list
This commit is contained in:
parent
8fb831d3de
commit
808ab9803e
@ -12,11 +12,16 @@ import PaginatedTable, {
|
||||
import AlertModal from 'components/AlertModal';
|
||||
import ErrorDetail from 'components/ErrorDetail';
|
||||
import DataListToolbar from 'components/DataListToolbar';
|
||||
import useRequest, { useDeleteItems } from 'hooks/useRequest';
|
||||
import useRequest, {
|
||||
useDeleteItems,
|
||||
useDismissableError,
|
||||
} from 'hooks/useRequest';
|
||||
import useSelected from 'hooks/useSelected';
|
||||
import { getQSConfig, parseQueryString } from 'util/qs';
|
||||
import WorkflowApprovalListItem from './WorkflowApprovalListItem';
|
||||
import useWsWorkflowApprovals from './useWsWorkflowApprovals';
|
||||
import WorkflowApprovalListApproveButton from './WorkflowApprovalListApproveButton';
|
||||
import WorkflowApprovalListDenyButton from './WorkflowApprovalListDenyButton';
|
||||
|
||||
const QS_CONFIG = getQSConfig('workflow_approvals', {
|
||||
page: 1,
|
||||
@ -104,7 +109,50 @@ function WorkflowApprovalsList() {
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const isLoading = isWorkflowApprovalsLoading || isDeleteLoading;
|
||||
const {
|
||||
error: approveApprovalError,
|
||||
isLoading: isApproveLoading,
|
||||
request: approveWorkflowApprovals,
|
||||
} = useRequest(
|
||||
useCallback(
|
||||
async () =>
|
||||
Promise.all(selected.map(({ id }) => WorkflowApprovalsAPI.approve(id))),
|
||||
[selected]
|
||||
),
|
||||
{}
|
||||
);
|
||||
|
||||
const handleApprove = async () => {
|
||||
await approveWorkflowApprovals();
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const {
|
||||
error: denyApprovalError,
|
||||
isLoading: isDenyLoading,
|
||||
request: denyWorkflowApprovals,
|
||||
} = useRequest(
|
||||
useCallback(
|
||||
async () =>
|
||||
Promise.all(selected.map(({ id }) => WorkflowApprovalsAPI.deny(id))),
|
||||
[selected]
|
||||
),
|
||||
{}
|
||||
);
|
||||
|
||||
const handleDeny = async () => {
|
||||
await denyWorkflowApprovals();
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const { error: actionError, dismissError: dismissActionError } =
|
||||
useDismissableError(approveApprovalError || denyApprovalError);
|
||||
|
||||
const isLoading =
|
||||
isWorkflowApprovalsLoading ||
|
||||
isDeleteLoading ||
|
||||
isApproveLoading ||
|
||||
isDenyLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -138,6 +186,16 @@ function WorkflowApprovalsList() {
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
<WorkflowApprovalListApproveButton
|
||||
key="approve"
|
||||
onApprove={handleApprove}
|
||||
selectedItems={selected}
|
||||
/>,
|
||||
<WorkflowApprovalListDenyButton
|
||||
key="deny"
|
||||
onDeny={handleDeny}
|
||||
selectedItems={selected}
|
||||
/>,
|
||||
<ToolbarDeleteButton
|
||||
key="delete"
|
||||
onDelete={handleDelete}
|
||||
@ -193,6 +251,19 @@ function WorkflowApprovalsList() {
|
||||
<ErrorDetail error={deletionError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
{actionError && (
|
||||
<AlertModal
|
||||
isOpen={actionError}
|
||||
variant="error"
|
||||
title={t`Error!`}
|
||||
onClose={dismissActionError}
|
||||
>
|
||||
{approveApprovalError
|
||||
? t`Failed to approve one or more workflow approval.`
|
||||
: t`Failed to deny one or more workflow approval.`}
|
||||
<ErrorDetail error={actionError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { t } from '@lingui/macro';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
||||
import { KebabifiedContext } from 'contexts/Kebabified';
|
||||
import { WorkflowApproval } from 'types';
|
||||
|
||||
function cannotApprove(item) {
|
||||
return !item.can_approve_or_deny;
|
||||
}
|
||||
|
||||
function WorkflowApprovalListApproveButton({ onApprove, selectedItems }) {
|
||||
const { isKebabified } = useContext(KebabifiedContext);
|
||||
|
||||
const renderTooltip = () => {
|
||||
if (selectedItems.length === 0) {
|
||||
return t`Select a row to approve`;
|
||||
}
|
||||
|
||||
const itemsUnableToApprove = selectedItems
|
||||
.filter(cannotApprove)
|
||||
.map((item) => item.name)
|
||||
.join(', ');
|
||||
|
||||
if (selectedItems.some(cannotApprove)) {
|
||||
return t`You are unable to act on the following workflow approvals: ${itemsUnableToApprove}`;
|
||||
}
|
||||
|
||||
return t`Approve`;
|
||||
};
|
||||
|
||||
const isDisabled =
|
||||
selectedItems.length === 0 || selectedItems.some(cannotApprove);
|
||||
|
||||
return (
|
||||
/* eslint-disable-next-line react/jsx-no-useless-fragment */
|
||||
<>
|
||||
{isKebabified ? (
|
||||
<DropdownItem
|
||||
key="approve"
|
||||
isDisabled={isDisabled}
|
||||
component="button"
|
||||
onClick={onApprove}
|
||||
>
|
||||
{t`Approve`}
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<Tooltip content={renderTooltip()} position="top">
|
||||
<div>
|
||||
<Button
|
||||
ouiaId="workflow-approval-approve-button"
|
||||
isDisabled={isDisabled}
|
||||
aria-label={t`Approve`}
|
||||
variant="primary"
|
||||
onClick={onApprove}
|
||||
>
|
||||
{t`Approve`}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
WorkflowApprovalListApproveButton.propTypes = {
|
||||
onApprove: PropTypes.func.isRequired,
|
||||
selectedItems: PropTypes.arrayOf(WorkflowApproval),
|
||||
};
|
||||
|
||||
WorkflowApprovalListApproveButton.defaultProps = {
|
||||
selectedItems: [],
|
||||
};
|
||||
|
||||
export default WorkflowApprovalListApproveButton;
|
||||
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import WorkflowApprovalListApproveButton from './WorkflowApprovalListApproveButton';
|
||||
|
||||
const workflowApproval = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
can_approve_or_deny: true,
|
||||
url: '/api/v2/workflow_approvals/218/',
|
||||
};
|
||||
|
||||
describe('<WorkflowApprovalListApproveButton />', () => {
|
||||
test('should render button', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListApproveButton
|
||||
onApprove={() => {}}
|
||||
selectedItems={[]}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('button')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should invoke onApprove prop', () => {
|
||||
const onApprove = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListApproveButton
|
||||
onApprove={onApprove}
|
||||
selectedItems={[workflowApproval]}
|
||||
/>
|
||||
);
|
||||
wrapper.find('button').simulate('click');
|
||||
wrapper.update();
|
||||
expect(onApprove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should disable button when no approve/deny permissions', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListApproveButton
|
||||
onApprove={() => {}}
|
||||
selectedItems={[{ ...workflowApproval, can_approve_or_deny: false }]}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('button[disabled]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should render tooltip', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListApproveButton
|
||||
onApprove={() => {}}
|
||||
selectedItems={[workflowApproval]}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('Tooltip')).toHaveLength(1);
|
||||
expect(wrapper.find('Tooltip').prop('content')).toEqual('Approve');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,76 @@
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { t } from '@lingui/macro';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
||||
import { KebabifiedContext } from 'contexts/Kebabified';
|
||||
import { WorkflowApproval } from 'types';
|
||||
|
||||
function cannotDeny(item) {
|
||||
return !item.can_approve_or_deny;
|
||||
}
|
||||
|
||||
function WorkflowApprovalListDenyButton({ onDeny, selectedItems }) {
|
||||
const { isKebabified } = useContext(KebabifiedContext);
|
||||
|
||||
const renderTooltip = () => {
|
||||
if (selectedItems.length === 0) {
|
||||
return t`Select a row to deny`;
|
||||
}
|
||||
|
||||
const itemsUnableToDeny = selectedItems
|
||||
.filter(cannotDeny)
|
||||
.map((item) => item.name)
|
||||
.join(', ');
|
||||
|
||||
if (selectedItems.some(cannotDeny)) {
|
||||
return t`You are unable to act on the following workflow approvals: ${itemsUnableToDeny}`;
|
||||
}
|
||||
|
||||
return t`Deny`;
|
||||
};
|
||||
|
||||
const isDisabled =
|
||||
selectedItems.length === 0 || selectedItems.some(cannotDeny);
|
||||
|
||||
return (
|
||||
/* eslint-disable-next-line react/jsx-no-useless-fragment */
|
||||
<>
|
||||
{isKebabified ? (
|
||||
<DropdownItem
|
||||
key="deny"
|
||||
isDisabled={isDisabled}
|
||||
component="button"
|
||||
onClick={onDeny}
|
||||
>
|
||||
{t`Deny`}
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<Tooltip content={renderTooltip()} position="top">
|
||||
<div>
|
||||
<Button
|
||||
ouiaId="workflow-approval-deny-button"
|
||||
isDisabled={isDisabled}
|
||||
aria-label={t`Deny`}
|
||||
variant="danger"
|
||||
onClick={onDeny}
|
||||
>
|
||||
{t`Deny`}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
WorkflowApprovalListDenyButton.propTypes = {
|
||||
onDeny: PropTypes.func.isRequired,
|
||||
selectedItems: PropTypes.arrayOf(WorkflowApproval),
|
||||
};
|
||||
|
||||
WorkflowApprovalListDenyButton.defaultProps = {
|
||||
selectedItems: [],
|
||||
};
|
||||
|
||||
export default WorkflowApprovalListDenyButton;
|
||||
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import WorkflowApprovalListDenyButton from './WorkflowApprovalListDenyButton';
|
||||
|
||||
const workflowApproval = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
can_approve_or_deny: true,
|
||||
url: '/api/v2/workflow_approvals/218/',
|
||||
};
|
||||
|
||||
describe('<WorkflowApprovalListDenyButton />', () => {
|
||||
test('should render button', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListDenyButton onDeny={() => {}} selectedItems={[]} />
|
||||
);
|
||||
expect(wrapper.find('button')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should invoke onDeny prop', () => {
|
||||
const onDeny = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListDenyButton
|
||||
onDeny={onDeny}
|
||||
selectedItems={[workflowApproval]}
|
||||
/>
|
||||
);
|
||||
wrapper.find('button').simulate('click');
|
||||
wrapper.update();
|
||||
expect(onDeny).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should disable button when no approve/deny permissions', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListDenyButton
|
||||
onDeny={() => {}}
|
||||
selectedItems={[{ ...workflowApproval, can_approve_or_deny: false }]}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('button[disabled]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should render tooltip', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowApprovalListDenyButton
|
||||
onDeny={() => {}}
|
||||
selectedItems={[workflowApproval]}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('Tooltip')).toHaveLength(1);
|
||||
expect(wrapper.find('Tooltip').prop('content')).toEqual('Deny');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user