Skip to content
82 changes: 82 additions & 0 deletions models/actions/require_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

// WIP RequireAction

package actions

import (
"context"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/builder"
)

type RequireAction struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"index"`
RepoName string `xorm:"VARCHAR(255)"`
WorkflowName string `xorm:"VARCHAR(255) UNIQUE(require_action) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

type GlobalWorkflow struct {
RepoName string
Filename string
}

func init() {
db.RegisterModel(new(RequireAction))
}

type FindRequireActionOptions struct {
db.ListOptions
RequireActionID int64
OrgID int64
RepoName string
}

func (opts FindRequireActionOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.OrgID > 0 {
cond = cond.And(builder.Eq{"org_id": opts.OrgID})
}
if opts.RequireActionID > 0 {
cond = cond.And(builder.Eq{"id": opts.RequireActionID})
}
if opts.RepoName != "" {
cond = cond.And(builder.Eq{"repo_name": opts.RepoName})
}
return cond
}

// LoadAttributes loads the attributes of the require action
func (r *RequireAction) LoadAttributes(ctx context.Context) error {
// place holder for now.
return nil
}

// if the workflow is removable
func (r *RequireAction) Removable(orgID int64) bool {
// everyone can remove for now
return r.OrgID == orgID
}

func AddRequireAction(ctx context.Context, orgID int64, repoName, workflowName string) (*RequireAction, error) {
ra := &RequireAction{
OrgID: orgID,
RepoName: repoName,
WorkflowName: workflowName,
}
return ra, db.Insert(ctx, ra)
}

func DeleteRequireAction(ctx context.Context, requireActionID int64) error {
if _, err := db.DeleteByID[RequireAction](ctx, requireActionID); err != nil {
return err
}
return nil
}
22 changes: 22 additions & 0 deletions models/migrations/v1_23/v295.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_23 //nolint

import (
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/xorm"
)

func AddRequireActionTable(x *xorm.Engine) error {
type RequireAction struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"index"`
RepoName string `xorm:"VARCHAR(255)"`
WorkflowName string `xorm:"VARCHAR(255) UNIQUE(require_action) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
return x.Sync(new(RequireAction))
}
19 changes: 18 additions & 1 deletion models/repo/repo_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,30 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
}

type ActionsConfig struct {
DisabledWorkflows []string
DisabledWorkflows []string
EnabledGlobalWorkflows []string
}

func (cfg *ActionsConfig) EnableWorkflow(file string) {
cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file)
}

func (cfg *ActionsConfig) DisableGlobalWorkflow(file string) {
cfg.EnabledGlobalWorkflows = util.SliceRemoveAll(cfg.EnabledGlobalWorkflows, file)
}

func (cfg *ActionsConfig) IsGlobalWorkflowEnabled(file string) bool {
return slices.Contains(cfg.EnabledGlobalWorkflows, file)
}

func (cfg *ActionsConfig) EnableGlobalWorkflow(file string) {
cfg.EnabledGlobalWorkflows = append(cfg.EnabledGlobalWorkflows, file)
}

func (cfg *ActionsConfig) GetGlobalWorkflow() []string {
return cfg.EnabledGlobalWorkflows
}

func (cfg *ActionsConfig) ToString() string {
return strings.Join(cfg.DisabledWorkflows, ",")
}
Expand Down
27 changes: 27 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3647,11 +3647,38 @@ runs.no_workflows.documentation = For more information on Gitea Actions, see <a
runs.no_runs = The workflow has no runs yet.
runs.empty_commit_message = (empty commit message)

require_action = Require Actions
require_action.require_action_manage_panel = Require Actions Management Panel
require_action.enable_global_workflow = How to Enable Global Workflow
require_action.id = ID
require_action.add = Add Global Workflow
require_action.add_require_action = Enable selected Workflow
require_action.new = Create New
require_action.status = Status
require_action.search = Search...
require_action.version = Version
require_action.repo = Repo Name
require_action.workflow = Workflow Filename
require_action.link = Link
require_action.remove = Remove
require_action.none = No Require Actions Available.

Choose a reason for hiding this comment

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

Suggested change
require_action.none = No Require Actions Available.
require_action.none = No Required Actions Available.

require_action.creation.failed = Create Global Require Action %s Failed.
require_action.creation.success = Create Global Require Action %s successfully.
require_action.deletion = Delete
require_action.deletion.description = Removing the Global Require Action is permanent and cannot be undone. Continue?
require_action.deletion.success = The Global Require Action has been removed.

workflow.disable = Disable Workflow
workflow.disable_success = Workflow '%s' disabled successfully.
workflow.enable = Enable Workflow
workflow.enable_success = Workflow '%s' enabled successfully.
workflow.disabled = Workflow is disabled.
workflow.global = Global
workflow.global_disable = Disable Global Require
workflow.global_disable_success = Global Require '%s' disabled successfully.
workflow.global_enable = Enable Global Require
workflow.global_enable_success = Global Require '%s' enabled successfully.
workflow.global_enabled = Global Require is disabled.

need_approval_desc = Need approval to run workflows for fork pull request.

Expand Down
14 changes: 14 additions & 0 deletions routers/web/org/setting/require_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

// WIP RequireAction

package setting

import (
"code.gitea.io/gitea/services/context"
)

