Skip to content

Instantly share code, notes, and snippets.

@gaelrottier
Last active Dec 20, 2015
Embed
What would you like to do?
Dashing widget to show the burndown form Pivotal Tracker

Description

This Dashing widget allows you to see the current burndown of your iteration, thanks to Pivotal Tracker's API

  • On the top of the widget, there is the current number of iteration.
  • At top left, there is the total number of points of the iteration.
  • On the top of the point of the graph representing the current day, there is the number of points left in the iteration.

Usage

  1. Add

    <li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
        <div data-id="burndown" data-view="Burndown"></div>
    </li>

    to your <dashboard>.erb file.

  2. Run dashing install 6194375 in your project's folder.

    OR

  3. Copy the html/scss/coffee files into a folder named burndown in the widgets folder.

  4. Copy the burndown.rb file in the jobs folder.

Then :

  1. Add gem 'pivotal-tracker', :git => 'git://github.com/amair/pivotal-tracker.git' to your gemfile.
  1. Run bundle install.
  2. Get a Pivotal Tracker API token from your Account Dashboard, or execute curl -u $USERNAME:$PASSWORD -X GET https://www.pivotaltracker.com/services/v3/tokens/active in the command line, then set TOKEN in burndown.rb to it.
  3. Get the id of the project you want to track, that can be found in the url of the project, https://www.pivotaltracker.com/s/projects/$PROJECT_ID, or by running curl -H "X-TrackerToken: $TOKEN" -X GET http://www.pivotaltracker.com/services/v3/projects in command line, and set PROJECT in burndown.rb to it.
  4. If you haven't been using Pivotal Tracker since the start of your project, set ITERATION_NUMBER_OFFSET in burndown.rb to the number of iterations you want to add to the total of iterations of the project.

Preview

burndown.jpg burndown.jpg

Contributing

If you want to contribute to the widget, see the Github repository.

Credits

This project was done during my internship at M.E.S.H., of which I thank very much the employees for their greeting during all the time I was with them.

License

This product is delivered under the MIT License

The MIT License (MIT)

Copyright (c) [2013] Gaël Rottier

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

