mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-06 16:15:52 +00: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:
@@ -128,6 +128,7 @@ export function createEmptyActionsRun(): ActionsRun {
|
||||
triggerEvent: '',
|
||||
pullRequest: null,
|
||||
jobs: [] as Array<ActionsJob>,
|
||||
jobSummaries: [],
|
||||
commit: {
|
||||
localeCommit: '',
|
||||
localePushedBy: '',
|
||||
|
||||
@@ -22,6 +22,11 @@ const props = defineProps<{
|
||||
const locale = props.locale;
|
||||
const store = createActionRunViewStore(props.actionsViewUrl);
|
||||
const {currentRun: run, runArtifacts: artifacts} = toRefs(store.viewData);
|
||||
const visibleJobSummaries = computed(() => {
|
||||
const summaries = run.value.jobSummaries || [];
|
||||
if (!props.jobId) return summaries;
|
||||
return summaries.filter((summary) => summary.jobId === props.jobId);
|
||||
});
|
||||
|
||||
type JobListItem = {
|
||||
job: ActionsJob;
|
||||
@@ -277,19 +282,35 @@ async function deleteArtifact(name: string) {
|
||||
</div>
|
||||
|
||||
<div class="action-view-right">
|
||||
<ActionRunSummaryView
|
||||
v-if="!props.jobId"
|
||||
:store="store"
|
||||
:locale="locale"
|
||||
:artifact-count="artifacts.length"
|
||||
/>
|
||||
<ActionRunJobView
|
||||
v-else
|
||||
:store="store"
|
||||
:locale="locale"
|
||||
:actions-view-url="props.actionsViewUrl"
|
||||
:job-id="props.jobId"
|
||||
/>
|
||||
<div class="action-view-right-panel">
|
||||
<ActionRunSummaryView
|
||||
v-if="!props.jobId"
|
||||
:store="store"
|
||||
:locale="locale"
|
||||
:artifact-count="artifacts.length"
|
||||
/>
|
||||
<ActionRunJobView
|
||||
v-else
|
||||
:store="store"
|
||||
:locale="locale"
|
||||
:actions-view-url="props.actionsViewUrl"
|
||||
:job-id="props.jobId"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="visibleJobSummaries.length" class="action-view-right-panel job-summary-section">
|
||||
<div class="job-summary-section-header">
|
||||
{{ locale.jobSummaries }}
|
||||
</div>
|
||||
<div class="job-summary-list">
|
||||
<div v-for="s in visibleJobSummaries" :key="s.jobId" class="job-summary-item">
|
||||
<div class="job-summary-header">
|
||||
<strong class="gt-ellipsis">{{ s.jobName || `Job ${s.jobId}` }}</strong>
|
||||
</div>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="markup job-summary-body" v-html="s.summaryHTML"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -463,25 +484,32 @@ async function deleteArtifact(name: string) {
|
||||
width: 70%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-view-right-panel {
|
||||
border: 1px solid var(--color-console-border);
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--color-console-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* begin fomantic button overrides */
|
||||
|
||||
.action-view-right .ui.button,
|
||||
.action-view-right .ui.button:focus {
|
||||
.action-view-right-panel .ui.button,
|
||||
.action-view-right-panel .ui.button:focus {
|
||||
background: transparent;
|
||||
color: var(--color-console-fg-subtle);
|
||||
}
|
||||
|
||||
.action-view-right .ui.button:hover {
|
||||
.action-view-right-panel .ui.button:hover {
|
||||
background: var(--color-console-hover-bg);
|
||||
color: var(--color-console-fg);
|
||||
}
|
||||
|
||||
.action-view-right .ui.button:active {
|
||||
.action-view-right-panel .ui.button:active {
|
||||
background: var(--color-console-active-bg);
|
||||
color: var(--color-console-fg);
|
||||
}
|
||||
@@ -499,4 +527,39 @@ async function deleteArtifact(name: string) {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
.job-summary-section {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.job-summary-section-header {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--color-console-border);
|
||||
background: var(--color-console-bg);
|
||||
color: var(--color-console-fg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.job-summary-list {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.job-summary-item {
|
||||
padding: 12px;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--color-console-hover-bg);
|
||||
border: 1px solid var(--color-console-border);
|
||||
}
|
||||
|
||||
.job-summary-header {
|
||||
color: var(--color-console-fg);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.job-summary-body {
|
||||
color: var(--color-console-fg);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user