diff --git a/docs/content/docs/concepts/workflow/images/example_pr_comment.png b/docs/content/docs/concepts/workflow/images/example_pr_comment.png new file mode 100644 index 0000000000..2cea797c2d Binary files /dev/null and b/docs/content/docs/concepts/workflow/images/example_pr_comment.png differ diff --git a/docs/content/docs/concepts/workflow/notifications.md b/docs/content/docs/concepts/workflow/notifications.md index c27f6c18db..bcf069ad41 100644 --- a/docs/content/docs/concepts/workflow/notifications.md +++ b/docs/content/docs/concepts/workflow/notifications.md @@ -12,6 +12,64 @@ On a workflow you can have 2 kinds of notifications: You can configure user notifications to send email or a message on jabber with different parameters. Inside the body of the notification you can customise the message thanks to the CDS variable templating with syntax like `{{.cds.myvar}}`. You can also use `HTML` to customise the message, then in order to let CDS interpret your message as an `HTML` one you just need to wrap all your message inside html tag like this `MyContentHere`. +## VCS Notifications + +You can configure for which node in your workflow CDS have to send a status on your repository service provider (Github, Bitbucket, ...). You can configure if you want to have a comment on your pull-request when your workflow fails or you can just disable pull-request comment to only have status of your pipelines. By default you already have a default template for your pull-request comment but you can customize it with different kinds of templating. To have access about the `node run` data and write some loops and conditions you can use the standard syntax as the [go templating](https://golang.org/pkg/text/template/#hdr-Actions) but with `[[` `]]` delimitters. You can also use the CDS interpolation engine with the same syntax you already know and use inside pipelines, for example: `{{.cds.workflow}}` to get the name of the workflow. + +For the go templating you have few variables you can use/iterate over. + +- `.Stages`: an array of stages with `.RunJobs`inside which are the array of runned jobs with their `.Name` + - `.RunJobs`: Inside a stage object which are the array of runned jobs + - `.Job.Action.Name`: The name of the runned job + - `.Job.Status`: The status of runned job +- `.Tests`: array of tests results + - `.Total`: total number of tests + - `.TotalOK`: total number of OK tests + - `.TotalKO`: total number of KO tests + - `.TotalSkipped`: total number of skipped tests + +If you need to know about other variable you can check data structure [here](https://github.com/ovh/cds/blob/master/sdk/workflow_run.go#L40). + +For example by default the template of pull-request comment is: + +``` +[[- if .Stages ]] +CDS Report [[.WorkflowNodeName]]#[[.Number]].[[.SubNumber]] [[ if eq .Status "Success" -]] ✔ [[ else ]][[ if eq .Status "Fail" -]] ✘ [[ else ]][[ if eq .Status "Stopped" -]] ■ [[ else ]]- [[ end ]] [[ end ]] [[ end ]] +[[- range $s := .Stages]] +[[- if $s.RunJobs ]] +* [[$s.Name]] +[[- range $j := $s.RunJobs]] + * [[$j.Job.Action.Name]] [[ if eq $j.Status "Success" -]] ✔ [[ else ]][[ if eq $j.Status "Fail" -]] ✘ [[ else ]][[ if eq $j.Status "Stopped" -]] ■ [[ else ]]- [[ end ]] [[ end ]] [[ end ]] +[[- end]] +[[- end]] +[[- end]] +[[- end]] + +[[- if .Tests ]] +[[- if gt .Tests.TotalKO 0]] +Unit Tests Report + +[[- range $ts := .Tests.TestSuites]] +* [[ $ts.Name ]] +[[range $tc := $ts.TestCases]] + [[- if or ($tc.Errors) ($tc.Failures) ]] * [[ $tc.Name ]] ✘ [[- end]] +[[end]] +[[- end]] +[[- end]] +[[- end]] +``` + +Which, for a pipeline with 1 stage and a job in failure, is displayed like this: + +``` +CDS Report build#11.0 ✘ +* Stage 1 + * my job ✘ +``` + +And displayed on GitHub: + +![example_pr_comment.png](../images/example_pr_comment.png?height=200px) ## Events If you need to trigger some specific actions on the technical side, like for example use a microservice which listens to all events in your workflow (updates, launch, stop, etc.), you can add an event integration like, for example, [Kafka]({{< relref "/docs/integrations/kafka/kafka_events.md">}}) and listen to the kafka topic to trigger some actions on your side. Events are more like sending notifications to machines instead of user notifications which are made for users. The see structure of sent events, you can look [here](https://github.com/ovh/cds/blob/master/sdk/event.go) and [here](https://github.com/ovh/cds/blob/master/sdk/event_workflow.go). diff --git a/docs/content/docs/integrations/bitbucket.md b/docs/content/docs/integrations/bitbucket.md index 423e854eb5..80bd8e6945 100644 --- a/docs/content/docs/integrations/bitbucket.md +++ b/docs/content/docs/integrations/bitbucket.md @@ -14,8 +14,8 @@ This integration enables some features: - [Git Repository Webhook]({{}}) - Easy to use action [CheckoutApplication]({{}}) and [GitClone]({{}}) for advanced usage - - Send [build notifications](https://developer.atlassian.com/server/bitbucket/how-tos/updating-build-status-for-commits/) on your Pull-Requests and Commits on Bitbucket - - Send comments on your Pull-Requests when a workflow is failed + - Send [build notifications](https://developer.atlassian.com/server/bitbucket/how-tos/updating-build-status-for-commits/) on your Pull-Requests and Commits on Bitbucket. [More informations]({{}}) + - [Send comments on your Pull-Requests when a workflow is failed]({{}}) ## How to configure Bitbucket Server integration diff --git a/docs/content/docs/integrations/bitbucketcloud.md b/docs/content/docs/integrations/bitbucketcloud.md index 931d948d26..75314b2e8c 100644 --- a/docs/content/docs/integrations/bitbucketcloud.md +++ b/docs/content/docs/integrations/bitbucketcloud.md @@ -14,7 +14,7 @@ This integration enables some features: - [Git Repository Webhook]({{}}) - Easy to use action [CheckoutApplication]({{}}) and [GitClone]({{}}) for advanced usage - - Send [build notifications](https://confluence.atlassian.com/bitbucket/check-build-status-in-a-pull-request-945541505.html) on your Pull-Requests and Commits on Bitbucket Cloud + - Send [build notifications](https://confluence.atlassian.com/bitbucket/check-build-status-in-a-pull-request-945541505.html) on your Pull-Requests and Commits on Bitbucket Cloud. [More informations]({{}}) ## How to configure Bitbucket Cloud integration diff --git a/docs/content/docs/integrations/github.md b/docs/content/docs/integrations/github.md index 9d5c5cc9bb..ab45be6b08 100644 --- a/docs/content/docs/integrations/github.md +++ b/docs/content/docs/integrations/github.md @@ -14,8 +14,8 @@ This integration enables some features: - [Git Repository Webhook]({{}}) - [Git Repository Poller]({{}}) - Easy to use action [CheckoutApplication]({{}}) and [GitClone]({{}}) for advanced usage - - Send build notifications on your Pull-Requests and Commits on GitHub - - Send comments on your Pull-Requests when a workflow is failed + - Send build notifications on your Pull-Requests and Commits on GitHub. [More informations]({{}}) + - Send comments on your Pull-Requests when a workflow is failed. [More informations]({{}}) ## Resume on what you have to do before using the GitHub Integration diff --git a/docs/content/docs/integrations/gitlab.md b/docs/content/docs/integrations/gitlab.md index c3ef334104..a0ebcea303 100644 --- a/docs/content/docs/integrations/gitlab.md +++ b/docs/content/docs/integrations/gitlab.md @@ -14,7 +14,7 @@ This integration enables some features: - [Git Repository Webhook]({{}}) - Easy to use action [CheckoutApplication]({{}}) and [GitClone]({{}}) for advanced usage - - Send build notifications on your Pull-Requests and Commits on GitLab + - Send build notifications on your Pull-Requests and Commits on GitLab. [More informations]({{}}) ## How to configure GitLab integration diff --git a/engine/api/api.go b/engine/api/api.go index fd368738d8..a780de046f 100644 --- a/engine/api/api.go +++ b/engine/api/api.go @@ -757,6 +757,10 @@ func (a *API) Serve(ctx context.Context) error { a.serviceAPIHeartbeat(ctx) }, a.PanicDump()) + migrate.Add(sdk.Migration{Name: "AddDefaultVCSNotifications", Release: "0.41.0", Mandatory: true, ExecFunc: func(ctx context.Context) error { + return migrate.AddDefaultVCSNotifications(ctx, a.Cache, a.DBConnectionFactory.GetDBMap) + }}) + isFreshInstall, errF := version.IsFreshInstall(a.mustDB()) if errF != nil { return sdk.WrapError(errF, "Unable to check if it's a fresh installation of CDS") diff --git a/engine/api/migrate/vcs_notifications.go b/engine/api/migrate/vcs_notifications.go new file mode 100644 index 0000000000..018048210c --- /dev/null +++ b/engine/api/migrate/vcs_notifications.go @@ -0,0 +1,92 @@ +package migrate + +import ( + "context" + + "github.com/go-gorp/gorp" + + "github.com/ovh/cds/engine/api/cache" + "github.com/ovh/cds/engine/api/workflow" + "github.com/ovh/cds/sdk" + "github.com/ovh/cds/sdk/log" +) + +func AddDefaultVCSNotifications(ctx context.Context, store cache.Store, DBFunc func() *gorp.DbMap) error { + db := DBFunc() + + res := []struct { + WorkflowID int64 `db:"workflow_id"` + NodeID int64 `db:"id"` + }{} + + query := `SELECT w_node.workflow_id, w_node.id + FROM w_node + JOIN w_node_context ON w_node_context.node_id = w_node.id + JOIN application ON application.id = w_node_context.application_id + WHERE application.repo_fullname <> ''` + + if _, err := db.Select(&res, query); err != nil { + return sdk.WrapError(err, "cannot get workflow id and node id to update") + } + + // switch to a hashmap of workflow id with key + nodesByWorkflowID := map[int64][]int64{} + for _, resp := range res { + nodesByWorkflowID[resp.WorkflowID] = append(nodesByWorkflowID[resp.WorkflowID], resp.NodeID) + } + + for workflowID, nodes := range nodesByWorkflowID { + + count, err := db.SelectInt("SELECT COUNT(id) FROM workflow_notification WHERE workflow_id = $1 and type = $2", workflowID, sdk.VCSUserNotification) + if err != nil { + log.Error("migrate.AddDefaultVCSNotifications> cannot count workflow_notification for workflow id %d", workflowID) + continue + } + if count != 0 { + continue + } + + notif := sdk.WorkflowNotification{ + Settings: sdk.UserNotificationSettings{ + Template: &sdk.UserNotificationTemplate{ + Body: sdk.DefaultWorkflowNodeRunReport, + }, + }, + WorkflowID: workflowID, + Type: sdk.VCSUserNotification, + NodeIDs: nodes, + } + dbNotif := workflow.Notification(notif) + + tx, err := db.Begin() + if err != nil { + log.Error("migrate.AddDefaultVCSNotifications> cannot begin transaction for workflow id %d : %v", workflowID, err) + continue + } + + //Insert the notification + if err := tx.Insert(&dbNotif); err != nil { + _ = tx.Rollback() + log.Error("migrate.AddDefaultVCSNotifications> Unable to insert workflow notification : %v", err) + continue + } + notif.ID = dbNotif.ID + + //Insert associations with sources + query := "INSERT INTO workflow_notification_source(workflow_notification_id, node_id) VALUES ($1, $2) ON CONFLICT DO NOTHING" + for i := range notif.NodeIDs { + if _, err := tx.Exec(query, notif.ID, notif.NodeIDs[i]); err != nil { + _ = tx.Rollback() + log.Error("migrate.AddDefaultVCSNotifications> Unable to insert associations between node %d and notification %d : %v", notif.NodeIDs[i], notif.ID, err) + continue + } + } + + if err := tx.Commit(); err != nil { + _ = tx.Rollback() + log.Error("migrate.AddDefaultVCSNotifications> cannot commit transaction for workflow id %d : %v", workflowID, err) + } + } + + return nil +} diff --git a/engine/api/notification.go b/engine/api/notification.go index e56bba6711..2ff2a0715f 100644 --- a/engine/api/notification.go +++ b/engine/api/notification.go @@ -27,6 +27,11 @@ func (api *API) getUserNotificationTypeHandler() service.Handler { SendToGroups: &sdk.False, Template: &sdk.UserNotificationTemplateJabber, }, + sdk.VCSUserNotification: { + Template: &sdk.UserNotificationTemplate{ + Body: sdk.DefaultWorkflowNodeRunReport, + }, + }, }, http.StatusOK) } } diff --git a/engine/api/workflow/dao.go b/engine/api/workflow/dao.go index 0ca3a71345..b689a11bb7 100644 --- a/engine/api/workflow/dao.go +++ b/engine/api/workflow/dao.go @@ -715,14 +715,43 @@ func Insert(db gorp.SqlExecutor, store cache.Store, w *sdk.Workflow, p *sdk.Proj return sdk.WrapError(err, "Insert> Unable to insert Workflow Data") } + customVcsNotif := false // Insert notifications for i := range w.Notifications { n := &w.Notifications[i] + if n.Type == sdk.VCSUserNotification { + customVcsNotif = true + } if err := InsertNotification(db, w, n); err != nil { return sdk.WrapError(err, "Unable to insert update workflow(%d) notification (%#v)", w.ID, n) } } + if !customVcsNotif { + notif := sdk.WorkflowNotification{ + Settings: sdk.UserNotificationSettings{ + Template: &sdk.UserNotificationTemplate{ + Body: sdk.DefaultWorkflowNodeRunReport, + }, + }, + WorkflowID: w.ID, + Type: sdk.VCSUserNotification, + } + for _, node := range w.WorkflowData.Array() { + if node.IsLinkedToRepo(w) { + if node.Ref == "" { + node.Ref = node.Name + } + notif.SourceNodeRefs = append(notif.SourceNodeRefs, node.Ref) + } + } + if len(notif.SourceNodeRefs) > 0 { + if err := InsertNotification(db, w, ¬if); err != nil { + return sdk.WrapError(err, "Unable to insert VCS workflow(%d) notification (%#v)", w.ID, notif) + } + } + } + dbWorkflow := Workflow(*w) if err := dbWorkflow.PostUpdate(db); err != nil { return sdk.WrapError(err, "Insert> Unable to create workflow data") diff --git a/engine/api/workflow/dao_notification.go b/engine/api/workflow/dao_notification.go index ebc298d616..a8e67585bf 100644 --- a/engine/api/workflow/dao_notification.go +++ b/engine/api/workflow/dao_notification.go @@ -2,6 +2,7 @@ package workflow import ( "database/sql" + "github.com/go-gorp/gorp" "github.com/ovh/cds/engine/api/database/gorpmapping" "github.com/ovh/cds/sdk" @@ -58,12 +59,29 @@ func loadNotification(db gorp.SqlExecutor, w *sdk.Workflow, id int64) (sdk.Workf if notifNode != nil { n.SourceNodeRefs = append(n.SourceNodeRefs, notifNode.Name) } - } return n, nil } +func loadVCSNotificationWithNodeID(db gorp.SqlExecutor, workflowID, nodeID int64) (sdk.WorkflowNotification, error) { + dbnotif := Notification{} + query := `SELECT workflow_notification.* + FROM workflow_notification + JOIN workflow_notification_source ON workflow_notification.id = workflow_notification_source.workflow_notification_id + WHERE workflow_notification.workflow_id = $1 AND workflow_notification_source.node_id = $2 AND workflow_notification.type = $3` + //Load the notification + if err := db.SelectOne(&dbnotif, query, workflowID, nodeID, sdk.VCSUserNotification); err != nil { + if err == sql.ErrNoRows { + return sdk.WorkflowNotification{}, nil + } + return sdk.WorkflowNotification{}, sdk.WrapError(err, "Unable to load notification for workflow id %d and node id %d", workflowID, nodeID) + } + dbnotif.WorkflowID = workflowID + + return sdk.WorkflowNotification(dbnotif), nil +} + func InsertNotification(db gorp.SqlExecutor, w *sdk.Workflow, n *sdk.WorkflowNotification) error { n.WorkflowID = w.ID n.ID = 0 @@ -122,5 +140,6 @@ func (no *Notification) PostGet(db gorp.SqlExecutor) error { if err := gorpmapping.JSONNullString(res, &no.Settings); err != nil { return sdk.WrapError(err, "cannot parse user notification") } + return nil } diff --git a/engine/api/workflow/workflow_run_event.go b/engine/api/workflow/workflow_run_event.go index d3c4533d98..3fe5f20f73 100644 --- a/engine/api/workflow/workflow_run_event.go +++ b/engine/api/workflow/workflow_run_event.go @@ -199,6 +199,21 @@ func sendVCSEventStatus(ctx context.Context, db gorp.SqlExecutor, store cache.St if !node.IsLinkedToRepo(&wr.Workflow) { return nil } + + notif, errN := loadVCSNotificationWithNodeID(db, wr.WorkflowID, node.ID) + if errN != nil { + return sdk.WrapError(errN, "cannot load notification") + } + + // vcs notification not enabled + if notif.ID == 0 { + return nil + } + + if nodeRun.VCSReport == "" { + nodeRun.VCSReport = notif.Settings.Template.Body + } + app = wr.Workflow.Applications[node.Context.ApplicationID] if node.Context.PipelineID > 0 { pip = wr.Workflow.Pipelines[node.Context.PipelineID] @@ -314,7 +329,7 @@ func sendVCSEventStatus(ctx context.Context, db gorp.SqlExecutor, store cache.St return fmt.Errorf("sendEvent> err:%s", err) } - if vcsConf.Type != "gerrit" { + if vcsConf.Type != "gerrit" && (notif.Settings.Template.DisableComment == nil || !*notif.Settings.Template.DisableComment) { //Check if this branch and this commit is a pullrequest prs, err := client.PullRequests(ctx, app.RepositoryFullname) if err != nil { diff --git a/sdk/exportentities/workflow_notif.go b/sdk/exportentities/workflow_notif.go index ba30aeb6b1..e8932522a1 100644 --- a/sdk/exportentities/workflow_notif.go +++ b/sdk/exportentities/workflow_notif.go @@ -67,7 +67,9 @@ func craftNotificationEntry(w sdk.Workflow, notif sdk.WorkflowNotification) ([]s entry.Settings.Template.Body = "" } if entry.Settings.Template.Body == "" && entry.Settings.Template.Subject == "" { - entry.Settings.Template = nil + if entry.Settings.Template.DisableComment == nil || !*entry.Settings.Template.DisableComment { + entry.Settings.Template = nil + } } } @@ -177,7 +179,10 @@ func ProcessNotificationValues(notif NotificationEntry) (sdk.WorkflowNotificatio n.Settings.Template.Subject = defaultTemplate.Subject } if n.Settings.Template.Body == "" { - n.Settings.Template.Subject = defaultTemplate.Body + n.Settings.Template.Body = defaultTemplate.Body + } + if n.Settings.Template.DisableComment == nil || !*n.Settings.Template.DisableComment { + n.Settings.Template.DisableComment = nil } } return n, nil diff --git a/sdk/exportentities/workflow_notif_test.go b/sdk/exportentities/workflow_notif_test.go index 314065b12a..ba81d3441b 100644 --- a/sdk/exportentities/workflow_notif_test.go +++ b/sdk/exportentities/workflow_notif_test.go @@ -84,6 +84,10 @@ notifications: Details : {{.cds.buildURL}} Triggered by : {{.cds.triggered_by.username}} Branch : {{.git.branch}} + - type: vcs + settings: + template: + disable_comment: true `, }, }, diff --git a/sdk/notif.go b/sdk/notif.go index b45b406636..f3e53daa4d 100644 --- a/sdk/notif.go +++ b/sdk/notif.go @@ -1,9 +1,19 @@ package sdk +import ( + "bytes" + "text/template" + "time" + + "github.com/ovh/cds/sdk/interpolate" + "github.com/ovh/venom" +) + //const const ( EmailUserNotification = "email" JabberUserNotification = "jabber" + VCSUserNotification = "vcs" ) //const @@ -38,6 +48,8 @@ type UserNotificationSettings struct { type UserNotificationTemplate struct { Subject string `json:"subject,omitempty" yaml:"subject,omitempty"` Body string `json:"body,omitempty" yaml:"body,omitempty"` + // For VCS + DisableComment *bool `json:"disable_comment,omitempty" yaml:"disable_comment,omitempty"` } //userNotificationInput is a way to parse notification @@ -69,5 +81,73 @@ Branch : {{.git.branch | default "n/a"}}`, UserNotificationTemplateMap = map[string]UserNotificationTemplate{ EmailUserNotification: UserNotificationTemplateEmail, JabberUserNotification: UserNotificationTemplateJabber, + VCSUserNotification: UserNotificationTemplate{ + Body: DefaultWorkflowNodeRunReport, + }, } ) + +const DefaultWorkflowNodeRunReport = `[[- if .Stages ]] +CDS Report [[.WorkflowNodeName]]#[[.Number]].[[.SubNumber]] [[ if eq .Status "Success" -]] ✔ [[ else ]][[ if eq .Status "Fail" -]] ✘ [[ else ]][[ if eq .Status "Stopped" -]] ■ [[ else ]]- [[ end ]] [[ end ]] [[ end ]] +[[- range $s := .Stages]] +[[- if $s.RunJobs ]] +* [[$s.Name]] +[[- range $j := $s.RunJobs]] + * [[$j.Job.Action.Name]] [[ if eq $j.Status "Success" -]] ✔ [[ else ]][[ if eq $j.Status "Fail" -]] ✘ [[ else ]][[ if eq $j.Status "Stopped" -]] ■ [[ else ]]- [[ end ]] [[ end ]] [[ end ]] +[[- end]] +[[- end]] +[[- end]] +[[- end]] + +[[- if .Tests ]] +[[- if gt .Tests.TotalKO 0]] +Unit Tests Report + +[[- range $ts := .Tests.TestSuites]] +* [[ $ts.Name ]] +[[range $tc := $ts.TestCases]] + [[- if or ($tc.Errors) ($tc.Failures) ]] * [[ $tc.Name ]] ✘ [[- end]] +[[end]] +[[- end]] +[[- end]] +[[- end]] +` + +func (nr WorkflowNodeRun) Report() (string, error) { + reportStr := DefaultWorkflowNodeRunReport + if nr.VCSReport != "" { + reportStr = nr.VCSReport + } + + tmpl, err := template.New("vcsreport").Delims("[[", "]]").Funcs(interpolate.InterpolateHelperFuncs).Parse(reportStr) + if err != nil { + return "", WrapError(err, "cannot create new template for first part") + } + + nrData := struct { + WorkflowNodeName string + Status string + Number int64 + SubNumber int64 + Stages []Stage + Start time.Time + Done time.Time + Tests *venom.Tests + }{ + WorkflowNodeName: nr.WorkflowNodeName, + Status: nr.Status, + Number: nr.Number, + SubNumber: nr.SubNumber, + Stages: nr.Stages, + Start: nr.Start, + Done: nr.Done, + Tests: nr.Tests, + } + + outFirst := new(bytes.Buffer) + if err := tmpl.Execute(outFirst, nrData); err != nil { + return "", WrapError(err, "cannot execute template for first part") + } + + return interpolate.Do(outFirst.String(), ParametersToMap(nr.BuildParameters)) +} diff --git a/sdk/workflow_run.go b/sdk/workflow_run.go index 74e4957824..eb8a446d52 100644 --- a/sdk/workflow_run.go +++ b/sdk/workflow_run.go @@ -1,9 +1,7 @@ package sdk import ( - "bytes" "fmt" - "html/template" "net/url" "sort" "strings" @@ -242,6 +240,7 @@ type WorkflowNodeRun struct { HookExecutionTimeStamp int64 `json:"hook_execution_timestamp,omitempty"` HookExecutionID string `json:"execution_id,omitempty"` Callback *WorkflowNodeOutgoingHookRunCallback `json:"callback,omitempty"` + VCSReport string `json:"vcs_report,omitempty"` } // WorkflowNodeOutgoingHookRunCallback is the callback coming from hooks uservice avec an outgoing hook execution @@ -454,43 +453,6 @@ func (w *WorkflowNodeRunArtifact) GetPath() string { return container } -const workflowNodeRunReport = `{{- if .Stages }} -CDS Report {{.WorkflowNodeName}}#{{.Number}}.{{.SubNumber}} {{ if eq .Status "Success" -}} ✔ {{ else }}{{ if eq .Status "Fail" -}} ✘ {{ else }}{{ if eq .Status "Stopped" -}} ■ {{ else }}- {{ end }} {{ end }} {{ end }} -{{- range $s := .Stages}} -{{- if $s.RunJobs }} -* {{$s.Name}} -{{- range $j := $s.RunJobs}} - * {{$j.Job.Action.Name}} {{ if eq $j.Status "Success" -}} ✔ {{ else }}{{ if eq $j.Status "Fail" -}} ✘ {{ else }}{{ if eq $j.Status "Stopped" -}} ■ {{ else }}- {{ end }} {{ end }} {{ end }} -{{- end}} -{{- end}} -{{- end}} -{{- end}} - -{{- if .Tests }} -{{- if gt .Tests.TotalKO 0}} -Unit Tests Report - -{{- range $ts := .Tests.TestSuites}} -* {{ $ts.Name }} -{{range $tc := $ts.TestCases}} - {{- if or ($tc.Errors) ($tc.Failures) }} * {{ $tc.Name }} ✘ {{- end}} -{{end}} -{{- end}} -{{- end}} -{{- end}} -` - -func (nr WorkflowNodeRun) Report() (string, error) { - t := template.New("") - t, err := t.Parse(workflowNodeRunReport) - if err != nil { - return "", err - } - out := new(bytes.Buffer) - errE := t.Execute(out, nr) - return out.String(), errE -} - type WorkflowQueue []WorkflowNodeJobRun func (q WorkflowQueue) Sort() { diff --git a/ui/src/app/model/workflow.model.ts b/ui/src/app/model/workflow.model.ts index a9d315251c..6cfff6f2b4 100644 --- a/ui/src/app/model/workflow.model.ts +++ b/ui/src/app/model/workflow.model.ts @@ -698,7 +698,7 @@ export class WorkflowPullItem { value: string; } -export const notificationTypes = ['jabber', 'email']; +export const notificationTypes = ['jabber', 'email', 'vcs']; export const notificationOnSuccess = ['always', 'change', 'never']; export const notificationOnFailure = ['always', 'change', 'never']; @@ -741,4 +741,5 @@ export class UserNotificationSettings { export class UserNotificationTemplate { subject: string; body: string; + disable_comment: boolean; } diff --git a/ui/src/app/shared/workflow/modal/node-add/workflow.trigger.component.ts b/ui/src/app/shared/workflow/modal/node-add/workflow.trigger.component.ts index 17d0f9e442..ecb83b7dd3 100644 --- a/ui/src/app/shared/workflow/modal/node-add/workflow.trigger.component.ts +++ b/ui/src/app/shared/workflow/modal/node-add/workflow.trigger.component.ts @@ -15,12 +15,12 @@ import cloneDeep from 'lodash-es/cloneDeep'; }) export class WorkflowTriggerComponent { - @ViewChild('triggerModal', {static: false}) + @ViewChild('triggerModal', { static: false }) triggerModal: ModalTemplate; modal: SuiActiveModal; - @ViewChild('nodeWizard', {static: false}) + @ViewChild('nodeWizard', { static: false }) nodeWizard: WorkflowNodeAddWizardComponent; - @ViewChild('worklflowAddOutgoingHook', {static: false}) + @ViewChild('worklflowAddOutgoingHook', { static: false }) worklflowAddOutgoingHook: WorkflowWizardOutgoingHookComponent; @Output() triggerEvent = new EventEmitter(); diff --git a/ui/src/app/views/workflow/show/notification/form/workflow.notification.form.component.ts b/ui/src/app/views/workflow/show/notification/form/workflow.notification.form.component.ts index ac2b20c4ca..482c072a9d 100644 --- a/ui/src/app/views/workflow/show/notification/form/workflow.notification.form.component.ts +++ b/ui/src/app/views/workflow/show/notification/form/workflow.notification.form.component.ts @@ -1,15 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Project } from 'app/model/project.model'; -import { - notificationOnFailure, - notificationOnSuccess, - notificationTypes, - WNode, - WNodeType, - Workflow, - WorkflowNotification, - WorkflowTriggerConditionCache -} from 'app/model/workflow.model'; +// tslint:disable-next-line: max-line-length +import { notificationOnFailure, notificationOnSuccess, notificationTypes, WNode, WNodeType, Workflow, WorkflowNotification, WorkflowTriggerConditionCache } from 'app/model/workflow.model'; import { NotificationService } from 'app/service/notification/notification.service'; import cloneDeep from 'lodash-es/cloneDeep'; import { finalize, first } from 'rxjs/operators'; @@ -43,6 +35,7 @@ export class WorkflowNotificationFormComponent implements OnInit { notifOnSuccess: Array; notifOnFailure: Array; selectedUsers: string; + commentEnabled = true; nodeError = false; loadingNotifTemplate = false; triggerConditions: WorkflowTriggerConditionCache; @@ -97,6 +90,10 @@ export class WorkflowNotificationFormComponent implements OnInit { }); } + if (this.notification && this.notification.type === 'vcs') { + this.commentEnabled = !this.notification.settings.template.disable_comment; + } + } formatNode(): void { @@ -117,9 +114,12 @@ export class WorkflowNotificationFormComponent implements OnInit { this.loading = true; - if (this.selectedUsers) { + if (this.selectedUsers != null) { this.notification.settings.recipients = this.selectedUsers.split(','); } + if (this.notification.type === 'vcs') { + this.notification.settings.template.disable_comment = !this.commentEnabled; + } this.updatedNotification.emit(this.notification); } diff --git a/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html b/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html index 3245747b3d..ad025e4eac 100644 --- a/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html +++ b/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html @@ -26,67 +26,84 @@ -
-
- - - - + +
+
+ + + + + + + + +
+
+ + + - - +
+
+ + {{ 'workflow_notification_on_start' | translate}} + +
-
- - - - - +
+
+ + + +
+
+ + {{ 'workflow_notification_to_group' | translate}} + +
+
+ + {{ 'workflow_notification_to_initiator' | translate}} + +
-
- - {{ 'workflow_notification_on_start' | translate}} - +
+ +
-
-
-
- - - +
+ +
-
- - {{ 'workflow_notification_to_group' | translate}} - +
+ + +
-
- - {{ 'workflow_notification_to_initiator' | translate}} + + +
+ + {{ 'workflow_notification_vcs_comment_enabled' | translate}}
-
-
- - -
-
- - -
-
- - - -
+
+ + +
+ + + { this.loadingNotifTemplate = false; this._cd.markForCheck(); - })).subscribe(data => { if (data && data[this.newNotification.type]) { this.newNotification.settings = data[this.newNotification.type]; diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 51f0763f65..855f347684 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -830,6 +830,8 @@ "workflow_notification_email_user": "Mails", "workflow_notification_form": "Add a notification", "workflow_notification_copy": "Copy", + "workflow_notification_vcs_comment_enabled": "Pull-request's comment enabled", + "workflow_notification_vcs_pr_comment_body": "Pull-request's comment body", "workflow_notification_explanation": "_A user notification can be useful to report the status of a workflow according to its status. Each pipeline in a workflow can be notified based on status in 'Success', 'Fail' or status change. The message sent to the recipients can be set using [CDS variables] (https://ovh.github.io/cds/docs/concepts/variables/). E-mail notifications can also contain HTML, cf. [User Notifications] documentation (https://ovh.github.io/cds/docs/concepts/workflow/notifications/) ._", "workflow_event_explanation": "_Here you can configure one or more integrations of type `Event`. This allows you to send all technical data in a backend to make it accessible by third-party applications such as Kafka or ElasticSearch. See the [Event Notifications] (https://ovh.github.io/cds/docs/concepts/workflow/notifications/) documentation for more information._", "workflow_no_event_integration": "You haven't any event integration on your project.", diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 0f0b700a00..f7e0c2b13f 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -830,7 +830,9 @@ "workflow_notification_email_user": "E-mails", "workflow_notification_form": "Ajouter une notification", "workflow_notification_copy": "Copié", - "workflow_notification_explanation": "_Une notification utilisateur peut être utile pour signaler le statut d'un workflow en fonction de son état. Chaque pipeline d'un workflow peut donner lieu à une notification en fonction de son statut en `Succès`, `En Echec` ou sur changement de statut. Le message envoyé aux destinataires peut être paramétré à l'aide de [variables CDS](https://ovh.github.io/cds/docs/concepts/variables/). Les notifications de type mail peuvent également contenir du HTML, cf. documentation [User Notifications](https://ovh.github.io/cds/docs/concepts/workflow/notifications/)._", + "workflow_notification_vcs_comment_enabled": "Commentaire de pull-request activé", + "workflow_notification_vcs_pr_comment_body": "Contenu du commentaire de pull-request", + "workflow_notification_explanation": "_Une notification utilisateur peut être utile pour signaler le status d'un workflow en fonction de son état. Chaque pipeline d'un workflow peut donner lieu à une notification en fonction de sont statut en `Succès`, `En Echec` ou sur changement de statut. Le message envoyé aux destinataires peut être paramétré à l'aide de [variables CDS](https://ovh.github.io/cds/docs/concepts/variables/). Les notifications de type mail peuvent également contenir du HTML, cf. documentation [User Notifications](https://ovh.github.io/cds/docs/concepts/workflow/notifications/)._", "workflow_event_explanation": "_Vous pouvez configurer ici une ou plusieurs intégrations de type `Event`. Cela vous permet d'envoyer toutes les données techniques dans un backend afin de les rendre accessibles par des applications tierces telles que Kafka ou ElasticSearch. Consultez la documentation [Event Notifications](https://ovh.github.io/cds/docs/concepts/workflow/notifications/) pour plus d'informations._", "workflow_no_event_integration": "Vous n'avez pas d'intégration de type évènement sur votre projet.", "workflow_event_form": "Intégrations - Évènements", @@ -893,4 +895,4 @@ "workflow_error_unknown_key_description": "Veuillez vérifier que la clé SSH avec laquelle vous avez lié votre application à votre dépôt git est bien active et indiqué sur votre dépôt git pour avoir les accès", "workflow_error_bad_vcs_strategy_title": "Mauvaise configuration des paramètres VCS de votre application", "workflow_error_bad_vcs_strategy_description": "Veuillez vérifier que la clé SSH avec laquelle vous avez lié votre application à votre dépôt git est bien active et indiqué sur votre dépôt git pour avoir les accès. Veuillez aussi vérifier que vous ayez bien une clé ssh de mentionnée dans votre fichier yaml d'application pointant sur votre dépôt git si vous avez mis le mode de clone SSH." -} +} \ No newline at end of file