diff --git a/models/repo/repo.go b/models/repo/repo.go index 3d1f2dcfa..b37948fea 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 7c1af95bf..cf9ff93d3 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 000000000..a76059401 --- /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 daf22d9fe..2f32a9df7 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 5a12f52dc..b0f4b6f89 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 abb1f6b66..af2ec21e4 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 f857a36b0..e70e360d5 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 0cc2d17f4..75c99ff19 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 e73cf71f6..9d256820a 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 d44f94931..eca08fa1b 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); }