new create webhook event

This commit is contained in:
Unknwon 2015-08-28 23:36:13 +08:00
parent a541ca16b6
commit f509c59ac1
16 changed files with 449 additions and 306 deletions

View file

@ -531,6 +531,12 @@ settings.content_type = Content Type
settings.secret = Secret settings.secret = Secret
settings.event_desc = Upon which events should this webhook be triggered? settings.event_desc = Upon which events should this webhook be triggered?
settings.event_push_only = Just the <code>push</code> event. settings.event_push_only = Just the <code>push</code> event.
settings.event_send_everything = I need <strong>everything</strong>.
settings.event_choose = Let me choose what I need.
settings.event_create = Create
settings.event_create_desc = Branch, or tag created
settings.event_push = Push
settings.event_push_desc = Git push to a repository
settings.active = Active settings.active = Active
settings.active_helper = Details regarding the event which triggered the hook will be delivered as well. settings.active_helper = Details regarding the event which triggered the hook will be delivered as well.
settings.add_hook_success = New webhook has been added. settings.add_hook_success = New webhook has been added.

View file

@ -16,6 +16,8 @@ import (
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git" "github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
@ -290,20 +292,50 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
} }
// CommitRepoAction adds new action for committing repository. // CommitRepoAction adds new action for committing repository.
func CommitRepoAction(userID, repoUserID int64, userName, actEmail string, func CommitRepoAction(
repoID int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitID string, newCommitID string) error { userID, repoUserID int64,
userName, actEmail string,
repoID int64,
repoUserName, repoName string,
refFullName string,
commit *base.PushCommits,
oldCommitID string, newCommitID string) error {
u, err := GetUserByID(userID)
if err != nil {
return fmt.Errorf("GetUserByID: %v", err)
}
repo, err := GetRepositoryByName(repoUserID, repoName)
if err != nil {
return fmt.Errorf("GetRepositoryByName: %v", err)
} else if err = repo.GetOwner(); err != nil {
return fmt.Errorf("GetOwner: %v", err)
}
isNewBranch := false
opType := COMMIT_REPO opType := COMMIT_REPO
// Check it's tag push or branch. // Check it's tag push or branch.
if strings.HasPrefix(refFullName, "refs/tags/") { if strings.HasPrefix(refFullName, "refs/tags/") {
opType = PUSH_TAG opType = PUSH_TAG
commit = &base.PushCommits{} commit = &base.PushCommits{}
} } else {
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
// if not the first commit, set the compareUrl // if not the first commit, set the compareUrl
if !strings.HasPrefix(oldCommitID, "0000000") { if !strings.HasPrefix(oldCommitID, "0000000") {
commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID) commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
} else {
isNewBranch = true
}
// Change repository bare status and update last updated time.
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
return fmt.Errorf("UpdateRepository: %v", err)
}
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
log.Debug("updateIssuesCommit: %v", err)
}
} }
bs, err := json.Marshal(commit) bs, err := json.Marshal(commit)
@ -313,26 +345,6 @@ func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
refName := git.RefEndName(refFullName) refName := git.RefEndName(refFullName)
// Change repository bare status and update last updated time.
repo, err := GetRepositoryByName(repoUserID, repoName)
if err != nil {
return fmt.Errorf("GetRepositoryByName: %v", err)
}
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
return fmt.Errorf("UpdateRepository: %v", err)
}
u, err := GetUserByID(userID)
if err != nil {
return fmt.Errorf("GetUserByID: %v", err)
}
err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits)
if err != nil {
log.Debug("updateIssuesCommit: ", err)
}
if err = NotifyWatchers(&Action{ if err = NotifyWatchers(&Action{
ActUserID: u.Id, ActUserID: u.Id,
ActUserName: userName, ActUserName: userName,
@ -345,32 +357,24 @@ func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
RefName: refName, RefName: refName,
IsPrivate: repo.IsPrivate, IsPrivate: repo.IsPrivate,
}); err != nil { }); err != nil {
return errors.New("NotifyWatchers: " + err.Error()) return fmt.Errorf("NotifyWatchers: %v", err)
} }
// New push event hook. repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
if err := repo.GetOwner(); err != nil { payloadRepo := &api.PayloadRepo{
return errors.New("GetOwner: " + err.Error()) ID: repo.ID,
} Name: repo.LowerName,
URL: repoLink,
ws, err := GetActiveWebhooksByRepoId(repo.ID) Description: repo.Description,
if err != nil { Website: repo.Website,
return errors.New("GetActiveWebhooksByRepoId: " + err.Error()) Watchers: repo.NumWatches,
} Owner: &api.PayloadAuthor{
Name: repo.Owner.DisplayName(),
// check if repo belongs to org and append additional webhooks Email: repo.Owner.Email,
if repo.Owner.IsOrganization() { UserName: repo.Owner.Name,
// get hooks for org },
orgws, err := GetActiveWebhooksByOrgId(repo.OwnerID) Private: repo.IsPrivate,
if err != nil {
return errors.New("GetActiveWebhooksByOrgId: " + err.Error())
}
ws = append(ws, orgws...)
}
if len(ws) == 0 {
return nil
} }
pusher_email, pusher_name := "", "" pusher_email, pusher_name := "", ""
@ -379,83 +383,66 @@ func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
pusher_email = pusher.Email pusher_email = pusher.Email
pusher_name = pusher.DisplayName() pusher_name = pusher.DisplayName()
} }
payloadSender := &api.PayloadUser{
UserName: pusher.Name,
ID: pusher.Id,
AvatarUrl: setting.AppUrl + pusher.RelAvatarLink(),
}
commits := make([]*PayloadCommit, len(commit.Commits)) switch opType {
case COMMIT_REPO: // Push
commits := make([]*api.PayloadCommit, len(commit.Commits))
for i, cmt := range commit.Commits { for i, cmt := range commit.Commits {
author_username := "" author_username := ""
author, err := GetUserByEmail(cmt.AuthorEmail) author, err := GetUserByEmail(cmt.AuthorEmail)
if err == nil { if err == nil {
author_username = author.Name author_username = author.Name
} }
commits[i] = &PayloadCommit{ commits[i] = &api.PayloadCommit{
Id: cmt.Sha1, ID: cmt.Sha1,
Message: cmt.Message, Message: cmt.Message,
Url: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1), URL: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
Author: &PayloadAuthor{ Author: &api.PayloadAuthor{
Name: cmt.AuthorName, Name: cmt.AuthorName,
Email: cmt.AuthorEmail, Email: cmt.AuthorEmail,
UserName: author_username, UserName: author_username,
}, },
} }
} }
p := &Payload{ p := &api.PushPayload{
Ref: refFullName, Ref: refFullName,
Before: oldCommitID,
After: newCommitID,
CompareUrl: setting.AppUrl + commit.CompareUrl,
Commits: commits, Commits: commits,
Repo: &PayloadRepo{ Repo: payloadRepo,
Id: repo.ID, Pusher: &api.PayloadAuthor{
Name: repo.LowerName,
Url: repoLink,
Description: repo.Description,
Website: repo.Website,
Watchers: repo.NumWatches,
Owner: &PayloadAuthor{
Name: repo.Owner.DisplayName(),
Email: repo.Owner.Email,
UserName: repo.Owner.Name,
},
Private: repo.IsPrivate,
},
Pusher: &PayloadAuthor{
Name: pusher_name, Name: pusher_name,
Email: pusher_email, Email: pusher_email,
UserName: userName, UserName: userName,
}, },
Before: oldCommitID, Sender: payloadSender,
After: newCommitID, }
CompareUrl: setting.AppUrl + commit.CompareUrl, if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, p); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
} }
for _, w := range ws { if isNewBranch {
w.GetEvent() return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
if !w.HasPushEvent() { Ref: refName,
continue RefType: "branch",
Repo: payloadRepo,
Sender: payloadSender,
})
} }
var payload BasePayload case PUSH_TAG: // Create
switch w.HookTaskType { return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
case SLACK: Ref: refName,
s, err := GetSlackPayload(p, w.Meta) RefType: "tag",
if err != nil { Repo: payloadRepo,
return errors.New("action.GetSlackPayload: " + err.Error()) Sender: payloadSender,
} })
payload = s
default:
payload = p
p.Secret = w.Secret
}
if err = CreateHookTask(&HookTask{
RepoID: repo.ID,
HookID: w.ID,
Type: w.HookTaskType,
URL: w.URL,
BasePayload: payload,
ContentType: w.ContentType,
EventType: HOOK_EVENT_PUSH,
IsSSL: w.IsSSL,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
}
} }
return nil return nil

