add disable workflow feature (#26413)

As title, that's simmilar with github.


![image](https://github.com/go-gitea/gitea/assets/25342410/9e8b2444-63e0-4e87-80da-730c1e4d09d6)



![image](https://github.com/go-gitea/gitea/assets/25342410/6c3a3345-3ba7-48c9-9acd-3e621632491b)

---------

Signed-off-by: a1012112796 <1012112796@qq.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
This commit is contained in:
a1012112796 2023-08-14 23:14:30 +08:00 committed by GitHub
parent 253737eb36
commit 19872063a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 2 deletions

View File

@ -391,7 +391,13 @@ func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit
Type: tp,
Config: new(IssuesConfig),
}
} else if tp == unit.TypeActions {
return &RepoUnit{
Type: tp,
Config: new(ActionsConfig),
}
}
return &RepoUnit{
Type: tp,
Config: new(UnitConfig),

View File

@ -6,6 +6,7 @@ package repo
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
@ -162,6 +163,42 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
return MergeStyleMerge
}
type ActionsConfig struct {
DisabledWorkflows []string
}
func (cfg *ActionsConfig) EnableWorkflow(file string) {
cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file)
}
func (cfg *ActionsConfig) ToString() string {
return strings.Join(cfg.DisabledWorkflows, ",")
}
func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool {
return util.SliceContains(cfg.DisabledWorkflows, file)
}
func (cfg *ActionsConfig) DisableWorkflow(file string) {
for _, workflow := range cfg.DisabledWorkflows {
if file == workflow {
return
}
}
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
}
// FromDB fills up a ActionsConfig from serialized format.
func (cfg *ActionsConfig) FromDB(bs []byte) error {
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a ActionsConfig to a serialized format.
func (cfg *ActionsConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}
// BeforeSet is invoked from XORM before setting the value of a field of this object.
func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
switch colName {
@ -175,7 +212,9 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
r.Config = new(PullRequestsConfig)
case unit.TypeIssues:
r.Config = new(IssuesConfig)
case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages, unit.TypeActions:
case unit.TypeActions:
r.Config = new(ActionsConfig)
case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages:
fallthrough
default:
r.Config = new(UnitConfig)
@ -218,6 +257,11 @@ func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
return r.Config.(*ExternalTrackerConfig)
}
// ActionsConfig returns config for unit.ActionsConfig
func (r *RepoUnit) ActionsConfig() *ActionsConfig {
return r.Config.(*ActionsConfig)
}
func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) {
var tmpUnits []*RepoUnit
if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil {

View File

@ -0,0 +1,30 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestActionsConfig(t *testing.T) {
cfg := &ActionsConfig{}
cfg.DisableWorkflow("test1.yaml")
assert.EqualValues(t, []string{"test1.yaml"}, cfg.DisabledWorkflows)
cfg.DisableWorkflow("test1.yaml")
assert.EqualValues(t, []string{"test1.yaml"}, cfg.DisabledWorkflows)
cfg.EnableWorkflow("test1.yaml")
assert.EqualValues(t, []string{}, cfg.DisabledWorkflows)
cfg.EnableWorkflow("test1.yaml")
assert.EqualValues(t, []string{}, cfg.DisabledWorkflows)
cfg.DisableWorkflow("test1.yaml")
cfg.DisableWorkflow("test2.yaml")
cfg.DisableWorkflow("test3.yaml")
assert.EqualValues(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString())
}

View File

@ -3491,6 +3491,11 @@ runs.status_no_select = All status
runs.no_results = No results matched.
runs.no_runs = The workflow has no runs yet.
workflow.disable = Disable Workflow
workflow.disable_success = Workflow '%s' disabled successfully.
workflow.enable = Enable Workflow
workflow.enable_success = Workflow '%s' enabled successfully.
need_approval_desc = Need approval to run workflows for fork pull request.
variables = Variables

View File

@ -137,6 +137,15 @@ func List(ctx *context.Context) {
actorID := ctx.FormInt64("actor")
status := ctx.FormInt("status")
ctx.Data["CurWorkflow"] = workflow
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
ctx.Data["ActionsConfig"] = actionsConfig
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
ctx.Data["AllowDisableOrEnableWorkflow"] = true
ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
}
// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
// they will be 0 by default, which indicates get all status or actors
ctx.Data["CurActor"] = actorID

View File

@ -17,6 +17,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
@ -572,3 +573,43 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
}
}
}
func DisableWorkflowFile(ctx *context_module.Context) {
disableOrEnableWorkflowFile(ctx, false)
}
func EnableWorkflowFile(ctx *context_module.Context) {
disableOrEnableWorkflowFile(ctx, true)
}
func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) {
workflow := ctx.FormString("workflow")
if len(workflow) == 0 {
ctx.ServerError("workflow", nil)
return
}
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig()
if isEnable {
cfg.EnableWorkflow(workflow)
} else {
cfg.DisableWorkflow(workflow)
}
if err := repo_model.UpdateRepoUnit(cfgUnit); err != nil {
ctx.ServerError("UpdateRepoUnit", err)
return
}
if isEnable {
ctx.Flash.Success(ctx.Tr("actions.workflow.enable_success", workflow))
} else {
ctx.Flash.Success(ctx.Tr("actions.workflow.disable_success", workflow))
}
redirectURL := fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s", ctx.Repo.RepoLink, url.QueryEscape(workflow),
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
ctx.JSONRedirect(redirectURL)
}

