From 95864504d2d2690c428c6349d4cad3d61bddff7e Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Tue, 20 Jan 2015 09:12:24 +1100 Subject: [PATCH] misc/dashboard/codereview: delete This dashboard is no longer in use, and doesn't work with Gerrit. Change-Id: Ib7c367dcad97322566610157b15e23db5bec58ff Reviewed-on: https://go-review.googlesource.com/3028 Reviewed-by: David Symonds --- misc/dashboard/codereview/app.yaml | 24 - misc/dashboard/codereview/cron.yaml | 4 - misc/dashboard/codereview/dashboard/cl.go | 493 ------------------ misc/dashboard/codereview/dashboard/front.go | 299 ----------- misc/dashboard/codereview/dashboard/gc.go | 47 -- misc/dashboard/codereview/dashboard/mail.go | 68 --- misc/dashboard/codereview/dashboard/people.go | 65 --- misc/dashboard/codereview/index.yaml | 25 - misc/dashboard/codereview/queue.yaml | 4 - .../codereview/static/gopherstamp.jpg | Bin 16996 -> 0 bytes misc/dashboard/codereview/static/icon.png | Bin 412 -> 0 bytes 11 files changed, 1029 deletions(-) delete mode 100644 misc/dashboard/codereview/app.yaml delete mode 100644 misc/dashboard/codereview/cron.yaml delete mode 100644 misc/dashboard/codereview/dashboard/cl.go delete mode 100644 misc/dashboard/codereview/dashboard/front.go delete mode 100644 misc/dashboard/codereview/dashboard/gc.go delete mode 100644 misc/dashboard/codereview/dashboard/mail.go delete mode 100644 misc/dashboard/codereview/dashboard/people.go delete mode 100644 misc/dashboard/codereview/index.yaml delete mode 100644 misc/dashboard/codereview/queue.yaml delete mode 100644 misc/dashboard/codereview/static/gopherstamp.jpg delete mode 100644 misc/dashboard/codereview/static/icon.png diff --git a/misc/dashboard/codereview/app.yaml b/misc/dashboard/codereview/app.yaml deleted file mode 100644 index 372eca5a155..00000000000 --- a/misc/dashboard/codereview/app.yaml +++ /dev/null @@ -1,24 +0,0 @@ -application: gocodereview -version: 1 -runtime: go -api_version: go1 - -inbound_services: -- mail - -handlers: -- url: /static/(.*) - static_files: static/\1 - upload: static/.* -- url: /_ah/mail/.* - script: _go_app - login: admin -- url: /_ah/queue/go/delay - script: _go_app - login: admin -- url: /(gc|update-cl) - script: _go_app - login: admin -- url: /.* - script: _go_app - login: required diff --git a/misc/dashboard/codereview/cron.yaml b/misc/dashboard/codereview/cron.yaml deleted file mode 100644 index 3d33d32b579..00000000000 --- a/misc/dashboard/codereview/cron.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cron: -- description: GC - url: /gc - schedule: every 6 hours diff --git a/misc/dashboard/codereview/dashboard/cl.go b/misc/dashboard/codereview/dashboard/cl.go deleted file mode 100644 index 0ef3303e9d7..00000000000 --- a/misc/dashboard/codereview/dashboard/cl.go +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dashboard - -// This file handles operations on the CL entity kind. - -import ( - "bytes" - "encoding/json" - "fmt" - "html/template" - "io" - "io/ioutil" - "net/http" - "net/url" - "regexp" - "sort" - "strings" - "time" - - "appengine" - "appengine/datastore" - "appengine/taskqueue" - "appengine/urlfetch" - "appengine/user" -) - -func init() { - http.HandleFunc("/assign", handleAssign) - http.HandleFunc("/update-cl", handleUpdateCL) -} - -const codereviewBase = "http://codereview.appspot.com" -const gobotBase = "http://research.swtch.com/gobot_codereview" - -var clRegexp = regexp.MustCompile(`\d+`) - -// CL represents a code review. -type CL struct { - Number string // e.g. "5903061" - Closed bool - Owner string // email address - - Created, Modified time.Time - - Description []byte `datastore:",noindex"` - FirstLine string `datastore:",noindex"` - LGTMs []string - NotLGTMs []string - LastUpdateBy string // author of most recent review message - LastUpdate string `datastore:",noindex"` // first line of most recent review message - - // Mail information. - Subject string `datastore:",noindex"` - Recipients []string `datastore:",noindex"` - LastMessageID string `datastore:",noindex"` - - // These are person IDs (e.g. "rsc"); they may be empty - Author string - Reviewer string -} - -// Reviewed reports whether the reviewer has replied to the CL. -// The heuristic is that the CL has been replied to if it is LGTMed -// or if the last CL message was from the reviewer. -func (cl *CL) Reviewed() bool { - if cl.LastUpdateBy == cl.Reviewer { - return true - } - if person := emailToPerson[cl.LastUpdateBy]; person != "" && person == cl.Reviewer { - return true - } - for _, who := range cl.LGTMs { - if who == cl.Reviewer { - return true - } - } - return false -} - -// DisplayOwner returns the CL's owner, either as their email address -// or the person ID if it's a reviewer. It is for display only. -func (cl *CL) DisplayOwner() string { - if p, ok := emailToPerson[cl.Owner]; ok { - return p - } - return cl.Owner -} - -func (cl *CL) FirstLineHTML() template.HTML { - s := template.HTMLEscapeString(cl.FirstLine) - // Embolden the package name. - if i := strings.Index(s, ":"); i >= 0 { - s = "" + s[:i] + "" + s[i:] - } - return template.HTML(s) -} - -func formatEmails(e []string) template.HTML { - x := make([]string, len(e)) - for i, s := range e { - s = template.HTMLEscapeString(s) - if !strings.Contains(s, "@") { - s = "" + s + "" - } - s = `" - x[i] = s - } - return template.HTML(strings.Join(x, ", ")) -} - -func (cl *CL) LGTMHTML() template.HTML { - return formatEmails(cl.LGTMs) -} - -func (cl *CL) NotLGTMHTML() template.HTML { - return formatEmails(cl.NotLGTMs) -} - -func (cl *CL) ModifiedAgo() string { - // Just the first non-zero unit. - units := [...]struct { - suffix string - unit time.Duration - }{ - {"d", 24 * time.Hour}, - {"h", time.Hour}, - {"m", time.Minute}, - {"s", time.Second}, - } - d := time.Now().Sub(cl.Modified) - for _, u := range units { - if d > u.unit { - return fmt.Sprintf("%d%s", d/u.unit, u.suffix) - } - } - return "just now" -} - -func handleAssign(w http.ResponseWriter, r *http.Request) { - c := appengine.NewContext(r) - - if r.Method != "POST" { - http.Error(w, "Bad method "+r.Method, 400) - return - } - - u := user.Current(c) - person, ok := emailToPerson[u.Email] - if !ok { - http.Error(w, "Not allowed", http.StatusUnauthorized) - return - } - - n, rev := r.FormValue("cl"), r.FormValue("r") - if !clRegexp.MatchString(n) { - c.Errorf("Bad CL %q", n) - http.Error(w, "Bad CL", 400) - return - } - if _, ok := preferredEmail[rev]; !ok && rev != "" { - c.Errorf("Unknown reviewer %q", rev) - http.Error(w, "Unknown reviewer", 400) - return - } - - key := datastore.NewKey(c, "CL", n, 0, nil) - - if rev != "" { - // Make sure the reviewer is listed in Rietveld as a reviewer. - url := codereviewBase + "/" + n + "/fields" - resp, err := urlfetch.Client(c).Get(url + "?field=reviewers") - if err != nil { - c.Errorf("Retrieving CL reviewer list failed: %v", err) - http.Error(w, err.Error(), 500) - return - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - c.Errorf("Failed reading body: %v", err) - http.Error(w, err.Error(), 500) - return - } - if resp.StatusCode != 200 { - c.Errorf("Retrieving CL reviewer list failed: got HTTP response %d\nBody: %s", resp.StatusCode, body) - http.Error(w, "Failed contacting Rietveld", 500) - return - } - - var apiResp struct { - Reviewers []string `json:"reviewers"` - } - if err := json.Unmarshal(body, &apiResp); err != nil { - // probably can't be retried - msg := fmt.Sprintf("Malformed JSON from %v: %v", url, err) - c.Errorf("%s", msg) - http.Error(w, msg, 500) - return - } - found := false - for _, r := range apiResp.Reviewers { - if emailToPerson[r] == rev { - found = true - break - } - } - if !found { - c.Infof("Adding %v as a reviewer of CL %v", rev, n) - - url := fmt.Sprintf("%s?cl=%s&r=%s&obo=%s", gobotBase, n, rev, person) - resp, err := urlfetch.Client(c).Get(url) - if err != nil { - c.Errorf("Gobot GET failed: %v", err) - http.Error(w, err.Error(), 500) - return - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - c.Errorf("Failed reading Gobot body: %v", err) - http.Error(w, err.Error(), 500) - return - } - if resp.StatusCode != 200 { - c.Errorf("Gobot GET failed: got HTTP response %d\nBody: %s", resp.StatusCode, body) - http.Error(w, "Failed contacting Gobot", 500) - return - } - - c.Infof("Gobot said %q", resp.Status) - } - } - - // Update our own record. - err := datastore.RunInTransaction(c, func(c appengine.Context) error { - cl := new(CL) - err := datastore.Get(c, key, cl) - if err != nil { - return err - } - cl.Reviewer = rev - _, err = datastore.Put(c, key, cl) - return err - }, nil) - if err != nil { - msg := fmt.Sprintf("Assignment failed: %v", err) - c.Errorf("%s", msg) - http.Error(w, msg, 500) - return - } - c.Infof("Assigned CL %v to %v", n, rev) -} - -func UpdateCLLater(c appengine.Context, n string, delay time.Duration) { - t := taskqueue.NewPOSTTask("/update-cl", url.Values{ - "cl": []string{n}, - }) - t.Delay = delay - if _, err := taskqueue.Add(c, t, "update-cl"); err != nil { - c.Errorf("Failed adding task: %v", err) - } -} - -func handleUpdateCL(w http.ResponseWriter, r *http.Request) { - c := appengine.NewContext(r) - - n := r.FormValue("cl") - if !clRegexp.MatchString(n) { - c.Errorf("Bad CL %q", n) - http.Error(w, "Bad CL", 400) - return - } - - if err := updateCL(c, n); err != nil { - c.Errorf("Failed updating CL %v: %v", n, err) - http.Error(w, "Failed update", 500) - return - } - - io.WriteString(w, "OK") -} - -// apiMessage describes the JSON sent back by Rietveld in the CL messages list. -type apiMessage struct { - Date string `json:"date"` - Text string `json:"text"` - Sender string `json:"sender"` - Recipients []string `json:"recipients"` - Approval bool `json:"approval"` -} - -// byDate implements sort.Interface to order the messages by date, earliest first. -// The dates are sent in RFC 3339 format, so string comparison matches time value comparison. -type byDate []*apiMessage - -func (x byDate) Len() int { return len(x) } -func (x byDate) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x byDate) Less(i, j int) bool { return x[i].Date < x[j].Date } - -// updateCL updates a single CL. If a retryable failure occurs, an error is returned. -func updateCL(c appengine.Context, n string) error { - c.Debugf("Updating CL %v", n) - key := datastore.NewKey(c, "CL", n, 0, nil) - - url := codereviewBase + "/api/" + n + "?messages=true" - resp, err := urlfetch.Client(c).Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - - raw, err := ioutil.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("Failed reading HTTP body: %v", err) - } - - // Special case for abandoned CLs. - if resp.StatusCode == 404 && bytes.Contains(raw, []byte("No issue exists with that id")) { - // Don't bother checking for errors. The CL might never have been saved, for instance. - datastore.Delete(c, key) - c.Infof("Deleted abandoned CL %v", n) - return nil - } - - if resp.StatusCode != 200 { - return fmt.Errorf("Update: got HTTP response %d", resp.StatusCode) - } - - var apiResp struct { - Description string `json:"description"` - Reviewers []string `json:"reviewers"` - Created string `json:"created"` - OwnerEmail string `json:"owner_email"` - Modified string `json:"modified"` - Closed bool `json:"closed"` - Subject string `json:"subject"` - Messages []*apiMessage `json:"messages"` - } - if err := json.Unmarshal(raw, &apiResp); err != nil { - // probably can't be retried - c.Errorf("Malformed JSON from %v: %v", url, err) - return nil - } - //c.Infof("RAW: %+v", apiResp) - sort.Sort(byDate(apiResp.Messages)) - - cl := &CL{ - Number: n, - Closed: apiResp.Closed, - Owner: apiResp.OwnerEmail, - Description: []byte(apiResp.Description), - FirstLine: apiResp.Description, - Subject: apiResp.Subject, - Author: emailToPerson[apiResp.OwnerEmail], - } - cl.Created, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Created) - if err != nil { - c.Errorf("Bad creation time %q: %v", apiResp.Created, err) - } - cl.Modified, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Modified) - if err != nil { - c.Errorf("Bad modification time %q: %v", apiResp.Modified, err) - } - if i := strings.Index(cl.FirstLine, "\n"); i >= 0 { - cl.FirstLine = cl.FirstLine[:i] - } - // Treat zero reviewers as a signal that the CL is completed. - // This could be after the CL has been submitted, but before the CL author has synced, - // but it could also be a CL manually edited to remove reviewers. - if len(apiResp.Reviewers) == 0 { - cl.Closed = true - } - - lgtm := make(map[string]bool) - notLGTM := make(map[string]bool) - rcpt := make(map[string]bool) - for _, msg := range apiResp.Messages { - s, rev := msg.Sender, false - if p, ok := emailToPerson[s]; ok { - s, rev = p, true - } - - line := firstLine(msg.Text) - if line != "" { - cl.LastUpdateBy = msg.Sender - cl.LastUpdate = line - } - - // CLs submitted by someone other than the CL owner do not immediately - // transition to "closed". Let's simulate the intention by treating - // messages starting with "*** Submitted as " from a reviewer as a - // signal that the CL is now closed. - if rev && strings.HasPrefix(msg.Text, "*** Submitted as ") { - cl.Closed = true - } - - if msg.Approval { - lgtm[s] = true - delete(notLGTM, s) // "LGTM" overrules previous "NOT LGTM" - } - if strings.Contains(line, "NOT LGTM") { - notLGTM[s] = true - delete(lgtm, s) // "NOT LGTM" overrules previous "LGTM" - } - - for _, r := range msg.Recipients { - rcpt[r] = true - } - } - for l := range lgtm { - cl.LGTMs = append(cl.LGTMs, l) - } - for l := range notLGTM { - cl.NotLGTMs = append(cl.NotLGTMs, l) - } - for r := range rcpt { - cl.Recipients = append(cl.Recipients, r) - } - sort.Strings(cl.LGTMs) - sort.Strings(cl.NotLGTMs) - sort.Strings(cl.Recipients) - - err = datastore.RunInTransaction(c, func(c appengine.Context) error { - ocl := new(CL) - err := datastore.Get(c, key, ocl) - if err != nil && err != datastore.ErrNoSuchEntity { - return err - } else if err == nil { - // LastMessageID and Reviewer need preserving. - cl.LastMessageID = ocl.LastMessageID - cl.Reviewer = ocl.Reviewer - } - _, err = datastore.Put(c, key, cl) - return err - }, nil) - if err != nil { - return err - } - c.Infof("Updated CL %v", n) - return nil -} - -// trailingSpaceRE matches trailing spaces. -var trailingSpaceRE = regexp.MustCompile(`(?m)[ \t\r]+$`) - -// removeRE is the list of patterns to skip over at the beginning of a -// message when looking for message text. -var removeRE = regexp.MustCompile(`(?m-s)\A(` + - // Skip leading "Hello so-and-so," generated by codereview plugin. - `(Hello(.|\n)*?\n\n)` + - - // Skip quoted text. - `|((On.*|.* writes|.* wrote):\n)` + - `|((>.*\n)+)` + - - // Skip lines with no letters. - `|(([^A-Za-z]*\n)+)` + - - // Skip links to comments and file info. - `|(http://codereview.*\n([^ ]+:[0-9]+:.*\n)?)` + - `|(File .*:\n)` + - - `)`, -) - -// firstLine returns the first interesting line of the message text. -func firstLine(text string) string { - // Cut trailing spaces. - text = trailingSpaceRE.ReplaceAllString(text, "") - - // Skip uninteresting lines. - for { - text = strings.TrimSpace(text) - m := removeRE.FindStringIndex(text) - if m == nil || m[0] != 0 { - break - } - text = text[m[1]:] - } - - // Chop line at newline or else at 74 bytes. - i := strings.Index(text, "\n") - if i >= 0 { - text = text[:i] - } - if len(text) > 74 { - text = text[:70] + "..." - } - return text -} diff --git a/misc/dashboard/codereview/dashboard/front.go b/misc/dashboard/codereview/dashboard/front.go deleted file mode 100644 index ea9fe0ddf03..00000000000 --- a/misc/dashboard/codereview/dashboard/front.go +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dashboard - -// This file handles the front page. - -import ( - "bytes" - "html/template" - "io" - "net/http" - "strings" - "sync" - "time" - - "appengine" - "appengine/datastore" - "appengine/user" -) - -func init() { - http.HandleFunc("/", handleFront) - http.HandleFunc("/favicon.ico", http.NotFound) -} - -// maximum number of active CLs to show in person-specific tables. -const maxCLs = 100 - -func handleFront(w http.ResponseWriter, r *http.Request) { - c := appengine.NewContext(r) - - data := &frontPageData{ - Reviewers: personList, - User: user.Current(c).Email, - IsAdmin: user.IsAdmin(c), - } - var currentPerson string - u := data.User - you := "you" - if e := r.FormValue("user"); e != "" { - u = e - you = e - } - currentPerson, data.UserIsReviewer = emailToPerson[u] - if !data.UserIsReviewer { - currentPerson = u - } - - var wg sync.WaitGroup - errc := make(chan error, 10) - activeCLs := datastore.NewQuery("CL"). - Filter("Closed =", false). - Order("-Modified") - - tableFetch := func(index int, f func(tbl *clTable) error) { - wg.Add(1) - go func() { - defer wg.Done() - start := time.Now() - if err := f(&data.Tables[index]); err != nil { - errc <- err - } - data.Timing[index] = time.Now().Sub(start) - }() - } - - data.Tables[0].Title = "CLs assigned to " + you + " for review" - if data.UserIsReviewer { - tableFetch(0, func(tbl *clTable) error { - q := activeCLs.Filter("Reviewer =", currentPerson).Limit(maxCLs) - tbl.Assignable = true - _, err := q.GetAll(c, &tbl.CLs) - return err - }) - } - - tableFetch(1, func(tbl *clTable) error { - q := activeCLs - if data.UserIsReviewer { - q = q.Filter("Author =", currentPerson) - } else { - q = q.Filter("Owner =", currentPerson) - } - q = q.Limit(maxCLs) - tbl.Title = "CLs sent by " + you - tbl.Assignable = true - _, err := q.GetAll(c, &tbl.CLs) - return err - }) - - tableFetch(2, func(tbl *clTable) error { - q := activeCLs.Limit(50) - tbl.Title = "Other active CLs" - tbl.Assignable = true - if _, err := q.GetAll(c, &tbl.CLs); err != nil { - return err - } - // filter - for i := len(tbl.CLs) - 1; i >= 0; i-- { - cl := tbl.CLs[i] - if cl.Owner == currentPerson || cl.Author == currentPerson || cl.Reviewer == currentPerson { - // Preserve order. - copy(tbl.CLs[i:], tbl.CLs[i+1:]) - tbl.CLs = tbl.CLs[:len(tbl.CLs)-1] - } - } - return nil - }) - - tableFetch(3, func(tbl *clTable) error { - q := datastore.NewQuery("CL"). - Filter("Closed =", true). - Order("-Modified"). - Limit(10) - tbl.Title = "Recently closed CLs" - tbl.Assignable = false - _, err := q.GetAll(c, &tbl.CLs) - return err - }) - - // Not really a table fetch. - tableFetch(0, func(_ *clTable) error { - var err error - data.LogoutURL, err = user.LogoutURL(c, "/") - return err - }) - - wg.Wait() - - select { - case err := <-errc: - c.Errorf("%v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - default: - } - - var b bytes.Buffer - if err := frontPage.ExecuteTemplate(&b, "front", &data); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - io.Copy(w, &b) -} - -type frontPageData struct { - Tables [4]clTable - Timing [4]time.Duration - - Reviewers []string - UserIsReviewer bool - - User, LogoutURL string // actual logged in user - IsAdmin bool -} - -type clTable struct { - Title string - Assignable bool - CLs []*CL -} - -var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{ - "selected": func(a, b string) string { - if a == b { - return "selected" - } - return "" - }, - "shortemail": func(s string) string { - if i := strings.Index(s, "@"); i >= 0 { - s = s[:i] - } - return s - }, -}).Parse(` - - - - Go code reviews - - - - - - - -

