github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/core/section/github/milestones.go (about)

     1  // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
     2  //
     3  // This software (Documize Community Edition) is licensed under
     4  // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
     5  //
     6  // You can operate outside the AGPL restrictions by purchasing
     7  // Documize Enterprise Edition and obtaining a commercial license
     8  // by contacting <sales@documize.com>.
     9  //
    10  // https://documize.com
    11  
    12  package github
    13  
    14  import (
    15  	"fmt"
    16  	"html/template"
    17  	"sort"
    18  
    19  	"github.com/documize/community/core/log"
    20  
    21  	gogithub "github.com/google/go-github/github"
    22  )
    23  
    24  type githubMilestone struct {
    25  	Repo         string       `json:"repo"`
    26  	Private      bool         `json:"private"`
    27  	Name         string       `json:"name"`
    28  	URL          template.URL `json:"url"`
    29  	IsOpen       bool         `json:"isopen"`
    30  	OpenIssues   int          `json:"openIssues"`
    31  	ClosedIssues int          `json:"closedIssues"`
    32  	CompleteMsg  string       `json:"completeMsg"`
    33  	DueDate      string       `json:"dueDate"`
    34  	UpdatedAt    string       `json:"updatedAt"`
    35  	Progress     uint         `json:"progress"`
    36  	IsMilestone  bool         `json:"isMilestone"`
    37  }
    38  
    39  // sort milestones in order that that should be presented.
    40  
    41  type milestonesToSort []githubMilestone
    42  
    43  func (s milestonesToSort) Len() int      { return len(s) }
    44  func (s milestonesToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
    45  func (s milestonesToSort) Less(i, j int) bool {
    46  	if s[i].Repo < s[j].Repo {
    47  		return true
    48  	}
    49  	if s[i].Repo > s[j].Repo {
    50  		return false
    51  	}
    52  	if !s[i].IsOpen && s[j].IsOpen {
    53  		return true
    54  	}
    55  	if s[i].IsOpen && !s[j].IsOpen {
    56  		return false
    57  	}
    58  	if s[i].Name != noMilestone && s[j].Name == noMilestone {
    59  		return true
    60  	}
    61  	if s[i].Name == noMilestone && s[j].Name != noMilestone {
    62  		return false
    63  	}
    64  	if s[i].Progress == s[j].Progress { // order equal progress milestones
    65  		return s[i].Name < s[j].Name
    66  	}
    67  	return s[i].Progress >= s[j].Progress // put more complete milestones first
    68  }
    69  
    70  const (
    71  	tagMilestonesData    = "milestonesData"
    72  	milestonesTimeFormat = "January 2 2006"
    73  	noMilestone          = "no milestone"
    74  )
    75  
    76  func init() {
    77  	reports[tagMilestonesData] = report{refreshMilestones, renderMilestones, milestonesTemplate}
    78  }
    79  
    80  func getMilestones(client *gogithub.Client, config *githubConfig) ([]githubMilestone, error) {
    81  
    82  	if !config.ShowMilestones {
    83  		return nil, nil
    84  	}
    85  
    86  	ret := []githubMilestone{}
    87  
    88  	hadRepo := make(map[string]bool)
    89  
    90  	for _, orb := range config.Lists {
    91  		if orb.Included {
    92  			rName := orb.Owner + "/" + orb.Repo
    93  
    94  			if !hadRepo[rName] {
    95  
    96  				for _, state := range []string{"open", "closed"} {
    97  
    98  					opts := &gogithub.MilestoneListOptions{
    99  						Sort:        "updated",
   100  						State:       state,
   101  						ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}}
   102  
   103  					guff, _, err := client.Issues.ListMilestones(orb.Owner, orb.Repo, opts)
   104  
   105  					if err != nil {
   106  						return ret, err
   107  					}
   108  
   109  					for _, v := range guff {
   110  						include := true
   111  						if state == "closed" {
   112  							if config.SincePtr != nil {
   113  								if (*config.SincePtr).After(*v.ClosedAt) {
   114  									include = false
   115  								}
   116  							}
   117  						}
   118  						if include {
   119  							dd := "no due date"
   120  							if v.DueOn != nil {
   121  								// TODO refactor to add message in red if the milestone is overdue
   122  								dd = "due " + (*v.DueOn).Format(milestonesTimeFormat) + ""
   123  							}
   124  							up := ""
   125  							if v.UpdatedAt != nil {
   126  								up = (*v.UpdatedAt).Format(milestonesTimeFormat)
   127  							}
   128  
   129  							progress := float64(*v.ClosedIssues*100) / float64(*v.OpenIssues+*v.ClosedIssues)
   130  
   131  							ret = append(ret, githubMilestone{
   132  								Repo:    repoName(rName),
   133  								Private: orb.Private,
   134  								Name:    *v.Title,
   135  								URL: template.URL(fmt.Sprintf(
   136  									"https://github.com/%s/%s/milestone/%d",
   137  									orb.Owner, orb.Repo, *v.Number)), // *v.HTMLURL does not give the correct value
   138  								IsOpen:       *v.State == "open",
   139  								OpenIssues:   *v.OpenIssues,
   140  								ClosedIssues: *v.ClosedIssues,
   141  								CompleteMsg:  fmt.Sprintf("%2.0f%%", progress),
   142  								DueDate:      dd,
   143  								UpdatedAt:    up,
   144  								Progress:     uint(progress),
   145  								IsMilestone:  true,
   146  							})
   147  						}
   148  					}
   149  
   150  				}
   151  			}
   152  			hadRepo[rName] = true
   153  		}
   154  
   155  	}
   156  
   157  	return ret, nil
   158  
   159  }
   160  
   161  func refreshMilestones(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) {
   162  
   163  	if !config.ShowMilestones {
   164  		return nil
   165  	}
   166  
   167  	gr.Milestones, err = getMilestones(client, config)
   168  	if err != nil {
   169  		log.Error("unable to get github milestones", err)
   170  		return err
   171  	}
   172  	gr.OpenMS = 0
   173  	gr.ClosedMS = 0
   174  	for _, v := range gr.Milestones {
   175  		if v.IsOpen {
   176  			gr.OpenMS++
   177  		} else {
   178  			gr.ClosedMS++
   179  		}
   180  	}
   181  	gr.HasMilestones = (gr.OpenMS + gr.ClosedMS) > 0
   182  
   183  	return nil
   184  }
   185  
   186  func renderMilestones(payload *githubRender, c *githubConfig) error {
   187  
   188  	if !c.ShowMilestones {
   189  		return nil
   190  	}
   191  
   192  	hadRepo := make(map[string]bool)
   193  	payload.RepoCount = 0
   194  	for _, orb := range payload.List {
   195  		rName := orb.Owner + "/" + orb.Repo
   196  		if !hadRepo[rName] {
   197  			if orb.Included {
   198  
   199  				payload.RepoCount++
   200  				issuesOpen, issuesClosed := 0, 0
   201  				for _, iss := range payload.Issues {
   202  					if iss.Repo == repoName(rName) {
   203  						if iss.Milestone == noMilestone {
   204  							if iss.IsOpen {
   205  								issuesOpen++
   206  							} else {
   207  								issuesClosed++
   208  							}
   209  						}
   210  					}
   211  				}
   212  				if issuesClosed+issuesOpen > 0 {
   213  					//payload.Milestones = append(payload.Milestones, githubMilestone{
   214  					//	Repo: orb.Repo, Private: orb.Private, Name: noMilestone, IsOpen: true,
   215  					//	OpenIssues: issuesOpen, ClosedIssues: issuesClosed, URL: template.URL(orb.URL),
   216  					//})
   217  				}
   218  
   219  				hadRepo[rName] = true
   220  			}
   221  		}
   222  	}
   223  
   224  	sort.Sort(milestonesToSort(payload.Milestones))
   225  
   226  	return nil
   227  }