Skip to content

Commit

Permalink
Fix commit status events (#33320)
Browse files Browse the repository at this point in the history
Fix #32873 
Fix #33201 
~Fix #33244~
~Fix #33302~

depends on ~#33396~

A part of this PR should be backported to v1.23 manually.
  • Loading branch information
lunny authored Feb 4, 2025
1 parent 3c46cd6 commit a4676db
Show file tree
Hide file tree
Showing 16 changed files with 130 additions and 0 deletions.
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,8 @@ settings.event_fork = Fork
settings.event_fork_desc = Repository forked.
settings.event_wiki = Wiki
settings.event_wiki_desc = Wiki page created, renamed, edited or deleted.
settings.event_statuses = Statuses
settings.event_statuses_desc = Commit Status updated from the API.
settings.event_release = Release
settings.event_release_desc = Release published, updated or deleted in a repository.
settings.event_push = Push
Expand Down
1 change: 1 addition & 0 deletions routers/web/repo/setting/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
webhook_module.HookEventWiki: form.Wiki,
webhook_module.HookEventRepository: form.Repository,
webhook_module.HookEventPackage: form.Package,
webhook_module.HookEventStatus: form.Status,
},
BranchFilter: form.BranchFilter,
}
Expand Down
1 change: 1 addition & 0 deletions services/forms/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ type WebhookForm struct {
Repository bool
Release bool
Package bool
Status bool
Active bool
BranchFilter string `binding:"GlobPattern"`
AuthorizationHeader string
Expand Down
6 changes: 6 additions & 0 deletions services/webhook/dingtalk.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ func (dc dingtalkConvertor) Package(p *api.PackagePayload) (DingtalkPayload, err
return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil
}

func (dc dingtalkConvertor) Status(p *api.CommitStatusPayload) (DingtalkPayload, error) {
text, _ := getStatusPayloadInfo(p, noneLinkFormatter, true)

return createDingtalkPayload(text, text, "Status Changed", p.TargetURL), nil
}

func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkPayload {
return DingtalkPayload{
MsgType: "actionCard",
Expand Down
6 changes: 6 additions & 0 deletions services/webhook/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error)
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
}

func (d discordConvertor) Status(p *api.CommitStatusPayload) (DiscordPayload, error) {
text, color := getStatusPayloadInfo(p, noneLinkFormatter, false)

return d.createPayload(p.Sender, text, "", p.TargetURL, color), nil
}

func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
meta := &DiscordMeta{}
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions services/webhook/feishu.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ func (fc feishuConvertor) Package(p *api.PackagePayload) (FeishuPayload, error)
return newFeishuTextPayload(text), nil
}

func (fc feishuConvertor) Status(p *api.CommitStatusPayload) (FeishuPayload, error) {
text, _ := getStatusPayloadInfo(p, noneLinkFormatter, true)

return newFeishuTextPayload(text), nil
}

func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
return newJSONRequest(pc, w, t, true)
Expand Down
12 changes: 12 additions & 0 deletions services/webhook/general.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,18 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w
return text, color
}

func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
refLink := linkFormatter(p.TargetURL, p.Context+"["+p.SHA+"]:"+p.Description)

