Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
438ac5a
feat: Add workflow dependencies visualization
xDarkmanx Dec 26, 2025
f685d5f
fix: Resolve all linting errors with correct CSS variables
xDarkmanx Dec 26, 2025
07094e1
Merge branch 'main' into feature/workflow-graph
xDarkmanx Dec 27, 2025
fbc6abf
Merge branch 'main' into feature/workflow-graph
xDarkmanx Dec 27, 2025
24c38e5
Merge branch 'main' into feature/workflow-graph
xDarkmanx Dec 27, 2025
39d9a64
Merge branch 'main' into feature/workflow-graph
xDarkmanx Jan 29, 2026
ce16e4f
Merge branch 'main' into feature/workflow-graph
xDarkmanx Jan 31, 2026
b7c8702
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 1, 2026
55386cd
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 2, 2026
2133b73
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 4, 2026
6e6decd
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 6, 2026
03362d4
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 6, 2026
0f9d18a
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 8, 2026
261b343
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 8, 2026
cd185dc
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 9, 2026
a6bac39
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 11, 2026
83e4b96
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 11, 2026
b2fbe35
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 11, 2026
e0ac315
fix: add dependency graph toggle with persistent state
xDarkmanx Feb 11, 2026
4d4e16e
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 13, 2026
4b87e2a
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 16, 2026
f59d768
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 18, 2026
0a86d2a
fix: move dependency graph button to header actions group and improve UX
xDarkmanx Feb 18, 2026
5192f05
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 19, 2026
4b3e863
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 19, 2026
d757d3c
fix: Revert language changes
xDarkmanx Feb 20, 2026
d88a537
fix: Improve RepoActionView console styling and WorkflowGraph UX
xDarkmanx Feb 20, 2026
6c6646e
fix: address code review feedback for WorkflowGraph.vue
xDarkmanx Feb 20, 2026
45d3e14
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 20, 2026
c27f1e4
Merge branch 'main' into feature/workflow-graph
xDarkmanx Feb 21, 2026
28bcf67
fix: apply review feedback for workflow graph
xDarkmanx Feb 21, 2026
95303ae
fix: change variable name storegeKey -> storageKey
xDarkmanx Feb 22, 2026
eb39135
refactor
wxiaoguang Feb 23, 2026
848efc1
refactor
wxiaoguang Feb 23, 2026
fa7c94e
refactor
wxiaoguang Feb 23, 2026
6f138c0
revert api change
wxiaoguang Feb 23, 2026
e240361
fix types
wxiaoguang Feb 23, 2026
af14a7b
revert unnecessary change
wxiaoguang Feb 23, 2026
6c976c6
fix prop, fix status
wxiaoguang Feb 23, 2026
e7a69b7
fix
wxiaoguang Feb 23, 2026
5e8d618
fix
wxiaoguang Feb 23, 2026
a04b250
fix dragging & click
wxiaoguang Feb 23, 2026
8a744bd
fix ui
wxiaoguang Feb 23, 2026
4c24c7c
fix "needs"
wxiaoguang Feb 23, 2026
f5648df
Merge branch 'main' into feature/workflow-graph
wxiaoguang Feb 23, 2026
ecb1ddb
fine tune
wxiaoguang Feb 23, 2026
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
1 change: 1 addition & 0 deletions options/locale/locale_en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3695,6 +3695,7 @@
"actions.runs.delete.description": "Are you sure you want to permanently delete this workflow run? This action cannot be undone.",
"actions.runs.not_done": "This workflow run is not done.",
"actions.runs.view_workflow_file": "View workflow file",
"actions.runs.workflow_graph": "Workflow Graph",
"actions.workflow.disable": "Disable Workflow",
"actions.workflow.disable_success": "Workflow '%s' disabled successfully.",
"actions.workflow.enable": "Enable Workflow",
Expand Down
11 changes: 9 additions & 2 deletions routers/web/devtest/mock_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo/actions"
Expand Down Expand Up @@ -58,8 +59,8 @@ func generateMockStepsLog(logCur actions.LogCursor, opts generateMockStepsLogOpt
}

