tenseleyflow/shithub / a617ec3

Browse files

S24: PR Checks tab loads suites+runs and renders output.summary markdown

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
a617ec3dfd1a076966e631f15640e7347f7da916
Parents
5c50e31
Tree
2854236

4 changed files

StatusFile+-
M internal/web/handlers/repo/pulls.go 62 3
M internal/web/handlers/repo/repo.go 3 1
M internal/web/static/css/shithub.css 37 0
M internal/web/templates/repo/pull_view.html 44 1
internal/web/handlers/repo/pulls.gomodified
@@ -3,7 +3,9 @@
33
 package repo
44
 
55
 import (
6
+	"encoding/json"
67
 	"errors"
8
+	"html/template"
79
 	"net/http"
810
 	"strconv"
911
 	"strings"
@@ -13,11 +15,13 @@ import (
1315
 	"github.com/jackc/pgx/v5/pgtype"
1416
 
1517
 	"github.com/tenseleyFlow/shithub/internal/auth/policy"
18
+	checksdb "github.com/tenseleyFlow/shithub/internal/checks/sqlc"
1619
 	"github.com/tenseleyFlow/shithub/internal/issues"
1720
 	issuesdb "github.com/tenseleyFlow/shithub/internal/issues/sqlc"
1821
 	"github.com/tenseleyFlow/shithub/internal/pulls"
1922
 	pullsdb "github.com/tenseleyFlow/shithub/internal/pulls/sqlc"
2023
 	repogit "github.com/tenseleyFlow/shithub/internal/repos/git"
24
+	mdrender "github.com/tenseleyFlow/shithub/internal/repos/markdown"
2125
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
2226
 	"github.com/tenseleyFlow/shithub/internal/web/middleware"
2327
 	"github.com/tenseleyFlow/shithub/internal/worker"
@@ -411,10 +415,65 @@ func (h *Handlers) pullFiles(w http.ResponseWriter, r *http.Request) {
411415
 	})
412416
 }
413417
 
414
-// pullChecks renders the Checks tab. v1 ships the visual scaffold;
415
-// the data wires in at S24.
418
+// pullChecks renders the Checks tab. Loads suites + runs grouped by
419
+// suite for the PR's head_oid, plus the markdown-rendered output.summary
420
+// for each run.
416421
 func (h *Handlers) pullChecks(w http.ResponseWriter, r *http.Request) {
417
-	h.renderPullPage(w, r, "checks", nil)
422
+	row, _, ok := h.loadRepoAndAuthorize(w, r, policy.ActionPullRead)
423
+	if !ok {
424
+		return
425
+	}
426
+	pr, ok := h.loadPullByNumber(w, r, row.ID)
427
+	if !ok {
428
+		return
429
+	}
430
+	type runRow struct {
431
+		R              checksdb.CheckRun
432
+		SummaryHTML    template.HTML
433
+		AppSlug        string
434
+	}
435
+	type suiteGroup struct {
436
+		Suite checksdb.CheckSuite
437
+		Runs  []runRow
438
+	}
439
+	groups := []suiteGroup{}
440
+	if pr.HeadOid != "" {
441
+		suites, _ := h.cq.ListCheckSuitesForCommit(r.Context(), h.d.Pool, checksdb.ListCheckSuitesForCommitParams{
442
+			RepoID: row.ID, HeadSha: pr.HeadOid,
443
+		})
444
+		for _, s := range suites {
445
+			runs, _ := h.cq.ListCheckRunsBySuite(r.Context(), h.d.Pool, s.ID)
446
+			rs := make([]runRow, 0, len(runs))
447
+			for _, run := range runs {
448
+				rs = append(rs, runRow{
449
+					R:           run,
450
+					SummaryHTML: renderCheckSummary(run.Output),
451
+					AppSlug:     s.AppSlug,
452
+				})
453
+			}
454
+			groups = append(groups, suiteGroup{Suite: s, Runs: rs})
455
+		}
456
+	}
457
+	h.renderPullPage(w, r, "checks", map[string]any{
458
+		"CheckGroups": groups,
459
+	})
460
+}
461
+
462
+// renderCheckSummary parses the JSON `output` blob and renders the
463
+// `summary` field as Markdown via the existing pipeline. Returns empty
464
+// HTML on any error so a malformed payload doesn't break the page.
465
+func renderCheckSummary(raw []byte) template.HTML {
466
+	if len(raw) == 0 {
467
+		return ""
468
+	}
469
+	var o struct {
470
+		Summary string `json:"summary"`
471
+	}
472
+	if err := json.Unmarshal(raw, &o); err != nil || o.Summary == "" {
473
+		return ""
474
+	}
475
+	html, _ := mdrender.RenderHTML([]byte(o.Summary))
476
+	return template.HTML(html) //nolint:gosec // sanitized by bluemonday UGCPolicy
418477
 }
