mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-06-19 07:30:50 +00:00
Actions Failure, Succes, Recover Webhooks (#7508)
Implement Actions Success, Failure and Recover webhooks for Forgejo, Gitea, Gogs, Slack, Discord, DingTalk, Telegram, Microsoft Teams, Feishu / Lark Suite, Matrix, WeCom (Wechat Work), Packagist. Some of these webhooks have not been manually tested. Implement settings for these new webhooks. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/7508): <!--number 7508 --><!--line 0 --><!--description QWN0aW9ucyBGYWlsdXJlLCBTdWNjZXMsIFJlY292ZXIgV2ViaG9va3M=-->Actions Failure, Succes, Recover Webhooks<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7508 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: christopher-besch <mail@chris-besch.com> Co-committed-by: christopher-besch <mail@chris-besch.com>
This commit is contained in:
parent
6ce9d764bc
commit
d17aa98262
27 changed files with 683 additions and 12 deletions
|
@ -299,6 +299,24 @@ func (w *Webhook) HasPackageEvent() bool {
|
|||
(w.ChooseEvents && w.Package)
|
||||
}
|
||||
|
||||
// HasActionRunFailureEvent returns if hook enabled action failure event.
|
||||
func (w *Webhook) HasActionRunFailureEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.ActionRunFailure)
|
||||
}
|
||||
|
||||
// HasActionRunRecoverEvent returns if hook enabled action recover event.
|
||||
func (w *Webhook) HasActionRunRecoverEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.ActionRunRecover)
|
||||
}
|
||||
|
||||
// HasActionRunSuccessEvent returns if hook enabled action success event.
|
||||
func (w *Webhook) HasActionRunSuccessEvent() bool {
|
||||
return w.SendEverything ||
|
||||
(w.ChooseEvents && w.ActionRunSuccess)
|
||||
}
|
||||
|
||||
// HasPullRequestReviewRequestEvent returns true if hook enabled pull request review request event.
|
||||
func (w *Webhook) HasPullRequestReviewRequestEvent() bool {
|
||||
return w.SendEverything ||
|
||||
|
@ -337,6 +355,9 @@ func (w *Webhook) EventCheckers() []struct {
|
|||
{w.HasReleaseEvent, webhook_module.HookEventRelease},
|
||||
{w.HasPackageEvent, webhook_module.HookEventPackage},
|
||||
{w.HasPullRequestReviewRequestEvent, webhook_module.HookEventPullRequestReviewRequest},
|
||||
{w.HasActionRunFailureEvent, webhook_module.HookEventActionRunFailure},
|
||||
{w.HasActionRunRecoverEvent, webhook_module.HookEventActionRunRecover},
|
||||
{w.HasActionRunSuccessEvent, webhook_module.HookEventActionRunSuccess},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,8 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||
"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
|
||||
"package", "pull_request_review_request",
|
||||
"package", "pull_request_review_request", "action_run_failure",
|
||||
"action_run_recover", "action_run_success",
|
||||
},
|
||||
(&Webhook{
|
||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||
|
@ -89,15 +90,78 @@ func TestWebhook_EventsArray(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateWebhook(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
unittest.AssertExistsAndLoadBean(t, hook)
|
||||
t.Run("Some chosen events 1", func(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":true,"events":{"create":false,"push":true,"pull_request":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
hookFromDb := unittest.AssertExistsAndLoadBean(t, hook)
|
||||
assert.Equal(t, []string{
|
||||
string(webhook_module.HookEventPush),
|
||||
string(webhook_module.HookEventPullRequest),
|
||||
}, hookFromDb.EventsArray())
|
||||
})
|
||||
|
||||
t.Run("Some chosen events 2", func(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":true,"events":{"action_run_recover":false,"action_run_success":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
hookFromDb := unittest.AssertExistsAndLoadBean(t, hook)
|
||||
assert.Equal(t, []string{string(webhook_module.HookEventActionRunSuccess)}, hookFromDb.EventsArray())
|
||||
})
|
||||
|
||||
t.Run("All events", func(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":true,"events":{"create":true,"delete":true,"fork":true,"issues":true,"issue_assign":true,"issue_label":true,"issue_milestone":true,"issue_comment":true,"push":true,"pull_request":true,"pull_request_assign":true,"pull_request_label":true,"pull_request_milestone":true,"pull_request_comment":true,"pull_request_review":true,"pull_request_sync":true,"pull_request_review_request":true,"wiki":true,"repository":true,"release":true,"package":true,"action_run_failure":true,"action_run_recover":true,"action_run_success":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
require.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
hookFromDb := unittest.AssertExistsAndLoadBean(t, hook)
|
||||
assert.Equal(t, []string{
|
||||
string(webhook_module.HookEventCreate),
|
||||
string(webhook_module.HookEventDelete),
|
||||
string(webhook_module.HookEventFork),
|
||||
string(webhook_module.HookEventPush),
|
||||
string(webhook_module.HookEventIssues),
|
||||
string(webhook_module.HookEventIssueAssign),
|
||||
string(webhook_module.HookEventIssueLabel),
|
||||
string(webhook_module.HookEventIssueMilestone),
|
||||
string(webhook_module.HookEventIssueComment),
|
||||
string(webhook_module.HookEventPullRequest),
|
||||
string(webhook_module.HookEventPullRequestAssign),
|
||||
string(webhook_module.HookEventPullRequestLabel),
|
||||
string(webhook_module.HookEventPullRequestMilestone),
|
||||
string(webhook_module.HookEventPullRequestComment),
|
||||
string(webhook_module.HookEventPullRequestReviewApproved),
|
||||
string(webhook_module.HookEventPullRequestReviewRejected),
|
||||
string(webhook_module.HookEventPullRequestReviewComment),
|
||||
string(webhook_module.HookEventPullRequestSync),
|
||||
string(webhook_module.HookEventWiki),
|
||||
string(webhook_module.HookEventRepository),
|
||||
string(webhook_module.HookEventRelease),
|
||||
string(webhook_module.HookEventPackage),
|
||||
string(webhook_module.HookEventPullRequestReviewRequest),
|
||||
// these aren't webhook event types
|
||||
// string(webhook_module.HookEventSchedule),
|
||||
// string(webhook_module.HookEventWorkflowDispatch),
|
||||
string(webhook_module.HookEventActionRunFailure),
|
||||
string(webhook_module.HookEventActionRunRecover),
|
||||
string(webhook_module.HookEventActionRunSuccess),
|
||||
},
|
||||
hookFromDb.EventsArray())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetWebhookByRepoID(t *testing.T) {
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
package structs
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ActionRunJob represents a job of a run
|
||||
// swagger:model
|
||||
type ActionRunJob struct {
|
||||
|
@ -23,3 +27,54 @@ type ActionRunJob struct {
|
|||
// the action run job status
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ActionRun represents an action run
|
||||
// swagger:model
|
||||
type ActionRun struct {
|
||||
// the action run id
|
||||
ID int64 `json:"id"`
|
||||
// the action run's title
|
||||
Title string `json:"title"`
|
||||
// the repo this action is part of
|
||||
Repo *Repository `json:"repository"`
|
||||
// the name of workflow file
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
// a unique number for each run of a repository
|
||||
Index int64 `json:"index_in_repo"`
|
||||
// the user that triggered this action run
|
||||
TriggerUser *User `json:"trigger_user"`
|
||||
// the cron id for the schedule trigger
|
||||
ScheduleID int64
|
||||
// the commit/tag/… the action run ran on
|
||||
PrettyRef string `json:"prettyref"`
|
||||
// has the commit/tag/… the action run ran on been deleted
|
||||
IsRefDeleted bool `json:"is_ref_deleted"`
|
||||
// the commit sha the action run ran on
|
||||
CommitSHA string `json:"commit_sha"`
|
||||
// If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
|
||||
IsForkPullRequest bool `json:"is_fork_pull_request"`
|
||||
// may need approval if it's a fork pull request
|
||||
NeedApproval bool `json:"need_approval"`
|
||||
// who approved this action run
|
||||
ApprovedBy int64 `json:"approved_by"`
|
||||
// the webhook event that causes the workflow to run
|
||||
Event string `json:"event"`
|
||||
// the payload of the webhook event that causes the workflow to run
|
||||
EventPayload string `json:"event_payload"`
|
||||
// the trigger event defined in the `on` configuration of the triggered workflow
|
||||
TriggerEvent string `json:"trigger_event"`
|
||||
// the current status of this run
|
||||
Status string `json:"status"`
|
||||
// when the action run was started
|
||||
Started time.Time `json:"started,omitempty"`
|
||||
// when the action run was stopped
|
||||
Stopped time.Time `json:"stopped,omitempty"`
|
||||
// when the action run was created
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
// when the action run was last updated
|
||||
Updated time.Time `json:"updated,omitempty"`
|
||||
// how long the action run ran for
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
// the url of this action run
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ var (
|
|||
_ Payloader = &RepositoryPayload{}
|
||||
_ Payloader = &ReleasePayload{}
|
||||
_ Payloader = &PackagePayload{}
|
||||
_ Payloader = &ActionPayload{}
|
||||
)
|
||||
|
||||
// _________ __
|
||||
|
@ -484,3 +485,36 @@ type PackagePayload struct {
|
|||
func (p *PackagePayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
// _ _ _
|
||||
// / \ ___| |_(_) ___ _ __
|
||||
// / _ \ / __| __| |/ _ \| '_ \
|
||||
// / ___ \ (__| |_| | (_) | | | |
|
||||
// /_/ \_\___|\__|_|\___/|_| |_|
|
||||
|
||||
// this name is ridiculous, yes
|
||||
// it's the sub-type of hook that has something to do with Forgejo Actions
|
||||
type HookActionAction string
|
||||
|
||||
const (
|
||||
HookActionFailure HookActionAction = "failure"
|
||||
HookActionRecover HookActionAction = "recover"
|
||||
HookActionSuccess HookActionAction = "success"
|
||||
)
|
||||
|
||||
// ActionPayload payload for action webhooks
|
||||
type ActionPayload struct {
|
||||
Action HookActionAction `json:"action"`
|
||||
Run *ActionRun `json:"run"`
|
||||
// the status of this run before it completed
|
||||
// this must be a not done status
|
||||
PriorStatus string `json:"prior_status"`
|
||||
// the last run for the same workflow
|
||||
// could be nil when Run is the first for it's workflow
|
||||
LastRun *ActionRun `json:"last_run,omitempty"`
|
||||
}
|
||||
|
||||
// JSONPayload return payload information
|
||||
func (p *ActionPayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package webhook
|
||||
|
||||
// HookEvents is a set of web hook events
|
||||
// update TestCreateWebhook in models/webhook/webhook_test.go when adding or changing values here
|
||||
type HookEvents struct {
|
||||
Create bool `json:"create"`
|
||||
Delete bool `json:"delete"`
|
||||
|
@ -26,9 +27,12 @@ type HookEvents struct {
|
|||
Repository bool `json:"repository"`
|
||||
Release bool `json:"release"`
|
||||
Package bool `json:"package"`
|
||||
ActionRunFailure bool `json:"action_run_failure"`
|
||||
ActionRunRecover bool `json:"action_run_recover"`
|
||||
ActionRunSuccess bool `json:"action_run_success"`
|
||||
}
|
||||
|
||||
// HookEvent represents events that will delivery hook.
|
||||
// HookEvent represents events that will deliver a hook.
|
||||
type HookEvent struct {
|
||||
PushOnly bool `json:"push_only"`
|
||||
SendEverything bool `json:"send_everything"`
|
||||
|
|
|
@ -7,6 +7,7 @@ package webhook
|
|||
type HookEventType string
|
||||
|
||||
// Types of hook events
|
||||
// update TestCreateWebhook in models/webhook/webhook_test.go when adding or changing values here
|
||||
const (
|
||||
HookEventCreate HookEventType = "create"
|
||||
HookEventDelete HookEventType = "delete"
|
||||
|
@ -33,6 +34,9 @@ const (
|
|||
HookEventPackage HookEventType = "package"
|
||||
HookEventSchedule HookEventType = "schedule"
|
||||
HookEventWorkflowDispatch HookEventType = "workflow_dispatch"
|
||||
HookEventActionRunFailure HookEventType = "action_run_failure"
|
||||
HookEventActionRunRecover HookEventType = "action_run_recover"
|
||||
HookEventActionRunSuccess HookEventType = "action_run_success"
|
||||
)
|
||||
|
||||
// Event returns the HookEventType as an event string
|
||||
|
@ -65,6 +69,12 @@ func (h HookEventType) Event() string {
|
|||
return "repository"
|
||||
case HookEventRelease:
|
||||
return "release"
|
||||
case HookEventActionRunFailure:
|
||||
return "action_run_failure"
|
||||
case HookEventActionRunRecover:
|
||||
return "action_run_recover"
|
||||
case HookEventActionRunSuccess:
|
||||
return "action_run_success"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -2486,6 +2486,13 @@ settings.event_pull_request_review_request_desc = Pull request review requested
|
|||
settings.event_pull_request_approvals = Pull request approvals
|
||||
settings.event_pull_request_merge = Pull request merge
|
||||
settings.event_pull_request_enforcement = Enforcement
|
||||
settings.event_header_action = Action Run events
|
||||
settings.event_action_failure = Failure
|
||||
settings.event_action_failure_desc = Action Run ended as failure.
|
||||
settings.event_action_recover = Recover
|
||||
settings.event_action_recover_desc = Action Run succeeded after last Action Run in the same workflow failed.
|
||||
settings.event_action_success = Success
|
||||
settings.event_action_success_desc = Action Run succeeded.
|
||||
settings.event_package = Package
|
||||
settings.event_package_desc = Package created or deleted in a repository.
|
||||
settings.branch_filter = Branch filter
|
||||
|
|
|
@ -175,6 +175,9 @@ func ParseHookEvent(form forms.WebhookCoreForm) *webhook_module.HookEvent {
|
|||
Wiki: form.Wiki,
|
||||
Repository: form.Repository,
|
||||
Package: form.Package,
|
||||
ActionRunFailure: form.ActionFailure,
|
||||
ActionRunRecover: form.ActionRecover,
|
||||
ActionRunSuccess: form.ActionSuccess,
|
||||
},
|
||||
BranchFilter: form.BranchFilter,
|
||||
}
|
||||
|
|
54
services/convert/action.go
Normal file
54
services/convert/action.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
actions_model "forgejo.org/models/actions"
|
||||
access_model "forgejo.org/models/perm/access"
|
||||
api "forgejo.org/modules/structs"
|
||||
)
|
||||
|
||||
// ToActionRun convert actions_model.User to api.ActionRun
|
||||
// the run needs all attributes loaded
|
||||
func ToActionRun(ctx context.Context, run *actions_model.ActionRun) *api.ActionRun {
|
||||
if run == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The doer is the one whose perspective is used to view this ActionRun.
|
||||
// In the best case we use the user that created the webhook.
|
||||
// Unfortunately we don't know who that was.
|
||||
// So instead we use the repo owner, who is able to create webhooks and allow others to do so by making them repo admins.
|
||||
// This is pretty close to perfect.
|
||||
doer := run.Repo.Owner
|
||||
permissionInRepo, _ := access_model.GetUserRepoPermission(ctx, run.Repo, doer)
|
||||
|
||||
return &api.ActionRun{
|
||||
ID: run.ID,
|
||||
Title: run.Title,
|
||||
Repo: ToRepo(ctx, run.Repo, permissionInRepo),
|
||||
WorkflowID: run.WorkflowID,
|
||||
Index: run.Index,
|
||||
TriggerUser: ToUser(ctx, run.TriggerUser, doer),
|
||||
ScheduleID: run.ScheduleID,
|
||||
PrettyRef: run.PrettyRef(),
|
||||
IsRefDeleted: run.IsRefDeleted,
|
||||
CommitSHA: run.CommitSHA,
|
||||
IsForkPullRequest: run.IsForkPullRequest,
|
||||
NeedApproval: run.NeedApproval,
|
||||
ApprovedBy: run.ApprovedBy,
|
||||
Event: run.Event.Event(),
|
||||
EventPayload: run.EventPayload,
|
||||
TriggerEvent: run.TriggerEvent,
|
||||
Status: run.Status.String(),
|
||||
Started: run.Started.AsTime(),
|
||||
Stopped: run.Stopped.AsTime(),
|
||||
Created: run.Created.AsTime(),
|
||||
Updated: run.Updated.AsTime(),
|
||||
Duration: run.Duration(),
|
||||
HTMLURL: run.HTMLURL(),
|
||||
}
|
||||
}
|
|
@ -277,6 +277,9 @@ type WebhookCoreForm struct {
|
|||
Wiki bool
|
||||
Repository bool
|
||||
Package bool
|
||||
ActionFailure bool
|
||||
ActionRecover bool
|
||||
ActionSuccess bool
|
||||
Active bool
|
||||
BranchFilter string `binding:"GlobPattern"`
|
||||
AuthorizationHeader string
|
||||
|
|
|
@ -207,6 +207,12 @@ func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, err
|
|||
return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil
|
||||
}
|
||||
|
||||
func (dc dingtalkConvertor) Action(p *api.ActionPayload) (DingtalkPayload, error) {
|
||||
text, _ := getActionPayloadInfo(p, noneLinkFormatter)
|
||||
|
||||
return createDingtalkPayload(text, text, "view action", p.Run.HTMLURL), nil
|
||||
}
|
||||
|
||||
func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
|
||||
return DingtalkPayload{
|
||||
MsgType: "actionCard",
|
||||
|
|
|
@ -325,6 +325,12 @@ func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error)
|
|||
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
|
||||
}
|
||||
|
||||
func (d discordConvertor) Action(p *api.ActionPayload) (DiscordPayload, error) {
|
||||
text, color := getActionPayloadInfo(p, noneLinkFormatter)
|
||||
|
||||
return d.createPayload(p.Run.TriggerUser, text, "", p.Run.HTMLURL, color), nil
|
||||
}
|
||||
|
||||
var _ shared.PayloadConvertor[DiscordPayload] = discordConvertor{}
|
||||
|
||||
func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
|
|
|
@ -191,6 +191,12 @@ func (fc feishuConvertor) Package(p *api.PackagePayload) (FeishuPayload, error)
|
|||
return newFeishuTextPayload(text), nil
|
||||
}
|
||||
|
||||
func (fc feishuConvertor) Action(p *api.ActionPayload) (FeishuPayload, error) {
|
||||
text, _ := getActionPayloadInfo(p, noneLinkFormatter)
|
||||
|
||||
return newFeishuTextPayload(text), nil
|
||||
}
|
||||
|
||||
type feishuConvertor struct{}
|
||||
|
||||
var _ shared.PayloadConvertor[FeishuPayload] = feishuConvertor{}
|
||||
|
|
|
@ -304,6 +304,25 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w
|
|||
return text, color
|
||||
}
|
||||
|
||||
func getActionPayloadInfo(p *api.ActionPayload, linkFormatter linkFormatter) (text string, color int) {
|
||||
runLink := linkFormatter(p.Run.HTMLURL, p.Run.Title)
|
||||
repoLink := linkFormatter(p.Run.Repo.HTMLURL, p.Run.Repo.FullName)
|
||||
|
||||
switch p.Action {
|
||||
case api.HookActionFailure:
|
||||
text = fmt.Sprintf("%s Action Failed in %s %s", runLink, repoLink, p.Run.PrettyRef)
|
||||
color = redColor
|
||||
case api.HookActionRecover:
|
||||
text = fmt.Sprintf("%s Action Recovered in %s %s", runLink, repoLink, p.Run.PrettyRef)
|
||||
color = greenColor
|
||||
case api.HookActionSuccess:
|
||||
text = fmt.Sprintf("%s Action Succeeded in %s %s", runLink, repoLink, p.Run.PrettyRef)
|
||||
color = greenColor
|
||||
}
|
||||
|
||||
return text, color
|
||||
}
|
||||
|
||||
// ToHook convert models.Webhook to api.Hook
|
||||
// This function is not part of the convert package to prevent an import cycle
|
||||
func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
|
||||
|
|
|
@ -270,6 +270,22 @@ func pullReleaseTestPayload() *api.ReleasePayload {
|
|||
}
|
||||
}
|
||||
|
||||
func ActionTestPayload() *api.ActionPayload {
|
||||
// this is not a complete action payload but enough for testing purposes
|
||||
return &api.ActionPayload{
|
||||
Run: &api.ActionRun{
|
||||
Repo: &api.Repository{
|
||||
HTMLURL: "http://localhost:3000/test/repo",
|
||||
Name: "repo",
|
||||
FullName: "test/repo",
|
||||
},
|
||||
PrettyRef: "main",
|
||||
HTMLURL: "http://localhost:3000/test/repo/actions/runs/69",
|
||||
Title: "Build release",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func pullRequestTestPayload() *api.PullRequestPayload {
|
||||
return &api.PullRequestPayload{
|
||||
Action: api.HookIssueOpened,
|
||||
|
@ -675,3 +691,36 @@ func TestGetIssueCommentPayloadInfo(t *testing.T) {
|
|||
assert.Equal(t, c.color, color, "case %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActionPayloadInfo(t *testing.T) {
|
||||
p := ActionTestPayload()
|
||||
|
||||
cases := []struct {
|
||||
action api.HookActionAction
|
||||
text string
|
||||
color int
|
||||
}{
|
||||
{
|
||||
api.HookActionFailure,
|
||||
"Build release Action Failed in test/repo main",
|
||||
redColor,
|
||||
},
|
||||
{
|
||||
api.HookActionSuccess,
|
||||
"Build release Action Succeeded in test/repo main",
|
||||
greenColor,
|
||||
},
|
||||
{
|
||||
api.HookActionRecover,
|
||||
"Build release Action Recovered in test/repo main",
|
||||
greenColor,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
p.Action = c.action
|
||||
text, color := getActionPayloadInfo(p, noneLinkFormatter)
|
||||
assert.Equal(t, c.text, text, "case %d", i)
|
||||
assert.Equal(t, c.color, color, "case %d", i)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -273,6 +273,12 @@ func (m matrixConvertor) Package(p *api.PackagePayload) (MatrixPayload, error) {
|
|||
return m.newPayload(text)
|
||||
}
|
||||
|
||||
func (m matrixConvertor) Action(p *api.ActionPayload) (MatrixPayload, error) {
|
||||
text, _ := getActionPayloadInfo(p, htmlLinkFormatter)
|
||||
|
||||
return m.newPayload(text)
|
||||
}
|
||||
|
||||
var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`)
|
||||
|
||||
func getMessageBody(htmlText string) string {
|
||||
|
|
|
@ -326,6 +326,23 @@ func (m msteamsConvertor) Package(p *api.PackagePayload) (MSTeamsPayload, error)
|
|||
), nil
|
||||
}
|
||||
|
||||
func (m msteamsConvertor) Action(p *api.ActionPayload) (MSTeamsPayload, error) {
|
||||
title, color := getActionPayloadInfo(p, noneLinkFormatter)
|
||||
|
||||
// TODO: is TriggerUser correct here?
|
||||
// if you'd like to test these proprietary services, see the discussion on: https://codeberg.org/forgejo/forgejo/pulls/7508
|
||||
return createMSTeamsPayload(
|
||||
p.Run.Repo,
|
||||
p.Run.TriggerUser,
|
||||
title,
|
||||
"",
|
||||
p.Run.HTMLURL,
|
||||
color,
|
||||
// TODO: does this make any sense?
|
||||
&MSTeamsFact{"Action:", p.Run.Title},
|
||||
), nil
|
||||
}
|
||||
|
||||
func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
|
||||
facts := make([]MSTeamsFact, 0, 2)
|
||||
if r != nil {
|
||||
|
|
|
@ -6,6 +6,7 @@ package webhook
|
|||
import (
|
||||
"context"
|
||||
|
||||
actions_model "forgejo.org/models/actions"
|
||||
issues_model "forgejo.org/models/issues"
|
||||
packages_model "forgejo.org/models/packages"
|
||||
"forgejo.org/models/perm"
|
||||
|
@ -887,6 +888,38 @@ func (m *webhookNotifier) PackageDelete(ctx context.Context, doer *user_model.Us
|
|||
notifyPackage(ctx, doer, pd, api.HookPackageDeleted)
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) ActionRunNowDone(ctx context.Context, run *actions_model.ActionRun, priorStatus actions_model.Status, lastRun *actions_model.ActionRun) {
|
||||
source := EventSource{
|
||||
Repository: run.Repo,
|
||||
Owner: run.TriggerUser,
|
||||
}
|
||||
|
||||
payload := &api.ActionPayload{
|
||||
Run: convert.ToActionRun(ctx, run),
|
||||
LastRun: convert.ToActionRun(ctx, lastRun),
|
||||
PriorStatus: priorStatus.String(),
|
||||
}
|
||||
|
||||
if run.Status.IsSuccess() {
|
||||
payload.Action = api.HookActionSuccess
|
||||
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventActionRunSuccess, payload); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
// send another event when this is a recover
|
||||
if lastRun != nil && !lastRun.Status.IsSuccess() {
|
||||
payload.Action = api.HookActionRecover
|
||||
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventActionRunRecover, payload); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
payload.Action = api.HookActionFailure
|
||||
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventActionRunFailure, payload); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_model.PackageDescriptor, action api.HookPackageAction) {
|
||||
source := EventSource{
|
||||
Repository: pd.Repository,
|
||||
|
|
|
@ -6,6 +6,7 @@ package webhook
|
|||
import (
|
||||
"testing"
|
||||
|
||||
actions_model "forgejo.org/models/actions"
|
||||
"forgejo.org/models/db"
|
||||
repo_model "forgejo.org/models/repo"
|
||||
"forgejo.org/models/unittest"
|
||||
|
@ -13,10 +14,12 @@ import (
|
|||
webhook_model "forgejo.org/models/webhook"
|
||||
"forgejo.org/modules/git"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/repository"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/structs"
|
||||
"forgejo.org/modules/test"
|
||||
webhook_module "forgejo.org/modules/webhook"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -119,3 +122,190 @@ func TestPushCommits(t *testing.T) {
|
|||
assert.Equal(t, "2c54faec6c45d31c1abfaecdab471eac6633738a", payloadContent.Commits[0].ID)
|
||||
})
|
||||
}
|
||||
|
||||
func assertActionEqual(t *testing.T, expectedRun *actions_model.ActionRun, actualRun *structs.ActionRun) {
|
||||
assert.NotNil(t, expectedRun)
|
||||
assert.NotNil(t, actualRun)
|
||||
// only test a few things
|
||||
assert.Equal(t, expectedRun.ID, actualRun.ID)
|
||||
assert.Equal(t, expectedRun.Status.String(), actualRun.Status)
|
||||
assert.Equal(t, expectedRun.Index, actualRun.Index)
|
||||
assert.Equal(t, expectedRun.RepoID, actualRun.Repo.ID)
|
||||
// convert to unix because of time zones
|
||||
assert.Equal(t, expectedRun.Stopped.AsTime().Unix(), actualRun.Stopped.Unix())
|
||||
assert.Equal(t, expectedRun.Title, actualRun.Title)
|
||||
assert.Equal(t, expectedRun.WorkflowID, actualRun.WorkflowID)
|
||||
}
|
||||
|
||||
func TestAction(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("services/webhook/TestPushCommits")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
triggerUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerID: triggerUser.ID})
|
||||
|
||||
oldSuccessRun := &actions_model.ActionRun{
|
||||
ID: 1,
|
||||
Status: actions_model.StatusSuccess,
|
||||
Index: 1,
|
||||
RepoID: repo.ID,
|
||||
Stopped: 1693648027,
|
||||
WorkflowID: "some_workflow",
|
||||
Title: "oldSuccessRun",
|
||||
TriggerUser: triggerUser,
|
||||
TriggerUserID: triggerUser.ID,
|
||||
TriggerEvent: "push",
|
||||
}
|
||||
oldSuccessRun.LoadAttributes(db.DefaultContext)
|
||||
oldFailureRun := &actions_model.ActionRun{
|
||||
ID: 1,
|
||||
Status: actions_model.StatusFailure,
|
||||
Index: 1,
|
||||
RepoID: repo.ID,
|
||||
Stopped: 1693648027,
|
||||
WorkflowID: "some_workflow",
|
||||
Title: "oldFailureRun",
|
||||
TriggerUser: triggerUser,
|
||||
TriggerUserID: triggerUser.ID,
|
||||
TriggerEvent: "push",
|
||||
}
|
||||
oldFailureRun.LoadAttributes(db.DefaultContext)
|
||||
newSuccessRun := &actions_model.ActionRun{
|
||||
ID: 1,
|
||||
Status: actions_model.StatusSuccess,
|
||||
Index: 1,
|
||||
RepoID: repo.ID,
|
||||
Stopped: 1693648327,
|
||||
WorkflowID: "some_workflow",
|
||||
Title: "newSuccessRun",
|
||||
TriggerUser: triggerUser,
|
||||
TriggerUserID: triggerUser.ID,
|
||||
TriggerEvent: "push",
|
||||
}
|
||||
newSuccessRun.LoadAttributes(db.DefaultContext)
|
||||
newFailureRun := &actions_model.ActionRun{
|
||||
ID: 1,
|
||||
Status: actions_model.StatusFailure,
|
||||
Index: 1,
|
||||
RepoID: repo.ID,
|
||||
Stopped: 1693648327,
|
||||
WorkflowID: "some_workflow",
|
||||
Title: "newFailureRun",
|
||||
TriggerUser: triggerUser,
|
||||
TriggerUserID: triggerUser.ID,
|
||||
TriggerEvent: "push",
|
||||
}
|
||||
newFailureRun.LoadAttributes(db.DefaultContext)
|
||||
|
||||
t.Run("Successful Run after Nothing", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Webhook.PayloadCommitLimit, 10)()
|
||||
|
||||
NewNotifier().ActionRunNowDone(db.DefaultContext, newSuccessRun, actions_model.StatusWaiting, nil)
|
||||
|
||||
// there's only one of these at the time
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_success' AND payload_content LIKE '%success%newSuccessRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunSuccess, hookTask.EventType)
|
||||
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionSuccess, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newSuccessRun, payloadContent.Run)
|
||||
assert.Nil(t, payloadContent.LastRun)
|
||||
})
|
||||
|
||||
t.Run("Successful Run after Failure", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Webhook.PayloadCommitLimit, 10)()
|
||||
|
||||
NewNotifier().ActionRunNowDone(db.DefaultContext, newSuccessRun, actions_model.StatusWaiting, oldFailureRun)
|
||||
|
||||
{
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_success' AND payload_content LIKE '%success%newSuccessRun%oldFailureRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunSuccess, hookTask.EventType)
|
||||
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionSuccess, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newSuccessRun, payloadContent.Run)
|
||||
assertActionEqual(t, oldFailureRun, payloadContent.LastRun)
|
||||
}
|
||||
{
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_recover' AND payload_content LIKE '%recover%newSuccessRun%oldFailureRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunRecover, hookTask.EventType)
|
||||
|
||||
log.Error("something: %s", hookTask.PayloadContent)
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionRecover, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newSuccessRun, payloadContent.Run)
|
||||
assertActionEqual(t, oldFailureRun, payloadContent.LastRun)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Successful Run after Success", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Webhook.PayloadCommitLimit, 10)()
|
||||
|
||||
NewNotifier().ActionRunNowDone(db.DefaultContext, newSuccessRun, actions_model.StatusWaiting, oldSuccessRun)
|
||||
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_success' AND payload_content LIKE '%success%newSuccessRun%oldSuccessRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunSuccess, hookTask.EventType)
|
||||
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionSuccess, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newSuccessRun, payloadContent.Run)
|
||||
assertActionEqual(t, oldSuccessRun, payloadContent.LastRun)
|
||||
})
|
||||
|
||||
t.Run("Failed Run after Nothing", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Webhook.PayloadCommitLimit, 10)()
|
||||
|
||||
NewNotifier().ActionRunNowDone(db.DefaultContext, newFailureRun, actions_model.StatusWaiting, nil)
|
||||
|
||||
// there should only be this one at the time
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_failure' AND payload_content LIKE '%failure%newFailureRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunFailure, hookTask.EventType)
|
||||
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionFailure, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newFailureRun, payloadContent.Run)
|
||||
assert.Nil(t, payloadContent.LastRun)
|
||||
})
|
||||
|
||||
t.Run("Failed Run after Failure", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Webhook.PayloadCommitLimit, 10)()
|
||||
|
||||
NewNotifier().ActionRunNowDone(db.DefaultContext, newFailureRun, actions_model.StatusWaiting, oldFailureRun)
|
||||
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_failure' AND payload_content LIKE '%failure%newFailureRun%oldFailureRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunFailure, hookTask.EventType)
|
||||
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionFailure, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newFailureRun, payloadContent.Run)
|
||||
assertActionEqual(t, oldFailureRun, payloadContent.LastRun)
|
||||
})
|
||||
|
||||
t.Run("Failed Run after Success", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Webhook.PayloadCommitLimit, 10)()
|
||||
|
||||
NewNotifier().ActionRunNowDone(db.DefaultContext, newFailureRun, actions_model.StatusWaiting, oldSuccessRun)
|
||||
|
||||
hookTask := unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{}, unittest.Cond("event_type == 'action_run_failure' AND payload_content LIKE '%failure%newFailureRun%oldSuccessRun%'"))
|
||||
assert.Equal(t, webhook_module.HookEventActionRunFailure, hookTask.EventType)
|
||||
|
||||
var payloadContent structs.ActionPayload
|
||||
require.NoError(t, json.Unmarshal([]byte(hookTask.PayloadContent), &payloadContent))
|
||||
assert.Equal(t, structs.HookActionFailure, payloadContent.Action)
|
||||
assert.Equal(t, actions_model.StatusWaiting.String(), payloadContent.PriorStatus)
|
||||
assertActionEqual(t, newFailureRun, payloadContent.Run)
|
||||
assertActionEqual(t, oldSuccessRun, payloadContent.LastRun)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ type PayloadConvertor[T any] interface {
|
|||
Release(*api.ReleasePayload) (T, error)
|
||||
Wiki(*api.WikiPayload) (T, error)
|
||||
Package(*api.PackagePayload) (T, error)
|
||||
Action(*api.ActionPayload) (T, error)
|
||||
}
|
||||
|
||||
func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (T, error) {
|
||||
|
@ -86,6 +87,8 @@ func NewPayload[T any](rc PayloadConvertor[T], data []byte, event webhook_module
|
|||
return convertUnmarshalledJSON(rc.Wiki, data)
|
||||
case webhook_module.HookEventPackage:
|
||||
return convertUnmarshalledJSON(rc.Package, data)
|
||||
case webhook_module.HookEventActionRunFailure, webhook_module.HookEventActionRunRecover, webhook_module.HookEventActionRunSuccess:
|
||||
return convertUnmarshalledJSON(rc.Action, data)
|
||||
}
|
||||
var t T
|
||||
return t, fmt.Errorf("newPayload unsupported event: %s", event)
|
||||
|
|
|
@ -142,6 +142,7 @@ func SlackLinkToRef(repoURL, ref string) string {
|
|||
return SlackLinkFormatter(url, refName)
|
||||
}
|
||||
|
||||
// TODO: fix spelling to Converter
|
||||
// Create implements payloadConvertor Create method
|
||||
func (s slackConvertor) Create(p *api.CreatePayload) (SlackPayload, error) {
|
||||
refLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
|
||||
|
@ -311,6 +312,12 @@ func (s slackConvertor) Repository(p *api.RepositoryPayload) (SlackPayload, erro
|
|||
return s.createPayload(text, nil), nil
|
||||
}
|
||||
|
||||
func (s slackConvertor) Action(p *api.ActionPayload) (SlackPayload, error) {
|
||||
text, _ := getActionPayloadInfo(p, SlackLinkFormatter)
|
||||
|
||||
return s.createPayload(text, nil), nil
|
||||
}
|
||||
|
||||
func (s slackConvertor) createPayload(text string, attachments []SlackAttachment) SlackPayload {
|
||||
return SlackPayload{
|
||||
Channel: s.Channel,
|
||||
|
|
|
@ -190,6 +190,10 @@ func (pc sourcehutConvertor) Package(_ *api.PackagePayload) (graphqlPayload[buil
|
|||
return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
|
||||
}
|
||||
|
||||
func (pc sourcehutConvertor) Action(_ *api.ActionPayload) (graphqlPayload[buildsVariables], error) {
|
||||
return graphqlPayload[buildsVariables]{}, shared.ErrPayloadTypeNotSupported
|
||||
}
|
||||
|
||||
// newPayload opens and adjusts the manifest to submit to the builds service
|
||||
//
|
||||
// in case of an error the Error field will be set, to be visible by the end-user under recent deliveries
|
||||
|
|
|
@ -205,6 +205,12 @@ func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, erro
|
|||
return createTelegramPayload(text), nil
|
||||
}
|
||||
|
||||
func (telegramConvertor) Action(p *api.ActionPayload) (TelegramPayload, error) {
|
||||
text, _ := getActionPayloadInfo(p, htmlLinkFormatter)
|
||||
|
||||
return createTelegramPayload(text), nil
|
||||
}
|
||||
|
||||
func createTelegramPayload(message string) TelegramPayload {
|
||||
return TelegramPayload{
|
||||
Message: markup.Sanitize(strings.TrimSpace(message)),
|
||||
|
|
|
@ -103,7 +103,7 @@ type EventSource struct {
|
|||
Owner *user_model.User
|
||||
}
|
||||
|
||||
// handle delivers hook tasks
|
||||
// handler delivers hook tasks
|
||||
func handler(items ...int64) []int64 {
|
||||
ctx := graceful.GetManager().HammerContext()
|
||||
|
||||
|
|
|
@ -201,6 +201,12 @@ func (wc wechatworkConvertor) Package(p *api.PackagePayload) (WechatworkPayload,
|
|||
return newWechatworkMarkdownPayload(text), nil
|
||||
}
|
||||
|
||||
func (wc wechatworkConvertor) Action(p *api.ActionPayload) (WechatworkPayload, error) {
|
||||
text, _ := getActionPayloadInfo(p, noneLinkFormatter)
|
||||
|
||||
return newWechatworkMarkdownPayload(text), nil
|
||||
}
|
||||
|
||||
type wechatworkConvertor struct{}
|
||||
|
||||
var _ shared.PayloadConvertor[WechatworkPayload] = wechatworkConvertor{}
|
||||
|
|
|
@ -153,6 +153,28 @@
|
|||
<span class="help">{{ctx.Locale.Tr "repo.settings.event_pull_request_review_request_desc"}}</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!-- Action Run Events -->
|
||||
<fieldset class="simple-grid grid-2">
|
||||
<legend>{{ctx.Locale.Tr "repo.settings.event_header_action"}}</legend>
|
||||
<!-- Action Run Failure -->
|
||||
<label>
|
||||
<input name="action_failure" type="checkbox" {{if .Webhook.ActionRunFailure}}checked{{end}}>
|
||||
{{ctx.Locale.Tr "repo.settings.event_action_failure"}}
|
||||
<span class="help">{{ctx.Locale.Tr "repo.settings.event_action_failure_desc"}}</span>
|
||||
</label>
|
||||
<!-- Action Run Recover -->
|
||||
<label>
|
||||
<input name="action_recover" type="checkbox" {{if .Webhook.ActionRunRecover}}checked{{end}}>
|
||||
{{ctx.Locale.Tr "repo.settings.event_action_recover"}}
|
||||
<span class="help">{{ctx.Locale.Tr "repo.settings.event_action_recover_desc"}}</span>
|
||||
</label>
|
||||
<!-- Action Run Success -->
|
||||
<label>
|
||||
<input name="action_success" type="checkbox" {{if .Webhook.ActionRunSuccess}}checked{{end}}>
|
||||
{{ctx.Locale.Tr "repo.settings.event_action_success"}}
|
||||
<span class="help">{{ctx.Locale.Tr "repo.settings.event_action_success_desc"}}</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
@ -337,6 +337,8 @@ func testWebhookFormsShared(t *testing.T, endpoint, name string, session *TestSe
|
|||
resp := session.MakeRequest(t, NewRequest(t, "GET", endpoint+"/"+name+"/new"), http.StatusOK)
|
||||
htmlForm := NewHTMLParser(t, resp.Body).Find(`form[action^="` + endpoint + `/"]`)
|
||||
|
||||
testWebhookFormsSharedChooseEvents(t, htmlForm)
|
||||
|
||||
// fill the form
|
||||
payload := map[string]string{
|
||||
"_csrf": htmlForm.Find(`input[name="_csrf"]`).AttrOr("value", ""),
|
||||
|
@ -472,3 +474,37 @@ func assertHasFlashMessages(t *testing.T, resp *httptest.ResponseRecorder, expec
|
|||
t.Errorf("unexpected flash message %q: %q", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func testWebhookFormsSharedChooseEvents(t *testing.T, htmlForm *goquery.Selection) {
|
||||
webhookTypes := []string{
|
||||
"create",
|
||||
"delete",
|
||||
"fork",
|
||||
"push",
|
||||
"repository",
|
||||
"release",
|
||||
"package",
|
||||
"wiki",
|
||||
"issues",
|
||||
"issue_assign",
|
||||
"issue_label",
|
||||
"issue_milestone",
|
||||
"issue_comment",
|
||||
"pull_request",
|
||||
"pull_request_assign",
|
||||
"pull_request_label",
|
||||
"pull_request_milestone",
|
||||
"pull_request_comment",
|
||||
"pull_request_review",
|
||||
"pull_request_sync",
|
||||
"pull_request_review_request",
|
||||
"action_failure",
|
||||
"action_recover",
|
||||
"action_success",
|
||||
}
|
||||
|
||||
// check all types of webhooks are present in the form
|
||||
for _, webhookType := range webhookTypes {
|
||||
assertInput(t, htmlForm, webhookType)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue