diff --git a/airbyte-webapp/src/components/JobItem/JobItem.tsx b/airbyte-webapp/src/components/JobItem/JobItem.tsx index ff8b9ff7b5e8..d9b0de93e84b 100644 --- a/airbyte-webapp/src/components/JobItem/JobItem.tsx +++ b/airbyte-webapp/src/components/JobItem/JobItem.tsx @@ -48,7 +48,7 @@ export const JobItem: React.FC = ({ job }) => { const didSucceed = didJobSucceed(job); const onExpand = () => { - setIsOpen(!isOpen); + setIsOpen((prevIsOpen) => !prevIsOpen); }; const onDetailsToggled = useCallback(() => { diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index ee7b45b4b6d5..f35ccb370b34 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -362,6 +362,8 @@ "connection.cancelSync": "Cancel Sync", "connection.cancelReset": "Cancel Reset", "connection.canceling": "Canceling...", + "connection.linkedJobNotFound": "Job not found", + "connection.returnToSyncHistory": "Return to Sync History", "form.frequency": "Replication frequency*", "form.cronExpression": "Cron expression*", diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/StatusView.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/StatusView.tsx index 0e637b776749..74dc2483778a 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/StatusView.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/StatusView.tsx @@ -2,12 +2,14 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; +import { Link, useLocation } from "react-router-dom"; import { Button, LoadingButton } from "components"; import { Card } from "components/base/Card"; import { Tooltip } from "components/base/Tooltip"; import EmptyResource from "components/EmptyResourceBlock"; import { RotateIcon } from "components/icons/RotateIcon"; +import { useAttemptLink } from "components/JobItem/attemptLinkUtils"; import { getFrequencyType } from "config/utils"; import { Action, Namespace } from "core/analytics"; @@ -52,15 +54,23 @@ const StatusView: React.FC = ({ connection }) => { const [activeJob, setActiveJob] = useState(); const [jobPageSize, setJobPageSize] = useState(JOB_PAGE_SIZE_INCREMENT); const analyticsService = useAnalyticsService(); - const { jobs, isPreviousData: isJobPageLoading } = useListJobs({ + const { jobId: linkedJobId } = useAttemptLink(); + const { pathname } = useLocation(); + const { + jobs, + totalJobCount, + isPreviousData: isJobPageLoading, + } = useListJobs({ configId: connection.connectionId, configTypes: ["sync", "reset_connection"], + includingJobId: linkedJobId ? Number(linkedJobId) : undefined, pagination: { pageSize: jobPageSize, }, }); - const moreJobPagesAvailable = jobs.length === jobPageSize; + const linkedJobNotFound = linkedJobId && jobs.length === 0; + const moreJobPagesAvailable = !linkedJobNotFound && jobPageSize < totalJobCount; useEffect(() => { const jobRunningOrPending = getJobRunningOrPending(jobs); @@ -74,6 +84,9 @@ const StatusView: React.FC = ({ connection }) => { // We need to disable button when job is canceled but the job list still has a running job } as ActiveJob) ); + + // necessary because request to listJobs may return a result larger than the current page size if a linkedJobId is passed in + setJobPageSize((prevJobPageSize) => Math.max(prevJobPageSize, jobs.length)); }, [jobs]); const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService(); @@ -171,7 +184,20 @@ const StatusView: React.FC = ({ connection }) => { } > - {jobs.length ? : } />} + {jobs.length ? ( + + ) : linkedJobNotFound ? ( + } + description={ + + + + } + /> + ) : ( + } /> + )} {(moreJobPagesAvailable || isJobPageLoading) && ( diff --git a/airbyte-webapp/src/services/job/JobService.tsx b/airbyte-webapp/src/services/job/JobService.tsx index ac4d58e6d955..2e8b025f963e 100644 --- a/airbyte-webapp/src/services/job/JobService.tsx +++ b/airbyte-webapp/src/services/job/JobService.tsx @@ -9,7 +9,7 @@ import { JobDebugInfoRead, JobInfoRead, JobListRequestBody, - JobWithAttemptsRead, + JobReadList, Pagination, } from "../../core/request/AirbyteClient"; import { useSuspenseQuery } from "../connector/useSuspenseQuery"; @@ -17,7 +17,8 @@ import { useSuspenseQuery } from "../connector/useSuspenseQuery"; export const jobsKeys = { all: ["jobs"] as const, lists: () => [...jobsKeys.all, "list"] as const, - list: (filters: string, pagination?: Pagination) => [...jobsKeys.lists(), { filters, pagination }] as const, + list: (filters: string, includingJobId?: number, pagination?: Pagination) => + [...jobsKeys.lists(), { filters, includingJobId, pagination }] as const, detail: (jobId: number) => [...jobsKeys.all, "details", jobId] as const, getDebugInfo: (jobId: number) => [...jobsKeys.all, "getDebugInfo", jobId] as const, cancel: (jobId: string) => [...jobsKeys.all, "cancel", jobId] as const, @@ -31,13 +32,18 @@ function useGetJobService() { export const useListJobs = (listParams: JobListRequestBody) => { const service = useGetJobService(); - const result = useQuery(jobsKeys.list(listParams.configId, listParams.pagination), () => service.list(listParams), { - refetchInterval: 2500, // every 2,5 seconds, - keepPreviousData: true, - suspense: true, - }); - // cast to JobWithAttemptsRead[] because (suspense: true) means we will never get undefined - return { jobs: result.data?.jobs as JobWithAttemptsRead[], isPreviousData: result.isPreviousData }; + const result = useQuery( + jobsKeys.list(listParams.configId, listParams.includingJobId, listParams.pagination), + () => service.list(listParams), + { + refetchInterval: 2500, // every 2,5 seconds, + keepPreviousData: true, + suspense: true, + } + ); + // cast to JobReadList because (suspense: true) means we will never get undefined + const jobReadList = result.data as JobReadList; + return { jobs: jobReadList.jobs, totalJobCount: jobReadList.totalJobCount, isPreviousData: result.isPreviousData }; }; export const useGetJob = (id: number, enabled = true) => {