419478
 
420479
 // pullEdit handles POST .../edit
internal/web/handlers/repo/repo.gomodified
@@ -21,6 +21,7 @@ import (
2121
 	"github.com/tenseleyFlow/shithub/internal/auth/policy"
2222
 	"github.com/tenseleyFlow/shithub/internal/auth/throttle"
2323
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
24
+	checksdb "github.com/tenseleyFlow/shithub/internal/checks/sqlc"
2425
 	issuesdb "github.com/tenseleyFlow/shithub/internal/issues/sqlc"
2526
 	pullsdb "github.com/tenseleyFlow/shithub/internal/pulls/sqlc"
2627
 	"github.com/tenseleyFlow/shithub/internal/repos"
@@ -63,6 +64,7 @@ type Handlers struct {
6364
 	uq *usersdb.Queries
6465
 	iq *issuesdb.Queries
6566
 	pq *pullsdb.Queries
67
+	cq *checksdb.Queries
6668
 }
6769
 
6870
 // New constructs the handler set, validating Deps.
@@ -82,7 +84,7 @@ func New(d Deps) (*Handlers, error) {
8284
 	if d.Limiter == nil {
8385
 		d.Limiter = throttle.NewLimiter()
8486
 	}
85
-	return &Handlers{d: d, rq: reposdb.New(), uq: usersdb.New(), iq: issuesdb.New(), pq: pullsdb.New()}, nil
87
+	return &Handlers{d: d, rq: reposdb.New(), uq: usersdb.New(), iq: issuesdb.New(), pq: pullsdb.New(), cq: checksdb.New()}, nil
8688
 }
8789
 
8890
 // MountNew registers /new (auth-required). Caller wraps with
internal/web/static/css/shithub.cssmodified
@@ -1347,3 +1347,40 @@ code {
13471347
 .shithub-pull-add-comment input, .shithub-pull-add-comment textarea {
13481348
   padding: 0.3rem 0.5rem; border: 1px solid var(--border-default); border-radius: 6px; font: inherit;
13491349
 }
1350
+
1351
+/* ========== PR Checks tab (S24) ========== */
1352
+.shithub-pull-checks { display: flex; flex-direction: column; gap: 0.75rem; padding: 0.5rem 0; }
1353
+.shithub-pull-check-suite {
1354
+  border: 1px solid var(--border-default);
1355
+  border-radius: 6px;
1356
+  padding: 0.75rem;
1357
+}
1358
+.shithub-pull-check-suite h3 {
1359
+  display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;
1360
+  font-size: 1rem; margin: 0 0 0.5rem;
1361
+}
1362
+.shithub-pull-check-app { font-family: var(--mono, monospace); font-size: 0.85rem; color: var(--fg-muted); }
1363
+.shithub-pull-check-suite-status { font-size: 0.8rem; padding: 0.05rem 0.4rem; border-radius: 999px; background: var(--canvas-subtle); }
1364
+.shithub-pull-check-runs { list-style: none; padding: 0; margin: 0; }
1365
+.shithub-pull-check-run {
1366
+  display: flex; gap: 0.5rem; align-items: baseline;
1367
+  padding: 0.3rem 0; border-bottom: 1px solid var(--border-default);
1368
+  flex-wrap: wrap;
1369
+}
1370
+.shithub-pull-check-run:last-child { border-bottom: none; }
1371
+.shithub-pull-check-status-completed { color: #1a7f37; }
1372
+.shithub-pull-check-status-in_progress { color: #9a6700; }
1373
+.shithub-pull-check-status-queued { color: var(--fg-muted); }
1374
+.shithub-pull-check-status-pending { color: var(--fg-muted); }
1375
+.shithub-pull-check-conclusion {
1376
+  font-size: 0.75rem; padding: 0.05rem 0.4rem; border-radius: 999px;
1377
+  text-transform: lowercase;
1378
+}
1379
+.shithub-pull-check-conclusion-success { background: #1a7f3722; color: #1a7f37; }
1380
+.shithub-pull-check-conclusion-failure { background: #cf222e22; color: #cf222e; }
1381
+.shithub-pull-check-conclusion-neutral { background: #d0d7de44; color: var(--fg-muted); }
1382
+.shithub-pull-check-conclusion-cancelled,
1383
+.shithub-pull-check-conclusion-skipped,
1384
+.shithub-pull-check-conclusion-stale { color: var(--fg-muted); background: var(--canvas-subtle); }
1385
+.shithub-pull-check-conclusion-timed_out { background: #9a670022; color: #9a6700; }
1386
+.shithub-pull-check-conclusion-action_required { background: #cf222e22; color: #cf222e; font-weight: 600; }
internal/web/templates/repo/pull_view.htmlmodified
@@ -243,7 +243,50 @@
243243
     </section>
244244
     {{ end }}
245245
   {{ else if eq .Tab "checks" }}
246
-    <p class="shithub-muted">Check engine ships in S24. Today this tab is a placeholder.</p>
246
+    {{ if .CheckGroups }}
247
+    <section class="shithub-pull-checks">
248
+      {{ range .CheckGroups }}
249
+      <div class="shithub-pull-check-suite">
250
+        <h3>
251
+          <span class="shithub-pull-check-app">{{ .Suite.AppSlug }}</span>
252
+          <span class="shithub-pull-check-suite-status shithub-pull-check-status-{{ printf "%s" .Suite.Status }}">
253
+            {{ printf "%s" .Suite.Status }}
254
+          </span>
255
+          {{ if .Suite.Conclusion.Valid }}
256
+            <span class="shithub-pull-check-conclusion shithub-pull-check-conclusion-{{ printf "%s" .Suite.Conclusion.CheckConclusion }}">
257
+              {{ printf "%s" .Suite.Conclusion.CheckConclusion }}
258
+            </span>
259
+          {{ end }}
260
+        </h3>
261
+        <ul class="shithub-pull-check-runs">
262
+          {{ range .Runs }}
263
+          <li class="shithub-pull-check-run">
264
+            <span class="shithub-pull-check-status shithub-pull-check-status-{{ printf "%s" .R.Status }}">●</span>
265
+            <strong>{{ .R.Name }}</strong>
266
+            {{ if .R.Conclusion.Valid }}
267
+              <span class="shithub-pull-check-conclusion shithub-pull-check-conclusion-{{ printf "%s" .R.Conclusion.CheckConclusion }}">
268
+                {{ printf "%s" .R.Conclusion.CheckConclusion }}
269
+              </span>
270
+            {{ else }}
271
+              <span class="shithub-muted">{{ printf "%s" .R.Status }}</span>
272
+            {{ end }}
273
+            {{ if .R.CompletedAt.Valid }}<small><time datetime="{{ .R.CompletedAt.Time.Format "2006-01-02T15:04:05Z" }}">{{ relativeTime .R.CompletedAt.Time }}</time></small>{{ end }}
274
+            {{ if .R.DetailsUrl }}<a href="{{ .R.DetailsUrl }}" rel="noopener noreferrer">details</a>{{ end }}
275
+            {{ if .SummaryHTML }}
276
+            <details>
277
+              <summary class="shithub-muted">summary</summary>
278
+              <div class="markdown-body">{{ .SummaryHTML }}</div>
279
+            </details>
280
+            {{ end }}
281
+          </li>
282
+          {{ end }}
283
+        </ul>
284
+      </div>
285
+      {{ end }}
286
+    </section>
287
+    {{ else }}
288
+    <p class="shithub-muted">No checks have reported on <code>{{ slice .PR.HeadOid 0 7 }}</code>. Post via <code>POST /api/v1/repos/{{ .Owner }}/{{ .Repo.Name }}/check-runs</code>.</p>
289
+    {{ end }}
247290
   {{ end }}
248291
 </section>
249292
 {{- end }}