Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix commit status events #33320

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2329,6 +2329,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)
})
}