8.2 KiB
- Home
- Components
- Waterfall
Waterfall
A hierarchical log display component for showing time-based log data with interactive controls and custom actions.
interface WaterfallProps {
logData: LogItemType[];
temporalCursor?: number;
panelWidth?: number;
onTemporalCursorChange?: (time: number) => void;
getIcon: (item: LogItemType) => ReactNode;
hoveredId?: string | null;
onItemHover?: (id: string | null) => void;
minWindow?: number;
maxWindow?: number;
zoomFactor?: number;
enabled?: boolean;
children?: React.ReactNode;
className?: string;
}
Types
type LogItemType = TreeDataItemV2 & {
type: "task" | "attempt" | "info" | "step";
icon?: "history" | "file-code" | "bot" | "check-circle" | "pause-circle";
createTime?: number;
startTime?: number;
duration?: number;
time?: number;
color?: "blue" | "green" | "orange" | "gray-light" | "gray-medium" | "purple";
isCollapsible?: boolean;
hasStripes?: boolean;
isHaltedStep?: boolean;
};
type TreeDataItemV2 = {
id: string;
parentId: string | null;
label: string;
isCollapsible?: boolean;
actions?: ReactNode;
disable?: boolean;
[key: string]: unknown;
};
Basic Usage
Job registered in queue generate-report Attempt 1 Fetch database records Job halted, waiting for resources... Waiting for image renderer... Render charts Assemble PDF Fetch database records Memory checkpoint data-validation Schema validation Data integrity check Generate validation report Cache cleared email-notification Prepare email template Attach report files Send via SMTP Webhook triggered System health check cleanup-process Remove temporary files Update job status All tasks completed -10s -5s 0ms 5s 10s 15s 20s 25s 30s 20.000s 19.900s 3.000s Halted 4.600s 6.200s 3.000s 12.000s 3.500s 33.000s ```jsx import { Waterfall, LogItemType } from "@vuer-ai/vuer-uikit"; import React, { useState, useMemo } from "react"; import { Bot, CheckCircle2, FileCode, History, Info, PauseCircle, Eye, EyeClosed, Trash2, } from "lucide-react"; import { cn } from "@vuer-ai/vuer-uikit";
const logData: LogItemType[] = [ { id: "0", parentId: null, indent: 0, etype: "info", label: "Job registered in queue", icon: "history", time: 0, color: "purple", }, { id: "1", parentId: null, indent: 0, etype: "task", label: "generate-report", icon: "file-code", createTime: 0, startTime: 0, duration: 20, color: "blue", isCollapsible: true, hasStripes: true, }, { id: "2", parentId: "1", indent: 1, etype: "attempt", label: "Attempt 1", icon: "bot", createTime: 0.1, startTime: 0.1, duration: 19.9, color: "blue", isCollapsible: true, }, { id: "3", parentId: "2", indent: 2, etype: "step", label: "Fetch database records", icon: "check-circle", createTime: 0.2, startTime: 0.5, duration: 3, color: "green", }, { id: "4", parentId: "2", indent: 2, etype: "step", label: "Job halted, waiting for resources...", icon: "pause-circle", startTime: 4, duration: 2, color: "orange", isHaltedStep: true, }, // ... more log items ];
const getIcon = (item: LogItemType) => { const iconColor = (item: LogItemType) => { if (item.label === "generate-report" || item.label === "Assemble PDF") return "text-blue-500"; if (item.icon === "file-code") return "text-muted-foreground"; return ""; };
switch (item.icon) { case "history": return ; case "file-code": return <FileCode className={cn("size-4 shrink-0", iconColor(item))} />; case "bot": return ; case "check-circle": return ; case "pause-circle": return ; default: return ; } };
// Helper functions const getAllChildrenIds = (parentId: string, allItems: LogItemType[]): string[] => { const children: string[] = []; const directChildren = allItems.filter(item => item.parentId === parentId);
for (const child of directChildren) { children.push(child.id); children.push(...getAllChildrenIds(child.id, allItems)); }
return children; };
const isIndirectlyHidden = (itemId: string, hiddenItems: Set, allItems: LogItemType[]): boolean => { const item = allItems.find(i => i.id === itemId); if (!item || !item.parentId) return false;
if (hiddenItems.has(item.parentId)) { return true; }
return isIndirectlyHidden(item.parentId, hiddenItems, allItems); };
// State management const [expandedItems, setExpandedItems] = useState(new Set(["1", "2"])); const [hoveredId, setHoveredId] = useState<string | null>(null); const [hiddenItems, setHiddenItems] = useState<Set>(new Set()); const [deletedItems, setDeletedItems] = useState<Set>(new Set());
// Toggle visibility function const toggleItemVisibility = (itemId: string) => { const wasHidden = hiddenItems.has(itemId);
setHiddenItems(prev => { const newSet = new Set(prev); if (newSet.has(itemId)) { newSet.delete(itemId); } else { newSet.add(itemId); } return newSet; });
// If hiding an expanded item, collapse it if (!wasHidden && expandedItems.has(itemId)) { setExpandedItems(prev => { const newSet = new Set(prev); newSet.delete(itemId); return newSet; }); } };
// Delete item function const deleteItem = (itemId: string) => { const itemsToDelete = [itemId, ...getAllChildrenIds(itemId, logData)];
setDeletedItems(prev => { const newSet = new Set(prev); itemsToDelete.forEach(id => newSet.add(id)); return newSet; });
setHiddenItems(prev => { const newSet = new Set(prev); itemsToDelete.forEach(id => newSet.delete(id)); return newSet; });
setExpandedItems(prev => { const newSet = new Set(prev); itemsToDelete.forEach(id => newSet.delete(id)); return newSet; }); };
// Create actions for each item const createActions = (itemId: string, isDirectlyHidden: boolean, isIndirectlyHiddenItem: boolean, isHovered: boolean) => { const visibilityButton = ( <button onClick={(e) => { e.stopPropagation(); toggleItemVisibility(itemId); }} className='hover:bg-shadow-secondary rounded p-1' > {isDirectlyHidden ? ( ) : isIndirectlyHiddenItem ? (
const deleteButton = ( <button onClick={(e) => { e.stopPropagation(); deleteItem(itemId); }} className='hover:bg-shadow-secondary rounded p-1' > <Trash2 className={cn('size-3', (isDirectlyHidden || isIndirectlyHiddenItem) ? 'text-icon-tertiary' : 'text-icon-primary' )} /> );
return (
// Process log data with actions and visibility states const processedLogData = useMemo(() => { return logData .filter(item => !deletedItems.has(item.id)) .map(item => { const isDirectlyHidden = hiddenItems.has(item.id); const isIndirectlyHiddenItem = isIndirectlyHidden(item.id, hiddenItems, logData); const isItemHidden = isDirectlyHidden || isIndirectlyHiddenItem; const isHovered = hoveredId === item.id;
return {
...item,
actions: createActions(item.id, isDirectlyHidden, isIndirectlyHiddenItem, isHovered),
disable: isItemHidden,
isSelectable: !isItemHidden,
};
});
}, [logData, deletedItems, hiddenItems, hoveredId]);
return (