View file

@ -122,9 +122,8 @@ func (u *User) HomeLink() string {
return setting.AppSubUrl + "/" + u.Name return setting.AppSubUrl + "/" + u.Name
} }
// AvatarLink returns user gravatar link. func (u *User) RelAvatarLink() string {
func (u *User) AvatarLink() string { defaultImgUrl := "/img/avatar_default.jpg"
defaultImgUrl := setting.AppSubUrl + "/img/avatar_default.jpg"
if u.Id == -1 { if u.Id == -1 {
return defaultImgUrl return defaultImgUrl
} }
@ -135,7 +134,7 @@ func (u *User) AvatarLink() string {
if !com.IsExist(imgPath) { if !com.IsExist(imgPath) {
return defaultImgUrl return defaultImgUrl
} }
return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id) return "/avatars/" + com.ToStr(u.Id)
case setting.DisableGravatar, setting.OfflineMode: case setting.DisableGravatar, setting.OfflineMode:
if !com.IsExist(imgPath) { if !com.IsExist(imgPath) {
img, err := avatar.RandomImage([]byte(u.Email)) img, err := avatar.RandomImage([]byte(u.Email))
@ -161,13 +160,22 @@ func (u *User) AvatarLink() string {
log.Info("New random avatar created: %d", u.Id) log.Info("New random avatar created: %d", u.Id)
} }
return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id) return "/avatars/" + com.ToStr(u.Id)
case setting.Service.EnableCacheAvatar: case setting.Service.EnableCacheAvatar:
return setting.AppSubUrl + "/avatar/" + u.Avatar return "/avatar/" + u.Avatar
} }
return setting.GravatarSource + u.Avatar return setting.GravatarSource + u.Avatar
} }
// AvatarLink returns user gravatar link.
func (u *User) AvatarLink() string {
link := u.RelAvatarLink()
if link[0] == '/' {
return setting.AppSubUrl + link
}
return link
}
// NewGitSig generates and returns the signature of given user. // NewGitSig generates and returns the signature of given user.
func (u *User) NewGitSig() *git.Signature { func (u *User) NewGitSig() *git.Signature {
return &git.Signature{ return &git.Signature{

View file

@ -15,6 +15,8 @@ import (
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/modules/httplib" "github.com/gogits/gogs/modules/httplib"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
@ -54,9 +56,18 @@ func IsValidHookContentType(name string) bool {
return ok return ok
} }
type HookEvents struct {
Create bool `json:"create"`
Push bool `json:"push"`
}
// HookEvent represents events that will delivery hook. // HookEvent represents events that will delivery hook.
type HookEvent struct { type HookEvent struct {
PushOnly bool `json:"push_only"` PushOnly bool `json:"push_only"`
SendEverything bool `json:"send_everything"`
ChooseEvents bool `json:"choose_events"`
HookEvents `json:"events"`
} }
type HookStatus int type HookStatus int
@ -94,8 +105,8 @@ func (w *Webhook) GetEvent() {
} }
} }
func (w *Webhook) GetSlackHook() *Slack { func (w *Webhook) GetSlackHook() *SlackMeta {
s := &Slack{} s := &SlackMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil { if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error(4, "webhook.GetSlackHook(%d): %v", w.ID, err) log.Error(4, "webhook.GetSlackHook(%d): %v", w.ID, err)
} }
@ -114,12 +125,16 @@ func (w *Webhook) UpdateEvent() error {
return err return err
} }
// HasCreateEvent returns true if hook enabled create event.
func (w *Webhook) HasCreateEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.Create)
}
// HasPushEvent returns true if hook enabled push event. // HasPushEvent returns true if hook enabled push event.
func (w *Webhook) HasPushEvent() bool { func (w *Webhook) HasPushEvent() bool {
if w.PushOnly { return w.PushOnly || w.SendEverything ||
return true (w.ChooseEvents && w.HookEvents.Push)
}
return false
} }
// CreateWebhook creates a new web hook. // CreateWebhook creates a new web hook.
@ -140,9 +155,9 @@ func GetWebhookByID(id int64) (*Webhook, error) {
return w, nil return w, nil
} }
// GetActiveWebhooksByRepoId returns all active webhooks of repository. // GetActiveWebhooksByRepoID returns all active webhooks of repository.
func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) { func GetActiveWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
err = x.Where("repo_id=?", repoId).And("is_active=?", true).Find(&ws) err = x.Where("repo_id=?", repoID).And("is_active=?", true).Find(&ws)
return ws, err return ws, err
} }
@ -181,9 +196,9 @@ func GetWebhooksByOrgId(orgID int64) (ws []*Webhook, err error) {
return ws, err return ws, err
} }
// GetActiveWebhooksByOrgId returns all active webhooks for an organization. // GetActiveWebhooksByOrgID returns all active webhooks for an organization.
func GetActiveWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) { func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
err = x.Where("org_id=?", orgId).And("is_active=?", true).Find(&ws) err = x.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws)
return ws, err return ws, err
} }
@ -230,58 +245,10 @@ func IsValidHookTaskType(name string) bool {
type HookEventType string type HookEventType string
const ( const (
HOOK_EVENT_CREATE HookEventType = "create"
HOOK_EVENT_PUSH HookEventType = "push" HOOK_EVENT_PUSH HookEventType = "push"
) )
// FIXME: just use go-gogs-client structs maybe?
type PayloadAuthor struct {
Name string `json:"name"`
Email string `json:"email"`
UserName string `json:"username"`
}
type PayloadCommit struct {
Id string `json:"id"`
Message string `json:"message"`
Url string `json:"url"`
Author *PayloadAuthor `json:"author"`
}
type PayloadRepo struct {
Id int64 `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
Description string `json:"description"`
Website string `json:"website"`
Watchers int `json:"watchers"`
Owner *PayloadAuthor `json:"owner"`
Private bool `json:"private"`
}
type BasePayload interface {
GetJSONPayload() ([]byte, error)
}
// Payload represents a payload information of hook.
type Payload struct {
Secret string `json:"secret"`
Ref string `json:"ref"`
Commits []*PayloadCommit `json:"commits"`
Repo *PayloadRepo `json:"repository"`
Pusher *PayloadAuthor `json:"pusher"`
Before string `json:"before"`
After string `json:"after"`
CompareUrl string `json:"compare_url"`
}
func (p Payload) GetJSONPayload() ([]byte, error) {
data, err := json.MarshalIndent(p, "", " ")
if err != nil {
return []byte{}, err
}
return data, nil
}
// HookRequest represents hook task request information. // HookRequest represents hook task request information.
type HookRequest struct { type HookRequest struct {
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
@ -302,7 +269,7 @@ type HookTask struct {
UUID string UUID string
Type HookTaskType Type HookTaskType
URL string URL string
BasePayload `xorm:"-"` api.Payloader `xorm:"-"`
PayloadContent string `xorm:"TEXT"` PayloadContent string `xorm:"TEXT"`
ContentType HookContentType ContentType HookContentType
EventType HookEventType EventType HookEventType
@ -367,13 +334,13 @@ func (t *HookTask) MarshalJSON(v interface{}) string {
// HookTasks returns a list of hook tasks by given conditions. // HookTasks returns a list of hook tasks by given conditions.
func HookTasks(hookID int64, page int) ([]*HookTask, error) { func HookTasks(hookID int64, page int) ([]*HookTask, error) {
tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Desc("id").Find(&tasks) return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
} }
// CreateHookTask creates a new hook task, // CreateHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent. // it handles conversion from Payload to PayloadContent.
func CreateHookTask(t *HookTask) error { func CreateHookTask(t *HookTask) error {
data, err := t.BasePayload.GetJSONPayload() data, err := t.Payloader.JSONPayload()
if err != nil { if err != nil {
return err return err
} }
@ -389,6 +356,71 @@ func UpdateHookTask(t *HookTask) error {
return err return err
} }
// PrepareWebhooks adds new webhooks to task queue for given payload.
func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
if err := repo.GetOwner(); err != nil {
return fmt.Errorf("GetOwner: %v", err)
}
ws, err := GetActiveWebhooksByRepoID(repo.ID)
if err != nil {
return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err)
}
// check if repo belongs to org and append additional webhooks
if repo.Owner.IsOrganization() {
// get hooks for org
orgws, err := GetActiveWebhooksByOrgID(repo.OwnerID)
if err != nil {
return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
}
ws = append(ws, orgws...)
}
if len(ws) == 0 {
return nil
}
for _, w := range ws {
w.GetEvent()
switch event {
case HOOK_EVENT_CREATE:
if !w.HasCreateEvent() {
continue
}
case HOOK_EVENT_PUSH:
if !w.HasPushEvent() {
continue
}
}
switch w.HookTaskType {
case SLACK:
p, err = GetSlackPayload(p, event, w.Meta)
if err != nil {
return fmt.Errorf("GetSlackPayload: %v", err)
}
default:
p.SetSecret(w.Secret)
}
if err = CreateHookTask(&HookTask{
RepoID: repo.ID,
HookID: w.ID,
Type: w.HookTaskType,
URL: w.URL,
Payloader: p,
ContentType: w.ContentType,
EventType: HOOK_EVENT_PUSH,
IsSSL: w.IsSSL,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
}
}
return nil
}
type hookQueue struct { type hookQueue struct {
// Make sure one repository only occur once in the queue. // Make sure one repository only occur once in the queue.
lock sync.Mutex lock sync.Mutex

View file

@ -9,13 +9,18 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/setting"
) )
const ( const (
SLACK_COLOR string = "#dd4b39" SLACK_COLOR string = "#dd4b39"
) )
type Slack struct { type SlackMeta struct {
Channel string `json:"channel"` Channel string `json:"channel"`
} }
@ -34,7 +39,9 @@ type SlackAttachment struct {
Text string `json:"text"` Text string `json:"text"`
} }
func (p SlackPayload) GetJSONPayload() ([]byte, error) { func (p *SlackPayload) SetSecret(_ string) {}
func (p *SlackPayload) JSONPayload() ([]byte, error) {
data, err := json.Marshal(p) data, err := json.Marshal(p)
if err != nil { if err != nil {
return []byte{}, err return []byte{}, err
@ -42,63 +49,6 @@ func (p SlackPayload) GetJSONPayload() ([]byte, error) {
return data, nil return data, nil
} }
func GetSlackPayload(p *Payload, meta string) (*SlackPayload, error) {
slack := &Slack{}
slackPayload := &SlackPayload{}
if err := json.Unmarshal([]byte(meta), &slack); err != nil {
return slackPayload, errors.New("GetSlackPayload meta json:" + err.Error())
}
// TODO: handle different payload types: push, new branch, delete branch etc.
// when they are added to gogs. Only handles push now
return getSlackPushPayload(p, slack)
}
func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
// n new commits
refSplit := strings.Split(p.Ref, "/")
branchName := refSplit[len(refSplit)-1]
var commitString string
if len(p.Commits) == 1 {
commitString = "1 new commit"
if p.CompareUrl != "" {
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
}
} else {
commitString = fmt.Sprintf("%d new commits", len(p.Commits))
if p.CompareUrl != "" {
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
}
}
repoLink := SlackLinkFormatter(p.Repo.Url, p.Repo.Name)
branchLink := SlackLinkFormatter(p.Repo.Url+"/src/"+branchName, branchName)
text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.Name)
var attachmentText string
// for each commit, generate attachment text
for i, commit := range p.Commits {
attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.Url, commit.Id[:7]), SlackTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
// add linebreak to each commit but the last
if i < len(p.Commits)-1 {
attachmentText += "\n"
}
}
slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}}
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: "gogs",
IconUrl: "https://raw.githubusercontent.com/gogits/gogs/master/public/img/favicon.png",
UnfurlLinks: 0,
LinkNames: 0,
Attachments: slackAttachments,
}, nil
}
// see: https://api.slack.com/docs/formatting // see: https://api.slack.com/docs/formatting
func SlackTextFormatter(s string) string { func SlackTextFormatter(s string) string {
// take only first line of commit // take only first line of commit
@ -113,3 +63,81 @@ func SlackTextFormatter(s string) string {
func SlackLinkFormatter(url string, text string) string { func SlackLinkFormatter(url string, text string) string {
return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text)) return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
} }
func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) {
// created tag/branch
refName := git.RefEndName(p.Ref)
repoLink := SlackLinkFormatter(p.Repo.URL, p.Repo.Name)
refLink := SlackLinkFormatter(p.Repo.URL+"/src/"+refName, refName)
text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: setting.AppName,
IconUrl: setting.AppUrl + "/img/favicon.png",
}, nil
}
func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) {
// n new commits
var (
branchName = git.RefEndName(p.Ref)
commitString string
)
if len(p.Commits) == 1 {
commitString = "1 new commit"
if len(p.CompareUrl) > 0 {
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
}
} else {
commitString = fmt.Sprintf("%d new commits", len(p.Commits))
if p.CompareUrl != "" {
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
}
}
repoLink := SlackLinkFormatter(p.Repo.URL, p.Repo.Name)
branchLink := SlackLinkFormatter(p.Repo.URL+"/src/"+branchName, branchName)
text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.Name)
var attachmentText string
// for each commit, generate attachment text
for i, commit := range p.Commits {
attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
// add linebreak to each commit but the last
if i < len(p.Commits)-1 {
attachmentText += "\n"
}
}
slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}}
return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: setting.AppName,
IconUrl: setting.AppUrl + "/img/favicon.png",
Attachments: slackAttachments,
}, nil
}
func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) {
s := new(SlackPayload)
slack := &SlackMeta{}
if err := json.Unmarshal([]byte(meta), &slack); err != nil {
return s, errors.New("GetSlackPayload meta json:" + err.Error())
}
switch event {
case HOOK_EVENT_CREATE:
return getSlackCreatePayload(p.(*api.CreatePayload), slack)
case HOOK_EVENT_PUSH:
return getSlackPushPayload(p.(*api.PushPayload), slack)
}
return s, nil
}

View file

@ -67,10 +67,24 @@ func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
// \/ \/ \/ \/ \/ \/ // \/ \/ \/ \/ \/ \/
type WebhookForm struct { type WebhookForm struct {
PushOnly bool Events string
Create bool
Push bool
Active bool Active bool
} }
func (f WebhookForm) PushOnly() bool {
return f.Events == "push_only"
}
func (f WebhookForm) SendEverything() bool {
return f.Events == "send_everything"
}
func (f WebhookForm) ChooseEvents() bool {
return f.Events == "choose_events"
}
type NewWebhookForm struct { type NewWebhookForm struct {
PayloadURL string `binding:"Required;Url"` PayloadURL string `binding:"Required;Url"`
ContentType int `binding:"Required"` ContentType int `binding:"Required"`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -367,6 +367,23 @@ function initRepository() {
} }
}; };
function initWebhook() {
if ($('.new.webhook').length == 0) {
return;
}
$('.events.checkbox input').change(function () {
if ($(this).is(':checked')) {
$('.events.fields').show();
}
});
$('.non-events.checkbox input').change(function () {
if ($(this).is(':checked')) {
$('.events.fields').hide();
}
});
}
$(document).ready(function () { $(document).ready(function () {
csrf = $('meta[name=_csrf]').attr("content"); csrf = $('meta[name=_csrf]').attr("content");
@ -473,4 +490,5 @@ $(document).ready(function () {
initCommentForm(); initCommentForm();
initInstall(); initInstall();
initRepository(); initRepository();
initWebhook();
}); });

View file

@ -77,3 +77,11 @@
} }
} }
} }
.new.webhook {
.events.fields {
.column {
padding-left: 40px;
}
}
}

View file

@ -83,7 +83,7 @@ func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) {
ctx.JSON(422, &base.ApiJsonErr{"missing config option: channel", base.DOC_URL}) ctx.JSON(422, &base.ApiJsonErr{"missing config option: channel", base.DOC_URL})
return return
} }
meta, err := json.Marshal(&models.Slack{ meta, err := json.Marshal(&models.SlackMeta{
Channel: channel, Channel: channel,
}) })
if err != nil { if err != nil {
@ -141,7 +141,7 @@ func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) {
if w.HookTaskType == models.SLACK { if w.HookTaskType == models.SLACK {
if channel, ok := form.Config["channel"]; ok { if channel, ok := form.Config["channel"]; ok {
meta, err := json.Marshal(&models.Slack{ meta, err := json.Marshal(&models.SlackMeta{
Channel: channel, Channel: channel,
}) })
if err != nil { if err != nil {

View file

@ -320,6 +320,18 @@ func WebhooksNew(ctx *middleware.Context) {
ctx.HTML(200, orCtx.NewTemplate) ctx.HTML(200, orCtx.NewTemplate)
} }
func ParseHookEvent(form auth.WebhookForm) *models.HookEvent {
return &models.HookEvent{
PushOnly: form.PushOnly(),
SendEverything: form.SendEverything(),
ChooseEvents: form.ChooseEvents(),
HookEvents: models.HookEvents{
Create: form.Create,
Push: form.Push,
},
}
}
func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooks"] = true
@ -349,9 +361,7 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) {
URL: form.PayloadURL, URL: form.PayloadURL,
ContentType: contentType, ContentType: contentType,
Secret: form.Secret, Secret: form.Secret,
HookEvent: &models.HookEvent{ HookEvent: ParseHookEvent(form.WebhookForm),
PushOnly: form.PushOnly,
},
IsActive: form.Active, IsActive: form.Active,
HookTaskType: models.GOGS, HookTaskType: models.GOGS,
OrgID: orCtx.OrgID, OrgID: orCtx.OrgID,
@ -385,7 +395,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
return return
} }
meta, err := json.Marshal(&models.Slack{ meta, err := json.Marshal(&models.SlackMeta{
Channel: form.Channel, Channel: form.Channel,
}) })
if err != nil { if err != nil {
@ -397,9 +407,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
RepoID: orCtx.RepoID, RepoID: orCtx.RepoID,
URL: form.PayloadURL, URL: form.PayloadURL,
ContentType: models.JSON, ContentType: models.JSON,
HookEvent: &models.HookEvent{ HookEvent: ParseHookEvent(form.WebhookForm),
PushOnly: form.PushOnly,
},
IsActive: form.Active, IsActive: form.Active,
HookTaskType: models.SLACK, HookTaskType: models.SLACK,
Meta: string(meta), Meta: string(meta),
@ -491,9 +499,7 @@ func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) {
w.URL = form.PayloadURL w.URL = form.PayloadURL
w.ContentType = contentType w.ContentType = contentType
w.Secret = form.Secret w.Secret = form.Secret
w.HookEvent = &models.HookEvent{ w.HookEvent = ParseHookEvent(form.WebhookForm)
PushOnly: form.PushOnly,
}
w.IsActive = form.Active w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
ctx.Handle(500, "UpdateEvent", err) ctx.Handle(500, "UpdateEvent", err)
@ -523,7 +529,7 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
return return
} }
meta, err := json.Marshal(&models.Slack{ meta, err := json.Marshal(&models.SlackMeta{
Channel: form.Channel, Channel: form.Channel,
}) })
if err != nil { if err != nil {
@ -533,9 +539,7 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
w.URL = form.PayloadURL w.URL = form.PayloadURL
w.Meta = string(meta) w.Meta = string(meta)
w.HookEvent = &models.HookEvent{ w.HookEvent = ParseHookEvent(form.WebhookForm)
PushOnly: form.PushOnly,
}
w.IsActive = form.Active w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
ctx.Handle(500, "UpdateEvent", err) ctx.Handle(500, "UpdateEvent", err)

View file

@ -26,4 +26,22 @@
</div> </div>
</footer> </footer>
</body> </body>
<!-- Third-party libraries -->
{{if .RequireHighlightJS}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/highlight-8.7/default.css">
<script src="{{AppSubUrl}}/js/libs/highlight-8.7.pack.js"></script>
{{end}}
{{if .RequireMinicolors}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.minicolors-2.1.12.css">
<script src="{{AppSubUrl}}/js/libs/jquery.minicolors-2.1.12.min.js"></script>
{{end}}
{{if .RequireDatetimepicker}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.datetimepicker-2.4.5.css">
<script src="{{AppSubUrl}}/js/libs/jquery.datetimepicker-2.4.5.js"></script>
{{end}}
{{if .RequireDropzone}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/dropzone-4.0.1.css">
<script src="{{AppSubUrl}}/js/libs/dropzone-4.0.1.js"></script>
{{end}}
</html> </html>

View file

@ -25,24 +25,6 @@
<script src="{{AppSubUrl}}/js/semantic-2.0.8.min.js"></script> <script src="{{AppSubUrl}}/js/semantic-2.0.8.min.js"></script>
<script src="{{AppSubUrl}}/js/gogs.js?v={{AppVer}}"></script> <script src="{{AppSubUrl}}/js/gogs.js?v={{AppVer}}"></script>
<!-- Third-party libraries -->
{{if .RequireHighlightJS}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/highlight-8.7/default.css">
<script src="{{AppSubUrl}}/js/libs/highlight-8.7.pack.js"></script>
{{end}}
{{if .RequireMinicolors}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.minicolors-2.1.12.css">
<script src="{{AppSubUrl}}/js/libs/jquery.minicolors-2.1.12.min.js"></script>
{{end}}
{{if .RequireDatetimepicker}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/jquery.datetimepicker-2.4.5.css">
<script src="{{AppSubUrl}}/js/libs/jquery.datetimepicker-2.4.5.js"></script>
{{end}}
{{if .RequireDropzone}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/dropzone-4.0.1.css">
<script src="{{AppSubUrl}}/js/libs/dropzone-4.0.1.js"></script>
{{end}}
<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title> <title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
</head> </head>
<body> <body>

View file

@ -28,7 +28,7 @@
{{if .IsSucceed}} {{if .IsSucceed}}
<span class="ui green label">{{.ResponseInfo.Status}}</span> <span class="ui green label">{{.ResponseInfo.Status}}</span>
{{else}} {{else}}
<span class="ui red label">500</span> <span class="ui red label">{{.ResponseInfo.Status}}</span>
{{end}} {{end}}
{{else}} {{else}}
<span class="ui label">N/A</span> <span class="ui label">N/A</span>

View file

@ -1,14 +1,52 @@
<div class="field"> <div class="field">
<h4>{{.i18n.Tr "repo.settings.event_desc"}}</h4> <h4>{{.i18n.Tr "repo.settings.event_desc"}}</h4>
<div class="grouped fields"> <div class="grouped event type fields">
<div class="field"> <div class="field">
<div class="ui radio checkbox checked"> <div class="ui radio non-events checkbox">
<input class="hidden" name="push_only" type="radio" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}> <input class="hidden" name="events" type="radio" value="push_only" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_push_only" | Str2html}}</label> <label>{{.i18n.Tr "repo.settings.event_push_only" | Str2html}}</label>
</div> </div>
</div> </div>
<div class="field">
<div class="ui radio non-events checkbox">
<input class="hidden" name="events" type="radio" value="send_everything" {{if .Webhook.SendEverything}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_send_everything" | Str2html}}</label>
</div>
</div>
<div class="field">
<div class="ui radio events checkbox">
<input class="hidden" name="events" type="radio" value="choose_events" {{if .Webhook.ChooseEvents}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_choose" | Str2html}}</label>
</div>
</div>
</div>
<div class="events fields ui grid" {{if not .Webhook.ChooseEvents}}style="display:none"{{end}}>
<!-- Create -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="create" type="checkbox" tabindex="0" {{if .Webhook.Create}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_create"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_create_desc"}}</span>
</div>
</div>
</div>
<!-- Push -->
<div class="seven wide column">
<div class="field">
<div class="ui checkbox">
<input class="hidden" name="push" type="checkbox" tabindex="0" {{if .Webhook.Push}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.event_push"}}</label>
<span class="help">{{.i18n.Tr "repo.settings.event_push_desc"}}</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="ui divider"></div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<input class="hidden" name="active" type="checkbox" tabindex="0" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}}> <input class="hidden" name="active" type="checkbox" tabindex="0" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}}>