package main import ( "fmt" "regexp" "strconv" "strings" "github.com/andygrunwald/go-jira" ) type IssueReader struct { auth jira.BasicAuthTransport //auth data client *jira.Client //jira client score map[string]int //score information settings *DashSettings available bool //flag letting us know if the client is currentl available } func NewIssueReader(user, pass string, s *DashSettings) *IssueReader { ir := &IssueReader{ auth: jira.BasicAuthTransport{ Username: strings.TrimSpace(user), Password: strings.TrimSpace(pass), }, score: make(map[string]int), settings: s, available: false, } ir.Reconnect() return ir } func (i *IssueReader) Reconnect() { i.client = nil if i.settings.Jira.Url != "" { var err error i.client, err = jira.NewClient(i.auth.Client(), strings.TrimSpace(i.settings.Jira.Url)) if err != nil { DashLogger.Log(fmt.Sprintf("%v", err)) i.available = false } else { i.available = true } } } // forcibly drops the client (recoverable only through manual reconnection) func (i *IssueReader) DropClient() { i.client = nil i.available = false } func (i *IssueReader) IsAvailable() bool { return i.available } 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) BuildBountyJQL() string { var epiclink string epiclink = "(" for k, epicid := range i.settings.Jira.Epics { epiclink = epiclink + `"Epic Link" = ` + epicid if k != len(i.settings.Jira.Epics)-1 { epiclink = epiclink + " OR " } } epiclink = epiclink + ")" query := fmt.Sprintf(`project=%s AND %s AND Status = Accepted`, i.settings.Jira.Project, epiclink) return query } func (i *IssueReader) RefreshScores() map[string]int { poIssues, er := i.GetAllIssues(i.client, i.BuildBountyJQL()) if er != nil { DashLogger.Log(fmt.Sprintf("%v\n", er)) } else { i.ZeroScores() //run through each to check if we've got bounty points to award for _, issue := range poIssues { teamId := i.ParseCustomTeamField(issue.Fields.Unknowns[i.settings.Jira.Teamfield]) 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 { DashLogger.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 i.settings.Teams { 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(i.settings.Criteria.Pattern) if c != nil { matches := re.FindStringSubmatch(c.Body) //our returned matches must be larger than the specified match index if len(matches) > i.settings.Criteria.MatchId { //submatch must be an integer, or else we simply don't update the result a, err := strconv.Atoi(matches[i.settings.Criteria.MatchId]) 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 i.settings.ProductOwners { 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) { if client == nil { i.available = false return nil, nil } 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 } } }