Merge pull request #8 from gojp/badges

Add experimental badge support
This commit is contained in:
Shawn Smith
2015-02-07 03:29:43 -08:00
2 changed files with 110 additions and 57 deletions

114
main.go
View File

@@ -94,6 +94,7 @@ type score struct {
type checksResp struct {
Checks []score `json:"checks"`
Average float64 `json:"average"`
Grade Grade `json:"grade"`
Files int `json:"files"`
Issues int `json:"issues"`
Repo string `json:"repo"`
@@ -117,47 +118,69 @@ func getFromCache(repo string) (checksResp, error) {
return resp, nil
}
func checkHandler(w http.ResponseWriter, r *http.Request) {
repo := r.FormValue("repo")
// Grade represents a grade returned by the server, which is normally
// somewhere between A+ (highest) and F (lowest).
type Grade string
// The Grade constants below indicate the current available
// grades.
const (
GradeAPlus Grade = "A+"
GradeA = "A"
GradeB = "B"
GradeC = "C"
GradeD = "D"
GradeE = "E"
GradeF = "F"
)
// getGrade is a helper for getting the grade for a percentage
func getGrade(percentage float64) Grade {
switch true {
case percentage > 90:
return GradeAPlus
case percentage > 80:
return GradeA
case percentage > 70:
return GradeB
case percentage > 60:
return GradeC
case percentage > 50:
return GradeD
case percentage > 40:
return GradeE
default:
return GradeF
}
}
func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
url := repo
if !strings.HasPrefix(url, "https://github.com/") {
url = "https://github.com/" + url
}
w.Header().Set("Content-Type", "application/json")
// if this is a GET request, fetch from cached version in mongo
if r.Method == "GET" {
if !forceRefresh {
resp, err := getFromCache(repo)
if err != nil {
// just log the error and continue
log.Println(err)
} else {
b, err := json.Marshal(resp)
if err != nil {
log.Println("ERROR: could not marshal json:", err)
http.Error(w, err.Error(), 500)
return
}
w.Write(b)
log.Println("Loaded from cache!", repo)
return
resp.Grade = getGrade(resp.Average * 100) // grade is not stored for some repos, yet
return resp, nil
}
}
// fetch the repo and grade it
err := clone(url)
if err != nil {
log.Println("ERROR: could not clone repo: ", err)
http.Error(w, fmt.Sprintf("Could not clone repo: %v", err), 500)
return
return checksResp{}, fmt.Errorf("Could not clone repo: %v", err)
}
dir := dirName(url)
filenames, err := check.GoFiles(dir)
if err != nil {
log.Println("ERROR: could not get filenames: ", err)
http.Error(w, fmt.Sprintf("Could not get filenames: %v", err), 500)
return
return checksResp{}, fmt.Errorf("Could not get filenames: %v", err)
}
checks := []check.Check{check.GoFmt{Dir: dir, Filenames: filenames},
check.GoVet{Dir: dir, Filenames: filenames},
@@ -198,6 +221,22 @@ func checkHandler(w http.ResponseWriter, r *http.Request) {
resp.Average = avg / float64(len(checks))
resp.Issues = len(issues)
resp.Grade = getGrade(resp.Average * 100)
return resp, nil
}
func checkHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
repo := r.FormValue("repo")
forceRefresh := r.Method != "GET" // if this is a GET request, try fetch from cached version in mongo first
resp, err := newChecksResp(repo, forceRefresh)
if err != nil {
b, _ := json.Marshal(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write(b)
}
b, err := json.Marshal(resp)
if err != nil {
@@ -224,9 +263,35 @@ func reportHandler(w http.ResponseWriter, r *http.Request, org, repo string) {
http.ServeFile(w, r, "templates/home.html")
}
func makeReportHandler(fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc {
func badgeURL(grade Grade) string {
colorMap := map[Grade]string{
GradeAPlus: "brightgreen",
GradeA: "brightgreen",
GradeB: "yellowgreen",
GradeC: "yellow",
GradeD: "orange",
GradeE: "red",
GradeF: "red",
}
url := fmt.Sprintf("https://img.shields.io/badge/go_report-%s-%s.svg", grade, colorMap[grade])
return url
}
func badgeHandler(w http.ResponseWriter, r *http.Request, org, repo string) {
name := fmt.Sprintf("%s/%s", org, repo)
resp, err := newChecksResp(name, false)
if err != nil {
log.Printf("ERROR: fetching badge for %s: %v", name, err)
http.Redirect(w, r, "https://img.shields.io/badge/go%20report-error-lightgrey.svg", http.StatusTemporaryRedirect)
return
}
http.Redirect(w, r, badgeURL(resp.Grade), http.StatusTemporaryRedirect)
}
func makeHandler(name string, fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
validPath := regexp.MustCompile(`^/report/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`)
validPath := regexp.MustCompile(fmt.Sprintf(`^/%s/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`, name))
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
@@ -244,7 +309,8 @@ func main() {
http.HandleFunc("/assets/", assetsHandler)
http.HandleFunc("/checks", checkHandler)
http.HandleFunc("/report/", makeReportHandler(reportHandler))
http.HandleFunc("/report/", makeHandler("report", reportHandler))
http.HandleFunc("/badge/", makeHandler("badge", badgeHandler))
http.HandleFunc("/", homeHandler)
fmt.Println("Running on 127.0.01:8080...")

View File

@@ -258,31 +258,16 @@
</tr>
</script>
<script id="template-lastrefresh" type="text/x-handlebars-template">
<p>Last refresh: {{last_refresh}} <a href=""><strong>Update now</strong></a></p>
<!--
<p>Last refresh: {{last_refresh}} <a class="refresh-button" href=""><strong>Update now</strong></a></p>
<div class="col-md-12 text-right">
<a class="copy-button" data-repo="{{repo}}" href="/badge/{{repo}}"><img src="/badge/{{repo}}"/></a>
</div>
-->
</script>
<script type="text/javascript">
var loading = false;
// add a helper for getting the grade for a percentage
var getGrade = function(percentage) {
switch(true){
case percentage > 90:
return "A+";
case percentage > 80:
return "A";
case percentage > 70:
return "B";
case percentage > 60:
return "C";
case percentage > 50:
return "D";
case percentage > 40:
return "E";
default:
return "F";
}
};
Handlebars.registerHelper('gradeMessage', function(grade, options) {
var gradeMessages = {
"A+": "Excellent!",
@@ -356,10 +341,8 @@
}
var populateResults = function(data){
console.log("data is", data);
var checks = data.checks;
var $resultsText = $(".results-text");
data.grade = getGrade(data.average * 100);
$resultsText.html($(templates.grade(data)));
var $table = $(".table.results");
@@ -379,19 +362,30 @@
$(".container-results").removeClass('hidden').slideDown();
$lastRefresh = $(templates.lastrefresh(data));
$(".container-update").html($lastRefresh).find("a").on("click", function(e){
$div = $(".container-update").html($lastRefresh);
$div.find("a.refresh-button").on("click", function(e){
loadData.call($("form#check-form")[0], false);
return false;
});
};
function alertMessage(msg){
var html = templates.alert({message: msg});
var $alert = $(html);
$alert.on("click", function(){
$(this).closest(".alert").remove();
});
$alert.hide();
$alert.appendTo("#notifications");
$alert.slideDown();
}
var loadData = function(getRequest){
loading = true;
var $form = $(this),
url = $form.attr("action"),
method = $form.attr("method"),
data = {};
console.log("GOnna make", getRequest ? "GET" : "POST");
shrinkHeader();
hideResults();
showGopher();
@@ -402,14 +396,7 @@
data: data,
dataType: "json"
}).fail(function(xhr, status, err){;
var html = templates.alert({message: "<strong>Oops!</strong> There was an error processing your request: " + err});
var $alert = $(html);
$alert.on("click", function(){
$(this).closest(".alert").remove();
});
$alert.hide();
$alert.appendTo("#notifications");
$alert.slideDown();
alertMessage("<strong>Oops!</strong> There was an error processing your request: " + err);
}).done(function(data, textStatus, jqXHR){
hideGopher();
populateResults(data);