"use client"; import { AlertTriangle, ArrowDown, ArrowUp, ArrowUpDown, CheckCircle, ChevronLeft, ChevronRight, ExternalLink, Info, Loader2, RefreshCw } from 'lucide-react'; import Image from 'next/image'; import { useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useState } from "react"; import { getWorkflowRunsApiV1SuperuserWorkflowRunsGet } from '@/client/sdk.gen'; import { FilterBuilder } from "@/components/filters/FilterBuilder"; import { MediaPreviewButton, MediaPreviewDialog } from '@/components/MediaPreviewDialog'; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useAuth } from '@/lib/auth'; import{ superadminFilterAttributes } from "@/lib/filterAttributes"; import { decodeFiltersFromURL, encodeFiltersToURL } from '@/lib/filters'; import { impersonateAsSuperadmin } from '@/lib/utils'; import { ActiveFilter } from '@/types/filters'; interface WorkflowRun { id: number; name: string; workflow_id: number; workflow_name?: string; user_id?: number; organization_id?: number; organization_name?: string; mode: string; is_completed: boolean; recording_url?: string; transcript_url?: string; usage_info?: Record; cost_info?: Record; initial_context?: Record; gathered_context?: Record; created_at: string; } interface WorkflowRunsResponse { workflow_runs: WorkflowRun[]; total_count: number; page: number; limit: number; total_pages: number; } export default function RunsPage() { const router = useRouter(); const searchParams = useSearchParams(); const [runs, setRuns] = useState([]); const [currentPage, setCurrentPage] = useState(() => { const pageParam = searchParams.get('page'); return pageParam ? parseInt(pageParam, 10) : 1; }); const [totalPages, setTotalPages] = useState(1); const [totalCount, setTotalCount] = useState(0); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(""); const [isExecutingFilters, setIsExecutingFilters] = useState(false); const [autoRefresh, setAutoRefresh] = useState(false); const [isAutoRefreshing, setIsAutoRefreshing] = useState(false); const limit = 50; // Initialize filters from URL const [activeFilters, setActiveFilters] = useState(() => { return decodeFiltersFromURL(searchParams, superadminFilterAttributes); }); // Applied filters are the ones actually used for fetching (only updated on Apply click) const [appliedFilters, setAppliedFilters] = useState(() => { return decodeFiltersFromURL(searchParams, superadminFilterAttributes); }); // Sort state (initialized from URL) const [sortBy, setSortBy] = useState(() => { return searchParams.get('sort_by') || null; }); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(() => { const order = searchParams.get('sort_order'); return order === 'asc' ? 'asc' : 'desc'; }); const [selectedRowId, setSelectedRowId] = useState(null); const auth = useAuth(); // Media preview dialog const mediaPreview = MediaPreviewDialog(); const fetchRuns = useCallback(async ( page: number, filters?: ActiveFilter[], isAutoRefresh = false, sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc' ) => { if (!auth.isAuthenticated) return; // Don't show loading state for auto-refresh to prevent UI flicker if (!isAutoRefresh) { setIsLoading(true); } else { setIsAutoRefreshing(true); } setError(""); try { let filterParam = undefined; if (filters && filters.length > 0) { const filterData = filters.map(filter => ({ attribute: filter.attribute.id, type: filter.attribute.type, value: filter.value, })); filterParam = JSON.stringify(filterData); } const response = await getWorkflowRunsApiV1SuperuserWorkflowRunsGet({ query: { page, limit, ...(filterParam && { filters: filterParam }), ...(sortByParam && { sort_by: sortByParam }), ...(sortOrderParam && { sort_order: sortOrderParam }), }, }); if (response.data) { const data = response.data as WorkflowRunsResponse; setRuns(data.workflow_runs); setCurrentPage(data.page); setTotalPages(data.total_pages); setTotalCount(data.total_count); } } catch (err) { setError("Failed to fetch workflow runs. Please try again."); console.error("Fetch runs error:", err); } finally { if (!isAutoRefresh) { setIsLoading(false); } else { setIsAutoRefreshing(false); } } }, [limit, auth.isAuthenticated]); const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => { const params = new URLSearchParams(); params.set('page', page.toString()); // Add filters to URL if present if (filters && filters.length > 0) { const filterString = encodeFiltersToURL(filters); if (filterString) { const filterParams = new URLSearchParams(filterString); filterParams.forEach((value, key) => params.set(key, value)); } } // Add sort to URL if present if (sortByParam) { params.set('sort_by', sortByParam); params.set('sort_order', sortOrderParam || 'desc'); } router.push(`/superadmin/runs?${params.toString()}`); }, [router]); useEffect(() => { // Fetch runs when auth is available and when page/sort changes if (auth.isAuthenticated) { fetchRuns(currentPage, appliedFilters, false, sortBy, sortOrder); } }, [currentPage, auth.isAuthenticated, appliedFilters, fetchRuns, sortBy, sortOrder]); // Auto-refresh every 5 seconds when enabled and filters are active useEffect(() => { // Only set up interval if auto-refresh is enabled and there are applied filters if (!autoRefresh || appliedFilters.length === 0) { return; } const intervalId = setInterval(() => { // Pass true to indicate this is an auto-refresh fetchRuns(currentPage, appliedFilters, true, sortBy, sortOrder); }, 5000); // Cleanup interval on unmount or when dependencies change return () => clearInterval(intervalId); }, [currentPage, appliedFilters, fetchRuns, autoRefresh, sortBy, sortOrder]); const handlePageChange = (page: number) => { setCurrentPage(page); updatePageInUrl(page, appliedFilters, sortBy, sortOrder); fetchRuns(page, appliedFilters, false, sortBy, sortOrder); }; const handleApplyFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); // Reset to first page when applying filters setAppliedFilters(activeFilters); // Update applied filters updatePageInUrl(1, activeFilters, sortBy, sortOrder); await fetchRuns(1, activeFilters, false, sortBy, sortOrder); setIsExecutingFilters(false); }, [activeFilters, fetchRuns, updatePageInUrl, sortBy, sortOrder]); const handleFiltersChange = useCallback((filters: ActiveFilter[]) => { setActiveFilters(filters); }, []); const handleClearFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); setAppliedFilters([]); // Clear applied filters updatePageInUrl(1, [], sortBy, sortOrder); // Clear filters from URL await fetchRuns(1, [], false, sortBy, sortOrder); // Fetch all runs without filters setIsExecutingFilters(false); }, [fetchRuns, updatePageInUrl, sortBy, sortOrder]); const handleSort = useCallback((field: string) => { // Reset to first page when sort changes setCurrentPage(1); const newSortBy = field; let newSortOrder: 'asc' | 'desc' = 'desc'; if (sortBy === field) { newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } setSortBy(newSortBy); setSortOrder(newSortOrder); updatePageInUrl(1, appliedFilters, newSortBy, newSortOrder); }, [sortBy, sortOrder, updatePageInUrl, appliedFilters]); /** * ---------------------------------------------------------------------------------- * Helpers * ---------------------------------------------------------------------------------- */ const formatDate = (dateString: string) => new Date(dateString).toLocaleString(); const calculateDuration = (isCompleted: boolean, usageInfo?: Record) => { if (isCompleted && typeof usageInfo?.call_duration_seconds === 'number') { return `${Number(usageInfo.call_duration_seconds).toFixed(2)}s`; } return '-'; }; /** * Wrapper around shared impersonation util – we only need to fetch the * current superadmin token and then delegate the heavy lifting. */ const impersonateAndMaybeRedirect = useCallback( async (targetUserId: number | undefined, redirectPath?: string) => { if (!targetUserId || !auth.isAuthenticated) return; try { const token = await auth.getAccessToken(); await impersonateAsSuperadmin({ accessToken: token, userId: targetUserId, redirectPath, openInNewTab: true, }); } catch (err) { console.error('Failed to impersonate user', err); alert('Failed to impersonate the user. Please try again.'); } }, [auth], ); if (isLoading && runs.length === 0) { return (
Loading workflow runs...
); } return (

Workflow Runs

View and manage all workflow runs across organizations

{error && (
{error}
)} 0} />
All Workflow Runs Showing {runs.length} of {totalCount} total runs
{isAutoRefreshing && (
Refreshing...
)}
{runs.length === 0 ? (
No workflow runs found.
) : ( <>
ID Workflow Status Disposition Tags handleSort('duration')} >
Duration {sortBy === 'duration' ? ( sortOrder === 'asc' ? : ) : ( )}
Details handleSort('created_at')} >
Created At {sortBy === 'created_at' ? ( sortOrder === 'asc' ? : ) : ( )}
Actions
{runs.map((run) => ( #{run.id}
{run.workflow_name ? ( run.workflow_name.length > 15 ? `${run.workflow_name.substring(0, 15)}...` : run.workflow_name ) : 'Unknown Workflow'} ID: {String(run.workflow_id).length > 12 ? `${String(run.workflow_id).substring(0, 12)}...` : run.workflow_id}
{run.is_completed ? ( ) : ( )} {run.gathered_context?.mapped_call_disposition ? ( {run.gathered_context.mapped_call_disposition as string} ) : ( - )} {Array.isArray(run.gathered_context?.call_tags) && run.gathered_context.call_tags.length > 0 ? (
{run.gathered_context.call_tags.map((tag: string) => ( {tag} ))}
) : ( - )}
{calculateDuration(run.is_completed, run.usage_info)}
{run.gathered_context && (

Gathered Context

                                                                            {JSON.stringify(run.gathered_context, null, 2)}
                                                                        
)} {run.usage_info && (

Usage Info

                                                                            {JSON.stringify(run.usage_info, null, 2)}
                                                                        
)} {!run.gathered_context && !run.usage_info && ( - )}
{formatDate(run.created_at)}
{/* Quick-link to open the workflow inside the *regular* app after successfully impersonating the owner of the workflow. */}
))}
{/* Pagination */} {totalPages > 1 && (
Page {currentPage} of {totalPages} ({totalCount} total runs)
{/* Page numbers */} {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { let pageNum; if (totalPages <= 5) { pageNum = i + 1; } else if (currentPage <= 3) { pageNum = i + 1; } else if (currentPage >= totalPages - 2) { pageNum = totalPages - 4 + i; } else { pageNum = currentPage - 2 + i; } return ( ); })}
)} )}
{/* Media Preview Dialog */} {mediaPreview.dialog}
); }