This commit is contained in:
Jackie Elliott 2018-08-10 16:17:42 -07:00
Родитель cae05ad228
Коммит ab7be770af
19 изменённых файлов: 593 добавлений и 221 удалений

19
Gopkg.lock сгенерированный
Просмотреть файл

@ -4,8 +4,8 @@
[[projects]]
name = "cloud.google.com/go"
packages = ["civil"]
revision = "aad3f485ee528456e0768f20397b4d9dd941e755"
version = "v0.25.0"
revision = "64a2037ec6be8a4b0c1d1f706ed35b428b989239"
version = "v0.26.0"
[[projects]]
branch = "master"
@ -36,8 +36,8 @@
"services/servicebus/mgmt/2017-04-01/servicebus",
"version"
]
revision = "fad8443a79b0e755c18c3bec29a8d2bedab0b421"
version = "v15.3.0"
revision = "4e8cbbfb1aeab140cd0fa97fd16b64ee18c3ca6a"
version = "v19.1.0"
[[projects]]
name = "github.com/Azure/azure-service-bus-go"
@ -45,8 +45,7 @@
".",
"atom"
]
revision = "88613d3356e8ce24659eb44cc5bd6a34e250703a"
version = "v0.1.0"
revision = "4eb9c25e4b2f11d1bfab85cd415c3d703f7358ba"
[[projects]]
name = "github.com/Azure/buffalo-azure"
@ -560,7 +559,7 @@
"md4",
"ssh/terminal"
]
revision = "80fca2ff14a3fb7db622c9a5462f8ce9ea026cba"
revision = "f027049dab0ad238e394a753dba2d14753473a04"
[[projects]]
branch = "master"
@ -570,7 +569,7 @@
"html",
"html/atom"
]
revision = "f4c29de78a2a91c00474a2e689954305c350adf9"
revision = "19491d39cadbd9cd33f26ca22cc89ba4ba38251c"
[[projects]]
branch = "master"
@ -585,7 +584,7 @@
"unix",
"windows"
]
revision = "2be389f392cd91df245b41638f818a1c041f0c08"
revision = "acbc56fc7007d2a01796d5bde54f39e3b3e95945"
[[projects]]
name = "google.golang.org/appengine"
@ -628,6 +627,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "69ab168166b6ab604139c01a8547de8f0893dce5490dd00b9edd4a4a840fbd75"
inputs-digest = "0868c77c0d85e31091d756abe875eb679d8521106e70cafbdcd032a252e0b0d3"
solver-name = "gps-cdcl"
solver-version = 1

Просмотреть файл

@ -27,7 +27,7 @@
[[constraint]]
name = "github.com/Azure/azure-service-bus-go"
version = "0.1.0"
revision = "4eb9c25e4b2f11d1bfab85cd415c3d703f7358ba"
[[constraint]]
name = "github.com/Azure/buffalo-azure"

Просмотреть файл

