import { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState, } from 'react' export enum PipelineStatusRequestStatus { None = 'None', StartRequested = 'StartRequested', StopRequested = 'StopRequested', RestartRequested = 'RestartRequested', } interface PipelineRequestStatusContextType { requestStatus: Record pipelineStatusSnapshot: Record setRequestStatus: ( pipelineId: number, status: PipelineStatusRequestStatus, snapshotStatus?: string ) => void getRequestStatus: (pipelineId: number) => PipelineStatusRequestStatus updatePipelineStatus: (pipelineId: number, backendStatus: string | undefined) => void } interface PipelineRequestStatusProviderProps { children: ReactNode } const PipelineRequestStatusContext = createContext( undefined ) // [Joshen] Leaving a comment for future investigation // Do we need this? Afaict the status is getting returned from the API so we might not need // to track the pipeline status on the client side export const PipelineRequestStatusProvider = ({ children }: PipelineRequestStatusProviderProps) => { const [requestStatus, setRequestStatusState] = useState< Record >({}) const [pipelineStatusSnapshot, setPipelineStatusSnapshot] = useState< Record >({}) const timeoutsRef = useRef>({}) const REQUEST_TIMEOUT_MS = 10_000 const setRequestStatus = ( pipelineId: number, status: PipelineStatusRequestStatus, snapshotStatus?: string ) => { setRequestStatusState((prev) => ({ ...prev, [pipelineId]: status, })) setPipelineStatusSnapshot((prev) => { if (status === PipelineStatusRequestStatus.None) { const { [pipelineId]: _omit, ...rest } = prev return rest } // Only set snapshot when provided to avoid undefined entries if (snapshotStatus !== undefined) { return { ...prev, [pipelineId]: snapshotStatus } } return prev }) // Clear existing timeout for this pipeline const existing = timeoutsRef.current[pipelineId] if (existing !== undefined) { clearTimeout(existing) delete timeoutsRef.current[pipelineId] } // Start auto-reset timer for non-None states if (status !== PipelineStatusRequestStatus.None) { const id = window.setTimeout(() => { // If still pending, clear to None to show backend state setRequestStatusState((prev) => { if (prev[pipelineId] && prev[pipelineId] !== PipelineStatusRequestStatus.None) { return { ...prev, [pipelineId]: PipelineStatusRequestStatus.None } } return prev }) setPipelineStatusSnapshot((prev) => { const { [pipelineId]: _omit, ...rest } = prev return rest }) delete timeoutsRef.current[pipelineId] }, REQUEST_TIMEOUT_MS) timeoutsRef.current[pipelineId] = id } } const getRequestStatus = (pipelineId: number): PipelineStatusRequestStatus => { return requestStatus[pipelineId] || PipelineStatusRequestStatus.None } const updatePipelineStatus = useCallback( (pipelineId: number, newStatus: string | undefined) => { const currentRequestStatus = requestStatus[pipelineId] || PipelineStatusRequestStatus.None if (currentRequestStatus === PipelineStatusRequestStatus.None) return // Only remove when backend status differs from snapshot const snapshotStatus = pipelineStatusSnapshot[pipelineId] if (newStatus !== snapshotStatus) { setRequestStatus(pipelineId, PipelineStatusRequestStatus.None) } }, [requestStatus, pipelineStatusSnapshot] ) // Cleanup all timers on unmount useEffect(() => { return () => { Object.values(timeoutsRef.current).forEach((id) => clearTimeout(id)) timeoutsRef.current = {} } }, []) return ( {children} ) } export const usePipelineRequestStatus = () => { const context = useContext(PipelineRequestStatusContext) if (context === undefined) { throw new Error('usePipelineRequestStatus must be used within a PipelineRequestStatusProvider') } return context }