Skip to content

Commit 505e539

Browse files
ayusht2810Kshitij-Katiyarraghavaggarwal2308
authored
[MM-356] Add feature to subscribe to release and workflow events (#765)
* [MM-356] Add feature to publish workflow failure events * [MM-356] Update webhook event name * [MM-356] Update go-github package and included workflow name in the template * [MM-356]: added releases to subscription feature list * [MM-356]: fixed ci * Updated package-lock file * [MM-356]: Added success testcase for workflow notification and reverted package-lock changes * [MM-356]: reverted the package-lock changes * [MM-356]: minor updates in template_test.go file --------- Co-authored-by: kshitij katiyar <[email protected]> Co-authored-by: raghavaggarwal2308 <[email protected]>
1 parent b6d576c commit 505e539

File tree

5 files changed

+158
-4
lines changed

5 files changed

+158
-4
lines changed

server/plugin/command.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const (
2727
featurePullReviews = "pull_reviews"
2828
featureStars = "stars"
2929
featureReleases = "releases"
30+
featureWorkflowFailure = "workflow_failure"
31+
featureWorkflowSuccess = "workflow_success"
3032
featureDiscussions = "discussions"
3133
featureDiscussionComments = "discussion_comments"
3234
)
@@ -48,6 +50,8 @@ var validFeatures = map[string]bool{
4850
featurePullReviews: true,
4951
featureStars: true,
5052
featureReleases: true,
53+
featureWorkflowFailure: true,
54+
featureWorkflowSuccess: true,
5155
featureDiscussions: true,
5256
featureDiscussionComments: true,
5357
}
@@ -904,7 +908,7 @@ func getAutocompleteData(config *Configuration) *model.AutocompleteData {
904908

905909
subscriptionsAdd := model.NewAutocompleteData("add", "[owner/repo] [features] [flags]", "Subscribe the current channel to receive notifications about opened pull requests and issues for an organization or repository. [features] and [flags] are optional arguments")
906910
subscriptionsAdd.AddTextArgument("Owner/repo to subscribe to", "[owner/repo]", "")
907-
subscriptionsAdd.AddNamedTextArgument("features", "Comma-delimited list of one or more of: issues, pulls, pulls_merged, pulls_created, pushes, creates, deletes, issue_creations, issue_comments, pull_reviews, releases, discussions, discussion_comments, label:\"<labelname>\". Defaults to pulls,issues,creates,deletes", "", `/[^,-\s]+(,[^,-\s]+)*/`, false)
911+
subscriptionsAdd.AddNamedTextArgument("features", "Comma-delimited list of one or more of: issues, pulls, pulls_merged, pulls_created, pushes, creates, deletes, issue_creations, issue_comments, pull_reviews, releases, workflow_success, workflow_failure, discussions, discussion_comments, label:\"<labelname>\". Defaults to pulls,issues,creates,deletes", "", `/[^,-\s]+(,[^,-\s]+)*/`, false)
908912

909913
if config.GitHubOrg != "" {
910914
subscriptionsAdd.AddNamedStaticListArgument("exclude-org-member", "Events triggered by organization members will not be delivered (the organization config should be set, otherwise this flag has not effect)", false, []model.AutocompleteListItem{

server/plugin/subscriptions.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ func (s *Subscription) Stars() bool {
123123
return strings.Contains(s.Features.String(), featureStars)
124124
}
125125

126+
func (s *Subscription) Workflows() bool {
127+
return strings.Contains(s.Features.String(), featureWorkflowFailure) || strings.Contains(s.Features.String(), featureWorkflowSuccess)
128+
}
129+
126130
func (s *Subscription) Release() bool {
127131
return strings.Contains(s.Features.String(), featureReleases)
128132
}

server/plugin/template.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ func init() {
113113
return commit.GetCommitter()
114114
}
115115

116+
funcMap["workflowJobFailedStep"] = func(steps []*github.TaskStep) string {
117+
for _, step := range steps {
118+
if step.GetConclusion() == workflowJobFail {
119+
return step.GetName()
120+
}
121+
}
122+
123+
return ""
124+
}
125+
116126
masterTemplate = template.Must(template.New("master").Funcs(funcMap).Parse(""))
117127

118128
// The user template links to the corresponding GitHub user. If the GitHub user is a known
@@ -158,6 +168,11 @@ func init() {
158168
`[#{{.GetNumber}} {{.GetTitle}}]({{.GetHTMLURL}})`,
159169
))
160170

171+
// The workflow job links to the corresponding workflow.
172+
template.Must(masterTemplate.New("workflowJob").Parse(
173+
`[{{.GetName}}]({{.GetHTMLURL}})`,
174+
))
175+
161176
// The release links to the corresponding release.
162177
template.Must(masterTemplate.New("release").Parse(
163178
`[{{.GetTagName}}]({{.GetHTMLURL}})`,
@@ -412,6 +427,8 @@ Assignees: {{range $i, $el := .Assignees -}} {{- if $i}}, {{end}}{{template "use
412427
" * `issue_comments` - includes new issue comments\n" +
413428
" * `issue_creations` - includes new issues only \n" +
414429
" * `pull_reviews` - includes pull request reviews\n" +
430+
" * `workflow_failure` - includes workflow job failure\n" +
431+
" * `workflow_success` - includes workflow job success\n" +
415432
" * `releases` - includes release created and deleted\n" +
416433
" * `label:<labelname>` - limit pull request and issue events to only this label. Must include `pulls` or `issues` in feature list when using a label.\n" +
417434
" * `discussions` - includes new discussions\n" +
@@ -437,6 +454,11 @@ Assignees: {{range $i, $el := .Assignees -}} {{- if $i}}, {{end}}{{template "use
437454
{{- end }} by {{template "user" .GetSender}}
438455
It now has **{{.GetRepo.GetStargazersCount}}** stars.`))
439456

457+
template.Must(masterTemplate.New("newWorkflowJob").Funcs(funcMap).Parse(`
458+
{{template "repo" .GetRepo}} {{.GetWorkflowJob.GetWorkflowName}} workflow {{if eq .GetWorkflowJob.GetConclusion "success"}}succeeded{{else}}failed{{end}} (triggered by {{template "user" .GetSender}})
459+
{{if eq .GetWorkflowJob.GetConclusion "failure"}}Job failed: {{template "workflowJob" .GetWorkflowJob}}
460+
Step failed: {{.GetWorkflowJob.Steps | workflowJobFailedStep}}
461+
{{end}}Commit: {{.GetRepo.GetHTMLURL}}/commit/{{.GetWorkflowJob.GetHeadSHA}}`))
440462
template.Must(masterTemplate.New("newReleaseEvent").Funcs(funcMap).Parse(`
441463
{{template "repo" .GetRepo}} {{template "user" .GetSender}}
442464
{{- if eq .GetAction "created" }} created a release {{template "release" .GetRelease}}

server/plugin/template_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,80 @@ func TestGitHubUsernameRegex(t *testing.T) {
14771477
}
14781478
}
14791479

1480+
func TestWorkflowJobNotification(t *testing.T) {
1481+
t.Run("failed", func(t *testing.T) {
1482+
expected := `
1483+
[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) mock-workflow-name workflow failed (triggered by [panda](https://github.com/panda))
1484+
Job failed: [mock-workflow-job](https://github.com/mattermost/mattermost-plugin-github/actions/runs/12345/job/67890)
1485+
Step failed: mock-job-2
1486+
Commit: https://github.com/mattermost/mattermost-plugin-github/commit/1234567890`
1487+
1488+
actual, err := renderTemplate("newWorkflowJob", &github.WorkflowJobEvent{
1489+
Repo: &repo,
1490+
Sender: &user,
1491+
Action: sToP(actionCompleted),
1492+
WorkflowJob: &github.WorkflowJob{
1493+
Conclusion: sToP("failure"),
1494+
Name: sToP("mock-workflow-job"),
1495+
HeadSHA: sToP("1234567890"),
1496+
HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/actions/runs/12345/job/67890"),
1497+
WorkflowName: sToP("mock-workflow-name"),
1498+
Steps: []*github.TaskStep{
1499+
{
1500+
Name: sToP("mock-job-1"),
1501+
Conclusion: sToP("success"),
1502+
},
1503+
{
1504+
Name: sToP("mock-job-2"),
1505+
Conclusion: sToP("failure"),
1506+
},
1507+
{
1508+
Name: sToP("mock-job-3"),
1509+
Conclusion: sToP("success"),
1510+
},
1511+
},
1512+
},
1513+
})
1514+
require.NoError(t, err)
1515+
require.Equal(t, expected, actual)
1516+
})
1517+
1518+
t.Run("success", func(t *testing.T) {
1519+
expected := `
1520+
[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) mock-workflow-name workflow succeeded (triggered by [panda](https://github.com/panda))
1521+
Commit: https://github.com/mattermost/mattermost-plugin-github/commit/1234567890`
1522+
1523+
actual, err := renderTemplate("newWorkflowJob", &github.WorkflowJobEvent{
1524+
Repo: &repo,
1525+
Sender: &user,
1526+
Action: sToP(actionCompleted),
1527+
WorkflowJob: &github.WorkflowJob{
1528+
Conclusion: sToP("success"),
1529+
Name: sToP("mock-workflow-job"),
1530+
HeadSHA: sToP("1234567890"),
1531+
HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/actions/runs/12345/job/67890"),
1532+
WorkflowName: sToP("mock-workflow-name"),
1533+
Steps: []*github.TaskStep{
1534+
{
1535+
Name: sToP("mock-job-1"),
1536+
Conclusion: sToP("success"),
1537+
},
1538+
{
1539+
Name: sToP("mock-job-2"),
1540+
Conclusion: sToP("success"),
1541+
},
1542+
{
1543+
Name: sToP("mock-job-3"),
1544+
Conclusion: sToP("success"),
1545+
},
1546+
},
1547+
},
1548+
})
1549+
require.NoError(t, err)
1550+
require.Equal(t, expected, actual)
1551+
})
1552+
}
1553+
14801554
func sToP(s string) *string {
14811555
return &s
14821556
}

server/plugin/webhook.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ const (
2828
actionLabeled = "labeled"
2929
actionAssigned = "assigned"
3030

31-
actionCreated = "created"
32-
actionDeleted = "deleted"
33-
actionEdited = "edited"
31+
actionCreated = "created"
32+
actionDeleted = "deleted"
33+
actionEdited = "edited"
34+
actionCompleted = "completed"
35+
36+
workflowJobFail = "failure"
37+
workflowJobSuccess = "success"
3438

3539
postPropGithubRepo = "gh_repo"
3640
postPropGithubObjectID = "gh_object_id"
@@ -282,6 +286,11 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
282286
handler = func() {
283287
p.postStarEvent(event)
284288
}
289+
case *github.WorkflowJobEvent:
290+
repo = event.GetRepo()
291+
handler = func() {
292+
p.postWorkflowJobEvent(event)
293+
}
285294
case *github.ReleaseEvent:
286295
repo = event.GetRepo()
287296
handler = func() {
@@ -1350,6 +1359,47 @@ func (p *Plugin) postStarEvent(event *github.StarEvent) {
13501359
}
13511360
}
13521361

1362+
func (p *Plugin) postWorkflowJobEvent(event *github.WorkflowJobEvent) {
1363+
if event.GetAction() != actionCompleted {
1364+
return
1365+
}
1366+
1367+
// Create a post only when the workflow job is completed and has either failed or succeeded
1368+
if event.GetWorkflowJob().GetConclusion() != workflowJobFail && event.GetWorkflowJob().GetConclusion() != workflowJobSuccess {
1369+
return
1370+
}
1371+
1372+
repo := event.GetRepo()
1373+
subs := p.GetSubscribedChannelsForRepository(repo)
1374+
1375+
if len(subs) == 0 {
1376+
return
1377+
}
1378+
1379+
newWorkflowJobMessage, err := renderTemplate("newWorkflowJob", event)
1380+
if err != nil {
1381+
p.client.Log.Warn("Failed to render template", "Error", err.Error())
1382+
return
1383+
}
1384+
1385+
for _, sub := range subs {
1386+
if !sub.Workflows() {
1387+
continue
1388+
}
1389+
1390+
post := &model.Post{
1391+
UserId: p.BotUserID,
1392+
Type: "custom_git_workflow_job",
1393+
Message: newWorkflowJobMessage,
1394+
ChannelId: sub.ChannelID,
1395+
}
1396+
1397+
if err = p.client.Post.CreatePost(post); err != nil {
1398+
p.client.Log.Warn("Error webhook post", "Post", post, "Error", err.Error())
1399+
}
1400+
}
1401+
}
1402+
13531403
func (p *Plugin) makeBotPost(message, postType string) *model.Post {
13541404
return &model.Post{
13551405
UserId: p.BotUserID,

0 commit comments

Comments
 (0)