mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-23 21:23:41 +09:00
feat(actions): add job summaries (GITHUB_STEP_SUMMARY) (#37500)
- Add GitHub-style Actions **job summaries** support
(`GITHUB_STEP_SUMMARY` / `workflow/SUMMARY.md`) and render them on the
run Summary view.
- Store uploaded summaries internally in the DB (not as downloadable
artifacts).
- Add runtime-token endpoint for runners to upload summaries:
- `PUT
/api/actions_pipeline/_apis/pipelines/workflows/{run_id}/jobs/{job_id}/summary`
- Advertise support to runners via `RunnerService.Declare` response
header:
- `X-Gitea-Actions-Capabilities: job-summary`
- Devtest: extend `/devtest/repo-action-view/...` to include mock
`jobSummaries` for previewing UI rendering.
## Compatibility
- New Gitea + old runner: no summary upload → UI shows nothing (no
behavior change)
- New runner + old Gitea: capability not advertised → runner skips
upload (no behavior change)
## Screenshot:
<img width="2017" height="729"
src="https://github.com/user-attachments/assets/31f8b945-50c4-40e1-9f40-382901a53013"
/>
Fixes #23721
PR on gitea-runner https://gitea.com/gitea/runner/pulls/917
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
co-authored by
silverwind
Claude
parent
b1c088e9cf
commit
3b1e75764e
@@ -15,6 +15,7 @@ import (
|
||||
actions_model "gitea.dev/models/actions"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/templates"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
@@ -89,6 +90,7 @@ func MockActionsRunsJobs(ctx *context.Context) {
|
||||
resp.State.Run.CanDeleteArtifact = true
|
||||
resp.State.Run.WorkflowID = "workflow-id.yml"
|
||||
resp.State.Run.TriggerEvent = "push"
|
||||
renderUtils := templates.NewRenderUtils(ctx)
|
||||
user2, _ := user_model.GetUserByID(ctx, 2)
|
||||
if user2 == nil {
|
||||
user2 = &user_model.User{Name: "user2"}
|
||||
@@ -196,6 +198,20 @@ func MockActionsRunsJobs(ctx *context.Context) {
|
||||
resp.State.Run.CanRerun = runID == 30 && isLatestAttempt
|
||||
resp.State.Run.CanRerunFailed = runID == 30 && isLatestAttempt
|
||||
|
||||
// Mock job summaries so the devtest page can preview the Summary panel rendering.
|
||||
resp.State.Run.JobSummaries = []*actions.ViewJobSummary{
|
||||
{
|
||||
JobID: runID * 10,
|
||||
JobName: "job 100 (testsubname)",
|
||||
SummaryHTML: renderUtils.MarkdownToHtml("### Devtest job summary\n\n- Markdown rendering\n- Links: [example](https://example.com)\n\n```sh\necho hello\n```\n"),
|
||||
},
|
||||
{
|
||||
JobID: runID*10 + 2,
|
||||
JobName: "ULTRA LOOOOOOOOOOOONG job name 102 that exceeds the limit",
|
||||
SummaryHTML: renderUtils.MarkdownToHtml("### Another summary\n\nThis demonstrates multiple job summaries in one run.\n\n- Item A\n- Item B\n"),
|
||||
},
|
||||
}
|
||||
|
||||
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
|
||||
Name: "artifact-a",
|
||||
Size: 100 * 1024,
|
||||
|
||||
@@ -315,6 +315,8 @@ type ViewResponse struct {
|
||||
Duration string `json:"duration"`
|
||||
TriggeredAt int64 `json:"triggeredAt"` // unix seconds for relative time
|
||||
TriggerEvent string `json:"triggerEvent"` // e.g. pull_request, push, schedule
|
||||
|
||||
JobSummaries []*ViewJobSummary `json:"jobSummaries,omitempty"`
|
||||
} `json:"run"`
|
||||
CurrentJob struct {
|
||||
Title string `json:"title"`
|
||||
@@ -344,6 +346,12 @@ type ViewJob struct {
|
||||
CallUses string `json:"callUses,omitempty"`
|
||||
}
|
||||
|
||||
type ViewJobSummary struct {
|
||||
JobID int64 `json:"jobId"`
|
||||
JobName string `json:"jobName"`
|
||||
SummaryHTML template.HTML `json:"summaryHTML"`
|
||||
}
|
||||
|
||||
type ViewRunAttempt struct {
|
||||
Attempt int64 `json:"attempt"`
|
||||
Status string `json:"status"`
|
||||
@@ -649,12 +657,42 @@ func fillViewRunResponseSummary(ctx *context_module.Context, resp *ViewResponse,
|
||||
resp.State.Run.PullRequest = refInfo.PullRequest
|
||||
resp.State.Run.TriggerEvent = run.TriggerEvent
|
||||
|
||||
// Legacy runs (LatestAttemptID == 0) have no attempt; their artifacts all share run_attempt_id=0,
|
||||
// so passing 0 here scopes to this run's legacy artifacts only.
|
||||
// Legacy runs (LatestAttemptID == 0) have no attempt; their artifacts and summaries all
|
||||
// share run_attempt_id=0, so passing 0 here scopes to this run's legacy rows only.
|
||||
var runAttemptID int64
|
||||
if attempt != nil {
|
||||
runAttemptID = attempt.ID
|
||||
}
|
||||
|
||||
// Each step's markdown is rendered independently so an unclosed construct
|
||||
// in one step can't bleed into the next.
|
||||
// On a single-job view only that job's summaries are needed; the run view shows all.
|
||||
// Scoping server-side avoids rendering every job's markdown on each 1s poll.
|
||||
summaries, err := actions_model.ListActionRunJobSummaries(ctx, ctx.Repo.Repository.ID, run.ID, runAttemptID, ctx.PathParamInt64("job"))
|
||||
if err != nil {
|
||||
ctx.ServerError("ListActionRunJobSummaries", err)
|
||||
return
|
||||
}
|
||||
if len(summaries) > 0 {
|
||||
jobNameByID := make(map[int64]string, len(jobs))
|
||||
for _, j := range jobs {
|
||||
jobNameByID[j.ID] = j.Name
|
||||
}
|
||||
renderUtils := templates.NewRenderUtils(ctx)
|
||||
var current *ViewJobSummary
|
||||
for _, s := range summaries {
|
||||
if s.ContentType != actions_model.JobSummaryContentTypeMarkdown {
|
||||
log.Warn("Skip unsupported job summary content type %q for run %d job %d step %d", s.ContentType, s.RunID, s.JobID, s.StepIndex)
|
||||
continue
|
||||
}
|
||||
if current == nil || current.JobID != s.JobID {
|
||||
current = &ViewJobSummary{JobID: s.JobID, JobName: jobNameByID[s.JobID]}
|
||||
resp.State.Run.JobSummaries = append(resp.State.Run.JobSummaries, current)
|
||||
}
|
||||
current.SummaryHTML += renderUtils.MarkdownToHtml(s.Content)
|
||||
}
|
||||
}
|
||||
|
||||
arts, err := actions_model.ListUploadedArtifactsMetaByRunAttempt(ctx, ctx.Repo.Repository.ID, run.ID, runAttemptID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListUploadedArtifactsMetaByRunAttempt", err)
|
||||
|
||||
Reference in New Issue
Block a user