From 9824d88d5392f6f823df88def3856cdd78c8adc3 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 6 Feb 2026 09:01:06 +0530 Subject: [PATCH] chore: add sort order in URL params --- api/services/campaign/rate_limiter.py | 2 +- pipecat | 2 +- ui/src/app/superadmin/runs/page.tsx | 41 ++++++++----- .../components/WorkflowExecutions.tsx | 60 +++++++++++++------ ui/src/components/filters/FilterBuilder.tsx | 11 ++-- .../components/workflow-runs/CampaignRuns.tsx | 59 ++++++++++++------ .../workflow-runs/WorkflowRunsTable.tsx | 32 ++++++++-- 7 files changed, 148 insertions(+), 59 deletions(-) diff --git a/api/services/campaign/rate_limiter.py b/api/services/campaign/rate_limiter.py index 5c1120d..a69be17 100644 --- a/api/services/campaign/rate_limiter.py +++ b/api/services/campaign/rate_limiter.py @@ -13,7 +13,7 @@ class RateLimiter: def __init__(self): self.redis_client: Optional[aioredis.Redis] = None - self.stale_call_timeout = 1800 # 30 minutes in seconds + self.stale_call_timeout = 300 # 5 minutes in seconds async def _get_redis(self) -> aioredis.Redis: """Get or create Redis connection""" diff --git a/pipecat b/pipecat index 866bf1c..e618bb9 160000 --- a/pipecat +++ b/pipecat @@ -1 +1 @@ -Subproject commit 866bf1c5685e7fadf2af012d8769ebbc35297db0 +Subproject commit e618bb98dfde6224ef9f4e15769580790719b269 diff --git a/ui/src/app/superadmin/runs/page.tsx b/ui/src/app/superadmin/runs/page.tsx index 2b2b663..d20cb94 100644 --- a/ui/src/app/superadmin/runs/page.tsx +++ b/ui/src/app/superadmin/runs/page.tsx @@ -92,9 +92,14 @@ export default function RunsPage() { return decodeFiltersFromURL(searchParams, superadminFilterAttributes); }); - // Sort state - const [sortBy, setSortBy] = useState(null); - const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + // 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'; + }); // Dialog state for comment editing const [isCommentDialogOpen, setIsCommentDialogOpen] = useState(false); @@ -167,7 +172,7 @@ export default function RunsPage() { } }, [limit, accessToken]); - const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[]) => { + const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => { const params = new URLSearchParams(); params.set('page', page.toString()); @@ -180,6 +185,12 @@ export default function RunsPage() { } } + // 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]); @@ -208,7 +219,7 @@ export default function RunsPage() { const handlePageChange = (page: number) => { setCurrentPage(page); - updatePageInUrl(page, appliedFilters); + updatePageInUrl(page, appliedFilters, sortBy, sortOrder); fetchRuns(page, appliedFilters, false, sortBy, sortOrder); }; @@ -216,7 +227,7 @@ export default function RunsPage() { setIsExecutingFilters(true); setCurrentPage(1); // Reset to first page when applying filters setAppliedFilters(activeFilters); // Update applied filters - updatePageInUrl(1, activeFilters); + updatePageInUrl(1, activeFilters, sortBy, sortOrder); await fetchRuns(1, activeFilters, false, sortBy, sortOrder); setIsExecutingFilters(false); }, [activeFilters, fetchRuns, updatePageInUrl, sortBy, sortOrder]); @@ -229,7 +240,7 @@ export default function RunsPage() { setIsExecutingFilters(true); setCurrentPage(1); setAppliedFilters([]); // Clear applied filters - updatePageInUrl(1, []); // Clear filters from URL + 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]); @@ -238,15 +249,16 @@ export default function RunsPage() { // Reset to first page when sort changes setCurrentPage(1); + const newSortBy = field; + let newSortOrder: 'asc' | 'desc' = 'desc'; if (sortBy === field) { - // Toggle order if same field - setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc'); - } else { - // New field, default to desc - setSortBy(field); - setSortOrder('desc'); + newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } - }, [sortBy]); + + setSortBy(newSortBy); + setSortOrder(newSortOrder); + updatePageInUrl(1, appliedFilters, newSortBy, newSortOrder); + }, [sortBy, sortOrder, updatePageInUrl, appliedFilters]); // Save comment function declared outside JSX (requirement #2) const saveAdminComment = useCallback(async () => { @@ -345,6 +357,7 @@ export default function RunsPage() { isExecuting={isExecutingFilters} autoRefresh={autoRefresh} onAutoRefreshChange={setAutoRefresh} + hasAppliedFilters={appliedFilters.length > 0} /> diff --git a/ui/src/app/workflow/[workflowId]/components/WorkflowExecutions.tsx b/ui/src/app/workflow/[workflowId]/components/WorkflowExecutions.tsx index 4e7c51d..d05702a 100644 --- a/ui/src/app/workflow/[workflowId]/components/WorkflowExecutions.tsx +++ b/ui/src/app/workflow/[workflowId]/components/WorkflowExecutions.tsx @@ -30,9 +30,14 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti const [isExecutingFilters, setIsExecutingFilters] = useState(false); const [configuredAttributes, setConfiguredAttributes] = useState(availableAttributes); - // Sort state - const [sortBy, setSortBy] = useState(null); - const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + // 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 { accessToken } = useUserConfig(); @@ -41,6 +46,11 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti return decodeFiltersFromURL(searchParams, availableAttributes); }); + // Applied filters are the ones actually used for fetching (only updated on Apply click) + const [appliedFilters, setAppliedFilters] = useState(() => { + return decodeFiltersFromURL(searchParams, availableAttributes); + }); + // Load disposition codes from workflow configuration const loadDispositionCodes = useCallback(async () => { if (!accessToken) return; @@ -130,7 +140,7 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti } }, [workflowId, accessToken]); - const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[]) => { + const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => { const params = new URLSearchParams(); params.set('page', page.toString()); @@ -143,17 +153,24 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti } } + // Add sort to URL if present + if (sortByParam) { + params.set('sort_by', sortByParam); + params.set('sort_order', sortOrderParam || 'desc'); + } + router.push(`/workflow/${workflowId}/runs?${params.toString()}`, { scroll: false }); }, [router, workflowId]); useEffect(() => { - fetchWorkflowRuns(currentPage, activeFilters, sortBy, sortOrder); - }, [currentPage, activeFilters, fetchWorkflowRuns, sortBy, sortOrder]); + fetchWorkflowRuns(currentPage, appliedFilters, sortBy, sortOrder); + }, [currentPage, appliedFilters, fetchWorkflowRuns, sortBy, sortOrder]); const handleApplyFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); // Reset to first page when applying filters - updatePageInUrl(1, activeFilters); + setAppliedFilters(activeFilters); + updatePageInUrl(1, activeFilters, sortBy, sortOrder); await fetchWorkflowRuns(1, activeFilters, sortBy, sortOrder); setIsExecutingFilters(false); }, [activeFilters, fetchWorkflowRuns, updatePageInUrl, sortBy, sortOrder]); @@ -165,29 +182,36 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti const handleClearFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); - updatePageInUrl(1, []); // Clear filters from URL + setActiveFilters([]); + setAppliedFilters([]); + updatePageInUrl(1, [], sortBy, sortOrder); // Clear filters from URL await fetchWorkflowRuns(1, [], sortBy, sortOrder); // Fetch all workflows without filters setIsExecutingFilters(false); }, [fetchWorkflowRuns, updatePageInUrl, sortBy, sortOrder]); const handlePageChange = useCallback((page: number) => { setCurrentPage(page); - updatePageInUrl(page, activeFilters); - }, [updatePageInUrl, activeFilters]); + updatePageInUrl(page, appliedFilters, sortBy, sortOrder); + }, [updatePageInUrl, appliedFilters, 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) { - // Toggle order if same field - setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc'); - } else { - // New field, default to desc - setSortBy(field); - setSortOrder('desc'); + newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } - }, [sortBy]); + + setSortBy(newSortBy); + setSortOrder(newSortOrder); + updatePageInUrl(1, appliedFilters, newSortBy, newSortOrder); + }, [sortBy, sortOrder, updatePageInUrl, appliedFilters]); + + const handleReload = useCallback(() => { + fetchWorkflowRuns(currentPage, appliedFilters, sortBy, sortOrder); + }, [fetchWorkflowRuns, currentPage, appliedFilters, sortBy, sortOrder]); return (
@@ -205,11 +229,13 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti onApplyFilters={handleApplyFilters} onClearFilters={handleClearFilters} isExecutingFilters={isExecutingFilters} + hasAppliedFilters={appliedFilters.length > 0} sortBy={sortBy} sortOrder={sortOrder} onSort={handleSort} workflowId={workflowId} accessToken={accessToken} + onReload={handleReload} />
); diff --git a/ui/src/components/filters/FilterBuilder.tsx b/ui/src/components/filters/FilterBuilder.tsx index 467f1b4..5fc0786 100644 --- a/ui/src/components/filters/FilterBuilder.tsx +++ b/ui/src/components/filters/FilterBuilder.tsx @@ -38,6 +38,7 @@ interface FilterBuilderProps { isExecuting?: boolean; autoRefresh?: boolean; onAutoRefreshChange?: (enabled: boolean) => void; + hasAppliedFilters?: boolean; } export const FilterBuilder: React.FC = ({ @@ -49,6 +50,7 @@ export const FilterBuilder: React.FC = ({ isExecuting = false, autoRefresh = false, onAutoRefreshChange, + hasAppliedFilters = false, }) => { const [selectedAttribute, setSelectedAttribute] = useState(""); const [expandedFilters, setExpandedFilters] = useState>(new Set()); @@ -69,7 +71,8 @@ export const FilterBuilder: React.FC = ({ if (isModifierPressed && event.key === 'Enter') { event.preventDefault(); const allFiltersValid = activeFilters.every(f => f.isValid); - if (activeFilters.length > 0 && allFiltersValid && !isExecuting) { + const canApply = (activeFilters.length > 0 && allFiltersValid) || (activeFilters.length === 0 && hasAppliedFilters); + if (canApply && !isExecuting) { onApplyFilters(); } } @@ -79,7 +82,7 @@ export const FilterBuilder: React.FC = ({ return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [activeFilters, isExecuting, onApplyFilters]); + }, [activeFilters, isExecuting, onApplyFilters, hasAppliedFilters]); const addFilter = useCallback((attributeId: string) => { const attribute = availableAttributes.find(attr => attr.id === attributeId); @@ -411,7 +414,7 @@ export const FilterBuilder: React.FC = ({ )} {/* Apply Filters Button */} - {activeFilters.length > 0 && ( + {(activeFilters.length > 0 || hasAppliedFilters) && (
{/* Auto-refresh toggle on the left */} {onAutoRefreshChange && ( @@ -440,7 +443,7 @@ export const FilterBuilder: React.FC = ({
)} @@ -123,10 +130,25 @@ export function WorkflowRunsTable({ ) : ( - Workflow Runs - - {subtitle || `Showing ${runs.length} of ${totalCount} total runs`} - +
+
+ Workflow Runs + + {subtitle || `Showing ${runs.length} of ${totalCount} total runs`} + +
+ {onReload && ( + + )} +