Add details related workflow job on the workflow approval details

Add details related workflow job on the work flow approval details

Remove not used prop isLoading, fix, and expand unit-tests related to
workflow approval details.
This commit is contained in:
nixocio 2022-05-05 11:04:53 -04:00
parent e8e2f820d2
commit 458a1fc035
4 changed files with 651 additions and 174 deletions

View File

@ -368,7 +368,7 @@ describe('<AdHocCommandsWizard/>', () => {
response: {
config: {
method: 'get',
url: '/api/v2/credentals',
url: '/api/v2/credentials',
},
data: 'An error occurred',
status: 403,

View File

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

View File

@ -1,14 +1,28 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { t } from '@lingui/macro';
import { Link, useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import {
Divider as PFDivider,
Title as PFTitle,
Chip,
} from '@patternfly/react-core';
import AlertModal from 'components/AlertModal';
import { CardBody, CardActionsRow } from 'components/Card';
import ChipGroup from 'components/ChipGroup';
import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading';
import DeleteButton from 'components/DeleteButton';
import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
import ErrorDetail from 'components/ErrorDetail';
import { CardBody, CardActionsRow } from 'components/Card';
import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
import { VariablesDetail } from 'components/CodeEditor';
import { formatDateString, secondsToHHMMSS } from 'util/dates';
import { WorkflowApprovalsAPI, WorkflowJobsAPI } from 'api';
import {
WorkflowApprovalsAPI,
WorkflowJobTemplatesAPI,
WorkflowJobsAPI,
} from 'api';
import useRequest, { useDismissableError } from 'hooks/useRequest';
import { WorkflowApproval } from 'types';
import StatusLabel from 'components/StatusLabel';
@ -16,8 +30,23 @@ import {
getDetailPendingLabel,
getStatus,
} from '../shared/WorkflowApprovalUtils';
import WorkflowApprovalControls from '../shared/WorkflowApprovalControls';
const Divider = styled(PFDivider)`
margin-top: var(--pf-global--spacer--lg);
margin-bottom: var(--pf-global--spacer--lg);
`;
const Title = styled(PFTitle)`
margin-top: var(--pf-global--spacer--xl);
--pf-c-title--m-md--FontWeight: 700;
`;
const WFDetailList = styled(DetailList)`
padding: 0px var(--pf-global--spacer--lg);
`;
function WorkflowApprovalDetail({ workflowApproval }) {
const { id: workflowApprovalId } = useParams();
const [isKebabOpen, setIsKebabModalOpen] = useState(false);
@ -80,6 +109,43 @@ function WorkflowApprovalDetail({ workflowApproval }) {
{}
);
const workflowJobTemplateId =
workflowApproval.summary_fields.workflow_job_template.id;
const {
error: fetchWorkflowJobError,
isLoading: isLoadingWorkflowJob,
request: fetchWorkflowJob,
result: workflowJob,
} = useRequest(
useCallback(async () => {
if (!workflowJobTemplateId) {
return {};
}
const { data: workflowJobTemplate } =
await WorkflowJobTemplatesAPI.readDetail(workflowJobTemplateId);
let jobId = null;
if (workflowJobTemplate.summary_fields?.current_job) {
jobId = workflowJobTemplate.summary_fields.current_job.id;
} else if (workflowJobTemplate.summary_fields?.last_job) {
jobId = workflowJobTemplate.summary_fields.last_job.id;
}
const { data } = await WorkflowJobsAPI.readDetail(jobId);
return data;
}, [workflowJobTemplateId]),
{
workflowJob: null,
isLoading: true,
}
);
useEffect(() => {
fetchWorkflowJob();
}, [fetchWorkflowJob]);
const handleCancel = async () => {
setIsKebabModalOpen(false);
await cancelWorkflowApprovals();
@ -95,7 +161,18 @@ function WorkflowApprovalDetail({ workflowApproval }) {
workflowApproval?.summary_fields?.workflow_job_template;
const isLoading =
isDeleteLoading || isApproveLoading || isDenyLoading || isCancelLoading;
isApproveLoading ||
isCancelLoading ||
isDeleteLoading ||
isDenyLoading ||
isLoadingWorkflowJob;
if (isLoadingWorkflowJob) {
return <ContentLoading />;
}
if (fetchWorkflowJobError) {
return <ContentError error={fetchWorkflowJobError} />;
}
return (
<CardBody>
@ -146,19 +223,6 @@ function WorkflowApprovalDetail({ workflowApproval }) {
value={workflowApproval.job_explanation}
dataCy="wa-detail-explanation"
/>
<Detail
label={t`Workflow Job`}
value={
sourceWorkflowJob && sourceWorkflowJob?.id ? (
<Link to={`/jobs/workflow/${sourceWorkflowJob?.id}`}>
{`${sourceWorkflowJob?.id} - ${sourceWorkflowJob?.name}`}
</Link>
) : (
t`Deleted`
)
}
dataCy="wa-detail-source-job"
/>
<Detail
label={t`Workflow Job Template`}
value={
@ -194,6 +258,88 @@ function WorkflowApprovalDetail({ workflowApproval }) {
value={secondsToHHMMSS(workflowApproval.elapsed)}
/>
</DetailList>
<Title headingLevel="h2">{t`Workflow job details`}</Title>
<Divider />
<WFDetailList gutter="sm">
<Detail
label={t`Workflow Job`}
value={
sourceWorkflowJob && sourceWorkflowJob?.id ? (
<Link to={`/jobs/workflow/${sourceWorkflowJob?.id}`}>
{`${sourceWorkflowJob?.id} - ${sourceWorkflowJob?.name}`}
</Link>
) : (
t`Deleted`
)
}
dataCy="wa-detail-source-job"
/>
{workflowJob?.limit ? (
<Detail
label={t`Limit`}
value={workflowJob.limit}
dataCy="wa-detail-source-job-limit"
/>
) : null}
{workflowJob?.scm_branch ? (
<Detail
label={t`Source Control Branch`}
value={workflowJob.scm_branch}
dataCy="wa-detail-source-job-scm"
/>
) : null}
{workflowJob?.summary_fields?.inventory ? (
<Detail
label={t`Inventory`}
value={
workflowJob.summary_fields.inventory ? (
<Link
to={`/inventories/${
workflowJob.summary_fields.inventory?.kind === 'smart'
? 'smart_inventory'
: 'inventory'
}/${workflowJob.summary_fields.inventory?.id}/details`}
>
{workflowJob.summary_fields.inventory?.name}
</Link>
) : (
' '
)
}
dataCy="wa-detail-inventory"
/>
) : null}
{workflowJob?.summary_fields?.labels?.results?.length > 0 && (
<Detail
fullWidth
label={t`Labels`}
value={
<ChipGroup
numChips={5}
totalChips={workflowJob.summary_fields.labels.results.length}
ouiaId="wa-detail-label-chips"
>
{workflowJob.summary_fields.labels.results.map((label) => (
<Chip key={label.id} isReadOnly>
{label.name}
</Chip>
))}
</ChipGroup>
}
/>
)}
{workflowJob?.extra_vars ? (
<VariablesDetail
dataCy="wa-detail-variables"
id="wa-detail-extra-vars"
label={t`Variables`}
name="extra_vars"
rows={5}
value={workflowJob.extra_vars}
/>
) : null}
</WFDetailList>
<CardActionsRow>
{workflowApproval.status === 'pending' &&
workflowApproval.can_approve_or_deny && (

View File

@ -1,6 +1,10 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { WorkflowApprovalsAPI } from 'api';
import {
WorkflowApprovalsAPI,
WorkflowJobTemplatesAPI,
WorkflowJobsAPI,
} from 'api';
import { formatDateString } from 'util/dates';
import {
mountWithContexts,
@ -12,18 +16,278 @@ import mockWorkflowApprovals from '../data.workflowApprovals.json';
const workflowApproval = mockWorkflowApprovals.results[0];
jest.mock('../../../api');
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: () => ({
id: 218,
}),
}));
const workflowJobTemplate = {
id: 8,
type: 'workflow_job_template',
url: '/api/v2/workflow_job_templates/8/',
related: {
named_url: '/api/v2/workflow_job_templates/00++/',
created_by: '/api/v2/users/1/',
modified_by: '/api/v2/users/1/',
last_job: '/api/v2/workflow_jobs/111/',
workflow_jobs: '/api/v2/workflow_job_templates/8/workflow_jobs/',
schedules: '/api/v2/workflow_job_templates/8/schedules/',
launch: '/api/v2/workflow_job_templates/8/launch/',
webhook_key: '/api/v2/workflow_job_templates/8/webhook_key/',
webhook_receiver: '/api/v2/workflow_job_templates/8/github/',
workflow_nodes: '/api/v2/workflow_job_templates/8/workflow_nodes/',
labels: '/api/v2/workflow_job_templates/8/labels/',
activity_stream: '/api/v2/workflow_job_templates/8/activity_stream/',
notification_templates_started:
'/api/v2/workflow_job_templates/8/notification_templates_started/',
notification_templates_success:
'/api/v2/workflow_job_templates/8/notification_templates_success/',
notification_templates_error:
'/api/v2/workflow_job_templates/8/notification_templates_error/',
notification_templates_approvals:
'/api/v2/workflow_job_templates/8/notification_templates_approvals/',
access_list: '/api/v2/workflow_job_templates/8/access_list/',
object_roles: '/api/v2/workflow_job_templates/8/object_roles/',
survey_spec: '/api/v2/workflow_job_templates/8/survey_spec/',
copy: '/api/v2/workflow_job_templates/8/copy/',
},
summary_fields: {
last_job: {
id: 111,
name: '00',
description: '',
finished: '2022-05-10T17:29:52.978531Z',
status: 'successful',
failed: false,
},
last_update: {
id: 111,
name: '00',
description: '',
status: 'successful',
failed: false,
},
created_by: {
id: 1,
username: 'admin',
first_name: '',
last_name: '',
},
modified_by: {
id: 1,
username: 'admin',
first_name: '',
last_name: '',
},
object_roles: {
admin_role: {
description: 'Can manage all aspects of the workflow job template',
name: 'Admin',
id: 34,
},
execute_role: {
description: 'May run the workflow job template',
name: 'Execute',
id: 35,
},
read_role: {
description: 'May view settings for the workflow job template',
name: 'Read',
id: 36,
},
approval_role: {
description: 'Can approve or deny a workflow approval node',
name: 'Approve',
id: 37,
},
},
user_capabilities: {
edit: true,
delete: true,
start: true,
schedule: true,
copy: true,
},
labels: {
count: 1,
results: [
{
id: 2,
name: 'Test2',
},
],
},
survey: {
title: '',
description: '',
},
recent_jobs: [
{
id: 111,
status: 'successful',
finished: '2022-05-10T17:29:52.978531Z',
canceled_on: null,
type: 'workflow_job',
},
{
id: 104,
status: 'failed',
finished: '2022-05-10T15:26:22.233170Z',
canceled_on: null,
type: 'workflow_job',
},
],
},
created: '2022-05-05T14:13:36.123027Z',
modified: '2022-05-05T17:44:44.071447Z',
name: '00',
description: '',
last_job_run: '2022-05-10T17:29:52.978531Z',
last_job_failed: false,
next_job_run: null,
status: 'successful',
extra_vars: '{\n "foo": "bar",\n "baz": "qux"\n}',
organization: null,
survey_enabled: true,
allow_simultaneous: true,
ask_variables_on_launch: true,
inventory: null,
limit: null,
scm_branch: '',
ask_inventory_on_launch: true,
ask_scm_branch_on_launch: true,
ask_limit_on_launch: true,
webhook_service: 'github',
webhook_credential: null,
};
const workflowJob = {
id: 111,
type: 'workflow_job',
url: '/api/v2/workflow_jobs/111/',
related: {
created_by: '/api/v2/users/1/',
modified_by: '/api/v2/users/1/',
unified_job_template: '/api/v2/workflow_job_templates/8/',
workflow_job_template: '/api/v2/workflow_job_templates/8/',
notifications: '/api/v2/workflow_jobs/111/notifications/',
workflow_nodes: '/api/v2/workflow_jobs/111/workflow_nodes/',
labels: '/api/v2/workflow_jobs/111/labels/',
activity_stream: '/api/v2/workflow_jobs/111/activity_stream/',
relaunch: '/api/v2/workflow_jobs/111/relaunch/',
cancel: '/api/v2/workflow_jobs/111/cancel/',
},
summary_fields: {
inventory: {
id: 1,
name: 'Demo Inventory',
description: '',
has_active_failures: false,
total_hosts: 2,
hosts_with_active_failures: 0,
total_groups: 0,
has_inventory_sources: false,
total_inventory_sources: 0,
inventory_sources_with_failures: 0,
organization_id: 1,
kind: '',
},
workflow_job_template: {
id: 8,
name: '00',
description: '',
},
unified_job_template: {
id: 8,
name: '00',
description: '',
unified_job_type: 'workflow_job',
},
created_by: {
id: 1,
username: 'admin',
first_name: '',
last_name: '',
},
modified_by: {
id: 1,
username: 'admin',
first_name: '',
last_name: '',
},
user_capabilities: {
delete: true,
start: true,
},
labels: {
count: 1,
results: [
{
id: 2,
name: 'Test2',
},
],
},
},
created: '2022-05-10T15:26:45.730965Z',
modified: '2022-05-10T15:26:46.150107Z',
name: '00',
description: '',
unified_job_template: 8,
launch_type: 'manual',
status: 'successful',
failed: false,
started: '2022-05-10T15:26:46.149825Z',
finished: '2022-05-10T17:29:52.978531Z',
canceled_on: null,
elapsed: 7386.829,
job_args: '',
job_cwd: '',
job_env: {},
job_explanation: '',
result_traceback: '',
launched_by: {
id: 1,
name: 'admin',
type: 'user',
url: '/api/v2/users/1/',
},
work_unit_id: null,
workflow_job_template: 8,
extra_vars: '{"foo": "bar", "baz": "qux", "first_one": 10}',
allow_simultaneous: true,
job_template: null,
is_sliced_job: false,
inventory: 1,
limit: 'localhost',
scm_branch: 'main',
webhook_service: '',
webhook_credential: null,
webhook_guid: '',
};
describe('<WorkflowApprovalDetail />', () => {
test('initially renders successfully', () => {
mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
beforeEach(() => {
WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({
data: workflowJobTemplate,
});
WorkflowJobsAPI.readDetail.mockResolvedValue({ data: workflowJob });
});
test('should render Details', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
afterEach(() => {
jest.clearAllMocks();
});
test('should render Details', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
function assertDetail(label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
@ -50,123 +314,169 @@ describe('<WorkflowApprovalDetail />', () => {
);
assertDetail('Last Modified', formatDateString(workflowApproval.modified));
assertDetail('Elapsed', '00:00:22');
assertDetail('Limit', 'localhost');
assertDetail('Source Control Branch', 'main');
const linkInventory = wrapper
.find('Detail[label="Inventory"]')
.find('Link');
expect(linkInventory.prop('to')).toEqual(
'/inventories/inventory/1/details'
);
assertDetail('Labels', 'Test2');
expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
'{"foo": "bar", "baz": "qux", "first_one": 10}'
);
expect(wrapper.find('WorkflowApprovalControls').length).toBe(1);
});
test('should show expiration date/time', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
approval_expiration: '2020-10-10T17:13:12.067947Z',
}}
/>
);
test('should show expiration date/time', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
approval_expiration: '2020-10-10T17:13:12.067947Z',
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find(`Detail[label="Expires"] dd`).text()).toBe(
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
);
});
test('should show finished date/time', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
finished: '2020-10-10T17:13:12.067947Z',
}}
/>
);
test('should show finished date/time', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
finished: '2020-10-10T17:13:12.067947Z',
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find(`Detail[label="Finished"] dd`).text()).toBe(
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
);
});
test('should show canceled date/time', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
canceled_on: '2020-10-10T17:13:12.067947Z',
}}
/>
);
test('should show canceled date/time', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
canceled_on: '2020-10-10T17:13:12.067947Z',
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find(`Detail[label="Canceled"] dd`).text()).toBe(
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
);
});
test('should show explanation', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
job_explanation: 'Some explanation text',
}}
/>
);
test('should show explanation', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
job_explanation: 'Some explanation text',
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find(`Detail[label="Explanation"] dd`).text()).toBe(
'Some explanation text'
);
});
test('should show status when not pending', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
status: 'successful',
summary_fields: {
...workflowApproval.summary_fields,
approved_or_denied_by: {
id: 1,
username: 'Foobar',
test('should show status when not pending', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
status: 'successful',
summary_fields: {
...workflowApproval.summary_fields,
approved_or_denied_by: {
id: 1,
username: 'Foobar',
},
},
},
}}
/>
);
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find('StatusLabel').text()).toBe('Approved');
});
test('should show actor when available', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
summary_fields: {
...workflowApproval.summary_fields,
approved_or_denied_by: {
id: 1,
username: 'Foobar',
test('should show actor when available', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
summary_fields: {
...workflowApproval.summary_fields,
approved_or_denied_by: {
id: 1,
username: 'Foobar',
},
},
},
}}
/>
);
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find(`Detail[label="Actor"] dd`).text()).toBe('Foobar');
});
test('action buttons should be hidden when user cannot approve or deny', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
can_approve_or_deny: false,
}}
/>
);
test('action buttons should be hidden when user cannot approve or deny', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
can_approve_or_deny: false,
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find('WorkflowApprovalActionButtons').length).toBe(0);
});
test('only the delete button should render when approval is not pending', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
can_approve_or_deny: true,
status: 'successful',
}}
/>
);
test('only the delete button should render when approval is not pending', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
can_approve_or_deny: true,
status: 'successful',
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find('WorkflowApprovalControls').length).toBe(0);
expect(wrapper.find('Button[aria-label="Approve"]').length).toBe(0);
expect(wrapper.find('DeleteButton').length).toBe(1);
@ -176,9 +486,13 @@ describe('<WorkflowApprovalDetail />', () => {
WorkflowApprovalsAPI.approve.mockImplementationOnce(() =>
Promise.reject(new Error())
);
const wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
await act(async () => {
wrapper.find('DropdownToggleAction').invoke('onClick')();
});
@ -202,9 +516,13 @@ describe('<WorkflowApprovalDetail />', () => {
WorkflowApprovalsAPI.deny.mockImplementationOnce(() =>
Promise.reject(new Error())
);
const wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
await act(async () => wrapper.find('Toggle').prop('onToggle')(true));
wrapper.update();
await waitForElement(
@ -232,45 +550,53 @@ describe('<WorkflowApprovalDetail />', () => {
);
});
test('delete button should be hidden when user cannot delete', () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
status: 'successful',
summary_fields: {
...workflowApproval.summary_fields,
user_capabilities: {
delete: false,
start: false,
test('delete button should be hidden when user cannot delete', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
status: 'successful',
summary_fields: {
...workflowApproval.summary_fields,
user_capabilities: {
delete: false,
start: false,
},
approved_or_denied_by: {
id: 1,
username: 'Foobar',
},
},
approved_or_denied_by: {
id: 1,
username: 'Foobar',
},
},
}}
/>
);
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find('DeleteButton').length).toBe(0);
});
test('delete button should be hidden when job is pending and approve, action buttons should render', async () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
summary_fields: {
...workflowApproval.summary_fields,
user_capabilities: {
delete: true,
start: false,
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
summary_fields: {
...workflowApproval.summary_fields,
user_capabilities: {
delete: true,
start: false,
},
},
},
can_approve_or_deny: true,
}}
/>
);
can_approve_or_deny: true,
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find('DeleteButton').length).toBe(0);
expect(wrapper.find('WorkflowApprovalControls').length).toBe(1);
@ -297,11 +623,15 @@ describe('<WorkflowApprovalDetail />', () => {
});
test('Delete button is visible and approve action is not', async () => {
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={mockWorkflowApprovals.results[1]}
/>
);
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={mockWorkflowApprovals.results[1]}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
expect(wrapper.find('DeleteButton').length).toBe(1);
expect(wrapper.find('DeleteButton').prop('isDisabled')).toBe(false);
expect(wrapper.find('WorkflowApprovalControls').length).toBe(0);
@ -311,21 +641,25 @@ describe('<WorkflowApprovalDetail />', () => {
WorkflowApprovalsAPI.destroy.mockImplementationOnce(() =>
Promise.reject(new Error())
);
const wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
status: 'successful',
summary_fields: {
...workflowApproval.summary_fields,
approved_or_denied_by: {
id: 1,
username: 'Foobar',
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<WorkflowApprovalDetail
workflowApproval={{
...workflowApproval,
status: 'successful',
summary_fields: {
...workflowApproval.summary_fields,
approved_or_denied_by: {
id: 1,
username: 'Foobar',
},
},
},
}}
/>
);
}}
/>
);
});
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
await waitForElement(
wrapper,
'WorkflowApprovalDetail Button[aria-label="Delete"]'