func RedirectToRepoSetting(ctx *context.Context) {
ctx.Redirect(ctx.Org.OrgLink + "/settings/actions/require_action")
}
6 changes: 6 additions & 0 deletions routers/web/repo/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func List(ctx *context.Context) {
workflow := ctx.FormString("workflow")
actorID := ctx.FormInt64("actor")
status := ctx.FormInt("status")
isGlobal := false
ctx.Data["CurWorkflow"] = workflow

actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
Expand All @@ -153,6 +154,8 @@ func List(ctx *context.Context) {
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
ctx.Data["AllowDisableOrEnableWorkflow"] = true
ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
ctx.Data["CurGlobalWorkflowEnable"] = actionsConfig.IsGlobalWorkflowEnabled(workflow)
isGlobal = actionsConfig.IsGlobalWorkflowEnabled(workflow)
}

// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
Expand Down Expand Up @@ -209,6 +212,9 @@ func List(ctx *context.Context) {
pager.AddParamString("workflow", workflow)
pager.AddParamString("actor", fmt.Sprint(actorID))
pager.AddParamString("status", fmt.Sprint(status))
if isGlobal {
pager.AddParamString("global", fmt.Sprint(isGlobal))
}
ctx.Data["Page"] = pager
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0

Expand Down
49 changes: 38 additions & 11 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,33 +685,60 @@ func EnableWorkflowFile(ctx *context_module.Context) {
}

func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) {
disableOrEnable(ctx, isEnable, false)
}

func disableOrEnable(ctx *context_module.Context, isEnable, isglobal 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)
if isglobal {
if isEnable {
cfg.DisableGlobalWorkflow(workflow)
} else {
cfg.EnableGlobalWorkflow(workflow)
}
} else {
cfg.DisableWorkflow(workflow)
if isEnable {
cfg.EnableWorkflow(workflow)
} else {
cfg.DisableWorkflow(workflow)
}
}

if err := repo_model.UpdateRepoUnit(ctx, cfgUnit); err != nil {
ctx.ServerError("UpdateRepoUnit", err)
return
}

if isEnable {
ctx.Flash.Success(ctx.Tr("actions.workflow.enable_success", workflow))
if isglobal {
if isEnable {
ctx.Flash.Success(ctx.Tr("actions.workflow.global_disable_success", workflow))
} else {
ctx.Flash.Success(ctx.Tr("actions.workflow.global_enable_success", workflow))
}
} else {
ctx.Flash.Success(ctx.Tr("actions.workflow.disable_success", workflow))
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)
}

func DisableGlobalWorkflowFile(ctx *context_module.Context) {
disableOrEnableGlobalWorkflowFile(ctx, true)
}

func EnableGlobalWorkflowFile(ctx *context_module.Context) {
disableOrEnableGlobalWorkflowFile(ctx, false)
}

func disableOrEnableGlobalWorkflowFile(ctx *context_module.Context, isEnable bool) {
disableOrEnable(ctx, isEnable, true)
}
87 changes: 87 additions & 0 deletions routers/web/repo/setting/require_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

// WIP RequireAction

package setting

import (
"errors"
"net/http"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
shared "code.gitea.io/gitea/routers/web/shared/actions"
"code.gitea.io/gitea/services/context"
)

const (
// let start with org WIP
tplOrgRequireAction base.TplName = "org/settings/actions"
)

type requireActionsCtx struct {
OrgID int64
IsOrg bool
RequireActionTemplate base.TplName
RedirectLink string
}

func getRequireActionCtx(ctx *context.Context) (*requireActionsCtx, error) {
if ctx.Data["PageIsOrgSettings"] == true {
return &requireActionsCtx{
OrgID: ctx.Org.Organization.ID,
IsOrg: true,
RequireActionTemplate: tplOrgRequireAction,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/require_action",
}, nil
}
return nil, errors.New("unable to set Require Actions context")
}

// Listing all RequireAction
func RequireAction(ctx *context.Context) {
ctx.Data["ActionsTitle"] = ctx.Tr("actions.requires")
ctx.Data["PageType"] = "require_action"
ctx.Data["PageIsSharedSettingsRequireAction"] = true

vCtx, err := getRequireActionCtx(ctx)
if err != nil {
ctx.ServerError("getRequireActionCtx", err)
return
}

page := ctx.FormInt("page")
if page <= 1 {
page = 1
}
opts := actions_model.FindRequireActionOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: 10,
},
}
shared.SetRequireActionContext(ctx, opts)
ctx.Data["Link"] = vCtx.RedirectLink
shared.GlobalEnableWorkflow(ctx, ctx.Org.Organization.ID)
ctx.HTML(http.StatusOK, vCtx.RequireActionTemplate)
}

func RequireActionCreate(ctx *context.Context) {
vCtx, err := getRequireActionCtx(ctx)
if err != nil {
ctx.ServerError("getRequireActionCtx", err)
return
}
shared.CreateRequireAction(ctx, vCtx.OrgID, vCtx.RedirectLink)
}

func RequireActionDelete(ctx *context.Context) {
vCtx, err := getRequireActionCtx(ctx)
if err != nil {
ctx.ServerError("getRequireActionCtx", err)
return
}
shared.DeleteRequireAction(ctx, vCtx.RedirectLink)
}
Loading