Generated by Spark: Add workflow filtering by status (success, failed, running)

This commit is contained in:
2026-01-17 10:06:37 +00:00
committed by GitHub
parent be1f1d0959
commit eb895f70f5
3 changed files with 323 additions and 3 deletions

View File

@@ -32,6 +32,9 @@ import {
Copy,
Pencil,
Link,
CheckCircle,
XCircle,
ArrowsClockwise,
} from '@phosphor-icons/react'
import { toast } from 'sonner'
import { LazyInlineMonacoEditor } from '@/components/molecules/LazyInlineMonacoEditor'
@@ -65,11 +68,24 @@ export function WorkflowDesigner({ workflows, onWorkflowsChange }: WorkflowDesig
const [isDragging, setIsDragging] = useState(false)
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
const [connectingFrom, setConnectingFrom] = useState<string | null>(null)
const [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'failed' | 'running'>('all')
const canvasRef = useRef<HTMLDivElement>(null)
const selectedWorkflow = workflows.find((w) => w.id === selectedWorkflowId)
const selectedNode = selectedWorkflow?.nodes.find((n) => n.id === selectedNodeId)
const filteredWorkflows = workflows.filter((workflow) => {
if (statusFilter === 'all') return true
return workflow.status === statusFilter
})
const statusCounts = {
all: workflows.length,
success: workflows.filter((w) => w.status === 'success').length,
failed: workflows.filter((w) => w.status === 'failed').length,
running: workflows.filter((w) => w.status === 'running').length,
}
const handleCreateWorkflow = () => {
if (!newWorkflowName.trim()) {
toast.error('Please enter a workflow name')
@@ -325,9 +341,61 @@ export function WorkflowDesigner({ workflows, onWorkflowsChange }: WorkflowDesig
</Button>
</div>
<div>
<Label className="text-xs text-muted-foreground mb-2 block">Filter by Status</Label>
<Select value={statusFilter} onValueChange={(value: any) => setStatusFilter(value)}>
<SelectTrigger className="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
<div className="flex items-center justify-between gap-2 w-full">
<span>All Statuses</span>
<Badge variant="secondary" className="text-xs">
{statusCounts.all}
</Badge>
</div>
</SelectItem>
<SelectItem value="success">
<div className="flex items-center justify-between gap-2 w-full">
<span className="flex items-center gap-1.5">
<CheckCircle size={14} weight="fill" className="text-green-500" />
Success
</span>
<Badge variant="secondary" className="text-xs">
{statusCounts.success}
</Badge>
</div>
</SelectItem>
<SelectItem value="failed">
<div className="flex items-center justify-between gap-2 w-full">
<span className="flex items-center gap-1.5">
<XCircle size={14} weight="fill" className="text-red-500" />
Failed
</span>
<Badge variant="secondary" className="text-xs">
{statusCounts.failed}
</Badge>
</div>
</SelectItem>
<SelectItem value="running">
<div className="flex items-center justify-between gap-2 w-full">
<span className="flex items-center gap-1.5">
<ArrowsClockwise size={14} weight="bold" className="text-blue-500" />
Running
</span>
<Badge variant="secondary" className="text-xs">
{statusCounts.running}
</Badge>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<ScrollArea className="flex-1">
<div className="space-y-2">
{workflows.map((workflow) => (
{filteredWorkflows.map((workflow) => (
<Card
key={workflow.id}
className={`cursor-pointer transition-all ${
@@ -340,7 +408,7 @@ export function WorkflowDesigner({ workflows, onWorkflowsChange }: WorkflowDesig
<CardHeader className="p-4">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 flex-wrap">
<CardTitle className="text-sm truncate">{workflow.name}</CardTitle>
<Badge
variant={workflow.isActive ? 'default' : 'outline'}
@@ -348,19 +416,55 @@ export function WorkflowDesigner({ workflows, onWorkflowsChange }: WorkflowDesig
>
{workflow.isActive ? 'Active' : 'Inactive'}
</Badge>
{workflow.status && (
<Badge
variant="outline"
className={`text-xs flex items-center gap-1 ${
workflow.status === 'success'
? 'border-green-500 text-green-500'
: workflow.status === 'failed'
? 'border-red-500 text-red-500'
: 'border-blue-500 text-blue-500'
}`}
>
{workflow.status === 'success' && (
<>
<CheckCircle size={12} weight="fill" />
Success
</>
)}
{workflow.status === 'failed' && (
<>
<XCircle size={12} weight="fill" />
Failed
</>
)}
{workflow.status === 'running' && (
<>
<ArrowsClockwise size={12} weight="bold" className="animate-spin" />
Running
</>
)}
</Badge>
)}
</div>
{workflow.description && (
<CardDescription className="text-xs mt-1 line-clamp-2">
{workflow.description}
</CardDescription>
)}
<div className="flex gap-2 mt-2">
<div className="flex gap-2 mt-2 flex-wrap">
<Badge variant="outline" className="text-xs">
{workflow.nodes.length} nodes
</Badge>
<Badge variant="outline" className="text-xs">
{workflow.connections.length} connections
</Badge>
{workflow.lastRun && (
<Badge variant="outline" className="text-xs">
Last run: {new Date(workflow.lastRun).toLocaleDateString()}
</Badge>
)}
</div>
</div>
</div>
@@ -396,6 +500,18 @@ export function WorkflowDesigner({ workflows, onWorkflowsChange }: WorkflowDesig
</div>
</ScrollArea>
{filteredWorkflows.length === 0 && workflows.length > 0 && (
<div className="flex-1 flex items-center justify-center">
<div className="text-center text-muted-foreground">
<FlowArrow size={48} className="mx-auto mb-2 opacity-50" weight="duotone" />
<p className="text-sm">No workflows match this filter</p>
<Button size="sm" className="mt-2" onClick={() => setStatusFilter('all')}>
Clear Filter
</Button>
</div>
</div>
)}
{workflows.length === 0 && (
<div className="flex-1 flex items-center justify-center">
<div className="text-center text-muted-foreground">

View File

@@ -188,8 +188,210 @@
}
],
"isActive": true,
"status": "success",
"lastRun": 1704153600000,
"createdAt": 1704067200000,
"updatedAt": 1704067200000
},
{
"id": "workflow-2",
"name": "Email Notification Pipeline",
"description": "Send automated email notifications to users",
"nodes": [
{
"id": "node-4",
"type": "trigger",
"name": "Schedule Trigger",
"position": { "x": 100, "y": 100 },
"data": {
"label": "Daily at 9 AM"
},
"config": {
"triggerType": "schedule",
"scheduleExpression": "0 9 * * *"
}
},
{
"id": "node-5",
"type": "database",
"name": "Fetch Users",
"position": { "x": 300, "y": 100 },
"data": {
"label": "Get Active Users"
},
"config": {
"databaseQuery": "SELECT * FROM users WHERE active = true"
}
},
{
"id": "node-6",
"type": "lambda",
"name": "Send Emails",
"position": { "x": 500, "y": 100 },
"data": {
"label": "Process Email Queue"
},
"config": {
"lambdaCode": "// Send email logic"
}
}
],
"connections": [
{
"id": "conn-3",
"source": "node-4",
"target": "node-5"
},
{
"id": "conn-4",
"source": "node-5",
"target": "node-6"
}
],
"isActive": true,
"status": "running",
"lastRun": 1704240000000,
"createdAt": 1704067200000,
"updatedAt": 1704240000000
},
{
"id": "workflow-3",
"name": "Payment Processing",
"description": "Handle payment transactions and update order status",
"nodes": [
{
"id": "node-7",
"type": "trigger",
"name": "Payment Event",
"position": { "x": 100, "y": 100 },
"data": {
"label": "Payment Received"
},
"config": {
"triggerType": "webhook"
}
},
{
"id": "node-8",
"type": "api",
"name": "Validate Payment",
"position": { "x": 300, "y": 100 },
"data": {
"label": "Check Payment Gateway"
},
"config": {
"httpMethod": "POST",
"apiEndpoint": "https://api.stripe.com/v1/charges"
}
},
{
"id": "node-9",
"type": "condition",
"name": "Payment Valid?",
"position": { "x": 500, "y": 100 },
"data": {
"label": "Check Status"
},
"config": {
"condition": "payment.status === 'succeeded'"
}
},
{
"id": "node-10",
"type": "database",
"name": "Update Order",
"position": { "x": 700, "y": 100 },
"data": {
"label": "Mark as Paid"
},
"config": {
"databaseQuery": "UPDATE orders SET status = 'paid'"
}
}
],
"connections": [
{
"id": "conn-5",
"source": "node-7",
"target": "node-8"
},
{
"id": "conn-6",
"source": "node-8",
"target": "node-9"
},
{
"id": "conn-7",
"source": "node-9",
"target": "node-10"
}
],
"isActive": true,
"status": "failed",
"lastRun": 1704226800000,
"createdAt": 1704067200000,
"updatedAt": 1704226800000
},
{
"id": "workflow-4",
"name": "Data Backup Task",
"description": "Automated database backup and archival",
"nodes": [
{
"id": "node-11",
"type": "trigger",
"name": "Nightly Backup",
"position": { "x": 100, "y": 100 },
"data": {
"label": "Every Night at 2 AM"
},
"config": {
"triggerType": "schedule",
"scheduleExpression": "0 2 * * *"
}
},
{
"id": "node-12",
"type": "database",
"name": "Export Data",
"position": { "x": 300, "y": 100 },
"data": {
"label": "Create Backup"
},
"config": {
"databaseQuery": "pg_dump database"
}
},
{
"id": "node-13",
"type": "lambda",
"name": "Upload to S3",
"position": { "x": 500, "y": 100 },
"data": {
"label": "Store Backup"
},
"config": {
"lambdaCode": "// S3 upload logic"
}
}
],
"connections": [
{
"id": "conn-8",
"source": "node-11",
"target": "node-12"
},
{
"id": "conn-9",
"source": "node-12",
"target": "node-13"
}
],
"isActive": false,
"status": "success",
"lastRun": 1704171600000,
"createdAt": 1704067200000,
"updatedAt": 1704171600000
}
],
"project-lambdas": [

View File

@@ -237,6 +237,8 @@ export interface Workflow {
nodes: WorkflowNode[]
connections: WorkflowConnection[]
isActive: boolean
status?: 'success' | 'failed' | 'running'
lastRun?: number
createdAt: number
updatedAt: number
}