2020-02-29 00:49:55 +01:00
|
|
|
package model
|
|
|
|
|
2020-03-06 19:54:52 +01:00
|
|
|
import (
|
2020-04-27 10:42:29 +01:00
|
|
|
"bytes"
|
2020-10-07 18:52:00 +01:00
|
|
|
"encoding/json"
|
2020-03-06 19:54:52 +01:00
|
|
|
"fmt"
|
2020-09-24 18:01:18 +01:00
|
|
|
"os"
|
2020-04-27 10:42:29 +01:00
|
|
|
"strings"
|
2020-09-24 13:16:51 +01:00
|
|
|
"text/tabwriter"
|
2020-04-27 10:42:29 +01:00
|
|
|
"text/template"
|
2020-04-28 07:51:06 +01:00
|
|
|
|
|
|
|
"github.com/fatih/color"
|
2020-03-06 19:54:52 +01:00
|
|
|
)
|
|
|
|
|
2020-04-27 10:42:29 +01:00
|
|
|
type ConfigJira struct {
|
|
|
|
Url string `json:"url"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Token string `json:"token"`
|
|
|
|
Project string `json:"project"`
|
|
|
|
Epic string `json:"epic"`
|
|
|
|
IssueType string `json:"issue_type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConfigGithub struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
Repo string `json:"repo"`
|
|
|
|
Labels []string `json:"labels"`
|
|
|
|
}
|
|
|
|
|
2020-03-05 20:42:25 +01:00
|
|
|
// ToDo: add key-value extra params as a map to allow for customfield_whatever = team
|
2020-02-29 00:49:55 +01:00
|
|
|
type Campaign struct {
|
2020-04-28 10:21:31 +01:00
|
|
|
Jira ConfigJira `json:"jira"`
|
|
|
|
Github ConfigGithub `json:"github"`
|
|
|
|
Summary string `json:"summary"`
|
|
|
|
IssueTemplate string `json:"issue_template"`
|
|
|
|
FooterTemplate string `json:"footer_template"`
|
|
|
|
Tickets []*Ticket `json:"tickets,omitempty"`
|
2020-02-29 00:49:55 +01:00
|
|
|
}
|
2020-03-05 22:37:01 +01:00
|
|
|
|
2020-03-07 13:27:11 +01:00
|
|
|
func (c *Campaign) NextJiraUnpublishedTicket() *Ticket {
|
2020-03-05 22:37:01 +01:00
|
|
|
for _, ticket := range c.Tickets {
|
2020-04-27 11:22:15 +01:00
|
|
|
if !ticket.IsPublishedJira() {
|
2020-03-05 22:37:01 +01:00
|
|
|
return ticket
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-03-06 19:54:52 +01:00
|
|
|
|
2020-03-07 13:27:11 +01:00
|
|
|
func (c *Campaign) NextGithubUnpublishedTicket() *Ticket {
|
|
|
|
for _, ticket := range c.Tickets {
|
2020-04-27 11:22:15 +01:00
|
|
|
if ticket.IsPublishedJira() && !ticket.IsPublishedGithub() {
|
2020-03-07 13:27:11 +01:00
|
|
|
return ticket
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-27 11:22:15 +01:00
|
|
|
func (c *Campaign) PrintStatus() {
|
2020-04-28 07:51:06 +01:00
|
|
|
totalTickets := len(c.Tickets)
|
2020-09-24 12:41:48 +01:00
|
|
|
var totalPublishedJira, totalPublishedGithub, totalAssigned, totalClosed int
|
2020-04-28 07:51:06 +01:00
|
|
|
for _, t := range c.Tickets {
|
|
|
|
if t.IsPublishedJira() {
|
|
|
|
totalPublishedJira++
|
|
|
|
if t.IsPublishedGithub() {
|
|
|
|
totalPublishedGithub++
|
2020-09-24 12:41:48 +01:00
|
|
|
if t.IsAssigned() {
|
|
|
|
totalAssigned++
|
2020-09-24 13:02:04 +01:00
|
|
|
if t.IsClosed() {
|
|
|
|
totalClosed++
|
|
|
|
}
|
2020-09-24 12:41:48 +01:00
|
|
|
}
|
2020-04-28 07:51:06 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-06 19:54:52 +01:00
|
|
|
}
|
2020-04-28 07:51:06 +01:00
|
|
|
|
|
|
|
fmt.Printf("Current campaign for %s with summary\n%s\n\n", color.GreenString(c.Github.Repo), color.CyanString(c.Summary))
|
2020-10-03 11:51:20 +01:00
|
|
|
if totalTickets == 0 {
|
|
|
|
fmt.Println("There are no tickets in the campaign. Run \"campaigner add --help\" to find out how to add them.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-09-24 13:16:51 +01:00
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.AlignRight)
|
2020-10-03 11:47:45 +01:00
|
|
|
fmt.Fprintf(w, " %d\t-\ttotal tickets\t\n", totalTickets)
|
2020-10-03 13:07:05 +01:00
|
|
|
fmt.Fprintf(w, " %d\t%d%%\tpublished in Jira\t\n", totalPublishedJira, totalPublishedJira*100/totalTickets)
|
|
|
|
fmt.Fprintf(w, " %d\t%d%%\tpublished in Github\t\n", totalPublishedGithub, totalPublishedGithub*100/totalTickets)
|
|
|
|
fmt.Fprintf(w, " %d\t%d%%\tassigned\t\n", totalAssigned, totalAssigned*100/totalTickets)
|
|
|
|
fmt.Fprintf(w, " %d\t%d%%\tclosed\t\n\n", totalClosed, totalClosed*100/totalTickets)
|
2020-09-24 13:16:51 +01:00
|
|
|
w.Flush()
|
2020-03-06 19:54:52 +01:00
|
|
|
}
|
2020-04-27 10:42:29 +01:00
|
|
|
|
2020-10-15 15:42:22 +01:00
|
|
|
func (c *Campaign) PrintList(publishedOnly, printLinks bool) {
|
2020-10-07 18:52:00 +01:00
|
|
|
for _, t := range c.Tickets {
|
|
|
|
if t.IsPublishedJira() {
|
2020-10-15 15:42:22 +01:00
|
|
|
jiraLink := t.JiraLink
|
|
|
|
if printLinks {
|
|
|
|
jiraLink = c.GetJiraUrl(t)
|
|
|
|
}
|
|
|
|
|
2020-10-07 18:52:00 +01:00
|
|
|
var str string
|
|
|
|
if t.IsPublishedGithub() {
|
2020-10-15 15:42:22 +01:00
|
|
|
githubLink := fmt.Sprintf("#%d", t.GithubLink)
|
|
|
|
if printLinks {
|
|
|
|
githubLink = c.GetGithubUrl(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
str = fmt.Sprintf("[%s / %s] %s", color.BlueString(jiraLink), color.CyanString(githubLink), t.Summary)
|
2020-10-07 18:52:00 +01:00
|
|
|
} else {
|
2020-10-15 15:42:22 +01:00
|
|
|
str = fmt.Sprintf("[%s] %s", color.BlueString(jiraLink), t.Summary)
|
2020-10-07 18:52:00 +01:00
|
|
|
}
|
|
|
|
if t.GithubStatus != "" {
|
2020-10-07 19:11:53 +01:00
|
|
|
if t.IsClosed() {
|
2020-10-07 19:09:45 +01:00
|
|
|
str += fmt.Sprintf(" (%s)", color.MagentaString(t.GithubStatus))
|
2020-10-07 19:11:53 +01:00
|
|
|
} else {
|
|
|
|
str += fmt.Sprintf(" (%s)", color.GreenString(t.GithubStatus))
|
2020-10-07 19:09:45 +01:00
|
|
|
}
|
2020-10-07 18:52:00 +01:00
|
|
|
}
|
|
|
|
fmt.Println(str)
|
2020-10-07 19:06:29 +01:00
|
|
|
} else if !publishedOnly {
|
2020-10-15 15:43:21 +01:00
|
|
|
b, _ := json.Marshal(t.Data)
|
2020-10-07 19:00:55 +01:00
|
|
|
fmt.Printf("unpublished: %s\n", color.YellowString(string(b)))
|
2020-10-07 18:52:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 19:24:40 +01:00
|
|
|
func (c *Campaign) AddTickets(tickets []*Ticket, fileOnly bool) int {
|
2020-04-29 18:52:15 +01:00
|
|
|
c.Tickets = append(c.Tickets, tickets...)
|
2020-11-18 19:24:40 +01:00
|
|
|
removedTickets := c.RemoveDuplicateTickets(fileOnly)
|
|
|
|
return len(tickets) - removedTickets
|
2020-04-29 18:52:15 +01:00
|
|
|
}
|
|
|
|
|
2020-11-18 19:24:40 +01:00
|
|
|
func (c *Campaign) RemoveDuplicateTickets(fileOnly bool) int {
|
|
|
|
removedTickets := 0
|
2020-04-30 07:13:31 +01:00
|
|
|
datalessTickets := []*Ticket{}
|
2020-04-29 18:52:15 +01:00
|
|
|
ticketMap := map[string]*Ticket{}
|
|
|
|
for _, t := range c.Tickets {
|
|
|
|
filename, _ := t.Data["filename"].(string)
|
|
|
|
lineNo, _ := t.Data["lineNo"].(int)
|
2020-04-30 07:13:31 +01:00
|
|
|
|
|
|
|
if filename == "" {
|
|
|
|
datalessTickets = append(datalessTickets, t)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-04-29 18:52:15 +01:00
|
|
|
if fileOnly {
|
2020-11-18 19:24:40 +01:00
|
|
|
// a previous ticket for the filename already existed
|
|
|
|
if _, ok := ticketMap[filename]; ok {
|
|
|
|
removedTickets++
|
|
|
|
}
|
2020-04-29 18:52:15 +01:00
|
|
|
ticketMap[filename] = t
|
|
|
|
} else {
|
2020-11-18 19:24:40 +01:00
|
|
|
ticketKey := fmt.Sprintf("%s:%d", filename, lineNo)
|
|
|
|
// a previous ticket for the same key already existed
|
|
|
|
if _, ok := ticketMap[ticketKey]; ok {
|
|
|
|
removedTickets++
|
|
|
|
}
|
|
|
|
ticketMap[ticketKey] = t
|
2020-04-29 18:52:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanTickets := []*Ticket{}
|
2020-04-30 07:13:31 +01:00
|
|
|
// dataless tickets are added first as they come from already
|
|
|
|
// existing tickets in Jira
|
|
|
|
cleanTickets = append(cleanTickets, datalessTickets...)
|
2020-04-29 18:52:15 +01:00
|
|
|
for _, t := range ticketMap {
|
|
|
|
cleanTickets = append(cleanTickets, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Tickets = cleanTickets
|
2020-11-18 19:24:40 +01:00
|
|
|
return removedTickets
|
2020-09-21 08:27:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Campaign) GetPublishedGithubTickets() []*Ticket {
|
|
|
|
publishedTickets := []*Ticket{}
|
|
|
|
for _, ticket := range c.Tickets {
|
|
|
|
if ticket.IsPublishedGithub() {
|
|
|
|
publishedTickets = append(publishedTickets, ticket)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return publishedTickets
|
2020-04-29 18:52:15 +01:00
|
|
|
}
|
|
|
|
|
2020-04-27 10:42:29 +01:00
|
|
|
func (c *Campaign) FillTicket(t *Ticket) error {
|
|
|
|
summaryTmpl, err := template.New("").Parse(c.Summary)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var summaryBytes bytes.Buffer
|
|
|
|
if err := summaryTmpl.Execute(&summaryBytes, t.Data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Summary = summaryBytes.String()
|
|
|
|
|
2020-04-28 10:21:31 +01:00
|
|
|
descriptionTemplate, err := template.ParseFiles(c.IssueTemplate)
|
2020-04-27 10:42:29 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var descriptionBytes bytes.Buffer
|
|
|
|
if err := descriptionTemplate.Execute(&descriptionBytes, t.Data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Description = descriptionBytes.String()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Campaign) RepoComponents() (string, string) {
|
|
|
|
parts := strings.Split(c.Github.Repo, "/")
|
|
|
|
if len(parts) == 2 {
|
|
|
|
return parts[0], parts[1]
|
|
|
|
}
|
|
|
|
return "", ""
|
|
|
|
}
|
2020-04-29 21:54:49 +01:00
|
|
|
|
|
|
|
func (c *Campaign) GetJiraUrl(ticket *Ticket) string {
|
|
|
|
return fmt.Sprintf("%s/browse/%s", c.Jira.Url, ticket.JiraLink)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Campaign) GetGithubUrl(ticket *Ticket) string {
|
|
|
|
return fmt.Sprintf("https://github.com/%s/issues/%d", c.Github.Repo, ticket.GithubLink)
|
|
|
|
}
|