Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Separating out the Redmine Burndown plugin from our Redmine install.

  • Loading branch information...
commit 10a89f461f540f6d2c057134c134e0b1ef297c66 0 parents
@danhodos danhodos authored
24 README.markdown
@@ -0,0 +1,24 @@
+# Redmine Burndowns
+
+## Introduction
+
+This plugin adds a 'Burndown' tab to your Project Menu for any Project with the 'Burndowns' module enabled (Settings>Modules).
+
+This tab will show the Burndown chart for the current Sprint (cough Version cough), and also give a sidebar listing of all previous Sprints to see their Burndown chart as well. The Burndown Chart shows the current work remaining in red and an ideal trend line in grey.
+
+## Installation
+The Redmine Burndowns plugin depends on the excellent googlecharts gems by Matt Aimonetti. This can be installed with:
+
+ sudo gem install mattetti-googlecharts --source=http://gems.github.com
+
+If you'd like, you may also unpack the gem into your Redmine deploy by adding the following to your environment.rb file:
+
+ config.gem 'mattetti-googlecharts', :lib => 'gchart', :version => ">=1.3.6"
+
+and then running:
+
+ rake gems:unpack
+
+Copyright (c) 2009 [Scrum Alliance](www.scrumalliance.org), released under the MIT license.
+
+Authored by [Dan Hodos](mailto:danhodos[at]gmail[dot]com)
17 app/controllers/burndowns_controller.rb
@@ -0,0 +1,17 @@
+class BurndownsController < ApplicationController
+ unloadable
+ menu_item :burndown
+
+ before_filter :find_version_and_project, :authorize, :only => [:show]
+
+ def show
+ @chart = BurndownChart.new(@version)
+ end
+
+private
+ def find_version_and_project
+ @project = Project.find(params[:project_id])
+ @version = params[:id] ? @project.versions.find(params[:id]) : @project.current_version
+ render_error("There is no current Sprint for this Project") and return unless @version
+ end
+end
58 app/models/burndown_chart.rb
@@ -0,0 +1,58 @@
+class BurndownChart
+ attr_accessor :dates, :version, :start_date
+
+ delegate :to_s, :to => :chart
+
+ def initialize(version)
+ self.version = version
+
+ self.start_date = version.created_on.to_date
+ end_date = version.effective_date.to_date
+ self.dates = (start_date..end_date).inject([]) { |accum, date| accum << date }
+ end
+
+ def chart
+ Gchart.line(
+ :size => '750x400',
+ :data => data,
+ :axis_with_labels => 'x,y',
+ :axis_labels => [dates.map {|d| d.strftime("%m-%d") }],
+ :custom => "chxr=1,0,#{sprint_data.max}",
+ :line_colors => "DDDDDD,FF0000"
+ )
+ end
+
+ def data
+ [ideal_data, sprint_data]
+ end
+
+ def sprint_data
+ @sprint_data ||= dates.map do |date|
+ issues = all_issues.select {|issue| issue.created_on.to_date <= date }
+ issues.inject(0) do |total_hours_left, issue|
+ done_ratio_details = issue.journals.map(&:details).flatten.select {|detail| 'done_ratio' == detail.prop_key }
+ details_today_or_earlier = done_ratio_details.select {|a| a.journal.created_on.to_date <= date }
+
+ last_done_ratio_change = details_today_or_earlier.sort_by {|a| a.journal.created_on }.last
+
+ ratio = if last_done_ratio_change
+ last_done_ratio_change.value
+ elsif done_ratio_details.size > 0
+ 0
+ else
+ issue.done_ratio.to_i
+ end
+
+ total_hours_left += (issue.estimated_hours.to_i * (100-ratio.to_i)/100)
+ end
+ end
+ end
+
+ def ideal_data
+ [sprint_data.first, 0]
+ end
+
+ def all_issues
+ version.fixed_issues.find(:all, :include => [{:journals => :details}, :relations_from, :relations_to])
+ end
+end
12 app/views/burndowns/show.html.erb
@@ -0,0 +1,12 @@
+<h2><%= @version.name %> <%= l(:burndown) %></h2>
+
+<p><%= image_tag(@chart.to_s) %></p>
+
+<% content_for :sidebar do -%>
+ <h3><%= l(:label_version_plural) %></h3>
+ <ul id="sprint_burndown_list">
+ <% @project.versions.each do |version| -%>
+ <li<%= %Q[ class="selected"] if @version == version %>><%= link_to(version.name, show_burndown_path(:project_id => @project, :id => version)) %></li>
+ <% end -%>
+ </ul>
+<% end -%>
2  assets/stylesheets/burndowns.css
@@ -0,0 +1,2 @@
+#sprint_burndown_list { padding: 0; list-style-type: none; }
+ #sprint_burndown_list li.selected { font-weight: bold; }
23 init.rb
@@ -0,0 +1,23 @@
+require 'redmine'
+require 'gchart'
+
+require_dependency 'burndown_listener'
+require_dependency 'scrum_alliance/redmine/current_version_extension'
+
+require 'dispatcher'
+Dispatcher.to_prepare do
+ Project.class_eval { include ScrumAlliance::Redmine::CurrentVersionExtension }
+end
+
+Redmine::Plugin.register :burndown do
+ name 'Redmine Burndown plugin'
+ author 'Dan Hodos'
+ description 'Generates a simple Burndown chart for using Redmine in Scrum environments'
+ version '1.1.3'
+
+ project_module :burndowns do
+ permission :show_burndown, :burndowns => :show, :public => true
+ end
+
+ menu :project_menu, :burndown, { :controller => 'burndowns', :action => 'show' }, :param => :project_id, :before => :activity
+end
1  lang/en.yml
@@ -0,0 +1 @@
+burndown: Burndown
5 lib/burndown_listener.rb
@@ -0,0 +1,5 @@
+class BurndownListener < Redmine::Hook::ViewListener
+ def view_layouts_base_html_head(context={})
+ stylesheet_link_tag('burndowns', :plugin => 'redmine_burndown')
+ end
+end
9 lib/scrum_alliance/redmine/current_version_extension.rb
@@ -0,0 +1,9 @@
+module ScrumAlliance
+ module Redmine
+ module CurrentVersionExtension
+ def current_version
+ versions.detect {|version| version.created_on.to_date <= Date.current && version.effective_date >= Date.current }
+ end
+ end # CurrentVersionExtension
+ end # Redmine
+end # ScrumAlliance
2  routes.rb
@@ -0,0 +1,2 @@
+map.latest_burndown 'projects/:project_id/burndown', :controller => 'burndowns', :action => 'show'
+map.show_burndown 'projects/:project_id/burndowns/:id', :controller => 'burndowns', :action => 'show'
5 test/test_helper.rb
@@ -0,0 +1,5 @@
+# Load the normal Rails helper
+require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
+
+# Ensure that we are using the temporary fixture path
+Engines::Testing.set_fixture_path
Please sign in to comment.
Something went wrong with that request. Please try again.