class Dashing.Burndown extends Dashing.Widget
# Represents the percentage of the height of the widget which is occupied by the graph
RATIO = 0.9
ready: ->
@width = Dashing.widget_base_dimensions[0]
@widget_height = Dashing.widget_base_dimensions[1]
@graph_height = @widget_height * RATIO
@graph = new Rickshaw.Graph(
element: @node
interpolation: 'step-after'
width: @width
height: @graph_height
series: [
{
color: '#6FCEDB',
data: [{ x: 0, y: 0}]
}
]
)
if @get('points')
points = @get('points')
@graph.series[0].data = @get('points')
@placeMax(points)
$('.max-points').css('top', '3%')
@showInfo()
onData: (data) ->
if @graph
@graph.series[0].data = data.points
@showInfo()
@placeMax(data.points)
showInfo: ->
@graph.render()
@updateSeries()
@updateMaxDataIndex()
@placeInfo()
placeMax: (points) ->
$('.max-points').text(points[0].y)
updateSeries: ->
@series = @graph.series[0]
updateMaxDataIndex: ->
@maxDataIndex = @series.data[@series.data.length - 1].x
placeInfo: ->
points = ''
divPoints = $('.points')
for data in @series.data
if data.y isnt null
points = data.y
day = data.x
divPoints.text(points)
stepX = @width / @maxDataIndex
locX = stepX * day
stepY = @graph_height / @series.data[0].y
locY = stepY * points
divPoints.css('margin-left', "#{locX}px")
divPoints.css('bottom', "#{locY}px")
if points == 0
divPoints.css('display', 'none')
else
divPoints.css('display', 'block')
<h1 class="title" data-bind="title"></h1>
<div class="max-points"></div>
<div class="points"></div>
# ################################################
# ############## Burndown Job ######################
# ################################################
class BurndownJob
require 'singleton'
include Singleton
def initialize
@burndown = Burndown.new
SCHEDULER.every '10s', first_in: 0, allow_overlapping: false do
@burndown.update_burndown
end
end
end
# ##################################################
# ############## Burndown Class ######################
# ##################################################
class Burndown
require 'pivotal-tracker'
# The API key of your account
TOKEN = ''
# The number of the project to track
PROJECT =
# If you haven't been using Pivotal Tracker since the start of your project,
# set ITERATION_NUMBER_OFFSET to the number of iterations you want to add to the
# total of iterations of the project
ITERATION_NUMBER_OFFSET = 0
# @iteration represents the current iteration
# @iteration_number represents the number of the iteration
# @iteration_points represents the number of points defined in the iteration
# @points is an [Array] representing the points left to do for each day of the sprint
# @stories represents the stories which are estimaded and aren't releases
attr_accessor :iteration, :iteration_number, :iteration_points, :points, :stories
def initialize
PivotalTracker::Client.token = TOKEN
PivotalTracker::Client.use_ssl = true
end
# Gets and sends the information about the current iteration burndown
# (iteration number, iteration points, points) to the widget
def update_burndown
begin
@iteration = PivotalTracker::Project.find(PROJECT).iteration(:current)
@points = Array.new
rescue Exception
@iteration_number = @iteration_points = '0'
end
if @iteration
update_iteration_number
update_iteration_points
update_points
else
@points = { x: 0, y: 0}
end
send_data
end
# Gets the iteration number and sets @iteration_number to it
def update_iteration_number
@iteration_number = @iteration.id
end
# Gets the number of points defined in the iteration and sets @iteration_points to it
def update_iteration_points
@stories = @iteration.stories.select { |s| ! s.estimate.nil? && s.estimate != -1 && s.story_type != 'release' }
@iteration_points = 0
@stories.each do |s|
@iteration_points += s.estimate
end
end
# Returns the number of days between the [date] given and the start of the iteration
# @param [String] date from which calculate the diff
# @return [Fixnum] the difference
def retreive_nb_days_from_iteration(date)
# We create new dates with the given ones to avoid shift between the hours
given_date = DateTime.new(date.year, date.month, date.day)
iteration_date = DateTime.new(@iteration.start.year, @iteration.start.month, @iteration.start.day)
nb = ((iteration_date)..(given_date)).reject {|day| day.sunday? || day.saturday? }.size
nb - 1
end
# Sets @points to the points left for each day of the sprint
def update_points
nb_days = retreive_nb_days_from_iteration(DateTime.now)
iteration_length = retreive_nb_days_from_iteration(@iteration.finish)
@points << { x: 0, y: @iteration_points }
(0..iteration_length - 1).each do |i|
iteration_points = @iteration_points
@stories.select { |story| story.current_state == 'accepted' }.each do |s|
if i >= retreive_nb_days_from_iteration(s.accepted_at)
iteration_points -= s.estimate
end
end
if i > nb_days
iteration_points = nil
end
@points << { x: i + 1, y: iteration_points }
end
@points << { x: iteration_length + 1, y: nil }
end
# Sends the data to the widget 'burndown'
def send_data
iteration_count = @iteration_number + ITERATION_NUMBER_OFFSET
send_event('burndown', {title: "Iteration #{iteration_count}", points: @points})
end
end
# ##################################################
# ############## Job's execution ######################
# ##################################################
BurndownJob.instance
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #12B0C5;
$moreinfo-color: rgba(255, 255, 255, 0.3);
$tick-color: rgba(0, 0, 0, 0.4);
// ----------------------------------------------------------------------------
// Widget-burndown styles
// ----------------------------------------------------------------------------
.widget-burndown {
background-color: $background-color;
position: relative;
svg {
position: absolute;
opacity: 1;
fill-opacity: 1;
left: 0;
bottom: 0;
}
.title {
color: darken($background-color, 30%);
font-size: smaller;
top: -20px;
right: 0px;
left: 0px;
position: absolute;
margin-top: 1em;
}
.max-points, .points {
position: absolute;
z-index: 1;
font-size: 100%;
}
.max-points {
left: 0.5%;
}
.points {
line-height: 1em;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment