Adds toast to detail view and fixes non-disabled action button on list view

This commit is contained in:
Alex Corey
2022-08-02 15:48:11 -04:00
parent 1fca505b61
commit 93ea8a0919
8 changed files with 91 additions and 30 deletions

View File

@@ -91,7 +91,10 @@ function WorkflowApproval({ setBreadcrumb }) {
/> />
{workflowApproval && ( {workflowApproval && (
<Route path="/workflow_approvals/:id/details"> <Route path="/workflow_approvals/:id/details">
<WorkflowApprovalDetail workflowApproval={workflowApproval} /> <WorkflowApprovalDetail
fetchWorkflowApproval={fetchWorkflowApproval}
workflowApproval={workflowApproval}
/>
</Route> </Route>
)} )}
<Route key="not-found" path="*"> <Route key="not-found" path="*">

View File

@@ -1,5 +1,4 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link, useHistory, useParams } from 'react-router-dom'; import { Link, useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -27,6 +26,7 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import { WorkflowApproval } from 'types'; import { WorkflowApproval } from 'types';
import StatusLabel from 'components/StatusLabel'; import StatusLabel from 'components/StatusLabel';
import JobCancelButton from 'components/JobCancelButton'; import JobCancelButton from 'components/JobCancelButton';
import useToast, { AlertVariant } from 'hooks/useToast';
import WorkflowApprovalButton from '../shared/WorkflowApprovalButton'; import WorkflowApprovalButton from '../shared/WorkflowApprovalButton';
import WorkflowDenyButton from '../shared/WorkflowDenyButton'; import WorkflowDenyButton from '../shared/WorkflowDenyButton';
import { import {
@@ -48,9 +48,11 @@ const WFDetailList = styled(DetailList)`
padding: 0px var(--pf-global--spacer--lg); padding: 0px var(--pf-global--spacer--lg);
`; `;
function WorkflowApprovalDetail({ workflowApproval }) { function WorkflowApprovalDetail({ workflowApproval, fetchWorkflowApproval }) {
const { id: workflowApprovalId } = useParams(); const { id: workflowApprovalId } = useParams();
const history = useHistory(); const history = useHistory();
const { addToast, Toast, toastProps } = useToast();
const { const {
request: deleteWorkflowApproval, request: deleteWorkflowApproval,
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
@@ -102,6 +104,18 @@ function WorkflowApprovalDetail({ workflowApproval }) {
fetchWorkflowJob(); fetchWorkflowJob();
}, [fetchWorkflowJob]); }, [fetchWorkflowJob]);
const handleToast = useCallback(
(id, title) => {
addToast({
id,
title,
variant: AlertVariant.success,
hasTimeout: true,
});
fetchWorkflowApproval();
},
[addToast, fetchWorkflowApproval]
);
const sourceWorkflowJob = const sourceWorkflowJob =
workflowApproval?.summary_fields?.source_workflow_job; workflowApproval?.summary_fields?.source_workflow_job;
@@ -116,7 +130,9 @@ function WorkflowApprovalDetail({ workflowApproval }) {
if (fetchWorkflowJobError) { if (fetchWorkflowJobError) {
return <ContentError error={fetchWorkflowJobError} />; return <ContentError error={fetchWorkflowJobError} />;
} }
const showDeleteButton =
workflowApproval.status !== 'pending' &&
workflowApproval.summary_fields?.user_capabilities?.delete;
return ( return (
<CardBody> <CardBody>
<DetailList gutter="sm"> <DetailList gutter="sm">
@@ -289,31 +305,35 @@ function WorkflowApprovalDetail({ workflowApproval }) {
<WorkflowApprovalButton <WorkflowApprovalButton
workflowApproval={workflowApproval} workflowApproval={workflowApproval}
isDetailView isDetailView
onHandleToast={handleToast}
/> />
<WorkflowDenyButton <WorkflowDenyButton
workflowApproval={workflowApproval} workflowApproval={workflowApproval}
isDetailView isDetailView
onHandleToast={handleToast}
/> />
<JobCancelButton <JobCancelButton
title={t`Cancel Workflow`} title={t`Cancel Workflow`}
job={workflowApproval.summary_fields.source_workflow_job} job={{
...workflowApproval.summary_fields.source_workflow_job,
type: 'workflow_job',
}}
buttonText={t`Cancel Workflow`} buttonText={t`Cancel Workflow`}
cancelationMessage={t`This will cancel all subsequent nodes in this workflow. cancelationMessage={t`This will cancel all subsequent nodes in this workflow.
`} `}
/> />
</> </>
)} )}
{workflowApproval.status !== 'pending' && {showDeleteButton && (
workflowApproval.summary_fields?.user_capabilities?.delete && ( <DeleteButton
<DeleteButton name={workflowApproval.name}
name={workflowApproval.name} modalTitle={t`Delete Workflow Approval`}
modalTitle={t`Delete Workflow Approval`} onConfirm={deleteWorkflowApproval}
onConfirm={deleteWorkflowApproval} isDisabled={isLoading}
isDisabled={isLoading} >
> {t`Delete`}
{t`Delete`} </DeleteButton>
</DeleteButton> )}
)}
</CardActionsRow> </CardActionsRow>
{deleteError && ( {deleteError && (
<AlertModal <AlertModal
@@ -326,6 +346,7 @@ function WorkflowApprovalDetail({ workflowApproval }) {
<ErrorDetail error={deleteError} /> <ErrorDetail error={deleteError} />
</AlertModal> </AlertModal>
)} )}
<Toast {...toastProps} />
</CardBody> </CardBody>
); );
} }

View File

@@ -515,7 +515,10 @@ describe('<WorkflowApprovalDetail />', () => {
let wrapper; let wrapper;
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} /> <WorkflowApprovalDetail
workflowApproval={workflowApproval}
fetchWorkflowApproval={jest.fn()}
/>
); );
}); });
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0); waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
@@ -548,7 +551,10 @@ describe('<WorkflowApprovalDetail />', () => {
let wrapper; let wrapper;
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} /> <WorkflowApprovalDetail
workflowApproval={workflowApproval}
fetchWorkflowApproval={jest.fn()}
/>
); );
}); });
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0); waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);

View File

@@ -100,7 +100,10 @@ function WorkflowApprovalListItem({
<JobCancelButton <JobCancelButton
title={t`Cancel Workflow`} title={t`Cancel Workflow`}
showIconButton showIconButton
job={workflowApproval.summary_fields.source_workflow_job} job={{
...workflowApproval.summary_fields.source_workflow_job,
type: 'workflow_job',
}}
buttonText={t`Cancel Workflow`} buttonText={t`Cancel Workflow`}
isDisabled={hasBeenActedOn} isDisabled={hasBeenActedOn}
tooltip={ tooltip={

View File

@@ -9,9 +9,15 @@ import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail'; import ErrorDetail from 'components/ErrorDetail';
import { getStatus } from './WorkflowApprovalUtils'; import { getStatus } from './WorkflowApprovalUtils';
function WorkflowApprovalButton({ isDetailView, workflowApproval }) { function WorkflowApprovalButton({
isDetailView,
workflowApproval,
onHandleToast,
}) {
const { id } = workflowApproval; const { id } = workflowApproval;
const hasBeenActedOn = workflowApproval.status === 'successful'; const hasBeenActedOn =
Object.keys(workflowApproval.summary_fields.approved_or_denied_by || {})
.length > 0 || workflowApproval.status === 'canceled';
const { error: approveApprovalError, request: approveWorkflowApprovals } = const { error: approveApprovalError, request: approveWorkflowApprovals } =
useRequest( useRequest(
useCallback(async () => WorkflowApprovalsAPI.approve(id), [id]), useCallback(async () => WorkflowApprovalsAPI.approve(id), [id]),
@@ -20,6 +26,7 @@ function WorkflowApprovalButton({ isDetailView, workflowApproval }) {
const handleApprove = async () => { const handleApprove = async () => {
await approveWorkflowApprovals(); await approveWorkflowApprovals();
onHandleToast(workflowApproval.id, t`Successfully Approved`);
}; };
const { error: approveError, dismissError: dismissApproveError } = const { error: approveError, dismissError: dismissApproveError } =

View File

@@ -18,7 +18,10 @@ describe('<WorkflowApprovalButton/> shallow mount', () => {
test('initially render successfully', () => { test('initially render successfully', () => {
wrapper = shallowWithContexts( wrapper = shallowWithContexts(
<WorkflowApprovalButton workflowApproval={mockApprovalList[0]} /> <WorkflowApprovalButton
workflowApproval={mockApprovalList[0]}
onHandleToast={jest.fn()}
/>
); );
expect(wrapper.find('WorkflowApprovalButton')).toHaveLength(1); expect(wrapper.find('WorkflowApprovalButton')).toHaveLength(1);
@@ -39,7 +42,10 @@ describe('<WorkflowApprovalButton/>, full mount', () => {
test('should be disabled', () => { test('should be disabled', () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowApprovalButton workflowApproval={mockApprovalList[2]} /> <WorkflowApprovalButton
workflowApproval={mockApprovalList[2]}
onHandleToast={jest.fn()}
/>
); );
expect(wrapper.find(approveButton)).toHaveLength(1); expect(wrapper.find(approveButton)).toHaveLength(1);
expect(wrapper.find(approveButton).prop('isDisabled')).toBe(true); expect(wrapper.find(approveButton).prop('isDisabled')).toBe(true);
@@ -47,7 +53,10 @@ describe('<WorkflowApprovalButton/>, full mount', () => {
test('should handle approve', async () => { test('should handle approve', async () => {
act(() => { act(() => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowApprovalButton workflowApproval={mockApprovalList[0]} /> <WorkflowApprovalButton
workflowApproval={mockApprovalList[0]}
onHandleToast={jest.fn()}
/>
); );
}); });
await act(() => wrapper.find(approveButton).prop('onClick')()); await act(() => wrapper.find(approveButton).prop('onClick')());

View File

@@ -9,10 +9,12 @@ import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail'; import ErrorDetail from 'components/ErrorDetail';
import { getStatus } from './WorkflowApprovalUtils'; import { getStatus } from './WorkflowApprovalUtils';
function WorkflowDenyButton({ isDetailView, workflowApproval }) { function WorkflowDenyButton({ isDetailView, workflowApproval, onHandleToast }) {
const hasBeenActedOn = workflowApproval.status === 'failed'; const hasBeenActedOn =
const { id } = workflowApproval; Object.keys(workflowApproval.summary_fields.approved_or_denied_by || {})
.length > 0 || workflowApproval.status === 'canceled';
const { id } = workflowApproval;
const { error: denyApprovalError, request: denyWorkflowApprovals } = const { error: denyApprovalError, request: denyWorkflowApprovals } =
useRequest( useRequest(
useCallback(async () => WorkflowApprovalsAPI.deny(id), [id]), useCallback(async () => WorkflowApprovalsAPI.deny(id), [id]),
@@ -21,6 +23,7 @@ function WorkflowDenyButton({ isDetailView, workflowApproval }) {
const handleDeny = async () => { const handleDeny = async () => {
await denyWorkflowApprovals(); await denyWorkflowApprovals();
onHandleToast(workflowApproval.id, t`Successfully Denied`);
}; };
const { error: denyError, dismissError: dismissDenyError } = const { error: denyError, dismissError: dismissDenyError } =

View File

@@ -39,7 +39,10 @@ describe('<WorkflowDenyButton/>, full mount', () => {
test('should be disabled', () => { test('should be disabled', () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowDenyButton workflowApproval={mockApprovalList[1]} /> <WorkflowDenyButton
workflowApproval={mockApprovalList[2]}
onHandleToast={jest.fn()}
/>
); );
expect(wrapper.find(denyButton)).toHaveLength(1); expect(wrapper.find(denyButton)).toHaveLength(1);
expect(wrapper.find(denyButton).prop('isDisabled')).toBe(true); expect(wrapper.find(denyButton).prop('isDisabled')).toBe(true);
@@ -48,7 +51,10 @@ describe('<WorkflowDenyButton/>, full mount', () => {
test('should handle deny', async () => { test('should handle deny', async () => {
act(() => { act(() => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowDenyButton workflowApproval={mockApprovalList[0]} /> <WorkflowDenyButton
workflowApproval={mockApprovalList[0]}
onHandleToast={jest.fn()}
/>
); );
}); });
await act(() => wrapper.find(denyButton).prop('onClick')()); await act(() => wrapper.find(denyButton).prop('onClick')());
@@ -70,7 +76,10 @@ describe('<WorkflowDenyButton/>, full mount', () => {
); );
act(() => { act(() => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<WorkflowDenyButton workflowApproval={mockApprovalList[0]} /> <WorkflowDenyButton
workflowApproval={mockApprovalList[0]}
onHandleToast={jest.fn()}
/>
); );
}); });
await act(() => wrapper.find(denyButton).prop('onClick')()); await act(() => wrapper.find(denyButton).prop('onClick')());