@ -2,7 +2,6 @@ package actions
import (
"context"
"log"
"os"
"github.com/gobuffalo/buffalo"
@ -64,7 +63,6 @@ func App() *buffalo.App {
//Subscribe to eventgrid
eventgrid.RegisterSubscriber(app, "/specgithub", NewSpecgithubSubscriber(&eventgrid.BaseSubscriber{}))
log.Printf("Created handler")
//Create AMQP Listener
messages.ReceiveFromQueue(context.Background(), os.Getenv("CUSTOMCONNSTR_SERVICEBUS_CONNECTION_STRING"))

Просмотреть файл

@ -39,7 +39,7 @@ func (s *SpecgithubSubscriber) ReceivePullRequestEvent(c buffalo.Context, e even
if err := json.Unmarshal(e.Data, &payload); err != nil {
return c.Error(http.StatusBadRequest, errors.New("unable to unmarshal request data"))
}
messages.CheckAcknowledgement(payload)
messages.CheckAcknowledgement(c, payload)
// Replace the code below with your logic
return c.Render(200, render.JSON(map[string]string{"message": "Hopefully this works"}))
@ -53,7 +53,7 @@ func (s *SpecgithubSubscriber) ReceiveIssueCommentEvent(c buffalo.Context, e eve
return c.Error(http.StatusBadRequest, errors.New("unable to unmarshal request data"))
}
c.Logger().Debug("Check acknowledgement of comment on PR")
messages.CheckAcknowledgementComment(payload)
messages.CheckAcknowledgementComment(c, payload)
// Replace the code below with your logic
return c.Render(200, render.JSON(map[string]string{"message": "Hopefully this works"}))
@ -67,7 +67,7 @@ func (s *SpecgithubSubscriber) ReceiveLabelEvent(c buffalo.Context, e eventgrid.
return c.Error(http.StatusBadRequest, errors.New("unable to unmarshal request data"))
}
c.Logger().Debug("Check acknowledgement of comment on PR")
messages.CheckAcknowledgementLabel(payload)
messages.CheckAcknowledgementLabel(c, payload)
// Replace the code below with your logic
return c.Render(200, render.JSON(map[string]string{"message": "Hopefully this works"}))

Просмотреть файл

@ -1,5 +1,5 @@
development:
url: {{env "CUSTOMCONNSTR_DATABASE_URL"}}
url: {{envOr "CUSTOMCONNSTR_DATABASE_URL" "postgres://postgres:postgres@127.0.0.1:5432/spec_sla_bot_test?sslmode=disable"}}
test:
url: {{envOr "TEST_DATABASE_URL" "postgres://postgres:postgres@127.0.0.1:5432/spec_sla_bot_test?sslmode=disable"}}

Просмотреть файл

@ -1,6 +1,8 @@
package messages
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
@ -13,61 +15,114 @@ import (
"github.com/google/go-github/github"
)
var currentExpireTime time.Time
//SLAQueue name
const SLAQueue = "24hrgitevents"
func CheckAcknowledgement(event github.PullRequestEvent) {
if checkClosed(event, models.DB) || checkUnassigned(event, models.DB) || (event.PullRequest.Assignee == nil && checkOpened(event, models.DB)) {
err := UpsertPullRequestEntry(event, models.DB, false, time.Time{})
//MessageContent sent to service bus
type MessageContent struct {
PRID int64
HTMLURL string
//This should be an array due to the potential of multiple assignees
AssigneeLogin string
ManagerEmailReminder bool
}
//CheckAcknowledgement determines if a PullRequestEvent is a valid acknowledment of a PR
func CheckAcknowledgement(ctx context.Context, event github.PullRequestEvent) error {
if checkClosed(ctx, event, models.DB) || checkUnassigned(ctx, event, models.DB) || (event.PullRequest.Assignee == nil && checkOpened(ctx, event, models.DB)) {
err := UpsertPullRequestEntry(ctx, event, models.DB, false, time.Time{})
if err != nil {
log.Printf("Unable to update event number %d in CheckAcknowledged", *event.Number)
return fmt.Errorf("unable to update event number %d in CheckAcknowledgement", *event.Number)
}
} else if event.PullRequest.Assignee != nil && (checkAssigned(event, models.DB) || checkReviewed(event, models.DB) || checkEdited(event, models.DB) || checkLabeled(event, models.DB) || checkOpened(event, models.DB)) {
message := fmt.Sprintf("PR id, %d, URL, %s, Assignee, %s", *event.PullRequest.ID, *event.PullRequest.HTMLURL, *event.PullRequest.Assignee.Login)
log.Print(message)
err := SendToQueue(message, currentExpireTime)
log.Print("SENT TO QUEUE")
} else if event.PullRequest.Assignee != nil && (checkAssigned(ctx, event, models.DB) ||
checkReviewed(ctx, event, models.DB) ||
checkEdited(ctx, event, models.DB) ||
checkLabeled(ctx, event, models.DB) ||
checkOpened(ctx, event, models.DB)) {
messageStruct := MessageContent{
PRID: *event.PullRequest.ID,
HTMLURL: *event.PullRequest.HTMLURL,
AssigneeLogin: *event.PullRequest.Assignee.Login,
ManagerEmailReminder: false,
}
message, err := json.Marshal(messageStruct)
if err != nil {
fmt.Errorf("Unable to Marshal message struct for PR %d", *event.PullRequest.ID)
return err
}
err = SendToQueue(ctx, message, expireTime(time.Now()), SLAQueue)
if err != nil {
log.Printf("Message for event %d not delivered", *event.PullRequest.ID)
return err
}
}
return nil
}
func CheckAcknowledgementComment(event github.IssueCommentEvent) {
log.Print("CONNECT TO DEVELOPEMENT DB")
tx, err := pop.Connect("developement")
if err != nil {
log.Printf("Could not connect to the developement database")
} else if event.Issue != nil && event.Issue.IsPullRequest() && checkCommented(event, tx) {
message := fmt.Sprintf("PR id, %d, URL, %s, Assignee, %s", *event.Issue.ID, *event.Issue.URL, *event.Issue.Assignee.Login)
log.Print(message)
err = SendToQueue(message, currentExpireTime)
log.Print("SENT TO QUEUE")
//CheckAcknowledgementComment determines if an IssuesCommentEvent on a PR is a valid acknowledgement
func CheckAcknowledgementComment(ctx context.Context, event github.IssueCommentEvent) error {
if event.Issue != nil && event.Issue.IsPullRequest() && checkCommented(ctx, event, models.DB) {
assignees := event.Issue.Assignees
for _, assignee := range assignees {
id, err := uuid.NewV1()
if err != nil {
return err
}
q := models.DB.RawQuery(`INSERT INTO assignees (id, created_at, updated_at, login, type, html_url)
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (login) DO UPDATE SET html_url = ?`,
id, time.Now(), time.Now(), assignee.Login, assignee.Type, assignee.HTMLURL, assignee.HTMLURL)
exErr := q.Exec()
if exErr != nil {
log.Println("Unable to update event in checkAssigned")
return exErr
}
}
messageStruct := MessageContent{
PRID: *event.Issue.ID,
HTMLURL: *event.Issue.HTMLURL,
AssigneeLogin: *event.Issue.Assignee.Login,
}
message, err := json.Marshal(messageStruct)
if err != nil {
log.Printf("Message for event %d not delivered", *event.Issue.ID)
fmt.Errorf("Unable to Marshal message struct for PR %d", *event.Issue.ID)
return err
}
err = SendToQueue(ctx, message, expireTime(time.Now()), SLAQueue)
if err != nil {
return fmt.Errorf("Message for event %d not delivered: %v", *event.Issue.ID, err)
}
}
return nil
}
func CheckAcknowledgementLabel(event github.LabelEvent) {
log.Printf("Logic to handle a label event not implemented")
//CheckAcknowledgementLabel determines if a LabelEvent on a PR is a valid acknowledgement
func CheckAcknowledgementLabel(ctx context.Context, event github.LabelEvent) error {
panic("not implemented")
}
func updateTime() time.Time {
currentTime := time.Now()
//ExpireTime calculates the time a PR could violate the SLA depending on the current time
func expireTime(currentTime time.Time) time.Time {
//return currentTime.Add(slaDuration(currentTime.Weekday()))
return currentTime.Add(time.Minute * 2)
}
//slaDuration returns the amount of time an assignee has to respond to a PR given the day
func slaDuration(day time.Weekday) time.Duration {
delay := 24 * time.Hour
//Adjusted time for now for testing purposes
//if the current weekday is Friday
if int(currentTime.Weekday()) == 5 {
currentExpireTime := currentTime.Add(time.Minute * time.Duration(1))
return currentExpireTime
if day > time.Thursday {
delay = time.Duration(8-day) * time.Hour * 24
}
currentExpireTime := currentTime.Add(time.Minute * time.Duration(1))
return currentExpireTime
return delay
}
func checkCommented(event github.IssueCommentEvent, tx *pop.Connection) bool {
//CheckCommented determines if the comment on the PR was valid
func checkCommented(ctx context.Context, event github.IssueCommentEvent, tx *pop.Connection) bool {
//check that the issue is not nil and that the issue id is a pr id in the db
if event.Issue != nil && event.Issue.Assignee != nil && event.Sender != nil && event.Sender.Login != nil {
expireTime := updateTime()
expireTime := expireTime(time.Now())
validTime := true
//Check if the right person assignee commented
prs := []models.Pullrequest{}
@ -93,49 +148,43 @@ func checkCommented(event github.IssueCommentEvent, tx *pop.Connection) bool {
return false
}
func checkAssigned(event github.PullRequestEvent, tx *pop.Connection) bool {
//CheckAssigned Determines if the PullRequestEvent action was assigneed and if the assignement was valid
func checkAssigned(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "assigned") {
expireTime := updateTime()
expireTime := expireTime(time.Now())
validTime := false
if event.PullRequest != nil && event.PullRequest.Assignees != nil {
validTime = true
}
err := UpsertPullRequestEntry(event, tx, validTime, expireTime)
err := UpsertPullRequestEntry(ctx, event, tx, validTime, expireTime)
if err != nil {
return false
}
//Add new assignee if it does not exist in the repo
if event.PullRequest != nil && event.PullRequest.Assignees != nil {
assignees := event.PullRequest.Assignees
for _, assignee := range assignees {
id, err := uuid.NewV1()
if err != nil {
return false
}
q := tx.RawQuery(`INSERT INTO assignees (id, created_at, updated_at, login, type, html_url)
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (login) DO UPDATE SET html_url = ?`,
id, time.Now(), time.Now(), assignee.Login, assignee.Type, assignee.HTMLURL, assignee.HTMLURL)
exErr := q.Exec()
if exErr != nil {
log.Print(exErr)
log.Printf("Unable to update event number %d in checkAssigned", *event.Number)
return false
}
}
return true
err = AddAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
err = AddPullrequestAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
return true
}
return false
}
func checkUnassigned(event github.PullRequestEvent, tx *pop.Connection) bool {
//CheckUnassigned Determines if the PullRequestEvent action was unassigned and updates the database accordingly
func checkUnassigned(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "unassigned") {
//Update PR in DB to no longer accept messages until assigned
//if the assignee is nil update the expire time to be invalid
validTime := false
expireTime := time.Time{}
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(event, tx, validTime, expireTime)
err := UpsertPullRequestEntry(ctx, event, tx, validTime, expireTime)
if err != nil {
log.Printf("Unable to update event number %d in checkUnassigned", *event.Number)
return false
@ -146,39 +195,29 @@ func checkUnassigned(event github.PullRequestEvent, tx *pop.Connection) bool {
return false
}
func checkReviewed(event github.PullRequestEvent, tx *pop.Connection) bool {
//CheckAssigned Determines if the PullRequestEvent action was reviewed and if the review was valid
func checkReviewed(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "review_requested") {
if event.PullRequest.Assignee != nil && event.Sender != nil {
err := AddAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
if strings.EqualFold(*event.PullRequest.Assignee.Name, *event.Sender.Name) {
validTime := true
expireTime := updateTime()
expireTime := expireTime(time.Now())
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(event, tx, validTime, expireTime)
err := UpsertPullRequestEntry(ctx, event, tx, validTime, expireTime)
if err != nil {
log.Printf("Unable to update event number %d in checkReviewed", *event.Number)
return false
}
}
return true
}
}
}
return false
}
func checkLabeled(event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "labeled") {
if event.PullRequest.Assignee != nil && event.Sender != nil {
//Check that the label action was done by the assignee
if strings.EqualFold(*event.PullRequest.Assignee.Name, *event.Sender.Name) {
validTime := true
expireTime := updateTime()
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(event, tx, validTime, expireTime)
if err != nil {
log.Printf("Unable to update event number %d in checkLabeled", *event.Number)
return false
}
err = AddPullrequestAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
return true
}
@ -187,62 +226,129 @@ func checkLabeled(event github.PullRequestEvent, tx *pop.Connection) bool {
return false
}
func checkClosed(event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "closed") {
expireTime := time.Time{}
validTime := false
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(event, tx, validTime, expireTime)
//CheckLabeled Determines if the PullRequestEvent action was labeled and if the label action was valid
func checkLabeled(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "labeled") {
if event.PullRequest.Assignee != nil && event.Sender != nil {
err := AddAssigneeToDB(ctx, event, tx)
if err != nil {
log.Printf("Unable to update event number %d in checkClosed", *event.Number)
log.Println(err)
return false
}
//Check that the label action was done by the assignee
if strings.EqualFold(*event.PullRequest.Assignee.Name, *event.Sender.Name) {
validTime := true
expireTime := expireTime(time.Now())
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(ctx, event, tx, validTime, expireTime)
if err != nil {
log.Printf("Unable to update event number %d in checkLabeled", *event.Number)
return false
}
}
err = AddPullrequestAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
return true
}
}
}
return false
}
func checkOpened(event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "opened") {
//CheckClosed Determines if the PullRequestEvent action was closed and marks the PR as invalid
func checkClosed(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "closed") {
expireTime := time.Time{}
validTime := false
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(ctx, event, tx, validTime, expireTime)
if err != nil {
log.Printf("Unable to update event number %d in checkClosed", *event.Number)
return false
}
}
err := AddAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
err = AddPullrequestAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
return true
}
return false
}
//CheckOpened Determines if the PullRequestEvent action was open and updated the entry in the DB
func checkOpened(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "opened") {
expiration := time.Time{}
validTime := false
if event.PullRequest.Assignee != nil {
expireTime = updateTime()
expiration = expireTime(time.Now())
validTime = true
}
err := UpsertPullRequestEntry(event, tx, validTime, expireTime)
err := UpsertPullRequestEntry(ctx, event, tx, validTime, expiration)
if err != nil {
log.Printf("Unable to update event number %d in checkOpened", *event.Number)
return false
}
err = AddAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
err = AddPullrequestAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
return true
}
return false
}
func checkEdited(event github.PullRequestEvent, tx *pop.Connection) bool {
//CheckAssigned Determines if the PullRequestEvent action was reviewed and if the review was valid
func checkEdited(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) bool {
if strings.EqualFold(*event.Action, "edited") {
log.Print("made it to editied event")
if event.PullRequest.Assignee != nil && event.Sender != nil {
//Make sure the sender is the same as the assignee
//if strings.EqualFold(*event.PullRequest.Assignee.Name, *event.Sender.Name) {
expireTime := updateTime()
err := AddAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
expireTime := expireTime(time.Now())
if event.PullRequest != nil && event.Number != nil {
err := UpsertPullRequestEntry(event, tx, true, expireTime)
err := UpsertPullRequestEntry(ctx, event, tx, true, expireTime)
log.Print("updated event")
if err != nil {
log.Printf("Unable to update event number %d in checkEdited", *event.Number)
return false
}
}
err = AddPullrequestAssigneeToDB(ctx, event, tx)
if err != nil {
log.Println(err)
return false
}
return true
}
}
return false
}
func UpsertPullRequestEntry(event github.PullRequestEvent, tx *pop.Connection, valid_time bool, expire_time time.Time) error {
//UpsertPullRequestEntry performs an upsert on the pullrequests table to update or add a new pullrequest entry
func UpsertPullRequestEntry(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection, valid_time bool, expire_time time.Time) error {
id, err := uuid.NewV1()
if err != nil {
return err
@ -260,13 +366,78 @@ func UpsertPullRequestEntry(event github.PullRequestEvent, tx *pop.Connection, v
NullCheckInt(event.PullRequest.Commits), *event.PullRequest.StatusesURL, expire_time, valid_time, expire_time)
err = q.Exec()
if err != nil {
log.Printf("Unable to update event number %d in Upsert", *event.Number)
return fmt.Errorf("Could not complete upsert: %v", err)
}
return nil
}
//NullCheckTime
//AddAssigneeToDB adds the assigneeo of the pull request to the DB
func AddAssigneeToDB(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) error {
if event.PullRequest != nil && event.PullRequest.Assignees != nil {
assignees := event.PullRequest.Assignees
for _, assignee := range assignees {
assigneeID, err := uuid.NewV1()
if err != nil {
return err
}
q := tx.RawQuery(`INSERT INTO assignees (id, created_at, updated_at, login, type, html_url)
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (login) DO NOTHING`,
assigneeID, time.Now(), time.Now(), assignee.Login, assignee.Type, assignee.HTMLURL)
exErr := q.Exec()
if exErr != nil {
log.Printf("Unable to update event number %d in checkAssigned", *event.Number)
return exErr
}
}
return nil
}
return fmt.Errorf("Assignee is nil. Cannot add nil assignee to DB")
}
//AddPullrequestAssigneeToDB creates an entry in the pullrequest_assignee table
func AddPullrequestAssigneeToDB(ctx context.Context, event github.PullRequestEvent, tx *pop.Connection) error {
if event.PullRequest != nil && event.PullRequest.Assignee != nil {
//Get assignee ID
assignees := []models.Assignee{}
err := models.DB.RawQuery(`SELECT * FROM assignees WHERE login=?`, event.PullRequest.Assignee.Login).All(&assignees)
if err != nil {
return err
}
if assignees == nil {
log.Print("Assignee is not in the database")
return nil
}
assigneeID := assignees[0].ID
//Get pullrequest ID
prs := []models.Pullrequest{}
err = models.DB.RawQuery(`SELECT * FROM pullrequests WHERE git_prid=?`, event.PullRequest.ID).All(&prs)
if err != nil {
return err
}
if prs == nil {
log.Print("The pull request is not in the database")
return nil
}
pullrequestID := prs[0].ID
pullrequestAssigneeID, err := uuid.NewV1()
if err != nil {
return err
}
q := tx.RawQuery(`INSERT INTO pullrequest_assignees (id, created_at, updated_at, pullrequest_id, assignee_id)
VALUES (?, ?, ?, ?, ?)`,
pullrequestAssigneeID, time.Now(), time.Now(), pullrequestID, assigneeID)
exErr := q.Exec()
if exErr != nil {
log.Printf("Unable to update event number %d in checkAssigned", *event.Number)
return exErr
}
}
return nil
}
//NullCheckTime determines if the time.Time value is nil
func NullCheckTime(x *time.Time) time.Time {
if x == nil {
return time.Time{}

Просмотреть файл

@ -1,7 +1,32 @@
package messages
import "testing"
import (
"testing"
"time"
)
func Test_Add_To_DB(t *testing.T) {
}
func Test_slaDuration(t *testing.T) {
const oneDay = 24 * time.Hour
expected := map[time.Weekday]time.Duration{
time.Monday: oneDay,
time.Thursday: oneDay,
time.Friday: 3 * oneDay,
time.Saturday: 2 * oneDay,
time.Sunday: oneDay,
}
for tc, want := range expected {
t.Run(tc.String(), func(t *testing.T) {
got := slaDuration(tc)
if got != want {
t.Logf("got: %v want: %v", got, want)
t.Fail()
}
})
}
}

Двоичные данные
messages/debug.test Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -8,35 +8,34 @@ import (
"net/url"
"os"
"strconv"
"time"
"github.com/Azure/spec-sla-bot/models"
gomail "gopkg.in/gomail.v2"
)
//Email information structure
type Email struct {
Password string
EmailAddress string
Port int
}
//SendEmailToAssignee sends an email to a list of users
func SendEmailToAssignee(ctx context.Context, info *Message) error {
CreatePrimaryTemplate(info)
func SendEmailToAssignee(ctx context.Context, info *MessageContent) error {
err := CreatePrimaryTemplate(info)
if err != nil {
return err
}
b, err := ioutil.ReadFile("finalPrimaryTemplate.html")
if err != nil {
fmt.Print(err)
return err
}
str := string(b)
m := gomail.NewMessage()
//Get connection string from azure
emailUrl := os.Getenv("CUSTOMCONNSTR_EMAIL_URL")
//parse connection string url
parsed, err := url.Parse(emailUrl)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return err
}
port, _ := strconv.Atoi(parsed.Port())
password, _ := parsed.User.Password()
queryString := fmt.Sprintf("SELECT EmailLogin FROM [User] WHERE GitHubUser = '%s';", info.Assignee)
fmt.Println("email selection query: ", queryString)
//Determine email recipient from database
queryString := fmt.Sprintf("SELECT EmailLogin FROM [User] WHERE GitHubUser = '%s';", info.AssigneeLogin)
rows, err := InfraDB.QueryContext(ctx, queryString)
if err != nil {
return err
@ -44,30 +43,97 @@ func SendEmailToAssignee(ctx context.Context, info *Message) error {
defer rows.Close()
var emailTo string
if rows != nil {
log.Print("rows does not equal null")
for rows.Next() {
err = rows.Scan(&emailTo)
if err != nil {
return err
}
}
//Can be removed once we use a default email in the repo or use the infra monitor database
//to send the email
if emailTo == "" {
emailTo = "t-jaelli@microsoft.com"
}
log.Printf("ISSUE")
m.SetHeader("To", emailTo)
} else {
log.Printf("Cannot find email for %s to send SLA reminder email", info.Assignee)
return fmt.Errorf("Cannot find email for %s to send SLA reminder email", info.AssigneeLogin)
}
//Parse email connection string
emailInfo, err := parseEmailURL()
if err != nil {
return err
}
m.SetHeader("From", "t-jaelli@microsoft.com")
//Format email
str := string(b)
m := gomail.NewMessage()
m.SetHeader("From", emailInfo.EmailAddress)
m.SetHeader("To", emailTo)
m.SetHeader("Subject", "TEST")
m.SetHeader("Subject", "SLA Violation - Outstanding Pull Request")
m.SetBody("text/html", str)
d := gomail.NewDialer("smtp.office365.com", port, "t-jaelli@microsoft.com", password)
d := gomail.NewDialer("smtp.office365.com", emailInfo.Port, emailInfo.EmailAddress, emailInfo.Password)
if err = d.DialAndSend(m); err != nil {
return err
}
return nil
}
func SendEmailToManager(ctx context.Context, info *MessageContent) error {
//Get record of emails sent during date ran
emails := []models.Email{}
err := models.DB.RawQuery(`SELECT * FROM emails WHERE [time_sent] > '?' AND [time_sent] <= '?'`, time.Now().AddDate(0, 0, -7), time.Now()).All(&emails)
if err != nil {
log.Print("Could not make query")
return err
}
err = CreateManagerTemplate(emails)
if err != nil {
return err
}
b, err := ioutil.ReadFile("finalManagerTemplate.html")
if err != nil {
fmt.Print(err)
return err
}
//Parse email connection string
emailInfo, err := parseEmailURL()
if err != nil {
return err
}
str := string(b)
m := gomail.NewMessage()
m.SetHeader("From", emailInfo.EmailAddress)
//Will always be sent to the manager
m.SetHeader("To", "t-jaelli@microsoft.com")
m.SetHeader("Subject", "SLA Violations - A Week In Review")
m.SetBody("text/html", str)
d := gomail.NewDialer("smtp.office365.com", emailInfo.Port, emailInfo.EmailAddress, emailInfo.Password)
if err = d.DialAndSend(m); err != nil {
return err
}
return nil
}
func parseEmailURL() (*Email, error) {
emailURL := os.Getenv("CUSTOMCONNSTR_EMAIL_URL")
parsed, err := url.Parse(emailURL)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, err
}
port, _ := strconv.Atoi(parsed.Port())
password, _ := parsed.User.Password()
emailStruct := &Email{
Password: password,
Port: port,
EmailAddress: "t-jaelli@microsoft.com",
}
return emailStruct, nil
}

Просмотреть файл

@ -3,26 +3,41 @@ package messages
import (
"fmt"
"html/template"
"log"
"os"
"time"
"github.com/Azure/spec-sla-bot/models"
)
func CreatePrimaryTemplate(info *Message) {
//Map of function names to functions
//box := packr.NewBox("../templates")
//CreatePrimaryTemplate populates the finalPrimaryTemplate for the assignee email
func CreatePrimaryTemplate(info *MessageContent) error {
fmap := template.FuncMap{
"FormatNumber": FormatNumber,
"FormatAssignee": FormatAssignee}
t := template.Must(template.New("primaryTemplate.tmpl").Funcs(fmap).ParseFiles("./templates/primaryTemplate.tmpl"))
handle, err := os.Create("finalPrimaryTemplate.html")
err = t.Execute(handle, *info)
err = t.Execute(handle, info)
if err != nil {
return
return err
}
return nil
}
func CreateAssigneeTemplate() {
//CreateManagerTemplate should this accept something other than messageContent
func CreateManagerTemplate(emails []models.Email) error {
fmap := template.FuncMap{
"FormatNumber": FormatNumber,
"FormatAssignee": FormatAssignee}
t := template.Must(template.New("managerTemplate.tmpl").Funcs(fmap).ParseFiles("./templates/managerTemplate.tmpl"))
handle, err := os.Create("finalManagerTemplate.html")
err = t.Execute(handle, emails)
if err != nil {
return err
}
return nil
}
/*func CreateAssigneeTemplate() {
//Map of function names to functions
//box := packr.NewBox("../templates")
fmap := template.FuncMap{
@ -37,12 +52,11 @@ func CreateAssigneeTemplate() {
log.Fatal(err)
}
handle, err := os.Create("finalTemplate.html")
//fred, err := ioutil.TempFile()
err = t.Execute(handle, *result)
if err != nil {
panic(err)
}
}
}*/
func FormatNumber(number int) string {
return fmt.Sprintf("#%-5d", number)

Просмотреть файл

@ -8,10 +8,11 @@ import (
_ "github.com/denisenkom/go-mssqldb"
)
// InfraDB is a connection to the infra monitor database to be used
// throughout the application.
var InfraDB *sql.DB
func init() {
log.Print("made it to init in infra")
conn, err := sql.Open("mssql", os.Getenv("CUSTOMCONNSTR_INFRA_DATABASE_URL"))
if err != nil {
log.Fatal(err)

Просмотреть файл

@ -2,10 +2,9 @@ package messages
import (
"context"
"encoding/json"
"errors"
"log"
"strconv"
"strings"
"time"
"github.com/Azure/azure-service-bus-go"
@ -13,17 +12,11 @@ import (
"github.com/gobuffalo/uuid"
)
type Message struct {
PRID string
PullRequestURL string
Assignee string
}
//ReceiveFromQueue Sets up the queue to receive from service bus and determines whether
//or not an email should be sent to the assignees and updates the datebase accordingly
func ReceiveFromQueue(ctx context.Context, connStr string) (*servicebus.ListenerHandle, error) {
ns, err := servicebus.NewNamespace(servicebus.NamespaceWithConnectionString(connStr))
log.Print("new namespace created")
if err != nil {
log.Println(err)
return nil, err
}
@ -33,16 +26,16 @@ func ReceiveFromQueue(ctx context.Context, connStr string) (*servicebus.Listener
log.Printf("failed to build a new queue named %q\n", queueName)
return nil, err
}
log.Print("got queue to receive")
listenHandle, err := q.Receive(ctx, func(ctx context.Context, message *servicebus.Message) servicebus.DispositionAction {
messageStruct, err := parseMessage(message.Data)
messageStruct := &MessageContent{}
err := json.Unmarshal(message.Data, messageStruct)
log.Print("parsed message")
if err != nil {
log.Println(err)
return message.DeadLetter(err)
}
log.Print(messageStruct.Assignee)
if ShouldSend(messageStruct) {
if ShouldSendAssigneeEmail(messageStruct) {
err = SendEmailToAssignee(ctx, messageStruct)
if err != nil {
log.Println(err)
@ -53,6 +46,12 @@ func ReceiveFromQueue(ctx context.Context, connStr string) (*servicebus.Listener
log.Println("Unable to add the emails to the database")
return message.DeadLetter(err)
}
} else if messageStruct.ManagerEmailReminder {
err = SendEmailToManager(ctx, messageStruct)
if err != nil {
log.Println(err)
return message.DeadLetter(err)
}
}
return message.Complete()
})
@ -63,6 +62,7 @@ func ReceiveFromQueue(ctx context.Context, connStr string) (*servicebus.Listener
return listenHandle, nil
}
//getQueueToReveive gets the queue to receive messages from service bus
func getQueueToReceive(ns *servicebus.Namespace, queueName string) (*servicebus.Queue, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
@ -78,58 +78,62 @@ func getQueueToReceive(ns *servicebus.Namespace, queueName string) (*servicebus.
return nil, err
}
}
q, err := ns.NewQueue(ctx, queueName)
q, err := ns.NewQueue(queueName)
return q, err
}
//Redo this
func parseMessage(data []byte) (*Message, error) {
str := string(data[:])
if len(str) != 0 {
strSplit := strings.FieldsFunc(str, Split)
for i, v := range strSplit {
strSplit[i] = strings.TrimSpace(v)
}
return &Message{PRID: strSplit[1], PullRequestURL: strSplit[3], Assignee: strSplit[5]}, nil
}
return nil, errors.New("could not parse messages returned by service bus")
}
func Split(r rune) bool {
return r == ','
}
func ShouldSend(messageStruct *Message) bool {
gitPRID, _ := strconv.Atoi(messageStruct.PRID)
//ShouldSendAssigneeEmail determines if an email should be sent to the assignee based on
//whether the time is valid and the current time is greater than the expire
//time
func ShouldSendAssigneeEmail(messageStruct *MessageContent) bool {
prs := []models.Pullrequest{}
err := models.DB.Where("git_prid=?", int64(gitPRID)).All(&prs)
err := models.DB.Where("git_prid=?", messageStruct.PRID).All(&prs)
if err != nil || prs == nil {
log.Print("Could not make querey")
return false
}
for _, pr := range prs {
log.Print(time.Now().Sub(pr.ExpireTime))
//if pr.ValidTime && time.Now().Sub(pr.ExpireTime) >= 0 {
log.Print("returning true, should send message")
return true
//}
//log.Print(time.Now())
//log.Print(pr.ExpireTime)
if pr.ValidTime && time.Now().Sub(pr.ExpireTime) >= 0 {
log.Print("returning true, should send message")
return true
}
}
log.Print("pr was nil")
return false
}
func AddEmailToDB(messageStruct *Message) error {
gitPRID, _ := strconv.Atoi(messageStruct.PRID)
id, err := uuid.NewV1()
//AddEmailToDB adds an email entry to the database for use when sending
//the manager email
func AddEmailToDB(messageStruct *MessageContent) error {
emailID, err := uuid.NewV1()
if err != nil {
return err
}
//Do I need to populate assignee?
q := models.DB.RawQuery(`INSERT INTO emails (id, created_at, updated_at, pullrequest_id, time_sent)
VALUES (?, ?, ?, ?, ?)`,
id, time.Now(), time.Now(), gitPRID, time.Now())
emailID, time.Now(), time.Now(), int(messageStruct.PRID), time.Now())
err = q.Exec()
if err != nil {
log.Print(err)
return errors.New("Could not complete insert to add email to database")
}
assignees := []models.Assignee{}
err = models.DB.RawQuery(`SELECT * FROM assignees WHERE login=?`, messageStruct.AssigneeLogin).All(&assignees)
if err != nil {
return err
}
if assignees == nil {
log.Print("Assignee is not in the database")
return nil
}
assigneeID := assignees[0].ID
emailAssigneeID, err := uuid.NewV1()
if err != nil {
return err
}
q = models.DB.RawQuery(`INSERT INTO email_assignees (id, email_id, assignee_id, created_at,
updated_at) VALUES (?, ?, ?, ?, ?)`,
emailAssigneeID, emailID, assigneeID, time.Now(), time.Now())
err = q.Exec()
if err != nil {
log.Print(err)

Просмотреть файл

@ -2,6 +2,7 @@ package messages
import (
"context"
"fmt"
"log"
"os"
"time"
@ -9,36 +10,39 @@ import (
"github.com/Azure/azure-service-bus-go"
)
func SendToQueue(message string, postTime time.Time) error {
func SendToQueue(ctx context.Context, message []byte, postTime time.Time, queueName string) error {
connStr := os.Getenv("CUSTOMCONNSTR_SERVICEBUS_CONNECTION_STRING")
ns, err := servicebus.NewNamespace(servicebus.NamespaceWithConnectionString(connStr))
if err != nil {
return err
}
queueName := "24hrgitevents"
q, err := getQueueToSend(ns, queueName)
fmt.Println("Post Time: ", postTime, " Now: ", time.Now())
q, err := getQueueToSend(ns, queueName)
fmt.Println("Queue Name: ", queueName)
if err != nil {
log.Printf("failed to build a new queue named %q\n", queueName)
return err
}
postTime = postTime.Add(time.Minute * 10)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
msg := servicebus.NewMessageFromString(message)
msg := servicebus.NewMessage(message)
msg.SystemProperties = &servicebus.SystemProperties{
ScheduledEnqueueTime: &postTime,
}
log.Printf("ABOUT TO SEND MESSAGE")
log.Print(message)
q.Send(ctx, msg)
cancel()
fmt.Println("Scheduled Enqueue Time: ", msg.SystemProperties.ScheduledEnqueueTime)
err = q.Send(ctx, msg)
if err != nil {
return err
}
q.Close(ctx)
return nil
}
func getQueueToSend(ns *servicebus.Namespace, queueName string) (*servicebus.Queue, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
q, err := ns.NewQueue(ctx, queueName)
q, err := ns.NewQueue(queueName)
return q, err
}

Просмотреть файл

@ -1,8 +1,97 @@
package messages
import "testing"
import (
"context"
"fmt"
"math/rand"
"os"
"testing"
"time"
func Test_Send_To_Queue(t *testing.T) {
servicebus "github.com/Azure/azure-service-bus-go"
)
//as.Fail("Not Implemented!")
func Test_SendToQueue(t *testing.T) {
if testing.Short() {
t.Skip()
return
}
connStr := os.Getenv("CUSTOMCONNSTR_SERVICEBUS_CONNECTION_STRING")
if connStr == "" {
t.Skip()
return
}
want := RandomString(15)
tempQueueName := RandomString(10)
if testing.Verbose() {
t.Logf("queue name: %q", tempQueueName)
}
ns, err := servicebus.NewNamespace(servicebus.NamespaceWithConnectionString(connStr))
if err != nil {
t.Error(err)
return
}
queueManager := ns.NewQueueManager()
q, err := getQueueToSend(ns, tempQueueName)
if err != nil {
t.Error(err)
return
}
const waitTime = time.Duration(2 * time.Minute)
fmt.Println("Wait time: ", waitTime)
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second+waitTime)
defer cancel()
_, err = queueManager.Put(ctx, tempQueueName)
if err != nil {
t.Error(err)
return
}
defer queueManager.Delete(ctx, tempQueueName)
SendToQueue(ctx, want, time.Now().Add(waitTime), tempQueueName)
start := time.Now()
message, err := q.ReceiveOne(ctx)
defer message.Complete()
timeWaited := time.Now().Sub(start)
if err != nil {
t.Error(err)
return
}
got := string(message.Data)
if got != want {
t.Logf("got:\n\t%q\nwant:\n\t%q", got, want)
t.Fail()
} else {
t.Logf("message matched")
}
const buffer = time.Duration(30 * time.Second)
if min := waitTime - buffer; timeWaited < min {
t.Logf("received message after %v, expected to wait at least %v", timeWaited, min)
t.Fail()
} else if max := waitTime + buffer; timeWaited > max {
t.Logf("received message after %v, expected to wait no longer than %v", timeWaited, max)
t.Fail()
}
}
func RandomString(n int) string {
var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, n)
for i := range b {
b[i] = letter[rand.Intn(len(letter))]
}
return string(b)
}

Просмотреть файл

@ -18,8 +18,7 @@ type Assignee struct {
Type string `json:"type" db:"type"`
HtmlUrl string `json:"html_url" db:"html_url"`
Pullrequests Pullrequests `many_to_many:"pullrequest_assignees" db:"-"`
//Pullrequests *Pullrequest `many_to_many:"pullrequest_assignee"`
//Emails *Email `many_to_many:"email_assignee"`
Emails Emails `many_to_many:"email_assignee" db:"-"`
}
// String is not required by pop and may be deleted

Просмотреть файл

@ -16,7 +16,7 @@ type Email struct {
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
PullrequestID int `json:"pullrequest_id" db:"pullrequest_id"`
TimeSent string `json:"time_sent" db:"time_sent"`
Assignees *Assignee `many_to_many:"email_assignee"`
Assignees Assignees `many_to_many:"email_assignees"`
}
// String is not required by pop and may be deleted

Просмотреть файл

@ -15,6 +15,8 @@ type EmailAssignee struct {
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
EmailID uuid.UUID `json:"email_id" db:"email_id"`
AssigneeID string `json:"assignee_id" db:"assignee_id"`
Email Email `belongs_to:"emails" db:"-"`
Assignee Assignee `belongs_to:"assignees" db:"-"`
}
// String is not required by pop and may be deleted

Просмотреть файл

@ -8,7 +8,7 @@ th, td {
text-align: left;
}
</style>
You have an outstanding PR that has not been acknowledged in past 24 hours. Please acknowlege the PR to abide by the SLA.<br /><br /> Kindly, <br />Jackie (your favorite intern)<br>
Dear Samer, <br /><br />Below is a list of SLA violations in the past week.<br /><br /> Kindly, <br />Jackie (your favorite intern)<br>
<table style="width:100%">
<caption><br />Outstanding Pull Requests</caption>
<tr>

Просмотреть файл

@ -8,7 +8,7 @@ th, td {
text-align: left;
}
</style>
Dear {{.Assignee}},<br /><br /> You have an outstanding PR that has not been acknowledged in past 24 hours. Please acknowlege the PR to abide by the SLA.<br /><br /> Kindly, <br />Jackie (your favorite intern)<br>
Dear {{.AssigneeLogin}},<br /><br /> You have an outstanding PR that has not been acknowledged in past 24 hours. Please acknowlege the PR to abide by the SLA.<br /><br /> Kindly, <br />Jackie (your favorite intern)<br>
<table style="width:100%">
<caption><br />Outstanding Pull Requests</caption>
<tr>
@ -16,9 +16,9 @@ th, td {
<th>Assignee</th>
</tr>
<tr>
<td><a href={{.PullRequestURL}}>{{ .PRID }}</a></td>
{{if .Assignee -}}
<td>{{ .Assignee }}</td>
<td><a href={{.HTMLURL}}>{{ .PRID }}</a></td>
{{if .AssigneeLogin -}}
<td>{{ .AssigneeLogin }}</td>
{{- else}}
<td>NONE</td>
{{- end}}