mirror of
https://github.com/ansible/awx.git
synced 2026-03-13 15:09:32 -02:30
project: Add last job status as for feedback feature.
This commit is contained in:
@@ -24,6 +24,7 @@ import ProjectDetail from './ProjectDetail';
|
|||||||
import ProjectEdit from './ProjectEdit';
|
import ProjectEdit from './ProjectEdit';
|
||||||
import ProjectJobTemplatesList from './ProjectJobTemplatesList';
|
import ProjectJobTemplatesList from './ProjectJobTemplatesList';
|
||||||
import { OrganizationsAPI, ProjectsAPI } from '../../api';
|
import { OrganizationsAPI, ProjectsAPI } from '../../api';
|
||||||
|
import useWsProject from './useWsProject';
|
||||||
|
|
||||||
function Project({ i18n, setBreadcrumb }) {
|
function Project({ i18n, setBreadcrumb }) {
|
||||||
const { me = {} } = useConfig();
|
const { me = {} } = useConfig();
|
||||||
@@ -32,7 +33,7 @@ function Project({ i18n, setBreadcrumb }) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
request: fetchProjectAndRoles,
|
request: fetchProjectAndRoles,
|
||||||
result: { project, isNotifAdmin },
|
result: { projectDetail, isNotifAdmin },
|
||||||
isLoading: hasContentLoading,
|
isLoading: hasContentLoading,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
@@ -58,12 +59,12 @@ function Project({ i18n, setBreadcrumb }) {
|
|||||||
data.summary_fields.credentials = results;
|
data.summary_fields.credentials = results;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
project: data,
|
projectDetail: data,
|
||||||
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
||||||
};
|
};
|
||||||
}, [id]),
|
}, [id]),
|
||||||
{
|
{
|
||||||
project: null,
|
projectDetail: null,
|
||||||
notifAdminRes: null,
|
notifAdminRes: null,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -73,10 +74,12 @@ function Project({ i18n, setBreadcrumb }) {
|
|||||||
}, [fetchProjectAndRoles, location.pathname]);
|
}, [fetchProjectAndRoles, location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (project) {
|
if (projectDetail) {
|
||||||
setBreadcrumb(project);
|
setBreadcrumb(projectDetail);
|
||||||
}
|
}
|
||||||
}, [project, setBreadcrumb]);
|
}, [projectDetail, setBreadcrumb]);
|
||||||
|
|
||||||
|
const project = useWsProject(projectDetail);
|
||||||
|
|
||||||
const loadScheduleOptions = useCallback(() => {
|
const loadScheduleOptions = useCallback(() => {
|
||||||
return ProjectsAPI.readScheduleOptions(project.id);
|
return ProjectsAPI.readScheduleOptions(project.id);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { Fragment, useCallback } from 'react';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button, List, ListItem } from '@patternfly/react-core';
|
import { Button, List, ListItem, Tooltip } from '@patternfly/react-core';
|
||||||
import { Project } from '../../../types';
|
import { Project } from '../../../types';
|
||||||
import { Config } from '../../../contexts/Config';
|
import { Config } from '../../../contexts/Config';
|
||||||
|
|
||||||
@@ -22,6 +22,8 @@ import { toTitleCase } from '../../../util/strings';
|
|||||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||||
import ProjectSyncButton from '../shared/ProjectSyncButton';
|
import ProjectSyncButton from '../shared/ProjectSyncButton';
|
||||||
|
import StatusLabel from '../../../components/StatusLabel';
|
||||||
|
import { formatDateString } from '../../../util/dates';
|
||||||
|
|
||||||
function ProjectDetail({ project, i18n }) {
|
function ProjectDetail({ project, i18n }) {
|
||||||
const {
|
const {
|
||||||
@@ -84,9 +86,44 @@ function ProjectDetail({ project, i18n }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const generateLastJobTooltip = job => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div>{i18n._(t`MOST RECENT SYNC`)}</div>
|
||||||
|
<div>
|
||||||
|
{i18n._(t`JOB ID:`)} {job.id}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{i18n._(t`STATUS:`)} {job.status.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
{job.finished && (
|
||||||
|
<div>
|
||||||
|
{i18n._(t`FINISHED:`)} {formatDateString(job.finished)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList gutter="sm">
|
<DetailList gutter="sm">
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Last Job Status`)}
|
||||||
|
value={
|
||||||
|
summary_fields.last_job && (
|
||||||
|
<Tooltip
|
||||||
|
position="top"
|
||||||
|
content={generateLastJobTooltip(summary_fields.last_job)}
|
||||||
|
key={summary_fields.last_job.id}
|
||||||
|
>
|
||||||
|
<Link to={`/jobs/project/${summary_fields.last_job.id}`}>
|
||||||
|
<StatusLabel status={summary_fields.last_job.status} />
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Name`)}
|
label={i18n._(t`Name`)}
|
||||||
value={name}
|
value={name}
|
||||||
|
|||||||
68
awx/ui_next/src/screens/Project/useWsProject.js
Normal file
68
awx/ui_next/src/screens/Project/useWsProject.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import useWebsocket from '../../util/useWebsocket';
|
||||||
|
|
||||||
|
export default function useWsProjects(initialProject) {
|
||||||
|
const [project, setProject] = useState(initialProject);
|
||||||
|
const lastMessage = useWebsocket({
|
||||||
|
jobs: ['status_changed'],
|
||||||
|
control: ['limit_reached_1'],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setProject(initialProject);
|
||||||
|
}, [initialProject]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (
|
||||||
|
!project ||
|
||||||
|
!lastMessage?.unified_job_id ||
|
||||||
|
lastMessage.type !== 'project_update'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const last_status = project.summary_fields.last_job.status;
|
||||||
|
|
||||||
|
// In case if users spam the sync button, we will need to ensure
|
||||||
|
// the fluent UI on most recent sync tooltip and last job status.
|
||||||
|
// Thus, we will not update our last job status to `Pending` if
|
||||||
|
// there is current running job.
|
||||||
|
//
|
||||||
|
// For instance, we clicked sync for particular project for twice.
|
||||||
|
// For first sync, our last job status should immediately change
|
||||||
|
// to `Pending`, then `Waiting`, then `Running`, then result
|
||||||
|
// (which are `successful`, `failed`, `error`, `cancelled`.
|
||||||
|
// For second sync, if the status response is `pending` and we have
|
||||||
|
// running and waiting jobs, we should not update our UI to `Pending`,
|
||||||
|
// otherwise our most recent sync tooltip UI will lose our current running
|
||||||
|
// job and we cannot navigate to the job link through the link provided
|
||||||
|
// by most recent sync tooltip.
|
||||||
|
//
|
||||||
|
// More ideally, we should prevent any spamming on sync button using
|
||||||
|
// backend logic to reduce overload on server and we can have a
|
||||||
|
// less complex frontend implementation for fluent UI
|
||||||
|
if (
|
||||||
|
lastMessage.status === 'pending' &&
|
||||||
|
!['successful', 'failed', 'error', 'cancelled'].includes(last_status)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updatedProject = {
|
||||||
|
...project,
|
||||||
|
summary_fields: {
|
||||||
|
...project.summary_fields,
|
||||||
|
last_job: {
|
||||||
|
id: lastMessage.unified_job_id,
|
||||||
|
status: lastMessage.status,
|
||||||
|
finished: lastMessage.finished,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setProject(updatedProject);
|
||||||
|
},
|
||||||
|
[lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
);
|
||||||
|
|
||||||
|
return project;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user