Server-side sorting for execution search results
Sorting now applies to the entire result set via ClickHouse ORDER BY instead of only sorting the current page client-side. Default sort order is timestamp descending. Supported sort columns: startTime, status, agentId, routeId, correlationId, durationMs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { ExecutionSummary } from '../../api/types';
|
||||
import { StatusPill } from '../../components/shared/StatusPill';
|
||||
import { DurationBar } from '../../components/shared/DurationBar';
|
||||
import { AppBadge } from '../../components/shared/AppBadge';
|
||||
import { ProcessorTree } from './ProcessorTree';
|
||||
import { ExchangeDetail } from './ExchangeDetail';
|
||||
import { useExecutionSearch } from './use-execution-search';
|
||||
import styles from './ResultsTable.module.css';
|
||||
|
||||
interface ResultsTableProps {
|
||||
@@ -24,31 +25,6 @@ function formatTime(iso: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function compareFn(a: ExecutionSummary, b: ExecutionSummary, col: SortColumn, dir: SortDir): number {
|
||||
let cmp = 0;
|
||||
switch (col) {
|
||||
case 'startTime':
|
||||
cmp = a.startTime.localeCompare(b.startTime);
|
||||
break;
|
||||
case 'status':
|
||||
cmp = a.status.localeCompare(b.status);
|
||||
break;
|
||||
case 'agentId':
|
||||
cmp = a.agentId.localeCompare(b.agentId);
|
||||
break;
|
||||
case 'routeId':
|
||||
cmp = a.routeId.localeCompare(b.routeId);
|
||||
break;
|
||||
case 'correlationId':
|
||||
cmp = (a.correlationId ?? '').localeCompare(b.correlationId ?? '');
|
||||
break;
|
||||
case 'durationMs':
|
||||
cmp = a.durationMs - b.durationMs;
|
||||
break;
|
||||
}
|
||||
return dir === 'asc' ? cmp : -cmp;
|
||||
}
|
||||
|
||||
interface SortableThProps {
|
||||
label: string;
|
||||
column: SortColumn;
|
||||
@@ -76,21 +52,12 @@ function SortableTh({ label, column, activeColumn, direction, onSort, style }: S
|
||||
|
||||
export function ResultsTable({ results, loading }: ResultsTableProps) {
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
const [sortColumn, setSortColumn] = useState<SortColumn | null>(null);
|
||||
const [sortDir, setSortDir] = useState<SortDir>('desc');
|
||||
|
||||
const sortedResults = useMemo(() => {
|
||||
if (!sortColumn) return results;
|
||||
return [...results].sort((a, b) => compareFn(a, b, sortColumn, sortDir));
|
||||
}, [results, sortColumn, sortDir]);
|
||||
const sortColumn = useExecutionSearch((s) => s.sortField);
|
||||
const sortDir = useExecutionSearch((s) => s.sortDir);
|
||||
const setSort = useExecutionSearch((s) => s.setSort);
|
||||
|
||||
function handleSort(col: SortColumn) {
|
||||
if (sortColumn === col) {
|
||||
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
|
||||
} else {
|
||||
setSortColumn(col);
|
||||
setSortDir('desc');
|
||||
}
|
||||
setSort(col);
|
||||
}
|
||||
|
||||
if (loading && results.length === 0) {
|
||||
@@ -124,7 +91,7 @@ export function ResultsTable({ results, loading }: ResultsTableProps) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedResults.map((exec) => {
|
||||
{results.map((exec) => {
|
||||
const isExpanded = expandedId === exec.executionId;
|
||||
return (
|
||||
<ResultRow
|
||||
|
||||
@@ -9,6 +9,9 @@ function todayMidnight(): string {
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T00:00`;
|
||||
}
|
||||
|
||||
type SortColumn = 'startTime' | 'status' | 'agentId' | 'routeId' | 'correlationId' | 'durationMs';
|
||||
type SortDir = 'asc' | 'desc';
|
||||
|
||||
interface ExecutionSearchState {
|
||||
status: string[];
|
||||
timeFrom: string;
|
||||
@@ -22,6 +25,8 @@ interface ExecutionSearchState {
|
||||
live: boolean;
|
||||
offset: number;
|
||||
limit: number;
|
||||
sortField: SortColumn;
|
||||
sortDir: SortDir;
|
||||
|
||||
toggleLive: () => void;
|
||||
setStatus: (statuses: string[]) => void;
|
||||
@@ -35,6 +40,7 @@ interface ExecutionSearchState {
|
||||
setAgentId: (v: string) => void;
|
||||
setProcessorType: (v: string) => void;
|
||||
setOffset: (v: number) => void;
|
||||
setSort: (col: SortColumn) => void;
|
||||
clearAll: () => void;
|
||||
toSearchRequest: () => SearchRequest;
|
||||
}
|
||||
@@ -52,6 +58,8 @@ export const useExecutionSearch = create<ExecutionSearchState>((set, get) => ({
|
||||
live: true,
|
||||
offset: 0,
|
||||
limit: 25,
|
||||
sortField: 'startTime',
|
||||
sortDir: 'desc',
|
||||
|
||||
toggleLive: () => set((state) => ({ live: !state.live })),
|
||||
setStatus: (statuses) => set({ status: statuses, offset: 0 }),
|
||||
@@ -71,6 +79,12 @@ export const useExecutionSearch = create<ExecutionSearchState>((set, get) => ({
|
||||
setAgentId: (v) => set({ agentId: v, offset: 0 }),
|
||||
setProcessorType: (v) => set({ processorType: v, offset: 0 }),
|
||||
setOffset: (v) => set({ offset: v }),
|
||||
setSort: (col) =>
|
||||
set((state) => ({
|
||||
sortField: col,
|
||||
sortDir: state.sortField === col && state.sortDir === 'desc' ? 'asc' : 'desc',
|
||||
offset: 0,
|
||||
})),
|
||||
clearAll: () =>
|
||||
set({
|
||||
status: ['COMPLETED', 'FAILED', 'RUNNING'],
|
||||
@@ -83,6 +97,8 @@ export const useExecutionSearch = create<ExecutionSearchState>((set, get) => ({
|
||||
agentId: '',
|
||||
processorType: '',
|
||||
offset: 0,
|
||||
sortField: 'startTime',
|
||||
sortDir: 'desc',
|
||||
}),
|
||||
|
||||
toSearchRequest: (): SearchRequest => {
|
||||
@@ -102,6 +118,8 @@ export const useExecutionSearch = create<ExecutionSearchState>((set, get) => ({
|
||||
processorType: s.processorType || undefined,
|
||||
offset: s.offset,
|
||||
limit: s.limit,
|
||||
sortField: s.sortField,
|
||||
sortDir: s.sortDir,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user