@@ -234,16 +234,26 @@ def _prepare_dashboard_data(
234234 }
235235 )
236236
237- # Build per-task aggregate data (across all runs)
237+ # Build per-task aggregate data (across all runs). Each row carries
238+ # both ``process`` (may be empty for chore rows) and ``chore`` so the
239+ # dashboard can render a unified "Task target" column with a kind
240+ # indicator. ``task_target``/``task_kind`` are the canonical fields
241+ # for display; the raw kind fields stay populated for backward
242+ # compatibility with downstream consumers.
238243 task_data : Dict [str , Dict [str , Any ]] = {}
239244 for tr in task_results :
240245 sig = tr ["task_signature" ]
246+ process_val = tr .get ("process" ) or ""
247+ chore_val = tr .get ("chore" ) or ""
241248 if sig not in task_data :
242249 task_data [sig ] = {
243250 "task_signature" : sig ,
244251 "task_id" : tr ["task_id" ],
245252 "instance" : tr ["instance" ],
246- "process" : tr ["process" ],
253+ "process" : process_val ,
254+ "chore" : chore_val ,
255+ "task_target" : chore_val or process_val ,
256+ "task_kind" : "chore" if chore_val else "process" ,
247257 "durations" : [],
248258 "successes" : 0 ,
249259 "total" : 0 ,
@@ -263,6 +273,9 @@ def _prepare_dashboard_data(
263273 "task_id" : data ["task_id" ],
264274 "instance" : data ["instance" ],
265275 "process" : data ["process" ],
276+ "chore" : data ["chore" ],
277+ "task_target" : data ["task_target" ],
278+ "task_kind" : data ["task_kind" ],
266279 "executions" : data ["total" ],
267280 "success_rate" : (
268281 round (data ["successes" ] / data ["total" ] * 100 , 1 ) if data ["total" ] > 0 else 0
@@ -290,10 +303,15 @@ def _prepare_dashboard_data(
290303 outliers = []
291304 for tr in task_results :
292305 if tr ["duration_seconds" ] is not None :
306+ process_val = tr .get ("process" ) or ""
307+ chore_val = tr .get ("chore" ) or ""
293308 outliers .append (
294309 {
295310 "task_id" : tr ["task_id" ],
296- "process" : tr ["process" ],
311+ "process" : process_val ,
312+ "chore" : chore_val ,
313+ "task_target" : chore_val or process_val ,
314+ "task_kind" : "chore" if chore_val else "process" ,
297315 "instance" : tr ["instance" ],
298316 "run_id" : tr ["run_id" ],
299317 "duration" : round (tr ["duration_seconds" ], 2 ),
@@ -308,10 +326,15 @@ def _prepare_dashboard_data(
308326 failures = []
309327 for tr in task_results :
310328 if tr ["status" ] != "Success" :
329+ process_val = tr .get ("process" ) or ""
330+ chore_val = tr .get ("chore" ) or ""
311331 failures .append (
312332 {
313333 "task_id" : tr ["task_id" ],
314- "process" : tr ["process" ],
334+ "process" : process_val ,
335+ "chore" : chore_val ,
336+ "task_target" : chore_val or process_val ,
337+ "task_kind" : "chore" if chore_val else "process" ,
315338 "instance" : tr ["instance" ],
316339 "run_id" : tr ["run_id" ],
317340 "duration" : round (tr ["duration_seconds" ], 2 ) if tr ["duration_seconds" ] else 0 ,
@@ -322,13 +345,18 @@ def _prepare_dashboard_data(
322345 # Slim task_results for JS (only fields needed for interactive filtering)
323346 slim_task_results = []
324347 for tr in task_results :
348+ process_val = tr .get ("process" ) or ""
349+ chore_val = tr .get ("chore" ) or ""
325350 slim_task_results .append (
326351 {
327352 "run_id" : tr ["run_id" ],
328353 "task_id" : tr ["task_id" ],
329354 "task_signature" : tr ["task_signature" ],
330355 "instance" : tr ["instance" ],
331- "process" : tr ["process" ],
356+ "process" : process_val ,
357+ "chore" : chore_val ,
358+ "task_target" : chore_val or process_val ,
359+ "task_kind" : "chore" if chore_val else "process" ,
332360 "status" : tr ["status" ],
333361 "duration_seconds" : tr ["duration_seconds" ],
334362 "error_message" : tr .get ("error_message" ),
@@ -578,6 +606,11 @@ def generate_dashboard(
578606 .status-partial {{ color: #D97706; font-weight: 600; }}
579607 .high-cv {{ background: #FFF7ED; }}
580608 .duration-slow {{ color: #DC2626; font-weight: 600; }}
609+ .kind-tag {{
610+ display: inline-block; padding: 1px 5px; border-radius: 4px;
611+ font-size: 0.7rem; font-weight: 700; color: #475569;
612+ background: #E2E8F0; margin-right: 4px;
613+ }}
581614
582615 /* Accordion */
583616 .accordion {{ margin-bottom: 20px; }}
@@ -752,7 +785,7 @@ def generate_dashboard(
752785 <thead><tr>
753786 <th onclick="sortTaskTable(0)">Task ID <span class="sort-arrow"></span></th>
754787 <th onclick="sortTaskTable(1)">Instance <span class="sort-arrow"></span></th>
755- <th onclick="sortTaskTable(2)">Process <span class="sort-arrow"></span></th>
788+ <th onclick="sortTaskTable(2)">Task target <span class="sort-arrow"></span></th>
756789 <th onclick="sortTaskTable(3)">Avg (s) <span class="sort-arrow"></span></th>
757790 <th onclick="sortTaskTable(4)">Min (s) <span class="sort-arrow"></span></th>
758791 <th onclick="sortTaskTable(5)">Max (s) <span class="sort-arrow"></span></th>
@@ -768,7 +801,7 @@ def generate_dashboard(
768801 <h3>Top 10 Slowest Executions <span class="help-icon">?<span class="help-tip">How to read: These are the 10 longest-running individual task executions across the selected runs. The "vs Median" column tells you how many times slower each one was compared to the typical task — a high multiplier (e.g., 5×) means that execution was significantly slower than usual and may be worth investigating. Check whether the same task appears multiple times — if so, it is a consistent bottleneck.</span></span></h3>
769802 <table id="outlierTable">
770803 <thead><tr>
771- <th>Task ID</th><th>Process </th><th>Instance</th>
804+ <th>Task ID</th><th>Task target </th><th>Instance</th>
772805 <th>Run</th><th>Duration (s)</th><th>vs Median</th><th>Status</th>
773806 </tr></thead>
774807 <tbody id="outlierTableBody"></tbody>
@@ -779,7 +812,7 @@ def generate_dashboard(
779812 <h3>Failed Processes <span class="help-icon">?<span class="help-tip">How to read: Every task execution that did not succeed is listed here. Look for patterns — does the same task fail repeatedly across runs? That points to a systemic issue. If failures are scattered across different tasks, the problem may be environmental (server load, connectivity). Hover over the Error column to see the full error message for each failure.</span></span></h3>
780813 <table id="failureTable">
781814 <thead><tr>
782- <th>Task ID</th><th>Process </th><th>Instance</th>
815+ <th>Task ID</th><th>Task target </th><th>Instance</th>
783816 <th>Run</th><th>Duration (s)</th><th>Error</th>
784817 </tr></thead>
785818 <tbody id="failureTableBody"></tbody>
@@ -1260,14 +1293,14 @@ def generate_dashboard(
12601293 // Also store a lookup for tooltip labels
12611294 const sigLabel = {{}};
12621295 relevant.forEach(tr => {{
1263- if (!sigLabel[tr.task_signature]) sigLabel[tr.task_signature] = tr.process;
1296+ if (!sigLabel[tr.task_signature]) sigLabel[tr.task_signature] = tr.task_target || tr. process || tr.chore ;
12641297 }});
12651298
12661299 const datasets = runs.map((run, idx) => {{
12671300 const runTasks = relevant.filter(tr => tr.run_id === run.run_id);
12681301 return {{
12691302 label: formatDate(run.start_time),
1270- data: runTasks.map(t => ({{ x: sigIndex[t.task_signature], y: t.duration_seconds || 0, _sig: t.task_signature, _task: t.task_id, _proc: t.process }})),
1303+ data: runTasks.map(t => ({{ x: sigIndex[t.task_signature], y: t.duration_seconds || 0, _sig: t.task_signature, _task: t.task_id, _proc: t.task_target || t. process || t.chore }})),
12711304 backgroundColor: RUN_COLORS[idx % RUN_COLORS.length] + '80',
12721305 borderColor: RUN_COLORS[idx % RUN_COLORS.length],
12731306 pointRadius: 3,
@@ -1470,7 +1503,10 @@ def generate_dashboard(
14701503 const sig = tr.task_signature;
14711504 if (!taskData[sig]) {{
14721505 taskData[sig] = {{
1473- task_id: tr.task_id, instance: tr.instance, process: tr.process,
1506+ task_id: tr.task_id, instance: tr.instance,
1507+ process: tr.process, chore: tr.chore,
1508+ task_target: tr.task_target || tr.process || tr.chore,
1509+ task_kind: tr.task_kind || (tr.chore ? 'chore' : 'process'),
14741510 durations: [], successes: 0, total: 0
14751511 }};
14761512 }}
@@ -1492,7 +1528,7 @@ def generate_dashboard(
14921528 }}
14931529
14941530 function applyTaskSort(rows) {{
1495- const fields = ['task_id', 'instance', 'process ', 'avg', 'mn', 'mx', 'stdDev', 'successRate', 'total'];
1531+ const fields = ['task_id', 'instance', 'task_target ', 'avg', 'mn', 'mx', 'stdDev', 'successRate', 'total'];
14961532 const field = fields[taskTableSortCol];
14971533 rows.sort((a, b) => {{
14981534 const av = a[field], bv = b[field];
@@ -1556,10 +1592,12 @@ def generate_dashboard(
15561592 tbody.innerHTML = pageRows.map(r => {{
15571593 const cvClass = r.cv > 0.5 ? 'high-cv' : '';
15581594 const statusCls = r.successRate < 100 ? 'status-fail' : 'status-success';
1595+ const kindTag = r.task_kind === 'chore' ? '[C]' : '[P]';
1596+ const target = r.task_target || r.process || r.chore || '';
15591597 return `<tr class="${{cvClass}}">
15601598 <td class="mono">${{r.task_id}}</td>
15611599 <td>${{r.instance}}</td>
1562- <td>${{r.process }}</td>
1600+ <td><span class="kind-tag"> ${{kindTag}}</span> ${{target }}</td>
15631601 <td class="mono">${{r.avg.toFixed(2)}}</td>
15641602 <td class="mono">${{r.mn.toFixed(2)}}</td>
15651603 <td class="mono">${{r.mx.toFixed(2)}}</td>
@@ -1585,9 +1623,11 @@ def generate_dashboard(
15851623 tbody.innerHTML = sorted.map(o => {{
15861624 const statusCls = o.status === 'Success' ? 'status-success' : 'status-fail';
15871625 const vsMedian = o.duration_seconds - median;
1626+ const kindTag = o.task_kind === 'chore' || o.chore ? '[C]' : '[P]';
1627+ const target = o.task_target || o.process || o.chore || '';
15881628 return `<tr>
15891629 <td class="mono">${{o.task_id}}</td>
1590- <td>${{o.process }}</td>
1630+ <td><span class="kind-tag"> ${{kindTag}}</span> ${{target }}</td>
15911631 <td>${{o.instance}}</td>
15921632 <td class="mono">${{o.run_id}}</td>
15931633 <td class="mono duration-slow">${{o.duration_seconds.toFixed(2)}}</td>
@@ -1613,15 +1653,19 @@ def generate_dashboard(
16131653 }}
16141654
16151655 panel.style.display = 'block';
1616- tbody.innerHTML = filtered.map(f => `<tr>
1656+ tbody.innerHTML = filtered.map(f => {{
1657+ const kindTag = f.task_kind === 'chore' || f.chore ? '[C]' : '[P]';
1658+ const target = f.task_target || f.process || f.chore || '';
1659+ return `<tr>
16171660 <td class="mono">${{f.task_id}}</td>
1618- <td>${{f.process }}</td>
1661+ <td><span class="kind-tag"> ${{kindTag}}</span> ${{target }}</td>
16191662 <td>${{f.instance}}</td>
16201663 <td class="mono">${{f.run_id}}</td>
16211664 <td class="mono">${{f.duration_seconds != null ? f.duration_seconds.toFixed(2) : '-'}}</td>
16221665 <td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
16231666 title="${{(f.error_message || '').replace(/"/g, '"')}}">${{f.error_message || '-'}}</td>
1624- </tr>`).join('');
1667+ </tr>`;
1668+ }}).join('');
16251669 }}
16261670
16271671 function updateConfigDetails(runs) {{
0 commit comments