func MockActionsView(ctx *context.Context) {
ctx.Data["RunID"] = ctx.PathParam("run")
ctx.Data["JobID"] = ctx.PathParam("job")
ctx.Data["RunIndex"] = ctx.PathParam("run")
ctx.Data["JobIndex"] = ctx.PathParam("job")
ctx.HTML(http.StatusOK, "devtest/repo-action-view")
}

Expand All @@ -69,6 +70,7 @@ func MockActionsRunsJobs(ctx *context.Context) {
req := web.GetForm(ctx).(*actions.ViewRequest)
resp := &actions.ViewResponse{}
resp.State.Run.TitleHTML = `mock run title <a href="/">link</a>`
resp.State.Run.Link = setting.AppSubURL + "/devtest/repo-action-view/runs/" + strconv.FormatInt(runID, 10)
resp.State.Run.Status = actions_model.StatusRunning.String()
resp.State.Run.CanCancel = runID == 10
resp.State.Run.CanApprove = runID == 20
Expand Down Expand Up @@ -112,24 +114,29 @@ func MockActionsRunsJobs(ctx *context.Context) {

resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID * 10,
JobID: "job-100",
Name: "job 100",
Status: actions_model.StatusRunning.String(),
CanRerun: true,
Duration: "1h",
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID*10 + 1,
JobID: "job-101",
Name: "job 101",
Status: actions_model.StatusWaiting.String(),
CanRerun: false,
Duration: "2h",
Needs: []string{"job-100"},
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID*10 + 2,
JobID: "job-102",
Name: "job 102",
Status: actions_model.StatusFailure.String(),
CanRerun: false,
Duration: "3h",
Needs: []string{"job-100", "job-101"},
})

