diff --git a/changelog.d/changed-10646.md b/changelog.d/changed-10646.md new file mode 100644 index 0000000000..15ce71f861 --- /dev/null +++ b/changelog.d/changed-10646.md @@ -0,0 +1 @@ +- dashboard: restore shaded-area hive-vs-human graph in expanded throughput (#10646) diff --git a/src/pkg/dashboard/dashboard_pr_throughput_actor_colors_test.go b/src/pkg/dashboard/dashboard_pr_throughput_actor_colors_test.go index 96e4227d33..6056ad1c33 100644 --- a/src/pkg/dashboard/dashboard_pr_throughput_actor_colors_test.go +++ b/src/pkg/dashboard/dashboard_pr_throughput_actor_colors_test.go @@ -38,8 +38,9 @@ func TestPRThroughputActorColorTokensAreDistinct(t *testing.T) { for _, actor := range []string{"hive", "human", "other"} { stackRule := `.prt-stack .` + actor + ` { fill:var(--prt-actor-` + actor + `); }` + legendRule := `.prt-legend .` + actor + ` { background:var(--prt-actor-` + actor + `); }` trendRule := `.prt-trend-svg .area.` + actor + ` { fill:var(--prt-actor-` + actor + `);` - for _, want := range []string{stackRule, trendRule} { + for _, want := range []string{stackRule, legendRule, trendRule} { if !strings.Contains(dashboardHTML, want) { t.Fatalf("dashboardHTML is missing matching actor color rule %q", want) } diff --git a/src/pkg/dashboard/pr_throughput_ui_test.go b/src/pkg/dashboard/pr_throughput_ui_test.go index f8ece14783..84a1b5e0c1 100644 --- a/src/pkg/dashboard/pr_throughput_ui_test.go +++ b/src/pkg/dashboard/pr_throughput_ui_test.go @@ -36,6 +36,9 @@ func TestPRThroughputCardPinned(t *testing.T) { `Hive vs human`, @@ -125,7 +128,7 @@ func TestPRThroughputTrendStackedAreaAndLegendTooltip(t *testing.T) { script := `const assert = require('node:assert/strict'); let prtRole = 'merged'; function escapeHtml(v) { return String(v).replace(/[&<>"]/g, ch => ({'&':'&','<':'<','>':'>','"':'"'}[ch])); } -function renderSparkline(_node, values, opts) { return '' + values.join(',') + ''; } +function renderSparkline(_node, values, opts) { return '' + (opts.area ? '' : '') + values.join(',') + ''; } ` + jsFunc(t, html, "prtSparkTitle") + "\n" + jsFunc(t, html, "prtHiveShareSpark") + "\n" + jsFunc(t, html, "prtSeriesAllZero") + "\n" + jsFunc(t, html, "prtTrendSvg") + "\n" + jsFunc(t, html, "prtTrendStats") + "\n" + jsFunc(t, html, "prtTrendCaption") + "\n" + jsFunc(t, html, "prtHiveHumanTile") + ` const series = [ { hive: 4, human: 0, other: 6 }, @@ -149,6 +152,14 @@ assert.match(tile, /of merges\/closures · was 40%/); assert.match(tile, /hive 16 · human 0 · other automation 14 · hive 53% of merges\/closures this window, up from 40%/); assert.match(tile, /data-change-action="setPRThroughputRole"/); assert.match(tile, /data-label="hive share"/); +assert.match(tile, /class="sparkline-area"/); +` + jsFunc(t, html, "prtActorTrend") + ` +const trend = prtActorTrend({ series }); +assert.match(trend, /class="prt-trend"/); +assert.match(trend, /Hive vs human trend<\/strong>/); +assert.match(trend, /title="non-hive bots \(dependabot, renovate, GitHub Actions, …\)"/); +assert.match(trend, /human · 0/); +assert.match(trend, /class="prt-trend-svg"/); ` out, err := exec.Command(node, "-e", script).CombinedOutput() if err != nil { diff --git a/src/pkg/dashboard/static/index.html b/src/pkg/dashboard/static/index.html index 5bb8055b2a..d9d0a24e99 100644 --- a/src/pkg/dashboard/static/index.html +++ b/src/pkg/dashboard/static/index.html @@ -1424,8 +1424,8 @@ .prt-breakdown { color: var(--muted); font-size: var(--fs-xs); } .prt-breakdown b { color: var(--text); display:block; margin-bottom: var(--sp-1); } .prt-breakdown ol { margin: 0; padding-left: 1.2rem; } - .prt-attribution { display:grid; grid-template-columns:minmax(260px, 1fr); gap:var(--sp-3); margin:var(--sp-3) 0; } - .prt-matrix { border:1px solid var(--border); border-radius:var(--r-sm); padding:var(--sp-3); background:color-mix(in srgb, var(--bg) 85%, transparent); } + .prt-attribution { display:grid; grid-template-columns:minmax(260px, 1fr) minmax(260px, 1fr); gap:var(--sp-3); margin:var(--sp-3) 0; } + .prt-matrix, .prt-trend { border:1px solid var(--border); border-radius:var(--r-sm); padding:var(--sp-3); background:color-mix(in srgb, var(--bg) 85%, transparent); } .prt-matrix table { width:100%; border-collapse:collapse; font-size:var(--fs-xs); } .prt-matrix th, .prt-matrix td { padding:var(--sp-2) var(--sp-2); border-top:1px solid var(--border); text-align:left; vertical-align:middle; } .prt-matrix th { color:var(--muted); font-weight:600; border-top:0; } @@ -1433,11 +1433,17 @@ .prt-stack .hive { fill:var(--prt-actor-hive); } .prt-stack .human { fill:var(--prt-actor-human); } .prt-stack .other { fill:var(--prt-actor-other); } + .prt-legend .hive { background:var(--prt-actor-hive); } + .prt-legend .human { background:var(--prt-actor-human); } + .prt-legend .other { background:var(--prt-actor-other); } .prt-trend-svg .area { stroke:var(--bg); stroke-width:.6; stroke-linejoin:round; } .prt-trend-svg .area.other { fill:var(--prt-actor-other); opacity:.52; } .prt-trend-svg .area.human { fill:var(--prt-actor-human); opacity:.62; } .prt-trend-svg .area.hive { fill:var(--prt-actor-hive); opacity:.72; } + .prt-trend-head { display:flex; justify-content:space-between; gap:var(--sp-2); align-items:center; margin-bottom:var(--sp-2); } .prt-role-select { background:var(--bg); color:var(--text); border:1px solid var(--border); border-radius:var(--r-sm); padding:var(--sp-1) var(--sp-3); font-size:var(--fs-xs); } + .prt-legend { display:flex; gap:var(--sp-3); font-size:var(--fs-2xs); color:var(--muted); align-items:center; margin-top:var(--sp-1); } + .prt-legend i { display:inline-block; width:8px; height:8px; border-radius:var(--r-pill); margin-right:var(--sp-1); vertical-align:middle; } .prt-caption { color:var(--muted); font-size:var(--fs-xs); margin-top:var(--sp-2); } @media (max-width: 800px) { .prt-attribution { grid-template-columns:1fr; } } @media (max-width: 900px) { .lc-fleet.prt-headline { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @@ -16748,6 +16754,7 @@

What sh label: 'hive share', id: 'change-throughput:hive-share', wrapperClass: 'sparkline--flush', + area: true, }); } function prtBucketMetricSeries(dto) { @@ -16872,6 +16879,12 @@

What sh const sub = stats.total ? `of ${stats.shortNoun}${stats.prevPct == null ? '' : ` · was ${stats.prevPct}%`}` : 'No attribution data yet'; return `
${stats.cur == null ? '—' : stats.cur + '%'}
Hive vs human
${escapeHtml(sub)}
${prtHiveShareSpark(series)}
`; } + function prtActorTrend(dto) { + const opts = [['created','Created'],['reviewed','Reviewed'],['merged','Merged']].map(([v,l]) => ``).join(''); + const series = Array.isArray(dto.series) ? dto.series : []; + const legend = [['hive', 'hive', ''], ['human', 'human', ''], ['other', 'other automation', 'non-hive bots (dependabot, renovate, GitHub Actions, …)']].map(([key, label, title]) => `${label}${prtSeriesAllZero(series, key) ? ' · 0' : ''}`).join(''); + return `
Hive vs human trend
${prtTrendSvg(series) || '
No trend series yet.
'}
${legend}
${escapeHtml(prtTrendCaption(dto))}
`; + } function prtWindowControls() { const buttons = PRT_WINDOWS.map(([h, label]) => ``).join(''); return `
${buttons}
`; @@ -16920,7 +16933,7 @@

What sh
${merged}
Merged
${prtSeriesSpark(dto, 'merged', 'var(--green)')}
${closed}
Closed without merging
${prtSeriesSpark(dto, 'closed', 'var(--red)')}
${prtHiveHumanTile(dto)} - ${metricsHtml}
${prtActorMatrix(dto)}
${pathsHtml}${breakdowns ? `
${breakdowns}
` : ''}`; + ${metricsHtml}
${prtActorMatrix(dto)}${prtActorTrend(dto)}
${pathsHtml}${breakdowns ? `
${breakdowns}
` : ''}`; applySectionCollapse('pr-throughput-section'); }