text = fmt.Sprintf("Commit Status changed: %s", refLink)
color = greenColor
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName))
}

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) {
Expand Down
7 changes: 7 additions & 0 deletions services/webhook/matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ func (m matrixConvertor) Package(p *api.PackagePayload) (MatrixPayload, error) {
return m.newPayload(text)
}

func (m matrixConvertor) Status(p *api.CommitStatusPayload) (MatrixPayload, error) {
refLink := htmlLinkFormatter(p.TargetURL, p.Context+"["+p.SHA+"]:"+p.Description)
text := fmt.Sprintf("Commit Status changed: %s", refLink)

return m.newPayload(text)
}

var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`)

func getMessageBody(htmlText string) string {
Expand Down
14 changes: 14 additions & 0 deletions services/webhook/msteams.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,20 @@ func (m msteamsConvertor) Package(p *api.PackagePayload) (MSTeamsPayload, error)
), nil
}

func (m msteamsConvertor) Status(p *api.CommitStatusPayload) (MSTeamsPayload, error) {
title, color := getStatusPayloadInfo(p, noneLinkFormatter, false)

return createMSTeamsPayload(
p.Repo,
p.Sender,
title,
"",
p.TargetURL,
color,
&MSTeamsFact{"CommitStatus:", p.Context},
), 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 {
Expand Down
4 changes: 4 additions & 0 deletions services/webhook/packagist.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ func (pc packagistConvertor) Package(_ *api.PackagePayload) (PackagistPayload, e
return PackagistPayload{}, nil
}

func (pc packagistConvertor) Status(_ *api.CommitStatusPayload) (PackagistPayload, error) {
return PackagistPayload{}, nil
}

func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
meta := &PackagistMeta{}
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions services/webhook/payloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type payloadConvertor[T any] interface {
Release(*api.ReleasePayload) (T, error)
Wiki(*api.WikiPayload) (T, error)
Package(*api.PackagePayload) (T, error)
Status(*api.CommitStatusPayload) (T, error)
}

func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (t T, err error) {
Expand Down Expand Up @@ -77,6 +78,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.HookEventStatus:
return convertUnmarshalledJSON(rc.Status, data)
}
return t, fmt.Errorf("newPayload unsupported event: %s", event)
}
Expand Down
6 changes: 6 additions & 0 deletions services/webhook/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ func (s slackConvertor) Package(p *api.PackagePayload) (SlackPayload, error) {
return s.createPayload(text, nil), nil
}

func (s slackConvertor) Status(p *api.CommitStatusPayload) (SlackPayload, error) {
text, _ := getStatusPayloadInfo(p, SlackLinkFormatter, true)

return s.createPayload(text, nil), nil
}

// Push implements payloadConvertor Push method
func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
// n new commits
Expand Down
6 changes: 6 additions & 0 deletions services/webhook/telegram.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, erro
return createTelegramPayloadHTML(text), nil
}

func (t telegramConvertor) Status(p *api.CommitStatusPayload) (TelegramPayload, error) {
text, _ := getStatusPayloadInfo(p, htmlLinkFormatter, true)

return createTelegramPayloadHTML(text), nil
}

func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
// https://core.telegram.org/bots/api#formatting-options
return TelegramPayload{
Expand Down
6 changes: 6 additions & 0 deletions services/webhook/wechatwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ func (wc wechatworkConvertor) Package(p *api.PackagePayload) (WechatworkPayload,
return newWechatworkMarkdownPayload(text), nil
}

func (wc wechatworkConvertor) Status(p *api.CommitStatusPayload) (WechatworkPayload, error) {
text, _ := getStatusPayloadInfo(p, noneLinkFormatter, true)

return newWechatworkMarkdownPayload(text), nil
}

func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
var pc payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
return newJSONRequest(pc, w, t, true)
Expand Down
11 changes: 11 additions & 0 deletions templates/repo/settings/webhook/settings.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@
</div>
</div>

<!-- Status -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input name="status" type="checkbox" {{if .Webhook.HookEvents.Get "status"}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.event_statuses"}}</label>
<span class="help">{{ctx.Locale.Tr "repo.settings.event_statuses_desc"}}</span>
</div>
</div>
</div>

<!-- Issue Events -->
<div class="fourteen wide column">
<label>{{ctx.Locale.Tr "repo.settings.event_header_issue"}}</label>
Expand Down
39 changes: 39 additions & 0 deletions tests/integration/repo_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -66,6 +67,19 @@ func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, r
MakeRequest(t, req, http.StatusCreated)
}

func testCreateWebhookForRepo(t *testing.T, session *TestSession, webhookType, userName, repoName, url, eventKind string) {
csrf := GetUserCSRFToken(t, session)
req := NewRequestWithValues(t, "POST", "/"+userName+"/"+repoName+"/settings/hooks/"+webhookType+"/new", map[string]string{
"_csrf": csrf,
"payload_url": url,
"events": eventKind,
"active": "true",
"content_type": fmt.Sprintf("%d", webhook.ContentTypeJSON),
"http_method": "POST",
})
session.MakeRequest(t, req, http.StatusSeeOther)
}

func testAPICreateWebhookForOrg(t *testing.T, session *TestSession, userName, url, event string) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/"+userName+"/hooks", api.CreateHookOption{
Expand Down Expand Up @@ -562,3 +576,28 @@ func Test_WebhookStatus(t *testing.T) {
assert.EqualValues(t, commitID, payloads[0].SHA)
})
}

func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
var trigger string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
trigger = "push"
}, http.StatusOK)
defer provider.Close()

onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")

// create a push_only webhook from web UI
testCreateWebhookForRepo(t, session, "gitea", "user2", "repo1", provider.URL(), "push_only")

// 2. trigger the webhook with a push action
testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")

// 3. validate the webhook is triggered with right event
assert.EqualValues(t, "push", trigger)
})
}

0 comments on commit a4676db

Please sign in to comment.