var mockLogOptions []generateMockStepsLogOptions
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 @@ -143,11 +143,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:"jobId,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 @@ -248,10 +250,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
2 changes: 1 addition & 1 deletion routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1675,7 +1675,7 @@ func registerWebRoutes(m *web.Router) {
m.Any("/mail-preview", devtest.MailPreview)
m.Any("/mail-preview/*", devtest.MailPreviewRender)
m.Any("/{sub}", devtest.TmplCommon)
m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView)
m.Get("/repo-action-view/runs/{run}/jobs/{job}", devtest.MockActionsView)
m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
})
}
Expand Down
10 changes: 5 additions & 5 deletions templates/devtest/repo-action-view.tmpl
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{{template "base/head" .}}
<div class="page-content">
<div class="tw-flex tw-justify-center tw-items-center tw-gap-5">
<a href="/devtest/repo-action-view/10/100">Run:CanCancel</a>
<a href="/devtest/repo-action-view/20/200">Run:CanApprove</a>
<a href="/devtest/repo-action-view/30/300">Run:CanRerun</a>
<a href="/devtest/repo-action-view/runs/10/jobs/0">Run:CanCancel</a>
<a href="/devtest/repo-action-view/runs/20/jobs/1">Run:CanApprove</a>
<a href="/devtest/repo-action-view/runs/30/jobs/2">Run:CanRerun</a>
</div>
{{template "repo/actions/view_component" (dict
"RunIndex" (or .RunID 10)
"JobIndex" (or .JobID 100)
"RunIndex" (or .RunIndex 10)
"JobIndex" (or .JobIndex 0)
"ActionsURL" (print AppSubUrl "/devtest/actions-mock")
)}}
</div>
Expand Down
1 change: 1 addition & 0 deletions templates/repo/actions/view_component.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
data-locale-runs-scheduled="{{ctx.Locale.Tr "actions.runs.scheduled"}}"
data-locale-runs-commit="{{ctx.Locale.Tr "actions.runs.commit"}}"
data-locale-runs-pushed-by="{{ctx.Locale.Tr "actions.runs.pushed_by"}}"
data-locale-runs-workflow-graph="{{ctx.Locale.Tr "actions.runs.workflow_graph"}}"
data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}"
data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}"
data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}"
Expand Down
16 changes: 8 additions & 8 deletions web_src/css/themes/theme-gitea-light.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ gitea-theme-meta-info {
--color-secondary-hover: var(--color-secondary-dark-5);
--color-secondary-active: var(--color-secondary-dark-6);
/* console colors - used for actions console and console files */
--color-console-fg: #f7f8f9;
--color-console-fg-subtle: #bdc4cc;
--color-console-bg: #171b1e;
--color-console-border: #2e353b;
--color-console-hover-bg: #272d33;
--color-console-active-bg: #2e353b;
--color-console-menu-bg: #262b31;
--color-console-menu-border: #414b55;
--color-console-fg: #0d1117;
--color-console-fg-subtle: #40474d;
--color-console-bg: #ffffff;
--color-console-border: #d0d7de;
--color-console-hover-bg: #f1f3f5;
--color-console-active-bg: #d0d7de;
--color-console-menu-bg: #f8f9fb;
--color-console-menu-border: #d0d7de;
/* named colors */
--color-red: #db2828;
--color-orange: #f2711c;
Expand Down
85 changes: 44 additions & 41 deletions web_src/js/components/RepoActionView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ 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'
import {localUserSettings} from '../modules/user-settings.ts';

// 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';
import type {ActionsRunStatus, ActionsJob} from '../modules/gitea-actions.ts';

type StepContainerElement = HTMLElement & {
// To remember the last active logs container, for example: a batch of logs only starts a group but doesn't end it,
Expand Down Expand Up @@ -54,19 +53,10 @@ const LogLinePrefixCommandMap: Record<string, LogLineCommandName> = {
'::remove-matcher': 'hidden', // it has arguments
};


type Job = {
id: number;
name: string;
status: RunStatus;
canRerun: boolean;
duration: string;
}

type Step = {
summary: string,
duration: string,
status: RunStatus,
status: ActionsRunStatus,
}

type JobStepState = {
Expand Down Expand Up @@ -107,6 +97,7 @@ function isLogElementInViewport(el: Element, {extraViewPortHeight}={extraViewPor
type LocaleStorageOptions = {
autoScroll: boolean;
expandRunning: boolean;
showWorkflowGraph: boolean;
actionsLogShowSeconds: boolean;
actionsLogShowTimestamps: boolean;
};
Expand All @@ -116,29 +107,21 @@ export default defineComponent({
components: {
SvgIcon,
ActionRunStatus,
WorkflowGraph,
},
props: {
runIndex: {
type: String,
default: '',
},
jobIndex: {
type: String,
default: '',
},
actionsURL: {
type: String,
default: '',
},
runIndex: {type: Number, required: true},
jobIndex: {type: Number, required: true},
actionsURL: {type: String, required: true},
locale: {
type: Object as PropType<Record<string, any>>,
default: null,
},
},

data() {
const defaultViewOptions: LocaleStorageOptions = {autoScroll: true, expandRunning: false, actionsLogShowSeconds: false, actionsLogShowTimestamps: false};
const {autoScroll, expandRunning, actionsLogShowSeconds, actionsLogShowTimestamps} = localUserSettings.getJsonObject('actions-view-options', defaultViewOptions);
const defaultViewOptions: LocaleStorageOptions = {autoScroll: true, expandRunning: false, showWorkflowGraph: false, actionsLogShowSeconds: false, actionsLogShowTimestamps: false};
const {autoScroll, expandRunning, showWorkflowGraph, actionsLogShowSeconds, actionsLogShowTimestamps} = localUserSettings.getJsonObject('actions-view-options', defaultViewOptions);
return {
// internal state
loadingAbortController: null as AbortController | null,
Expand All @@ -147,6 +130,7 @@ export default defineComponent({
artifacts: [] as Array<Record<string, any>>,
menuVisible: false,
isFullScreen: false,
showWorkflowGraph: showWorkflowGraph,
timeVisible: {
'log-time-stamp': actionsLogShowTimestamps,
'log-time-seconds': actionsLogShowSeconds,
Expand All @@ -159,7 +143,7 @@ export default defineComponent({
link: '',
title: '',
titleHTML: '',
status: '' as RunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon
status: '' as ActionsRunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon
canCancel: false,
canApprove: false,
canRerun: false,
Expand All @@ -176,7 +160,7 @@ export default defineComponent({
// canRerun: false,
// duration: '',
// },
] as Array<Job>,
] as Array<ActionsJob>,
commit: {
localeCommit: '',
localePushedBy: '',
Expand Down Expand Up @@ -214,6 +198,9 @@ export default defineComponent({
optionAlwaysExpandRunning() {
this.saveLocaleStorageOptions();
},
showWorkflowGraph() {
this.saveLocaleStorageOptions();
},
},

async mounted() {
Expand Down Expand Up @@ -258,6 +245,7 @@ export default defineComponent({
const opts: LocaleStorageOptions = {
autoScroll: this.optionAlwaysAutoScroll,
expandRunning: this.optionAlwaysExpandRunning,
showWorkflowGraph: this.showWorkflowGraph,
actionsLogShowSeconds: this.timeVisible['log-time-seconds'],
actionsLogShowTimestamps: this.timeVisible['log-time-stamp'],
};
Expand Down Expand Up @@ -456,11 +444,11 @@ export default defineComponent({
}
},

isDone(status: RunStatus) {
isDone(status: ActionsRunStatus) {
return ['success', 'skipped', 'failure', 'cancelled'].includes(status);
},

isExpandable(status: RunStatus) {
isExpandable(status: ActionsRunStatus) {
return ['success', 'running', 'failure', 'cancelled'].includes(status);
},

Expand Down Expand Up @@ -514,15 +502,20 @@ export default defineComponent({
<!-- eslint-disable-next-line vue/no-v-html -->
<h2 class="action-info-summary-title-text" v-html="run.titleHTML"/>
</div>
<button class="ui basic small compact button primary" @click="approveRun()" v-if="run.canApprove">
{{ locale.approve }}
</button>
<button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel">
{{ locale.cancel }}
</button>
<button class="ui basic small compact button link-action tw-shrink-0" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
{{ locale.rerun_all }}
</button>
<div class="flex-text-block tw-shrink-0 tw-flex-wrap">
<button class="ui basic small compact button primary" @click="showWorkflowGraph = !showWorkflowGraph" :class="{ active: showWorkflowGraph }" v-if="run.jobs.length > 1">
{{ locale.workflowGraph }}
</button>
<button class="ui basic small compact button primary" @click="approveRun()" v-if="run.canApprove">
{{ locale.approve }}
</button>
<button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel">
{{ locale.cancel }}
</button>
<button class="ui basic small compact button link-action" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
{{ locale.rerun_all }}
</button>
</div>
</div>
<div class="action-commit-summary">
<span><a class="muted" :href="run.workflowLink"><b>{{ run.workflowID }}</b></a>:</span>
Expand All @@ -545,7 +538,7 @@ export default defineComponent({
<div class="action-view-left">
<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">
<a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="jobIndex === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id">
<div class="job-brief-item-left">
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
<span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span>
Expand Down Expand Up @@ -585,6 +578,15 @@ export default defineComponent({
</div>

<div class="action-view-right">
<WorkflowGraph
v-if="showWorkflowGraph && run.jobs.length > 1"
:jobs="run.jobs"
:current-job-index="jobIndex"
:run-link="run.link"
:workflow-id="run.workflowID"
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">
Expand Down Expand Up @@ -673,6 +675,7 @@ export default defineComponent({

.action-info-summary {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 8px;
Expand Down
Loading