Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(29)

Side by Side Diff: tools/bug_chomper/src/issue_tracker/issue_tracker.go

Issue 731123003: Remove bug_chomper (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/bug_chomper/run_server.sh ('k') | tools/bug_chomper/src/server/server.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /*
6 Utilities for interacting with the GoogleCode issue tracker.
7
8 Example usage:
9 issueTracker := issue_tracker.MakeIssueTraker(myOAuthConfigFile)
10 authURL := issueTracker.MakeAuthRequestURL()
11 // Visit the authURL to obtain an authorization code.
12 issueTracker.UpgradeCode(code)
13 // Now issueTracker can be used to retrieve and edit issues.
14 */
15 package issue_tracker
16
17 import (
18 "bytes"
19 "encoding/json"
20 "fmt"
21 "io/ioutil"
22 "net/http"
23 "net/url"
24 "strconv"
25 "strings"
26
27 "code.google.com/p/goauth2/oauth"
28 )
29
30 // BugPriorities are the possible values for "Priority-*" labels for issues.
31 var BugPriorities = []string{"Critical", "High", "Medium", "Low", "Never"}
32
33 var apiScope = []string{
34 "https://www.googleapis.com/auth/projecthosting",
35 "https://www.googleapis.com/auth/userinfo.email",
36 }
37
38 const issueApiURL = "https://www.googleapis.com/projecthosting/v2/projects/"
39 const issueURL = "https://code.google.com/p/skia/issues/detail?id="
40 const personApiURL = "https://www.googleapis.com/userinfo/v2/me"
41
42 // Enum for determining whether a label has been added, removed, or is
43 // unchanged.
44 const (
45 labelAdded = iota
46 labelRemoved
47 labelUnchanged
48 )
49
50 // loadOAuthConfig reads the OAuth given config file path and returns an
51 // appropriate oauth.Config.
52 func loadOAuthConfig(oauthConfigFile string) (*oauth.Config, error) {
53 errFmt := "failed to read OAuth config file: %s"
54 fileContents, err := ioutil.ReadFile(oauthConfigFile)
55 if err != nil {
56 return nil, fmt.Errorf(errFmt, err)
57 }
58 var decodedJson map[string]struct {
59 AuthURL string `json:"auth_uri"`
60 ClientId string `json:"client_id"`
61 ClientSecret string `json:"client_secret"`
62 TokenURL string `json:"token_uri"`
63 }
64 if err := json.Unmarshal(fileContents, &decodedJson); err != nil {
65 return nil, fmt.Errorf(errFmt, err)
66 }
67 config, ok := decodedJson["web"]
68 if !ok {
69 return nil, fmt.Errorf(errFmt, err)
70 }
71 return &oauth.Config{
72 ClientId: config.ClientId,
73 ClientSecret: config.ClientSecret,
74 Scope: strings.Join(apiScope, " "),
75 AuthURL: config.AuthURL,
76 TokenURL: config.TokenURL,
77 }, nil
78 }
79
80 // Issue contains information about an issue.
81 type Issue struct {
82 Id int `json:"id"`
83 Project string `json:"projectId"`
84 Title string `json:"title"`
85 Labels []string `json:"labels"`
86 }
87
88 // URL returns the URL of a given issue.
89 func (i Issue) URL() string {
90 return issueURL + strconv.Itoa(i.Id)
91 }
92
93 // IssueList represents a list of issues from the IssueTracker.
94 type IssueList struct {
95 TotalResults int `json:"totalResults"`
96 Items []*Issue `json:"items"`
97 }
98
99 // IssueTracker is the primary point of contact with the issue tracker,
100 // providing methods for authenticating to and interacting with it.
101 type IssueTracker struct {
102 OAuthConfig *oauth.Config
103 OAuthTransport *oauth.Transport
104 }
105
106 // MakeIssueTracker creates and returns an IssueTracker with authentication
107 // configuration from the given authConfigFile.
108 func MakeIssueTracker(authConfigFile string, redirectURL string) (*IssueTracker, error) {
109 oauthConfig, err := loadOAuthConfig(authConfigFile)
110 if err != nil {
111 return nil, fmt.Errorf(
112 "failed to create IssueTracker: %s", err)
113 }
114 oauthConfig.RedirectURL = redirectURL
115 return &IssueTracker{
116 OAuthConfig: oauthConfig,
117 OAuthTransport: &oauth.Transport{Config: oauthConfig},
118 }, nil
119 }
120
121 // MakeAuthRequestURL returns an authentication request URL which can be used
122 // to obtain an authorization code via user sign-in.
123 func (it IssueTracker) MakeAuthRequestURL() string {
124 // NOTE: Need to add XSRF protection if we ever want to run this on a pu blic
125 // server.
126 return it.OAuthConfig.AuthCodeURL(it.OAuthConfig.RedirectURL)
127 }
128
129 // IsAuthenticated determines whether the IssueTracker has sufficient
130 // permissions to retrieve and edit Issues.
131 func (it IssueTracker) IsAuthenticated() bool {
132 return it.OAuthTransport.Token != nil
133 }
134
135 // UpgradeCode exchanges the single-use authorization code, obtained by
136 // following the URL obtained from IssueTracker.MakeAuthRequestURL, for a
137 // multi-use, session token. This is required before IssueTracker can retrieve
138 // and edit issues.
139 func (it *IssueTracker) UpgradeCode(code string) error {
140 token, err := it.OAuthTransport.Exchange(code)
141 if err == nil {
142 it.OAuthTransport.Token = token
143 return nil
144 } else {
145 return fmt.Errorf(
146 "failed to exchange single-user auth code: %s", err)
147 }
148 }
149
150 // GetLoggedInUser retrieves the email address of the authenticated user.
151 func (it IssueTracker) GetLoggedInUser() (string, error) {
152 errFmt := "error retrieving user email: %s"
153 if !it.IsAuthenticated() {
154 return "", fmt.Errorf(errFmt, "User is not authenticated!")
155 }
156 resp, err := it.OAuthTransport.Client().Get(personApiURL)
157 if err != nil {
158 return "", fmt.Errorf(errFmt, err)
159 }
160 defer resp.Body.Close()
161 body, _ := ioutil.ReadAll(resp.Body)
162 if resp.StatusCode != http.StatusOK {
163 return "", fmt.Errorf(errFmt, fmt.Sprintf(
164 "user data API returned code %d: %v", resp.StatusCode, s tring(body)))
165 }
166 userInfo := struct {
167 Email string `json:"email"`
168 }{}
169 if err := json.Unmarshal(body, &userInfo); err != nil {
170 return "", fmt.Errorf(errFmt, err)
171 }
172 return userInfo.Email, nil
173 }
174
175 // GetBug retrieves the Issue with the given ID from the IssueTracker.
176 func (it IssueTracker) GetBug(project string, id int) (*Issue, error) {
177 errFmt := fmt.Sprintf("error retrieving issue %d: %s", id, "%s")
178 if !it.IsAuthenticated() {
179 return nil, fmt.Errorf(errFmt, "user is not authenticated!")
180 }
181 requestURL := issueApiURL + project + "/issues/" + strconv.Itoa(id)
182 resp, err := it.OAuthTransport.Client().Get(requestURL)
183 if err != nil {
184 return nil, fmt.Errorf(errFmt, err)
185 }
186 defer resp.Body.Close()
187 body, _ := ioutil.ReadAll(resp.Body)
188 if resp.StatusCode != http.StatusOK {
189 return nil, fmt.Errorf(errFmt, fmt.Sprintf(
190 "issue tracker returned code %d:%v", resp.StatusCode, st ring(body)))
191 }
192 var issue Issue
193 if err := json.Unmarshal(body, &issue); err != nil {
194 return nil, fmt.Errorf(errFmt, err)
195 }
196 return &issue, nil
197 }
198
199 // GetBugs retrieves all Issues with the given owner from the IssueTracker,
200 // returning an IssueList.
201 func (it IssueTracker) GetBugs(project string, owner string) (*IssueList, error) {
202 errFmt := "error retrieving issues: %s"
203 if !it.IsAuthenticated() {
204 return nil, fmt.Errorf(errFmt, "user is not authenticated!")
205 }
206 params := map[string]string{
207 "owner": url.QueryEscape(owner),
208 "can": "open",
209 "maxResults": "9999",
210 }
211 requestURL := issueApiURL + project + "/issues?"
212 first := true
213 for k, v := range params {
214 if first {
215 first = false
216 } else {
217 requestURL += "&"
218 }
219 requestURL += k + "=" + v
220 }
221 resp, err := it.OAuthTransport.Client().Get(requestURL)
222 if err != nil {
223 return nil, fmt.Errorf(errFmt, err)
224 }
225 defer resp.Body.Close()
226 body, _ := ioutil.ReadAll(resp.Body)
227 if resp.StatusCode != http.StatusOK {
228 return nil, fmt.Errorf(errFmt, fmt.Sprintf(
229 "issue tracker returned code %d:%v", resp.StatusCode, st ring(body)))
230 }
231
232 var bugList IssueList
233 if err := json.Unmarshal(body, &bugList); err != nil {
234 return nil, fmt.Errorf(errFmt, err)
235 }
236 return &bugList, nil
237 }
238
239 // SubmitIssueChanges creates a comment on the given Issue which modifies it
240 // according to the contents of the passed-in Issue struct.
241 func (it IssueTracker) SubmitIssueChanges(issue *Issue, comment string) error {
242 errFmt := "Error updating issue " + strconv.Itoa(issue.Id) + ": %s"
243 if !it.IsAuthenticated() {
244 return fmt.Errorf(errFmt, "user is not authenticated!")
245 }
246 oldIssue, err := it.GetBug(issue.Project, issue.Id)
247 if err != nil {
248 return fmt.Errorf(errFmt, err)
249 }
250 postData := struct {
251 Content string `json:"content"`
252 Updates struct {
253 Title *string `json:"summary"`
254 Labels []string `json:"labels"`
255 } `json:"updates"`
256 }{
257 Content: comment,
258 }
259 if issue.Title != oldIssue.Title {
260 postData.Updates.Title = &issue.Title
261 }
262 // TODO(borenet): Add other issue attributes, eg. Owner.
263 labels := make(map[string]int)
264 for _, label := range issue.Labels {
265 labels[label] = labelAdded
266 }
267 for _, label := range oldIssue.Labels {
268 if _, ok := labels[label]; ok {
269 labels[label] = labelUnchanged
270 } else {
271 labels[label] = labelRemoved
272 }
273 }
274 labelChanges := make([]string, 0)
275 for labelName, present := range labels {
276 if present == labelRemoved {
277 labelChanges = append(labelChanges, "-"+labelName)
278 } else if present == labelAdded {
279 labelChanges = append(labelChanges, labelName)
280 }
281 }
282 if len(labelChanges) > 0 {
283 postData.Updates.Labels = labelChanges
284 }
285
286 postBytes, err := json.Marshal(&postData)
287 if err != nil {
288 return fmt.Errorf(errFmt, err)
289 }
290 requestURL := issueApiURL + issue.Project + "/issues/" +
291 strconv.Itoa(issue.Id) + "/comments"
292 resp, err := it.OAuthTransport.Client().Post(
293 requestURL, "application/json", bytes.NewReader(postBytes))
294 if err != nil {
295 return fmt.Errorf(errFmt, err)
296 }
297 defer resp.Body.Close()
298 body, _ := ioutil.ReadAll(resp.Body)
299 if resp.StatusCode != http.StatusOK {
300 return fmt.Errorf(errFmt, fmt.Sprintf(
301 "Issue tracker returned code %d:%v", resp.StatusCode, st ring(body)))
302 }
303 return nil
304 }
OLDNEW
« no previous file with comments | « tools/bug_chomper/run_server.sh ('k') | tools/bug_chomper/src/server/server.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698