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 }