From 03d8987d939a6b67c73201a998f2d9f895bd4ee6 Mon Sep 17 00:00:00 2001 From: seiwailai Date: Fri, 16 Apr 2021 23:28:09 +0800 Subject: [PATCH] project: Add last job status as for feedback feature. --- awx/ui_next/src/screens/Project/Project.jsx | 15 ++-- .../Project/ProjectDetail/ProjectDetail.jsx | 41 ++++++++++- .../src/screens/Project/useWsProject.js | 68 +++++++++++++++++++ 3 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 awx/ui_next/src/screens/Project/useWsProject.js diff --git a/awx/ui_next/src/screens/Project/Project.jsx b/awx/ui_next/src/screens/Project/Project.jsx index 5c3a5a7564..e5cabdd46b 100644 --- a/awx/ui_next/src/screens/Project/Project.jsx +++ b/awx/ui_next/src/screens/Project/Project.jsx @@ -24,6 +24,7 @@ import ProjectDetail from './ProjectDetail'; import ProjectEdit from './ProjectEdit'; import ProjectJobTemplatesList from './ProjectJobTemplatesList'; import { OrganizationsAPI, ProjectsAPI } from '../../api'; +import useWsProject from './useWsProject'; function Project({ i18n, setBreadcrumb }) { const { me = {} } = useConfig(); @@ -32,7 +33,7 @@ function Project({ i18n, setBreadcrumb }) { const { request: fetchProjectAndRoles, - result: { project, isNotifAdmin }, + result: { projectDetail, isNotifAdmin }, isLoading: hasContentLoading, error: contentError, } = useRequest( @@ -58,12 +59,12 @@ function Project({ i18n, setBreadcrumb }) { data.summary_fields.credentials = results; } return { - project: data, + projectDetail: data, isNotifAdmin: notifAdminRes.data.results.length > 0, }; }, [id]), { - project: null, + projectDetail: null, notifAdminRes: null, } ); @@ -73,10 +74,12 @@ function Project({ i18n, setBreadcrumb }) { }, [fetchProjectAndRoles, location.pathname]); useEffect(() => { - if (project) { - setBreadcrumb(project); + if (projectDetail) { + setBreadcrumb(projectDetail); } - }, [project, setBreadcrumb]); + }, [projectDetail, setBreadcrumb]); + + const project = useWsProject(projectDetail); const loadScheduleOptions = useCallback(() => { return ProjectsAPI.readScheduleOptions(project.id); diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 0d33d9dfef..0e82027ba7 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -1,8 +1,8 @@ -import React, { useCallback } from 'react'; +import React, { Fragment, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; 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 { Config } from '../../../contexts/Config'; @@ -22,6 +22,8 @@ import { toTitleCase } from '../../../util/strings'; import useRequest, { useDismissableError } from '../../../util/useRequest'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; import ProjectSyncButton from '../shared/ProjectSyncButton'; +import StatusLabel from '../../../components/StatusLabel'; +import { formatDateString } from '../../../util/dates'; function ProjectDetail({ project, i18n }) { const { @@ -84,9 +86,44 @@ function ProjectDetail({ project, i18n }) { ); } + const generateLastJobTooltip = job => { + return ( + +
{i18n._(t`MOST RECENT SYNC`)}
+
+ {i18n._(t`JOB ID:`)} {job.id} +
+
+ {i18n._(t`STATUS:`)} {job.status.toUpperCase()} +
+ {job.finished && ( +
+ {i18n._(t`FINISHED:`)} {formatDateString(job.finished)} +
+ )} +
+ ); + }; + return ( + + + + + + ) + } + /> { + 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; +}