mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 05:15:02 -02:30
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:
@@ -368,7 +368,7 @@ describe('<AdHocCommandsWizard/>', () => {
|
|||||||
response: {
|
response: {
|
||||||
config: {
|
config: {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: '/api/v2/credentals',
|
url: '/api/v2/credentials',
|
||||||
},
|
},
|
||||||
data: 'An error occurred',
|
data: 'An error occurred',
|
||||||
status: 403,
|
status: 403,
|
||||||
|
|||||||
@@ -90,10 +90,7 @@ function WorkflowApproval({ setBreadcrumb }) {
|
|||||||
/>
|
/>
|
||||||
{workflowApproval && (
|
{workflowApproval && (
|
||||||
<Route path="/workflow_approvals/:id/details">
|
<Route path="/workflow_approvals/:id/details">
|
||||||
<WorkflowApprovalDetail
|
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
||||||
workflowApproval={workflowApproval}
|
|
||||||
isLoading={isLoading}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
<Route key="not-found" path="*">
|
<Route key="not-found" path="*">
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useEffect, useState } 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 {
|
||||||
|
Divider as PFDivider,
|
||||||
|
Title as PFTitle,
|
||||||
|
Chip,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
import AlertModal from 'components/AlertModal';
|
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 DeleteButton from 'components/DeleteButton';
|
||||||
import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
|
|
||||||
import ErrorDetail from 'components/ErrorDetail';
|
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 { formatDateString, secondsToHHMMSS } from 'util/dates';
|
||||||
import { WorkflowApprovalsAPI, WorkflowJobsAPI } from 'api';
|
import {
|
||||||
|
WorkflowApprovalsAPI,
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
WorkflowJobsAPI,
|
||||||
|
} from 'api';
|
||||||
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
||||||
import { WorkflowApproval } from 'types';
|
import { WorkflowApproval } from 'types';
|
||||||
import StatusLabel from 'components/StatusLabel';
|
import StatusLabel from 'components/StatusLabel';
|
||||||
@@ -16,8 +30,23 @@ import {
|
|||||||
getDetailPendingLabel,
|
getDetailPendingLabel,
|
||||||
getStatus,
|
getStatus,
|
||||||
} from '../shared/WorkflowApprovalUtils';
|
} from '../shared/WorkflowApprovalUtils';
|
||||||
|
|
||||||
import WorkflowApprovalControls from '../shared/WorkflowApprovalControls';
|
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 }) {
|
function WorkflowApprovalDetail({ workflowApproval }) {
|
||||||
const { id: workflowApprovalId } = useParams();
|
const { id: workflowApprovalId } = useParams();
|
||||||
const [isKebabOpen, setIsKebabModalOpen] = useState(false);
|
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 () => {
|
const handleCancel = async () => {
|
||||||
setIsKebabModalOpen(false);
|
setIsKebabModalOpen(false);
|
||||||
await cancelWorkflowApprovals();
|
await cancelWorkflowApprovals();
|
||||||
@@ -95,7 +161,18 @@ function WorkflowApprovalDetail({ workflowApproval }) {
|
|||||||
workflowApproval?.summary_fields?.workflow_job_template;
|
workflowApproval?.summary_fields?.workflow_job_template;
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isDeleteLoading || isApproveLoading || isDenyLoading || isCancelLoading;
|
isApproveLoading ||
|
||||||
|
isCancelLoading ||
|
||||||
|
isDeleteLoading ||
|
||||||
|
isDenyLoading ||
|
||||||
|
isLoadingWorkflowJob;
|
||||||
|
|
||||||
|
if (isLoadingWorkflowJob) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
if (fetchWorkflowJobError) {
|
||||||
|
return <ContentError error={fetchWorkflowJobError} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
@@ -146,19 +223,6 @@ function WorkflowApprovalDetail({ workflowApproval }) {
|
|||||||
value={workflowApproval.job_explanation}
|
value={workflowApproval.job_explanation}
|
||||||
dataCy="wa-detail-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
|
<Detail
|
||||||
label={t`Workflow Job Template`}
|
label={t`Workflow Job Template`}
|
||||||
value={
|
value={
|
||||||
@@ -194,6 +258,88 @@ function WorkflowApprovalDetail({ workflowApproval }) {
|
|||||||
value={secondsToHHMMSS(workflowApproval.elapsed)}
|
value={secondsToHHMMSS(workflowApproval.elapsed)}
|
||||||
/>
|
/>
|
||||||
</DetailList>
|
</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>
|
<CardActionsRow>
|
||||||
{workflowApproval.status === 'pending' &&
|
{workflowApproval.status === 'pending' &&
|
||||||
workflowApproval.can_approve_or_deny && (
|
workflowApproval.can_approve_or_deny && (
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { WorkflowApprovalsAPI } from 'api';
|
import {
|
||||||
|
WorkflowApprovalsAPI,
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
WorkflowJobsAPI,
|
||||||
|
} from 'api';
|
||||||
import { formatDateString } from 'util/dates';
|
import { formatDateString } from 'util/dates';
|
||||||
import {
|
import {
|
||||||
mountWithContexts,
|
mountWithContexts,
|
||||||
@@ -12,18 +16,278 @@ import mockWorkflowApprovals from '../data.workflowApprovals.json';
|
|||||||
const workflowApproval = mockWorkflowApprovals.results[0];
|
const workflowApproval = mockWorkflowApprovals.results[0];
|
||||||
|
|
||||||
jest.mock('../../../api');
|
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 />', () => {
|
describe('<WorkflowApprovalDetail />', () => {
|
||||||
test('initially renders successfully', () => {
|
beforeEach(() => {
|
||||||
mountWithContexts(
|
WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({
|
||||||
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
data: workflowJobTemplate,
|
||||||
);
|
});
|
||||||
|
WorkflowJobsAPI.readDetail.mockResolvedValue({ data: workflowJob });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render Details', () => {
|
afterEach(() => {
|
||||||
const wrapper = mountWithContexts(
|
jest.clearAllMocks();
|
||||||
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
});
|
||||||
);
|
|
||||||
|
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) {
|
function assertDetail(label, value) {
|
||||||
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
|
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
|
||||||
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
|
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
|
||||||
@@ -50,123 +314,169 @@ describe('<WorkflowApprovalDetail />', () => {
|
|||||||
);
|
);
|
||||||
assertDetail('Last Modified', formatDateString(workflowApproval.modified));
|
assertDetail('Last Modified', formatDateString(workflowApproval.modified));
|
||||||
assertDetail('Elapsed', '00:00:22');
|
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);
|
expect(wrapper.find('WorkflowApprovalControls').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show expiration date/time', () => {
|
test('should show expiration date/time', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
approval_expiration: '2020-10-10T17:13:12.067947Z',
|
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(
|
expect(wrapper.find(`Detail[label="Expires"] dd`).text()).toBe(
|
||||||
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
|
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show finished date/time', () => {
|
test('should show finished date/time', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
finished: '2020-10-10T17:13:12.067947Z',
|
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(
|
expect(wrapper.find(`Detail[label="Finished"] dd`).text()).toBe(
|
||||||
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
|
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show canceled date/time', () => {
|
test('should show canceled date/time', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
canceled_on: '2020-10-10T17:13:12.067947Z',
|
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(
|
expect(wrapper.find(`Detail[label="Canceled"] dd`).text()).toBe(
|
||||||
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
|
`${formatDateString('2020-10-10T17:13:12.067947Z')}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show explanation', () => {
|
test('should show explanation', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
job_explanation: 'Some explanation text',
|
workflowApproval={{
|
||||||
}}
|
...workflowApproval,
|
||||||
/>
|
job_explanation: 'Some explanation text',
|
||||||
);
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
|
||||||
expect(wrapper.find(`Detail[label="Explanation"] dd`).text()).toBe(
|
expect(wrapper.find(`Detail[label="Explanation"] dd`).text()).toBe(
|
||||||
'Some explanation text'
|
'Some explanation text'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show status when not pending', () => {
|
test('should show status when not pending', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
status: 'successful',
|
workflowApproval={{
|
||||||
summary_fields: {
|
...workflowApproval,
|
||||||
...workflowApproval.summary_fields,
|
status: 'successful',
|
||||||
approved_or_denied_by: {
|
summary_fields: {
|
||||||
id: 1,
|
...workflowApproval.summary_fields,
|
||||||
username: 'Foobar',
|
approved_or_denied_by: {
|
||||||
|
id: 1,
|
||||||
|
username: 'Foobar',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
});
|
||||||
|
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
|
||||||
expect(wrapper.find('StatusLabel').text()).toBe('Approved');
|
expect(wrapper.find('StatusLabel').text()).toBe('Approved');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show actor when available', () => {
|
test('should show actor when available', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
summary_fields: {
|
workflowApproval={{
|
||||||
...workflowApproval.summary_fields,
|
...workflowApproval,
|
||||||
approved_or_denied_by: {
|
summary_fields: {
|
||||||
id: 1,
|
...workflowApproval.summary_fields,
|
||||||
username: 'Foobar',
|
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');
|
expect(wrapper.find(`Detail[label="Actor"] dd`).text()).toBe('Foobar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('action buttons should be hidden when user cannot approve or deny', () => {
|
test('action buttons should be hidden when user cannot approve or deny', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
can_approve_or_deny: false,
|
workflowApproval={{
|
||||||
}}
|
...workflowApproval,
|
||||||
/>
|
can_approve_or_deny: false,
|
||||||
);
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
|
||||||
expect(wrapper.find('WorkflowApprovalActionButtons').length).toBe(0);
|
expect(wrapper.find('WorkflowApprovalActionButtons').length).toBe(0);
|
||||||
});
|
});
|
||||||
test('only the delete button should render when approval is not pending', () => {
|
|
||||||
const wrapper = mountWithContexts(
|
test('only the delete button should render when approval is not pending', async () => {
|
||||||
<WorkflowApprovalDetail
|
let wrapper;
|
||||||
workflowApproval={{
|
await act(async () => {
|
||||||
...workflowApproval,
|
wrapper = mountWithContexts(
|
||||||
can_approve_or_deny: true,
|
<WorkflowApprovalDetail
|
||||||
status: 'successful',
|
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('WorkflowApprovalControls').length).toBe(0);
|
||||||
expect(wrapper.find('Button[aria-label="Approve"]').length).toBe(0);
|
expect(wrapper.find('Button[aria-label="Approve"]').length).toBe(0);
|
||||||
expect(wrapper.find('DeleteButton').length).toBe(1);
|
expect(wrapper.find('DeleteButton').length).toBe(1);
|
||||||
@@ -176,9 +486,13 @@ describe('<WorkflowApprovalDetail />', () => {
|
|||||||
WorkflowApprovalsAPI.approve.mockImplementationOnce(() =>
|
WorkflowApprovalsAPI.approve.mockImplementationOnce(() =>
|
||||||
Promise.reject(new Error())
|
Promise.reject(new Error())
|
||||||
);
|
);
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
await act(async () => {
|
||||||
);
|
wrapper = mountWithContexts(
|
||||||
|
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('DropdownToggleAction').invoke('onClick')();
|
wrapper.find('DropdownToggleAction').invoke('onClick')();
|
||||||
});
|
});
|
||||||
@@ -202,9 +516,13 @@ describe('<WorkflowApprovalDetail />', () => {
|
|||||||
WorkflowApprovalsAPI.deny.mockImplementationOnce(() =>
|
WorkflowApprovalsAPI.deny.mockImplementationOnce(() =>
|
||||||
Promise.reject(new Error())
|
Promise.reject(new Error())
|
||||||
);
|
);
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
await act(async () => {
|
||||||
);
|
wrapper = mountWithContexts(
|
||||||
|
<WorkflowApprovalDetail workflowApproval={workflowApproval} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
|
||||||
await act(async () => wrapper.find('Toggle').prop('onToggle')(true));
|
await act(async () => wrapper.find('Toggle').prop('onToggle')(true));
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
await waitForElement(
|
await waitForElement(
|
||||||
@@ -232,45 +550,53 @@ describe('<WorkflowApprovalDetail />', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('delete button should be hidden when user cannot delete', () => {
|
test('delete button should be hidden when user cannot delete', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
status: 'successful',
|
workflowApproval={{
|
||||||
summary_fields: {
|
...workflowApproval,
|
||||||
...workflowApproval.summary_fields,
|
status: 'successful',
|
||||||
user_capabilities: {
|
summary_fields: {
|
||||||
delete: false,
|
...workflowApproval.summary_fields,
|
||||||
start: false,
|
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);
|
expect(wrapper.find('DeleteButton').length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('delete button should be hidden when job is pending and approve, action buttons should render', async () => {
|
test('delete button should be hidden when job is pending and approve, action buttons should render', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
summary_fields: {
|
workflowApproval={{
|
||||||
...workflowApproval.summary_fields,
|
...workflowApproval,
|
||||||
user_capabilities: {
|
summary_fields: {
|
||||||
delete: true,
|
...workflowApproval.summary_fields,
|
||||||
start: false,
|
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('DeleteButton').length).toBe(0);
|
||||||
expect(wrapper.find('WorkflowApprovalControls').length).toBe(1);
|
expect(wrapper.find('WorkflowApprovalControls').length).toBe(1);
|
||||||
@@ -297,11 +623,15 @@ describe('<WorkflowApprovalDetail />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Delete button is visible and approve action is not', async () => {
|
test('Delete button is visible and approve action is not', async () => {
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={mockWorkflowApprovals.results[1]}
|
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').length).toBe(1);
|
||||||
expect(wrapper.find('DeleteButton').prop('isDisabled')).toBe(false);
|
expect(wrapper.find('DeleteButton').prop('isDisabled')).toBe(false);
|
||||||
expect(wrapper.find('WorkflowApprovalControls').length).toBe(0);
|
expect(wrapper.find('WorkflowApprovalControls').length).toBe(0);
|
||||||
@@ -311,21 +641,25 @@ describe('<WorkflowApprovalDetail />', () => {
|
|||||||
WorkflowApprovalsAPI.destroy.mockImplementationOnce(() =>
|
WorkflowApprovalsAPI.destroy.mockImplementationOnce(() =>
|
||||||
Promise.reject(new Error())
|
Promise.reject(new Error())
|
||||||
);
|
);
|
||||||
const wrapper = mountWithContexts(
|
let wrapper;
|
||||||
<WorkflowApprovalDetail
|
await act(async () => {
|
||||||
workflowApproval={{
|
wrapper = mountWithContexts(
|
||||||
...workflowApproval,
|
<WorkflowApprovalDetail
|
||||||
status: 'successful',
|
workflowApproval={{
|
||||||
summary_fields: {
|
...workflowApproval,
|
||||||
...workflowApproval.summary_fields,
|
status: 'successful',
|
||||||
approved_or_denied_by: {
|
summary_fields: {
|
||||||
id: 1,
|
...workflowApproval.summary_fields,
|
||||||
username: 'Foobar',
|
approved_or_denied_by: {
|
||||||
|
id: 1,
|
||||||
|
username: 'Foobar',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
});
|
||||||
|
waitForElement(wrapper, 'WorkflowApprovalDetail', (el) => el.length > 0);
|
||||||
await waitForElement(
|
await waitForElement(
|
||||||
wrapper,
|
wrapper,
|
||||||
'WorkflowApprovalDetail Button[aria-label="Delete"]'
|
'WorkflowApprovalDetail Button[aria-label="Delete"]'
|
||||||
|
|||||||
Reference in New Issue
Block a user