package main import ( "log" "regexp" "strconv" "strings" "github.com/andygrunwald/go-jira" ) const ( JIRA_URL = "https://jirawms.mda.ca" ) type IssueReader struct { auth jira.BasicAuthTransport //auth data client *jira.Client //jira client score map[string]int //score information } func NewIssueReader(user, pass string) *IssueReader { ir := &IssueReader{ auth: jira.BasicAuthTransport{ Username: strings.TrimSpace(user), Password: strings.TrimSpace(pass), }, score: make(map[string]int), } var err error ir.client, err = jira.NewClient(ir.auth.Client(), strings.TrimSpace(JIRA_URL)) if err != nil { log.Fatal(err) } return ir } func (i *IssueReader) GetScores() map[string]int { return i.score } func (i *IssueReader) ZeroScores() { for k := range i.score { i.score[k] = 0 } } func (i *IssueReader) RefreshScores() map[string]int { i.ZeroScores() poIssues, er := i.GetAllIssues(i.client, GetBountyJQL()) if er != nil { log.Fatal(er) } //run through each to check if we've got bounty points to award for _, issue := range poIssues { teamId := i.ParseCustomTeamField(issue.Fields.Unknowns[CUSTOM_FIELD_TEAM]) i.ExtractScoreFromComments(teamId, issue.Key) } return i.GetScores() } // the team field is one of type string after a non-intuitive map decomposition, assert on interface func (i *IssueReader) ParseCustomTeamField(teamField interface{}) string { var str string = "" if teamField != nil { str = teamField.([]interface{})[0].(map[string]interface{})["value"].(string) } return str } // given a teamid and jira issue, validate potential PO bounty scores func (i *IssueReader) ExtractScoreFromComments(teamId string, key string) { if i.IsValidTeam(teamId) { is, _, err := i.client.Issue.Get(key, nil) if err != nil { log.Fatal(err) } comments := is.Fields.Comments.Comments for _, c := range comments { if i.CommentAuthorIsPO(c) { bp := i.ExtractBountyFromComment(c) i.score[teamId] = i.score[teamId] + bp } } } } // check if s exists in Scrum Teams list func (i *IssueReader) IsValidTeam(s string) bool { for _, t := range GetTeamNames() { if strings.Compare(strings.ToLower(s), strings.ToLower(t)) == 0 { return true } } return false } // given a jira.Comment, find and extract the associated PO Bounty points func (i *IssueReader) ExtractBountyFromComment(c *jira.Comment) int { result := 0 re := regexp.MustCompile(`POB: (\d+)`) if c != nil { matches := re.FindStringSubmatch(c.Body) //we're expecting exactly one principle match and one submatch (2 entries total) if len(matches) == 2 { //submatch must be an integer, or else we simply don't update the result a, err := strconv.Atoi(matches[1]) if err == nil { result = a } } } return result } // given a specific jira.Comment, find if it was authored by someone int he POList func (i *IssueReader) CommentAuthorIsPO(c *jira.Comment) bool { if c != nil { for _, p := range GetPOList() { if c.Author.Name == p { return true } } } return false } // retrieve all issues as per searchString JQL func (i *IssueReader) GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error) { last := 0 var issues []jira.Issue for { opt := &jira.SearchOptions{ MaxResults: 1000, // Max results can go up to 1000 StartAt: last, } chunk, resp, err := client.Issue.Search(searchString, opt) if err != nil { return nil, err } total := resp.Total if issues == nil { issues = make([]jira.Issue, 0, total) } issues = append(issues, chunk...) last = resp.StartAt + len(chunk) if last >= total { return issues, nil } } }