Skip to content

Workflow Planner

The Workflow Planner is a management view that shows when your scheduled workflows run, how long they actually take, and where they compete for the same external services. It is designed to answer operational questions such as:

  • What is running on Monday at 8 AM?
  • Are two workflows hitting the same SMTP server at the same time?
  • Where do I have free slots to schedule a new heavy job?
  • How saturated is each day of the week?

Unlike the Workflows view (which is operational, “run/edit”), the Planner is strategic: it helps you organize the execution time of the entire system.

From the side menu: Planner.

The route is /workflow-planner. It appears automatically for any authenticated user of the client.

The planner combines two real sources of information from the system:

  1. Workflows with active cron for the current client (workflows.workflow_json.schedule with active_schedule = true).
  2. Execution history from the workflowstates table, aggregated per node over a configurable window (7, 14, 30, 60 or 90 days) — used to compute the average duration of each node and draw the segments proportionally.

It does not use mock data. If your client has no workflows with active cron, the planner shows an explicit empty state. Manual workflows and webhook-triggered workflows are not shown in this version (planned for a second phase as “hot-spot analysis from history”).

A heatmap by day: each day is a cell showing how many total execution hours are planned. The darker, the higher the load.

  • Week mode shows the 7 days (Monday to Sunday).
  • Month mode shows 30 days (useful to detect monthly patterns).
  • Red warning icons mark days with detected service collisions.
  • Use it for a quick overview of weekly distribution and to spot overloaded or empty days.

A Gantt-style diagram for the selected day:

  • Fixed left column with workflow name + schedule + actions menu button.
  • Hour axis from 00h to 23h (with horizontal scroll if the screen is narrow; the left column stays sticky).
  • One row per workflow, with a colored block representing its execution.
  • Per-node segments inside the block, sized proportionally to the node’s average duration in the history. Colors are stable per module type (always the same color for mail_send, etc.).
  • “Free slots” row with green stripes marking periods with no workflow.
  • Blocks with a red border indicate that workflow has a detected collision with another.

Most real workflows last seconds. If we drew their exact duration on a 24h scale, blocks would be invisible (1 pixel). That’s why the planner enforces a 1-hour minimum visible duration per block. This:

  • Guarantees you can see the per-node segments.
  • Makes nearby-time collisions detectable (e.g. two crons at 08:00 and 08:30).
  • Does not affect the real history or average durations: only the graphical representation.

This is the most important part to understand. We do not consider it a collision just because two workflows overlap in time. The exact definition is:

A collision occurs when two distinct workflows execute a node of the same moduleName (same service type) in overlapping time windows.

A workflow is just an orchestrator. What actually competes for resources are its nodes, and specifically the external service each node calls:

  • If two workflows run at the same time but one talks to mail_send and the other to dataStore, they don’t bother each other: they use different resources.
  • If both run mail_send at the exact same second, they do bother each other: both fight for the same SMTP provider, may hit rate limits, deliver emails out of order, etc.

“Service collision” tells you what real operational problem you will have, not just that two crons cross.

How it is calculated (step-by-step algorithm)

Section titled “How it is calculated (step-by-step algorithm)”

For each block in the day (workflow x occurrence):

  1. The block is broken into per-node sub-intervals, proportional to each node’s average duration.

    Example: Sync CRM (08:00-09:00, 60min visible total, 3 nodes):

    Node%Interval
    webhook8%08:00-08:04
    transformation60%08:04-08:48
    mail_send32%08:48-09:00
  2. All pairs of sub-intervals between distinct blocks of the same day are compared. For each pair (intervalA, intervalB):

    • Same day?
    • Different workflows?
    • Same moduleName (root type)?
    • Time overlap? (max(startA, startB) < min(endA, endB))

    All four conditions must hold for it to count as a collision.

  3. If they all hold, the collision is recorded with the exact overlap time range.

Case 1 — Real collision on the same service

Section titled “Case 1 — Real collision on the same service”

Setup:

  • Sync CRM, cron 0 8 * * 1 (Monday 08:00). Nodes: webhook -> transformation -> mail_send.
  • Backup DB, cron 30 8 * * 1 (Monday 08:30). Nodes: dataStore -> mail_send.

Analysis:

Both workflows touch mail_send near the end of their execution. With the 1h minimum visible duration:

  • Sync CRM occupies 08:00-09:00; mail_send falls roughly at 08:48-09:00.
  • Backup DB occupies 08:30-09:30; mail_send falls roughly at 09:18-09:30.

If the proportions overlap in time, the collisions table will show an entry like:

DayScheduleServiceWorkflow AWorkflow B
Monday08:48-09:00mail_sendSync CRMBackup DB

What to do: change Backup DB cron to 09:00, or configure a different SMTP credential per flow.

Setup:

  • Invoice Cron, cron 0 6 * * 1 (Monday 06:00). Nodes: billing -> pdf_generator -> mail_send.
  • Stock Update, cron 15 6 * * 1 (Monday 06:15). Nodes: webhook -> transformation -> dataStore.

Analysis:

Visually the blocks overlap in the 06:15-07:00 range. But the service sets are disjoint: {billing, pdf_generator, mail_send} ∩ {webhook, transformation, dataStore} = empty.

The algorithm correctly does not count this as a collision. The “Detected service collisions” table will show 0 entries related to this pair.

Conclusion: visual overlap does not imply operational conflict. Even though the planner draws them on top of each other, both can run in parallel without competing for resources.

Case 3 — Three workflows, chained collisions

Section titled “Case 3 — Three workflows, chained collisions”

