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

Email alerts #287

Merged
merged 11 commits into from
Mar 13, 2017
5 changes: 3 additions & 2 deletions api/projects/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ func MustBeAdmin(c *gin.Context) {
func UpdateProject(c *gin.Context) {
project := c.MustGet("project").(models.Project)
var body struct {
Name string `json:"name"`
Name string `json:"name"`
Alert bool `json:"alert"`
}

if err := c.Bind(&body); err != nil {
return
}

if _, err := database.Mysql.Exec("update project set name=? where id=?", body.Name, project.ID); err != nil {
if _, err := database.Mysql.Exec("update project set name=?, alert=? where id=?", body.Name, body.Alert, project.ID); err != nil {
panic(err)
}

Expand Down
71 changes: 71 additions & 0 deletions api/tasks/alert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package tasks

import (
"bytes"
"html/template"
"strconv"

"github.com/ansible-semaphore/semaphore/models"
"github.com/ansible-semaphore/semaphore/util"
)

const emailTemplate = `Subject: Task '{{ .Alias }}' failed

Task {{ .TaskId }} with template '{{ .Alias }}' was failed!
Task log: <a href='{{ .TaskUrl }}'>{{ .TaskUrl }}</a>`

type Alert struct {
TaskId string
Alias string
TaskUrl string
}

func (t *task) sendMailAlert() {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code block (not specific to Go) could be rewritten to be more readable

Example:

if true {
  do something
} else {
  do the other stuff
}

Could be rewritten to be more expressive as:

if false {
  do something
  return
}

do the other stuff

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if util.Config.EmailAlert == true {

if t.alert == true {

mailHost := util.Config.EmailHost + ":" + util.Config.EmailPort

var mailBuffer bytes.Buffer
alert := Alert{TaskId: strconv.Itoa(t.task.ID), Alias: t.template.Alias, TaskUrl: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID)}
tpl := template.New("mail body template")
tpl, err := tpl.Parse(emailTemplate)
err = tpl.Execute(&mailBuffer, alert)

if err != nil {
t.log("Can't generate alert template!")
panic(err)
}

for _, user := range t.users {

userObj, err := models.FetchUser(user)

if userObj.Alert == true {
if err != nil {
t.log("Can't find user Email!")
panic(err)
}

t.log("Sending email to " + userObj.Email + " from " + util.Config.EmailSender)
err = util.SendMail(mailHost, util.Config.EmailSender, userObj.Email, mailBuffer)
if err != nil {
t.log("Can't send email!")
t.log("Error: " + err.Error())
panic(err)
}

} else {
t.log("Alerts disabled for user " + userObj.Name + ", nothing to do.")
}
}
} else {
t.log("Alerts disabled for this project, nothing to do.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if alerts are disabled, there's no need to log them :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
} else {
t.log("Alerts globally disabled, nothing to do.")
}

}
7 changes: 7 additions & 0 deletions api/tasks/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ type task struct {
environment models.Environment
users []int
projectID int
alert bool
}

func (t *task) fail() {
t.task.Status = "error"
t.updateStatus()
t.sendMailAlert()
}

func (t *task) run() {
Expand Down Expand Up @@ -144,6 +146,11 @@ func (t *task) populateDetails() error {
return err
}

//get project alert setting
if err := t.fetch("Alert setting not found!", &t.alert, "select alert from project where id=?", t.template.ProjectID); err != nil {
return err
}

// get project users
var users []struct {
ID int `db:"id"`
Expand Down
2 changes: 1 addition & 1 deletion api/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func updateUser(c *gin.Context) {
return
}

if _, err := database.Mysql.Exec("update user set name=?, username=?, email=? where id=?", user.Name, user.Username, user.Email, oldUser.ID); err != nil {
if _, err := database.Mysql.Exec("update user set name=?, username=?, email=?, alert=? where id=?", user.Name, user.Username, user.Email, user.Alert, oldUser.ID); err != nil {
panic(err)
}

Expand Down
2 changes: 2 additions & 0 deletions db/migrations/v2.3.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE user ADD alert BOOLEAN NOT NULL AFTER password;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if BOOLEAN type is new, check compatibility with versions of mysql/mariadb listed in wiki

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOOLEAN supported at least since MySQL 5.5 - https://dev.mysql.com/doc/refman/5.5/en/numeric-type-overview.html

ALTER TABLE project ADD alert BOOLEAN NOT NULL AFTER name;
1 change: 1 addition & 0 deletions db/versionHistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ func init() {
{Major: 1, Minor: 7},
{Major: 1, Minor: 8},
{Major: 1, Minor: 9},
{Major: 2, Minor: 3},
}
}
3 changes: 2 additions & 1 deletion make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ if [ "$1" == "ci_test" ]; then
"name": "circle_test"
},
"session_db": "127.0.0.1:6379",
"port": ":8010"
"port": ":8010",
"email_alert": false
}
EOF

Expand Down
1 change: 1 addition & 0 deletions models/Project.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Project struct {
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name" binding:"required"`
Created time.Time `db:"created" json:"created"`
Alert bool `db:"alert" json:"alert"`
}

func (project *Project) CreateProject() error {
Expand Down
1 change: 1 addition & 0 deletions models/User.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type User struct {
Name string `db:"name" json:"name" binding:"required"`
Email string `db:"email" json:"email" binding:"required"`
Password string `db:"password" json:"-"`
Alert bool `db:"alert" json:"alert"`
}

func FetchUser(userID int) (*User, error) {
Expand Down
6 changes: 5 additions & 1 deletion public/html/projects/edit.pug
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ form.form-horizontal
.col-sm-6
input.form-control(type="text" ng-model="projectName" placeholder="Project Name")

.form-group
label.control-label.col-sm-4 Allow alerts for this project
.col-sm-1: input.form-control(type="checkbox" title="Send email alerts about failed tasks" ng-model="alert")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the docs for checkboxes in bootstrap: .checkbox class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made it a bit less ugly


.form-group
.col-sm-6.col-sm-offset-4
button.btn.btn-success(ng-click="save(projectName)") Save
button.btn.btn-success(ng-click="save(projectName, alert)") Save

hr

Expand Down
3 changes: 3 additions & 0 deletions public/html/users/user.pug
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
.form-group
label.control-label.col-sm-4 Password
.col-sm-8: input.form-control(type="password" placeholder="Enter new password" ng-model="user.password")
.form-group
label.control-label.col-sm-4 Send alerts
.col-sm-2: input.form-control(type="checkbox" title="Send email alerts about failed tasks" ng-model="user.alert")
.form-group: .col-sm-8.col-sm-offset-4
button.btn.btn-success(ng-click="updateUser()") Update Profile
button.btn.btn-default(ng-if="$state.includes('users.user')" ui-sref="users.list") back
Expand Down
8 changes: 6 additions & 2 deletions public/js/controllers/projects/edit.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
define(function () {
app.registerController('ProjectEditCtrl', ['$scope', '$http', 'Project', '$state', function ($scope, $http, Project, $state) {
$scope.projectName = Project.name;
$scope.alert = Project.alert;

$scope.save = function (name) {
$http.put(Project.getURL(), { name: name }).success(function () {
console.log(Project.name);
console.log(Project);

$scope.save = function (name, alert) {
$http.put(Project.getURL(), { name: name, alert: alert }).success(function () {
swal('Saved', 'Project settings saved.', 'success');
}).error(function () {
swal('Error', 'Project settings were not saved', 'error');
Expand Down
1 change: 1 addition & 0 deletions public/js/factories/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ app.factory('ProjectFactory', ['$http', function ($http) {
var Project = function (project) {
this.id = project.id;
this.name = project.name;
this.alert = project.alert;
}

Project.prototype.getURL = function () {
Expand Down
1 change: 1 addition & 0 deletions semaphore-startup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ${SEMAPHORE_ADMIN}
${SEMAPHORE_ADMIN_EMAIL}
${SEMAPHORE_ADMIN_NAME}
${SEMAPHORE_ADMIN_PASSWORD}
n
EOF
fi

Expand Down
Loading