Go code reviews

- - -{{range $i, $tbl := .Tables}} - -{{if .CLs}} -{{range $cl := .CLs}} - - - - - - - -{{end}} -{{else}} - -{{end}} -{{end}} -

{{$tbl.Title}}

- {{if $tbl.Assignable}} - - - {{end}} - - {{.Number}}: {{.FirstLineHTML}} - {{if and .LGTMs $tbl.Assignable}}
LGTMs: {{.LGTMHTML}}{{end}} - {{if and .NotLGTMs $tbl.Assignable}}
NOT LGTMs: {{.NotLGTMHTML}}{{end}} - {{if .LastUpdateBy}}
({{.LastUpdateBy | shortemail}}) {{.LastUpdate}}{{end}} -
{{.ModifiedAgo}}{{if $.IsAdmin}}{{end}}
none
- -
-
-You are · logout
-datastore timing: {{range .Timing}} {{.}}{{end}} -
- - - -`)) diff --git a/misc/dashboard/codereview/dashboard/gc.go b/misc/dashboard/codereview/dashboard/gc.go deleted file mode 100644 index a80b375f6a2..00000000000 --- a/misc/dashboard/codereview/dashboard/gc.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dashboard - -// This file handles garbage collection of old CLs. - -import ( - "net/http" - "time" - - "appengine" - "appengine/datastore" -) - -func init() { - http.HandleFunc("/gc", handleGC) -} - -func handleGC(w http.ResponseWriter, r *http.Request) { - c := appengine.NewContext(r) - - // Delete closed CLs that haven't been modified in 168 hours (7 days). - cutoff := time.Now().Add(-168 * time.Hour) - q := datastore.NewQuery("CL"). - Filter("Closed =", true). - Filter("Modified <", cutoff). - Limit(100). - KeysOnly() - keys, err := q.GetAll(c, nil) - if err != nil { - c.Errorf("GetAll failed for old CLs: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if len(keys) == 0 { - return - } - - if err := datastore.DeleteMulti(c, keys); err != nil { - c.Errorf("DeleteMulti failed for old CLs: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - c.Infof("Deleted %d old CLs", len(keys)) -} diff --git a/misc/dashboard/codereview/dashboard/mail.go b/misc/dashboard/codereview/dashboard/mail.go deleted file mode 100644 index 838d082228b..00000000000 --- a/misc/dashboard/codereview/dashboard/mail.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dashboard - -// This file handles receiving mail. - -import ( - "net/http" - "net/mail" - "regexp" - "time" - - "appengine" - "appengine/datastore" -) - -func init() { - http.HandleFunc("/_ah/mail/", handleMail) -} - -var subjectRegexp = regexp.MustCompile(`.*code review (\d+):.*`) - -func handleMail(w http.ResponseWriter, r *http.Request) { - c := appengine.NewContext(r) - - defer r.Body.Close() - msg, err := mail.ReadMessage(r.Body) - if err != nil { - c.Errorf("mail.ReadMessage: %v", err) - return - } - - subj := msg.Header.Get("Subject") - m := subjectRegexp.FindStringSubmatch(subj) - if len(m) != 2 { - c.Debugf("Subject %q did not match /%v/", subj, subjectRegexp) - return - } - - c.Infof("Found issue %q", m[1]) - - // Track the MessageID. - key := datastore.NewKey(c, "CL", m[1], 0, nil) - err = datastore.RunInTransaction(c, func(c appengine.Context) error { - cl := new(CL) - err := datastore.Get(c, key, cl) - if err != nil && err != datastore.ErrNoSuchEntity { - return err - } - if err == datastore.ErrNoSuchEntity { - // Must set sentinel values for time.Time fields - // if this is a new entity. - cl.Created = time.Unix(0, 0) - cl.Modified = time.Unix(0, 0) - } - cl.LastMessageID = msg.Header.Get("Message-ID") - _, err = datastore.Put(c, key, cl) - return err - }, nil) - if err != nil { - c.Errorf("datastore transaction failed: %v", err) - } - - // Update the CL after a delay to give Rietveld a chance to catch up. - UpdateCLLater(c, m[1], 10*time.Second) -} diff --git a/misc/dashboard/codereview/dashboard/people.go b/misc/dashboard/codereview/dashboard/people.go deleted file mode 100644 index 7bab2537bc0..00000000000 --- a/misc/dashboard/codereview/dashboard/people.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dashboard - -// This file handles identities of people. - -import ( - "sort" -) - -var ( - emailToPerson = make(map[string]string) // email => person - preferredEmail = make(map[string]string) // person => email - personList []string -) - -func init() { - // People we assume have golang.org and google.com accounts, - // and prefer to use their golang.org address for code review. - gophers := [...]string{ - "adg", - "agl", - "bradfitz", - "campoy", - "cshapiro", - "dsymonds", - "gri", - "iant", - "khr", - "mpvl", - "nigeltao", - "r", - "rsc", - "sameer", - } - for _, p := range gophers { - personList = append(personList, p) - emailToPerson[p+"@golang.org"] = p - emailToPerson[p+"@google.com"] = p - preferredEmail[p] = p + "@golang.org" - } - // Other people. - others := map[string]string{ - "adonovan": "adonovan@google.com", - "brainman": "alex.brainman@gmail.com", - "ality": "ality@pbrane.org", - "dfc": "dave@cheney.net", - "dvyukov": "dvyukov@google.com", - "gustavo": "gustavo@niemeyer.net", - "jsing": "jsing@google.com", - "mikio": "mikioh.mikioh@gmail.com", - "minux": "minux.ma@gmail.com", - "remy": "remyoudompheng@gmail.com", - "rminnich": "rminnich@gmail.com", - } - for p, e := range others { - personList = append(personList, p) - emailToPerson[e] = p - preferredEmail[p] = e - } - - sort.Strings(personList) -} diff --git a/misc/dashboard/codereview/index.yaml b/misc/dashboard/codereview/index.yaml deleted file mode 100644 index a87073cc412..00000000000 --- a/misc/dashboard/codereview/index.yaml +++ /dev/null @@ -1,25 +0,0 @@ -indexes: - -- kind: CL - properties: - - name: Author - - name: Modified - direction: desc - -- kind: CL - properties: - - name: Owner - - name: Modified - direction: desc - -- kind: CL - properties: - - name: Closed - - name: Modified - direction: desc - -- kind: CL - properties: - - name: Reviewer - - name: Modified - direction: desc diff --git a/misc/dashboard/codereview/queue.yaml b/misc/dashboard/codereview/queue.yaml deleted file mode 100644 index 1a35facaf15..00000000000 --- a/misc/dashboard/codereview/queue.yaml +++ /dev/null @@ -1,4 +0,0 @@ -queue: -- name: update-cl - rate: 12/m - bucket_size: 1 diff --git a/misc/dashboard/codereview/static/gopherstamp.jpg b/misc/dashboard/codereview/static/gopherstamp.jpg deleted file mode 100644 index b17f3c82a6f0e4cbb7d23b5efac8d9d08f1b705e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16996 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 PHU3le3`_dBPiOxxtHjCs diff --git a/misc/dashboard/codereview/static/icon.png b/misc/dashboard/codereview/static/icon.png deleted file mode 100644 index c929ac8e9eeb6c032b2bfa076a2a539b5a98a3ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 412 zcmV;N0b~A&P)y*SevepRA3G> z7{n<~+<>OYuT5cEYo4lF#sXe(jBz(06`7Z{IidLw*3pX>oGIwf3O-T62i~=2t9Zl{ z3RuHjT!0<+@Ve27R=g>27MEzogc}eZq8;;C!o7kw(4}**pH#v=&f@~?q2+W2zAC6{ zy$8<<^5O#iHNq~~zGs|w6|H-!#GXr9mDPtI=*KqpaDWwz!AC*=vx_aizNaSaLJ_OD zHbK8PP0(691~7s}^u>KbSsoN@+xMlv_KN3m^Q(OG^B#m}k&L`ML>8miF~MDE7|@Om z&9kcF2yM8)q#NM2F|SX!GePg|tt@F?)bnk0!*>7x0RR61(qH0HdAI2R0000