Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/structs/repo_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ type ActionWorkflowStep struct {
// ActionWorkflowJob represents a WorkflowJob
type ActionWorkflowJob struct {
ID int64 `json:"id"`
JobID string `json:"job_id,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would revert changes to the rest api, since the UI does not use this struct. Might be a leftover from previous tries?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The needs and job_id fields are required for the workflow dependency graph functionality implemented in the WorkflowGraph.vue component. The component uses job_id to correctly map dependencies between jobs, especially when custom job names are used in the workflow definition. Without these fields, it is impossible to reliably determine the connections between nodes (edges will not be drawn).

Copy link
Contributor

@ChristopherHX ChristopherHX Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The needs and job_id fields are required for the workflow dependency graph functionality implemented

Yes I agree that you need those information to build the graph, but you do not use the gitea rest api structs in your vue component as far as I understood.

Your template uses the data from the view model

Changes of this file
https://github.com/go-gitea/gitea/blob/f685d5f8d586f3970820e5471706fe9b08d3b315/routers/web/repo/actions/view.go

This then directly use ActionRunJob to create this data for your vue component.

Additional Context As far I know Gitea does not allow for security related reasons to use the api/v1 from the ui code, because they are not protected against webbrowser cross site attacks and so on

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was developing the graph component, I considered keeping the data structures consistent between the API and the view layer. These fields might be needed for direct API usage by third-party applications, not just my component. If you think they won't be necessary, I can remove them.

URL string `json:"url"`
HTMLURL string `json:"html_url"`
RunID int64 `json:"run_id"`
Expand All @@ -175,6 +176,7 @@ type ActionWorkflowJob struct {
RunnerID int64 `json:"runner_id,omitempty"`
RunnerName string `json:"runner_name,omitempty"`
Steps []*ActionWorkflowStep `json:"steps"`
Needs []string `json:"needs,omitempty"`
// swagger:strfmt date-time
CreatedAt time.Time `json:"created_at"`
// swagger:strfmt date-time
Expand Down
14 changes: 9 additions & 5 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,13 @@ type ViewResponse struct {
}

type ViewJob struct {
ID int64 `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
CanRerun bool `json:"canRerun"`
Duration string `json:"duration"`
ID int64 `json:"id"`
JobID string `json:"job_id,omitempty"`
Name string `json:"name"`
Status string `json:"status"`
CanRerun bool `json:"canRerun"`
Duration string `json:"duration"`
Needs []string `json:"needs,omitempty"`
}

type ViewCommit struct {
Expand Down Expand Up @@ -247,10 +249,12 @@ func ViewPost(ctx *context_module.Context) {
for _, v := range jobs {
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &ViewJob{
ID: v.ID,
JobID: v.JobID,
Name: v.Name,
Status: v.Status.String(),
CanRerun: resp.State.Run.CanRerun,
Duration: v.Duration().String(),
Needs: v.Needs,
})
}

Expand Down
4 changes: 3 additions & 1 deletion services/convert/convert.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rest api only change as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they are affected.
However, these changes are backward compatible.
They are only required for the Dependency Graph functionality.

Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
}

return &api.ActionWorkflowJob{
ID: job.ID,
ID: job.ID,
JobID: job.JobID,
// missing api endpoint for this location
URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID),
HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex),
Expand All @@ -384,6 +385,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
RunnerID: runnerID,
RunnerName: runnerName,
Steps: steps,
Needs: job.Needs,
CreatedAt: job.Created.AsTime().UTC(),
StartedAt: job.Started.AsTime().UTC(),
CompletedAt: job.Stopped.AsTime().UTC(),
Expand Down
11 changes: 11 additions & 0 deletions templates/swagger/v1_json.tmpl
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rest api only change as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they are affected.
However, these changes are backward compatible.
They are only required for the Dependency Graph functionality.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 85 additions & 29 deletions web_src/js/components/RepoActionView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {renderAnsi} from '../render/ansi.ts';
import {POST, DELETE} from '../modules/fetch.ts';
import type {IntervalId} from '../types.ts';
import {toggleFullScreen} from '../utils.ts';
import WorkflowGraph from './WorkflowGraph.vue'

// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
Expand All @@ -28,9 +29,11 @@ type LogLineCommand = {

type Job = {
id: number;
job_id: string;
name: string;
status: RunStatus;
canRerun: boolean;
needs?: string[];
duration: string;
}

Expand Down Expand Up @@ -85,6 +88,7 @@ export default defineComponent({
components: {
SvgIcon,
ActionRunStatus,
WorkflowGraph
},
props: {
runIndex: {
Expand Down Expand Up @@ -115,6 +119,7 @@ export default defineComponent({
artifacts: [] as Array<Record<string, any>>,
menuVisible: false,
isFullScreen: false,
showSummary: true,
timeVisible: {
'log-time-stamp': false,
'log-time-seconds': false,
Expand Down Expand Up @@ -512,6 +517,16 @@ export default defineComponent({
</div>
<div class="action-view-body">
<div class="action-view-left">
<div class="summary-toggle">
<button
class="ui basic small button"
@click="showSummary = !showSummary"
:class="{ active: showSummary }"
>
<SvgIcon :name="showSummary ? 'octicon-chevron-down' : 'octicon-chevron-right'"/>
Summary
</button>
</div>
<div class="job-group-section">
<div class="job-brief-list">
<a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id">
Expand Down Expand Up @@ -554,7 +569,16 @@ export default defineComponent({
</div>

<div class="action-view-right">
<div class="job-info-header">
<WorkflowGraph
v-if="showSummary && run.jobs.length > 1"
:jobs="run.jobs"
:current-job-id="parseInt(jobIndex)"
class="workflow-graph-container"
/>

<div
class="job-info-header"
>
<div class="job-info-header-left gt-ellipsis">
<h3 class="job-info-header-title gt-ellipsis">
{{ currentJob.title }}
Expand Down Expand Up @@ -602,7 +626,11 @@ export default defineComponent({
</div>
</div>
<!-- always create the node because we have our own event listeners on it, don't use "v-if" -->
<div class="job-step-container" ref="stepsContainer" v-show="currentJob.steps.length">
<div
class="job-step-container"
ref="stepsContainer"
v-show="currentJob.steps.length"
>
<div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i">
<div class="job-step-summary" @click.stop="isExpandable(jobStep.status) && toggleStepLogs(i)" :class="[currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']">
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
Expand Down Expand Up @@ -660,6 +688,34 @@ export default defineComponent({
overflow-wrap: anywhere;
}

.summary-toggle {
margin: 16px 0 8px;
padding-bottom: 12px;
border-bottom: 1px solid var(--color-secondary);
}

.summary-toggle .ui.button {
padding: 6px 12px;
border: 1px solid var(--color-secondary);
border-radius: 6px;
background: transparent;
color: var(--color-text);
display: flex;
align-items: center;
gap: 6px;
}

.summary-toggle .ui.button:hover {
background: var(--color-hover);
border-color: var(--color-secondary);
}

.summary-toggle .ui.button.active {
background: var(--color-secondary-alpha-10);
border-color: var(--color-primary);
color: var(--color-primary);
}

.action-info-summary .ui.button {
margin: 0;
white-space: nowrap;
Expand Down Expand Up @@ -780,14 +836,14 @@ export default defineComponent({

.action-view-right {
flex: 1;
color: var(--color-console-fg-subtle);
color: var(--color-text);
max-height: 100%;
width: 70%;
display: flex;
flex-direction: column;
border: 1px solid var(--color-console-border);
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
background: var(--color-console-bg);
background: var(--color-body);
align-self: flex-start;
}

Expand All @@ -796,49 +852,49 @@ export default defineComponent({
.action-view-right .ui.button,
.action-view-right .ui.button:focus {
background: transparent;
color: var(--color-console-fg-subtle);
color: var(--color-text);
}

.action-view-right .ui.button:hover {
background: var(--color-console-hover-bg);
color: var(--color-console-fg);
background: var(--color-hover);
color: var(--color-text);
}

.action-view-right .ui.button:active {
background: var(--color-console-active-bg);
color: var(--color-console-fg);
background: var(--color-active);
color: var(--color-text);
}

/* end fomantic button overrides */

/* begin fomantic dropdown menu overrides */

.action-view-right .ui.dropdown .menu {
background: var(--color-console-menu-bg);
border-color: var(--color-console-menu-border);
background: var(--color-menu);
border-color: var(--color-secondary);
}

.action-view-right .ui.dropdown .menu > .item {
color: var(--color-console-fg);
color: var(--color-text);
}

.action-view-right .ui.dropdown .menu > .item:hover {
color: var(--color-console-fg);
background: var(--color-console-hover-bg);
color: var(--color-text);
background: var(--color-hover);
}

.action-view-right .ui.dropdown .menu > .item:active {
color: var(--color-console-fg);
background: var(--color-console-active-bg);
color: var(--color-text);
background: var(--color-active);
}

.action-view-right .ui.dropdown .menu > .divider {
border-top-color: var(--color-console-menu-border);
border-top-color: var(--color-secondary-alpha-30);
}

.action-view-right .ui.pointing.dropdown > .menu:not(.hidden)::after {
background: var(--color-console-menu-bg);
box-shadow: -1px -1px 0 0 var(--color-console-menu-border);
background: var(--color-menu);
box-shadow: -1px -1px 0 0 var(--color-secondary);
}

/* end fomantic dropdown menu overrides */
Expand All @@ -852,7 +908,7 @@ export default defineComponent({
top: 0;
height: 60px;
z-index: 1; /* above .job-step-container */
background: var(--color-console-bg);
background: var(--color-body);
border-radius: 3px;
}

Expand All @@ -861,13 +917,13 @@ export default defineComponent({
}

.job-info-header .job-info-header-title {
color: var(--color-console-fg);
color: var(--color-text);
font-size: 16px;
margin: 0;
}

.job-info-header .job-info-header-detail {
color: var(--color-console-fg-subtle);
color: var(--color-text);
font-size: 12px;
}

Expand All @@ -878,7 +934,7 @@ export default defineComponent({
.job-step-container {
max-height: 100%;
border-radius: 0 0 var(--border-radius) var(--border-radius);
border-top: 1px solid var(--color-console-border);
border-top: 1px solid var(--color-secondary);
z-index: 0;
}

Expand All @@ -894,8 +950,8 @@ export default defineComponent({
}

.job-step-container .job-step-summary.step-expandable:hover {
color: var(--color-console-fg);
background: var(--color-console-hover-bg);
color: var(--color-text);
background: var(--color-hover);
}

.job-step-container .job-step-summary .step-summary-msg {
Expand All @@ -907,8 +963,8 @@ export default defineComponent({
}

.job-step-container .job-step-summary.selected {
color: var(--color-console-fg);
background-color: var(--color-console-active-bg);
color: var(--color-text);
background-color: var(--color-active);
position: sticky;
top: 60px;
}
Expand Down Expand Up @@ -944,7 +1000,7 @@ export default defineComponent({

.job-log-line:hover,
.job-log-line:target {
background-color: var(--color-console-hover-bg);
background-color: var(--color-hover);
}

.job-log-line:target {
Expand Down
Loading