View File

@ -1200,6 +1200,8 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() {
m.Get("", actions.List)
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
m.Group("/runs/{run}", func() {
m.Combo("").

View File

@ -150,7 +150,14 @@ func notify(ctx context.Context, input *notifyInput) error {
if len(workflows) == 0 {
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
} else {
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
for _, wf := range workflows {
if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
log.Trace("repo %s has disable workflows %s", input.Repo.RepoPath(), wf.EntryName)
continue
}
if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget {
detectedWorkflows = append(detectedWorkflows, wf)
}

View File

@ -2,6 +2,8 @@
<div class="page-content repository actions">
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
<div class="ui stackable grid">
<div class="four wide column">
<div class="ui fluid vertical menu">
@ -13,12 +15,16 @@
{{svg "octicon-alert" 16 "text red"}}
</span>
{{end}}
{{if $.ActionsConfig.IsWorkflowDisabled .Entry.Name}}
<div class="ui red label">{{$.locale.Tr "disabled"}}</div>
{{end}}
</a>
{{end}}
</div>
</div>
<div class="twelve wide column content">
<div class="ui secondary filter stackable menu gt-je">
<div class="ui secondary filter menu gt-je gt-df gt-ac">
<!-- Actor -->
<div class="ui{{if not .Actors}} disabled{{end}} dropdown jump item">
<span class="text">{{.locale.Tr "actions.runs.actor"}}</span>
@ -57,6 +63,17 @@
{{end}}
</div>
</div>
{{if .AllowDisableOrEnableWorkflow}}
<button class="ui jump dropdown btn interact-bg gt-p-3">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
<a class="item link-action" data-url="{{$.Link}}/{{if .CurWorkflowDisabled}}enable{{else}}disable{{end}}?workflow={{$.CurWorkflow}}&actor={{.CurActor}}&status={{$.CurStatus}}">
{{if .CurWorkflowDisabled}}{{.locale.Tr "actions.workflow.enable"}}{{else}}{{.locale.Tr "actions.workflow.disable"}}{{end}}
</a>
</div>
</button>
{{end}}
</div>
{{template "repo/actions/runs_list" .}}
</div>

View File

@ -653,6 +653,18 @@ a.label,
color: var(--color-text);
}
/* replace item margin on secondary menu items with gap and remove both the
negative margins on the menu as well as margin on the items */
.ui.secondary.menu {
margin-left: 0;
margin-right: 0;
gap: .35714286em;
}
.ui.secondary.menu .item {
margin-left: 0;
margin-right: 0;
}
.ui.secondary.menu .dropdown.item:hover,
.ui.secondary.menu a.item:hover {
color: var(--color-text);
@ -670,6 +682,11 @@ a.label,
padding-right: 0.85714286em;
}
/* remove the menu clearfix so that it won't add undesired gaps when using "gap" */
.ui.menu::after {
content: normal;
}
.ui.menu .dropdown.item .menu {
background: var(--color-body);
}