buggerall.coffee | |
---|---|
# | * ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is buggerall.
*
* The Initial Developer of the Original Code is
* Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Kevin Dangoor (kdangoor@mozilla.com)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** * |
# | |
Wow. Running this through Docco shows me just how poorly it's documented. I'll work on fixing that as I go along. In the meantime, sorry! | |
Here we go! | buggerall = @buggerall = {}
getJSON = $.getJSON
ajax = $.ajax
buggerall.VERSION = "0.3"
buggerall.SERIALIZER_VERSION = 1
buggerall.Query = class Query
constructor: (opts) ->
@_queryCount = 0
@apiURL = opts.apiURL || 'https://api-dev.bugzilla.mozilla.org/latest/'
@viewURL = opts.viewURL || 'https://bugzilla.mozilla.org/show_bug.cgi?id='
if opts.query
@query = opts.query
if opts.bugid
@query = "id=#{opts.bugid}&#{@query}"
if opts.fields
@query += "&include_fields=" + opts.fields
else
@query += "&include_fields=id,status,summary,attachments,keywords,whiteboard,resolution,assigned_to,depends_on,last_change_time,creation_time" |
URL to directory containing JSON files for history buggerall will load history data from here first and then from bugzilla if that data is out of date | @historyCacheURL = opts.historyCacheURL
@includeHistory = opts.includeHistory
@whitespace = opts.whitespace |
Special support for computing the latest comment, which requires making separate requests in order to avoid download all of the comment data which can be quite large. | @computeLastCommentTime = opts.computeLastCommentTime
@result = undefined
getJSON: (url, callback) ->
if typeof(url) != "object"
params =
url: url
success: callback
else
params = url
params.dataType = 'json'
success = params.success
params.success = (data) =>
if !data
@.error = 'No data returned... bad query, perhaps? Go to <a target="_blank" href="' + lastURL + '">' + url + '</a> to try out the query (opens in a new window).'
@_callback this
@_queryDone()
return
success data
@_queryDone()
error = params.error
params.error = () =>
if error
error()
@_queryDone()
@_queryCount++
ajax params
run: (callback) ->
url = this.apiURL + 'bug?' + this.query
@_callback = callback
@getJSON url, @_bugResult
_bugResult: (data) =>
result = @result = {}
for bugData in data.bugs
bug = result[bugData.id] = new Bug(bugData)
if @includeHistory
@_loadHistory bug
if @computeLastCommentTime
@_getLatestComment bug
_queryDone: () =>
@_queryCount--
if not @_queryCount and @_callback
@_callback @
_loadHistory: (bug, forceBugzilla=false) ->
if not forceBugzilla and @historyCacheURL
url = @historyCacheURL + "#{bug.id}.json"
@getJSON
url: url
success: (data) ->
bug.history = _unserialize data
error: () =>
@_loadHistory(bug, true)
else
url = @apiURL + "bug/" + bug.id + "/history"
@getJSON url, (data) ->
bug._setHistoryFromQueryResult(data.history)
_getLatestComment: (bug) ->
url = @apiURL + "bug/" + bug.id + "/comment?include_fields=creation_time,creator"
@getJSON url, (data) ->
lastComment = data.comments[data.comments.length - 1]
bug.lastCommentTime = Date.parse(lastComment.creation_time)
bug.lastCommentCreator = lastComment.creator.name
merge: (otherQ) ->
for bugId of otherQ.result
if not @result[bugId]
@result[bugId] = otherQ.result[bugId]
serialize: () ->
data =
_version: buggerall.SERIALIZER_VERSION
for bugId of @result
data[bugId] = _serialize @result[bugId]
if @whitespace
return JSON.stringify data, null, 1
else
return JSON.stringify data
timeline: (daysback=30) ->
new buggerall.Timeline(@.result, daysback)
buggerall.getCachedResult = (url, callback) ->
getJSON(url, (data) ->
console.log "Have the data... gonna run with it"
if data._version != buggerall.SERIALIZER_VERSION
throw new Error("bugger all! I don't know how to handle data from version: #{data._version}. This is serializer version #{buggerall.SERIALIZER_VERSION}")
result = {}
for key of data
if key.substring(0, 1) == "_"
continue
result[key] = _unserialize data[key]
callback result
)
buggerall.Attachment = class Attachment
constructor: (data) ->
for key of data
if key == "creation_time" or key == "last_change_time"
@[key] = Date.parse data[key]
else
@[key] = data[key]
buggerall.Bug = class Bug
constructor: (data) ->
for key of data
if key == "attachments"
attachments = @attachments = {}
for attachment in data[key]
attachments[attachment.id] = new Attachment(attachment)
else if key == "history"
if data[key] instanceof History
@[key] = data[key]
else
@_setHistoryFromQueryResult(data[key])
else if key == "creation_time" or key == "last_change_time"
@[key] = Date.parse data[key]
else
@[key] = data[key]
getLatestPatch: () ->
if not @attachments
return null
latest = null
for id, attachment of @attachments
if not attachment.is_patch or attachment.is_obsolete
continue
if not latest or attachment.last_change_time > latest.last_change_time
latest = attachment
return latest
loadHistory: (url, callback) ->
getJSON url, (data) =>
@history = _unserialize(data)
callback(@)
|
Takes the data returned from a query and turns it into a proper History object. | _setHistoryFromQueryResult: (data) ->
history = @history = new History(@last_change_time)
changesets = history.changesets
for changeset in data
changesets.push new ChangeSet(@, changeset)
buggerall.History = class History
constructor: (lastChangeTime) ->
@lastChangeTime = lastChangeTime
@changesets = []
serialize: (includeWhitespace=true) ->
obj = _serialize @
if includeWhitespace
JSON.stringify obj, null, 1
else
JSON.stringify obj
buggerall.ChangeSet = class ChangeSet
constructor: (bug, data) ->
for key of data
if key == "change_time"
@[key] = Date.parse data[key]
else if key == "changes"
changes = this.changes = []
for change in data["changes"]
changes.push new Change(bug, change)
else
@[key] = data[key]
buggerall.Change = class Change
constructor: (bug, data) ->
for key of data
if key == "attachment_id" and bug.attachments
@attachment = bug.attachments[data["attachment_id"]]
else
@[key] = data[key]
_serialize = (obj) ->
if obj instanceof Array
arrayData = []
for item in obj
if item instanceof Object
arrayData.push _serialize(item)
else
arrayData.push item
return arrayData;
objData = {}
if obj instanceof Bug
objData._type = "Bug"
else if obj instanceof buggerall.Attachment
objData._type = "Attachment"
else if obj instanceof buggerall.ChangeSet
objData._type = "ChangeSet"
else if obj instanceof buggerall.Change
objData._type = "Change"
else if obj instanceof buggerall.History
objData._type = "History"
else if obj instanceof Date
objData._type = "Date" |
we need to eliminate the .000 milliseconds because date.js doesn't parse it properly, despite being the one generating it! | objData.value = obj.toISOString().replace ".000", ""
return objData
for own key, item of obj |
special handling for history, which is serialized separately for speed's sake | if objData._type == "Bug" and key == "history"
continue
if item instanceof Object
objData[key] = _serialize item
else
objData[key] = item
return objData
_unserialize = (obj) ->
if obj instanceof Array
arrayData = []
for item in obj
if item instanceof Object
arrayData.push _unserialize(item)
else
arrayData.push(item)
return arrayData
if obj._type
if obj._type == "Date"
return Date.parse obj.value
objData = new buggerall[obj._type]()
else
objData = {}
for key of obj
if key.substring(0, 1) == "_"
continue
item = obj[key]
if item instanceof Object
objData[key] = _unserialize item
else
objData[key] = obj[key]
return objData |
Timeline | |
The timeline makes it easy to gather the data for a simple, linear view of events. The goal is to make it easy to scan for updates related to a project. Events included:
| buggerall.Timeline = class Timeline
|
Parameters:
| constructor: (result, daysback) ->
events = @events = [] |
compute timestamp in milliseconds for 30 days ago 30 days * 24 hours/day * 60 min/hour * 60 sec/min * 1000 milli/sec | cutoff = new Date().getTime() - 30*24*60*60*1000
reviewRequestFlag = /^review\?\((.*)\)$/
reviewFlag = /^review([+-])$/;
for bugId, bug of result |
check for new bug | if bug.creation_time? and bug.creation_time > cutoff
events.push(new TimelineEntry(bugId, bug.creation_time, "newBug", ""))
|
events pulled from bug history | if bug.history?
for changeset in bug.history.changesets
if changeset.change_time < cutoff
continue
for change in changeset.changes
if change.field_name == "whiteboard"
events.push(new TimelineEntry(bugId, changeset.change_time, "whiteboard", change.added))
else if change.field_name == "summary"
events.push(new TimelineEntry(bugId, changeset.change_time, "summary", change.added))
else if change.field_name == "flag"
attachmentInfo = if change.attachment? then " for " + change.attachment.description else ""
reviewRequestFlagResult = reviewRequestFlag.exec(change.added)
reviewFlagResult = reviewFlag.exec(change.added)
if reviewRequestFlagResult
events.push(new TimelineEntry(bugId, changeset.change_time, "review", "r? " +reviewRequestFlagResult[1] + attachmentInfo))
else if reviewFlagResult
events.push(new TimelineEntry(bugId, changeset.change_time, "review", "r" + reviewFlagResult[1] + attachmentInfo))
|
new patch check | if bug.attachments?
for id, attachment of bug.attachments
if attachment.creation_time < cutoff or attachment.is_obsolete or not attachment.is_patch
continue
events.push(new TimelineEntry(bugId, attachment.creation_time, "newPatch", attachment.description))
|
handle latest comment event | if bug.lastCommentTime? and bug.lastCommentTime > cutoff
events.push(new TimelineEntry(bugId, bug.lastCommentTime, "newComment", "from " + bug.lastCommentCreator))
@sortEvents()
sortEvents: () -> |
sort the final result in descending timestamp order (most recent first) | @events.sort (a,b) ->
if a.when < b.when
return 1
else if a.when > b.when
return -1
return 0 |
TimelineEntry | |
A single entry in the timeline. Has the following properties:
| buggerall.TimelineEntry = class TimelineEntry
constructor: (@bugId, @when, @type, @detail) ->
|