Setup:

  • Email Reports A, cron 0 14 * * 5. Nodes: dataframe -> mail_send.
  • Email Reports B, cron 5 14 * * 5. Nodes: dataframe -> mail_send.
  • Email Reports C, cron 10 14 * * 5. Nodes: dataframe -> mail_send.

Analysis:

All three run within a 15-minute window. With the 1h minimum visible duration, their blocks are 14:00-15:00, 14:05-15:05 and 14:10-15:10. All end with mail_send.

The table will show three collisions (one per pair combination):

DayScheduleServiceWorkflow AWorkflow B
Friday14:48-15:00mail_sendReports AReports B
Friday14:53-15:00mail_sendReports AReports C
Friday14:53-15:05mail_sendReports BReports C

What to do: merge them into a single parallelizable workflow, or space the crons apart by at least the total estimated duration.

Setup: a freshly created workflow with active cron but no executions recorded in workflowstates within the window.

Analysis:

The backend returns the workflow with nodes: [] and totalDurationMs: 0. The planner draws it as a block of the minimum visible size (1h) with a single gray segment labeled “(no history)”.

It does not generate collisions because its dummy moduleName will never match a real service.

What to do: run the workflow at least once (manually or by waiting for the cron) so that real durations appear on the next planner load.

Each row has a context menu (three-dot icon in the left column) with six options:

OptionWhat it does
View summaryNavigates to the workflow summary (/workflows/summary/:id)
Go to EditorOpens the visual editor of the workflow
RunManually launches the workflow (does not affect planning)
ScheduleOpens a dialog to edit the cron and toggle the schedule
View incidentsShows a dialog with open and resolved incidents
View commentsOpens the comments box of the workflow

The dialog uses the CronExpressionField component (the same one used in the workflow editor). On save:

  1. It calls PUT /api/workflows/:id/schedule to update the cron and the active_schedule flag.
  2. It automatically reloads the entire planning data.
  3. It recomputes blocks, occurrences and collisions — because changing a cron may create or resolve a collision in another day/hour.

This makes the planner an interactive tool: you can move schedules around and see the effect immediately on the chart.

The Export Report button generates a horizontal A4 PDF with:

  1. Header: title, generation date and current view (Macro/Detail).
  2. Clean snapshot of the current chart (rendered with html2canvas, scale 2x for sharpness).
  3. Detected collisions table redrawn with jsPDF (selectable text, not an image) with columns: Day, Schedule, Service, Workflow A, Workflow B. Auto-paginates if there are many rows.

The report is useful for internal reviews, planning audits, or to document cron changes before/after.

The “History” selector (top right) controls how many days back are averaged to compute per-node durations:

WindowWhen to use it
7 daysDiagnosing a recent issue
14 daysShort-term trend view
30 daysDefault — recommended for regular planning
60 daysWorkflows with low frequency (weekly/biweekly)
90 daysMonthly workflows or long-term view

Changing the window triggers an automatic reload of the endpoint.

GET /api/workflow/planner-schedules?days=30

Requires authentication (Bearer token). Returns:

{
"success": true,
"data": [
{
"workflowId": 102,
"workflowName": "Sync CRM",
"cron": "0 8 * * 1",
"isplay": 1,
"occurrences": [
{ "dayOfWeek": 0, "startHour": 8.0 }
],
"totalDurationMs": 4500,
"executions": 28,
"nodes": [
{
"nodeId": "1",
"nodeName": "webhook_1",
"moduleName": "webhook",
"avgDurationMs": 200,
"executions": 28,
"proportion": 0.044
}
]
}
],
"windowDays": 30
}

Notes:

  • dayOfWeek: 0 = Monday, 6 = Sunday (ISO format).
  • startHour: decimal hour (e.g., 8.5 = 08:30).
  • nodeName is the instance (e.g., webhook_1); moduleName is the root type (e.g., webhook) used for collision detection.
  • proportion: percentage of the workflow’s total time taken by that node (0..1).
  • isplay: workflow status in the DB (1 = running, other values = paused/draft).

The endpoint omits workflows without active cron and workflows with isplay = 0 (deleted).

  1. No credential-level collision detection: two mail_send nodes with different SMTP accounts are still counted as a collision. Planned for phase 2 (requires reading each node’s configuration).
  2. No manual or webhook workflows: only workflows with active cron are shown. Phase 2 will analyze the history to display “hot spots” of manual/webhook executions.
  3. 1-hour minimum visible: very fast workflows (seconds) may look bigger than they are. This is a deliberate decision so collisions remain visible.
  4. Cron must be standard: the endpoint uses cron-parser. Unsupported expressions (years, exact seconds, custom aliases) are silently skipped.
  • Before scheduling a new workflow: open the planner, find a free green slot in the desired day and time range, and configure the cron to land there.
  • After a schedule change: reopen the planner and verify you didn’t create a collision when moving a workflow.
  • Weekly audit: export a PDF every Monday and archive it. It provides traceability of how the client’s planning evolves.
  • Diagnosing send failures: if you have intermittent errors in mail_send, check the planner for several workflows triggering emails simultaneously. The cause is often a rate-limit collision.
SymptomLikely causeFix
No workflow shows upThe client has no workflows with active_schedule = trueSchedule at least one workflow from the editor or the Schedule menu
Blocks appear empty as “(no history)“The workflow has never executed in the history windowRun it manually once or widen the history window
Visual overlap but 0 collisionsWorkflows use different services (correct case)No action needed — it is not a real collision
The cron shows the wrong hourThe workflow’s JSON schedule field was not updatedEdit the cron from the planner’s Schedule menu
PDF text gets cut offToo many collisions for one pagejsPDF auto-paginates, the full content is present