From 19872063a3c14256a1d89b2a104d63e7538a3a28 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Mon, 14 Aug 2023 23:14:30 +0800 Subject: [PATCH] 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 Co-authored-by: Jason Song --- models/repo/repo.go | 6 ++++ models/repo/repo_unit.go | 46 ++++++++++++++++++++++++++++- models/repo/repo_unit_test.go | 30 +++++++++++++++++++ options/locale/locale_en-US.ini | 5 ++++ routers/web/repo/actions/actions.go | 9 ++++++ routers/web/repo/actions/view.go | 41 +++++++++++++++++++++++++ routers/web/web.go | 2 ++ services/actions/notifier_helper.go | 7 +++++ templates/repo/actions/list.tmpl | 19 +++++++++++- web_src/css/base.css | 17 +++++++++++ 10 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 models/repo/repo_unit_test.go diff --git a/models/repo/repo.go b/models/repo/repo.go index 3d1f2dcfa8..b37948fea7 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -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), diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 7c1af95bf0..cf9ff93d32 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -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 { diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go new file mode 100644 index 0000000000..a760594013 --- /dev/null +++ b/models/repo/repo_unit_test.go @@ -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()) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index daf22d9fea..2f32a9df70 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 5a12f52dcd..b0f4b6f897 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -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="//actions") // they will be 0 by default, which indicates get all status or actors ctx.Data["CurActor"] = actorID diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index abb1f6b66b..af2ec21e4b 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -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) +} diff --git a/routers/web/web.go b/routers/web/web.go index f857a36b04..e70e360d59 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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(""). diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 0cc2d17f4e..75c99ff19c 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -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) } diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index e73cf71f6b..9d256820ae 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -2,6 +2,8 @@
{{template "repo/header" .}}
+ {{template "base/alert" .}} +
- {{template "repo/actions/runs_list" .}}
diff --git a/web_src/css/base.css b/web_src/css/base.css index d44f949318..eca08fa1b9 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -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); }