mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 10:41:05 -03:30
Adds toast to detail view and fixes non-disabled action button on list view
This commit is contained in:
@@ -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="*">
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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={
|
||||||
|
|||||||
@@ -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 } =
|
||||||
|
|||||||
@@ -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')());
|
||||||
|
|||||||
@@ -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 } =
|
||||||
|
|||||||
@@ -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')());
|
||||||
|
|||||||
Reference in New Issue
Block a user