Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a6ce0cf
commit a8b3e0e
Showing
17 changed files
with
9,779 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
**/.DS_Store | ||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
.column { | ||
float: left; | ||
} | ||
.header { | ||
border: solid 1px gray; | ||
border-radius: 2px; | ||
} | ||
.cards { | ||
margin: .5em; | ||
} | ||
.cards li { | ||
border: solid 1px gray; | ||
padding: 1em; | ||
} | ||
#progress-bar { | ||
text-align: right; | ||
color: #000; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>I kanban haz bugs | Kanbu.gs</title> | ||
<link href="lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen"/> | ||
<link href="css/styles.css" rel="stylesheet" media="screen"/> | ||
</head> | ||
<body> | ||
|
||
<h1>Kanbu.gs</h1> | ||
|
||
<form id="filters"> | ||
<fieldset> | ||
<label>whiteboard=</label> | ||
<input type="text" name="whiteboard" id="whiteboard"> | ||
<span class="help-block">Value to pass to the bugzilla api whiteboard= input parameter.</span> | ||
<button class="btn">Kanban ALL the bugs!</button> | ||
</fieldset> | ||
</form> | ||
|
||
<div class="progress progress-striped active"> | ||
<div id="progress-bar" class="bar" style="width: 25%;"><img src="https://bugzilla.mozilla.org/extensions/BMO/web/images/mozchomp.gif" width="16" height="16"/>Loading...</div> | ||
</div> | ||
|
||
<div id="board-stats"> | ||
Lead Time: <span id="lead-time"></span> <a href="#" title="Lead Time is between bugs created and marked VERIFIED:FIXED"><i class="icon-question-sign"></i></a><br/> | ||
Triage Time: <span id="triage-time"></span> <a href="#" title="Triage Time is between bugs created and selected (i.e., by adding 'p=' to their whiteboard)"><i class="icon-question-sign"></i></a> | ||
</div> | ||
|
||
<div id="selected-column" class="column"> | ||
<h3>Selected <span class="badge badge-info count">...</span></h3> | ||
<h4><span class="duration">...</span><a href="#" title="Selected time is between bugs selected and assigned."><i class="icon-question-sign"></i></a></h4> | ||
<ul id="selected"> | ||
</ul> | ||
</div> | ||
|
||
<div id="implement-column" class="column"> | ||
<h3>Implement <span class="badge badge-info count">...</span></h3> | ||
<h4><span class="duration">...</span><a href="#" title="Implement time is between bug assigned and pull request submitted for it."><i class="icon-question-sign"></i></a></h4> | ||
<ul id="implement"> | ||
</ul> | ||
</div> | ||
|
||
<div id="review-column" class="column"> | ||
<h3>Review <span class="badge badge-info count">...</span></h3> | ||
<h4><span class="duration">...</span><a href="#" title="Review time is between pull request submitted and merged (RESOLVED:FIXED)."><i class="icon-question-sign"></i></a></h4> | ||
<ul id="review"> | ||
</ul> | ||
</div> | ||
|
||
<div id="test-column" class="column"> | ||
<h3>Test <span class="badge badge-info count">...</span></h3> | ||
<h4><span class="duration">...</span><a href="#" title="Test time is between pull request merged and VERIFIED:FIXED."><i class="icon-question-sign"></i></a></h4> | ||
<ul id="test"> | ||
</ul> | ||
</div> | ||
|
||
<div id="released-column" class="column"> | ||
<h3>Released <span class="badge badge-info count">...</span></h3> | ||
<ul id="released"> | ||
</ul> | ||
</div> | ||
|
||
|
||
<script src="lib/jquery-1.8.3.min.js"></script> | ||
<script src="lib/underscore-min.js"></script> | ||
<script src="lib/bootstrap/js/bootstrap.min.js"></script> | ||
<script src="lib/date.js"></script> | ||
<script src="lib/time.js"></script> | ||
<script src="js/kanbugs.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
var states = ['selected', 'implement', 'review', 'test', 'released'], | ||
triageTimes = [], | ||
selectedTimes = [], | ||
assignedTimes = [], | ||
implementTimes = [], | ||
reviewTimes = [], | ||
testTimes = [], | ||
releaseTimes = [], | ||
ghBugs = [], | ||
bzBugs = [], | ||
kanBugs = {}, | ||
ghRequests = [], | ||
bzRequests = []; | ||
|
||
_.each(states, function(state) { | ||
kanBugs[state] = []; | ||
}); | ||
|
||
var formatDateTime = function(date_time_in) { | ||
var date_time = date_time_in.replace("T", " "); | ||
date_time = date_time_in.replace("Z", ""); | ||
return date_time; | ||
} | ||
|
||
var calculateAverageDays = function(timespan_array) { | ||
var sum = 0, | ||
avg = 0, | ||
avg_days = 0, | ||
count = timespan_array.length; | ||
for (var i=0; i < count; i++) { | ||
sum += timespan_array[i]; | ||
} | ||
avg = sum/count; | ||
avg_days = Math.round((avg/86400000) * 10)/10; | ||
return avg_days; | ||
} | ||
|
||
var recordColumnDuration = function(bug, state_datetime, previous_state_datetime, duration_property, times_array) { | ||
var ts = new TimeSpan(previous_state_datetime - state_datetime); | ||
var ts_ms = ts.getTotalMilliseconds(); | ||
if (ts_ms < 0) { | ||
console.log("Skipping negative column value for calculation."); | ||
return 0; | ||
} else { | ||
bug[duration_property] = ts_ms; | ||
times_array.push(ts_ms); | ||
return ts_ms; | ||
} | ||
} | ||
|
||
//console.log(kanBugs); | ||
|
||
var addGhBugs = function(data) { | ||
_.each(data, function(pull){ | ||
console.log("Pull " + pull.number + "..."); | ||
var bugRE = /fix bug (\d+)/i; | ||
var bugArray = bugRE.exec(pull.title); | ||
if (bugArray && typeof(bugArray[1]) != undefined) { | ||
var bugID = bugArray[1]; | ||
console.log("... fixes bug " + bugID); | ||
var ghBug = {id: bugID, state: pull.state, created_at: pull.created_at, merged_at: pull.merged_at}; | ||
ghBugs.push(ghBug); | ||
} | ||
}); | ||
//console.log(data); | ||
}; | ||
|
||
var loadGhBugs = function(){ | ||
// Get MDN pull requests from GitHub - both open and closed | ||
var github_client_id = '6f879db8324dca8c26f1', | ||
github_client_secret = 'da455accd2ff34dbbc52697c7649f49b717ec98d'; | ||
var githubURL = "https://api.github.com/repos/mozilla/kuma/pulls"; | ||
$('#progress-bar').text("Fetching open pull requests ..."); | ||
var getting_open_pulls = $.getJSON(githubURL, { | ||
client_id: github_client_id, | ||
client_secret: github_client_secret | ||
}); | ||
ghRequests.push(getting_open_pulls); | ||
// TODO: make this actually walk the pull request pages | ||
githubURL += "?state=closed&per_page=100"; | ||
for (var page=0; page < 10; page++) { | ||
var gettingPulls = $.getJSON(githubURL + "&page=" + page, { | ||
client_id: github_client_id, | ||
client_secret: github_client_secret | ||
}); | ||
$.when(gettingPulls).done(addGhBugs); | ||
ghRequests.push(gettingPulls); | ||
} | ||
}; | ||
|
||
var processBzBugs = function(data) { | ||
// console.log("processBzBugs"); | ||
// console.log("data.bugs: " + JSON.stringify(data.bugs)); | ||
var num_bugs_total = data.bugs.length, | ||
num_bugs_processed = 0; | ||
_.each(data.bugs, function(bug){ | ||
// console.log("processing bug:" + bug.id + "..."); | ||
var li = "<li><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug.id + "\" target=\"_blank\">" + bug.id + "</a></li>"; | ||
var $storyList = $('ul#story'); | ||
var $implementList = $('ul#implement'); | ||
var $reviewList = $('ul#review'); | ||
var $testList = $('ul#test'); | ||
var $releasedList = $('ul#released'); | ||
|
||
// Where is this bug now? | ||
// Start from the right of the board and work left | ||
if (bug.status == 'VERIFIED') { | ||
kanBugs['released'].push(bug); | ||
$releasedList.append(li); | ||
} else if (bug.status == 'RESOLVED') { | ||
kanBugs['test'].push(bug) | ||
$testList.append(li); | ||
|
||
// Check the list of GitHub bugs to see if a pull request is in for the bug | ||
} else if (_.contains(_.pluck(ghBugs, 'id'), bug.id.toString())){ | ||
kanBugs['review'].push(bug); | ||
$reviewList.append(li); | ||
} else if (bug.assigned_to.name !== 'nobody') { | ||
kanBugs['implement'].push(bug); | ||
$implementList.append(li); | ||
} else { | ||
kanBugs['selected'].push(bug); | ||
var $selectedList = $('ul#selected'); | ||
$selectedList.append(li); | ||
} | ||
|
||
// When did it hit each column? | ||
// columns based on simple bug fields | ||
bug['created_at'] = Date.parse(formatDateTime(bug.creation_time)); | ||
bug['merged_at'] = Date.parse(bug.cf_last_resolved); | ||
|
||
// columns based on bug history | ||
_.each(bug.history, function(entry) { | ||
_.each(entry.changes, function(change) { | ||
if (change.field_name == 'status' && | ||
change.added == 'VERIFIED') { | ||
bug['verified_at'] = Date.parse(formatDateTime(entry.change_time)); | ||
} else if (change.field_name == 'assigned_to' && | ||
change.removed == 'nobody@mozilla.org') { | ||
bug['assigned_at'] = Date.parse(formatDateTime(entry.change_time)); | ||
} else if (change.field_name == 'whiteboard' && | ||
change.removed == '' && | ||
change.added.indexOf('p=') > -1) { | ||
bug['selected_at'] = Date.parse(formatDateTime(entry.change_time)); | ||
} | ||
}); | ||
}); | ||
|
||
// column based on github | ||
_.each(ghBugs, function(ghBug) { | ||
if (ghBug.id == bug.id) { | ||
console.log("ghBug.id: " + ghBug.id + " matches bug.id: " + bug.id); | ||
bug['implemented_at'] = Date.parse(formatDateTime(ghBug.created_at)); | ||
console.log(bug['implemented_at']); | ||
console.log(bug['assigned_at']); | ||
} | ||
}); | ||
|
||
// How long did the bug take in each column? | ||
if (bug['selected_at']) { | ||
recordColumnDuration(bug, bug['created_at'], bug['selected_at'], 'tts', triageTimes); | ||
if (bug['assigned_at']) { | ||
recordColumnDuration(bug, bug['selected_at'], bug['assigned_at'], 'tta', selectedTimes); | ||
} | ||
} | ||
//console.log(bug['assigned_at'] + '...' + bug['implemented_at']); | ||
if (bug['assigned_at'] && bug['implemented_at']) { | ||
console.log("implemented_at: " + bug['implemented_at']); | ||
recordColumnDuration(bug, bug['assigned_at'], bug['implemented_at'], 'tti', implementTimes); | ||
} | ||
if (bug['merged_at'] && bug['implemented_at']) { | ||
recordColumnDuration(bug, bug['implemented_at'], bug['merged_at'], 'ttr', reviewTimes); | ||
} | ||
if (bug['verified_at'] && bug['merged_at']) { | ||
recordColumnDuration(bug, bug['merged_at'], bug['verified_at'], 'ttt', testTimes); | ||
} | ||
if (bug['verified_at']) { | ||
recordColumnDuration(bug, bug['created_at'], bug['verified_at'], 'ttc', releaseTimes); | ||
} | ||
num_bugs_processed++; | ||
percent_done = (num_bugs_processed / num_bugs_total) * 100; | ||
//console.log(percent_done); | ||
$('#progress-bar').width(percent_done + "%"); | ||
if (percent_done == 100) { | ||
$('.progress').remove(); | ||
} | ||
}); | ||
|
||
var triage_time = calculateAverageDays(triageTimes); | ||
$('#triage-time').html(triage_time + " days (" + triageTimes.length + " bugs)"); | ||
var selected_time = calculateAverageDays(selectedTimes); | ||
$('#selected-column .duration').html(selected_time + " days (" + selectedTimes.length + " bugs)"); | ||
//console.log(implementTimes); | ||
var implement_time = calculateAverageDays(implementTimes); | ||
$('#implement-column .duration').html(implement_time + "days (" + implementTimes.length + " bugs)"); | ||
var review_time = calculateAverageDays(reviewTimes); | ||
$('#review-column .duration').html(review_time + "days (" + reviewTimes.length + " bugs)"); | ||
var test_time = calculateAverageDays(testTimes); | ||
$('#test-column .duration').html(test_time + "days (" + testTimes.length + " bugs)"); | ||
var lead_time = calculateAverageDays(releaseTimes); | ||
$('#lead-time').html(lead_time + " days (" + releaseTimes.length + " bugs)"); | ||
|
||
_.each(_.keys(kanBugs), function(key) { | ||
// Show current count at top of column | ||
var selector = "#" + key + "-column .count"; | ||
$(selector).html($(kanBugs[key]).length); | ||
}); | ||
}; | ||
|
||
var kanbanAllTheBugs = function(e) { | ||
e.preventDefault(); | ||
console.log('kanbanAllTheBugs'); | ||
$('.progress').show(); | ||
loadGhBugs(); | ||
$.when.apply($, ghRequests).done(function(){ | ||
// Get bugs from Bugzilla | ||
var whiteboard = $('form#filters input#whiteboard').val(); | ||
var bugzillaURL = "https://api-dev.bugzilla.mozilla.org/latest/bug" + | ||
"?product=Mozilla%20Developer%20Network" + | ||
"&whiteboard=" + whiteboard + | ||
"&include_fields=id,summary,component,creation_time,creator,status,resolution,whiteboard,assigned_to,depends_on,blocks,history,cf_last_resolved"; | ||
$('#progress-bar').text("Fetching bugs from bugzilla ..."); | ||
console.log("fetching: " + bugzillaURL); | ||
var gettingBugs = $.getJSON(bugzillaURL); | ||
$.when(gettingBugs).done(processBzBugs); | ||
}); | ||
} | ||
|
||
$('.progress').hide(); | ||
$('form#filters button').click(kanbanAllTheBugs); |
Oops, something went wrong.