artisan-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/lib/artisan/activity/activity_interactor.rb +18 -0
  2. data/lib/artisan/activity/activity_presenter.rb +35 -0
  3. data/lib/artisan/activity/formatters/diff_changes.rb +41 -0
  4. data/lib/artisan/activity/formatters/name.rb +22 -0
  5. data/lib/artisan/activity/formatters/source.rb +16 -0
  6. data/lib/artisan/activity/formatters/temporal.rb +20 -0
  7. data/lib/artisan/activity/iteration_auditor.rb +44 -0
  8. data/lib/artisan/activity/project_auditor.rb +28 -0
  9. data/lib/artisan/activity/story_auditor.rb +42 -0
  10. data/lib/artisan/callbacks.rb +27 -0
  11. data/lib/artisan/crud_strategy.rb +23 -0
  12. data/lib/artisan/event_mailer.rb +22 -0
  13. data/lib/artisan/inviter.rb +36 -0
  14. data/lib/artisan/iterations/iteration_point_calculator.rb +28 -0
  15. data/lib/artisan/iterations/iteration_presenter.rb +20 -0
  16. data/lib/artisan/iterations/iteration_workflow_interactor.rb +60 -0
  17. data/lib/artisan/iterations/iteration_workflow_presenter.rb +11 -0
  18. data/lib/artisan/iterations/iterations.rb +47 -0
  19. data/lib/artisan/iterations/iterations_interactor.rb +48 -0
  20. data/lib/artisan/iterations/move_to_backlog.rb +28 -0
  21. data/lib/artisan/iterations/story_tags.rb +15 -0
  22. data/lib/artisan/member.rb +52 -0
  23. data/lib/artisan/no_op_callbacks.rb +6 -0
  24. data/lib/artisan/projects/api_key_generator.rb +26 -0
  25. data/lib/artisan/projects/archive_interactor.rb +29 -0
  26. data/lib/artisan/projects/completed_stories_presenter.rb +20 -0
  27. data/lib/artisan/projects/iteration_numberer.rb +15 -0
  28. data/lib/artisan/projects/project_creator.rb +51 -0
  29. data/lib/artisan/projects/projects_interactor.rb +58 -0
  30. data/lib/artisan/projects/projects_presenter.rb +46 -0
  31. data/lib/artisan/projects/story_point_summer.rb +16 -0
  32. data/lib/artisan/reports/average_stat_per_iteration_data.rb +55 -0
  33. data/lib/artisan/reports/burn_up_chart.rb +46 -0
  34. data/lib/artisan/reports/high_charts_data_retriever.rb +77 -0
  35. data/lib/artisan/reports/high_charts_interactor.rb +31 -0
  36. data/lib/artisan/reports/percentage_of_commitments_met_data.rb +47 -0
  37. data/lib/artisan/reports/signoff/pdf.rb +161 -0
  38. data/lib/artisan/reports/signoff/report.rb +120 -0
  39. data/lib/artisan/reports/velocity_report.rb +43 -0
  40. data/lib/artisan/repository.rb +51 -0
  41. data/lib/artisan/stories/pert_calculator.rb +62 -0
  42. data/lib/artisan/stories/stories_interactor.rb +78 -0
  43. data/lib/artisan/stories/story_collection.rb +66 -0
  44. data/lib/artisan/stories/story_exporter.rb +45 -0
  45. data/lib/artisan/stories/story_numberer.rb +15 -0
  46. data/lib/artisan/stories/story_presenter.rb +23 -0
  47. data/lib/artisan/stories/story_sorter.rb +37 -0
  48. data/lib/artisan/story_board.rb +45 -0
  49. data/lib/artisan/story_column_changer.rb +71 -0
  50. data/lib/artisan/story_exporter.rb +14 -0
  51. data/lib/artisan/team.rb +50 -0
  52. data/spec/crud_strategy_spec.rb +100 -0
  53. data/spec/event_mailer_spec.rb +86 -0
  54. data/spec/inviter_spec.rb +58 -0
  55. data/spec/member_spec.rb +58 -0
  56. data/spec/repository_spec.rb +32 -0
  57. data/spec/story_board_spec.rb +36 -0
  58. data/spec/story_collection_spec.rb +61 -0
  59. data/spec/story_column_changer_spec.rb +222 -0
  60. data/spec/story_exporter_spec.rb +25 -0
  61. data/spec/story_sorter_spec.rb +67 -0
  62. data/spec/team_spec.rb +107 -0
  63. metadata +128 -0
@@ -0,0 +1,46 @@
1
+ require 'artisan/repository'
2
+ require 'artisan/stories/story_collection'
3
+
4
+ module Artisan
5
+ module Projects
6
+ class ProjectsPresenter
7
+
8
+ def initialize(user)
9
+ @user = user
10
+ end
11
+
12
+ #TODO - PWP - optimize this method to use sql to create the returns rather than ruby
13
+ def archived
14
+ read_attributes_to_hash(@user.projects.archived)
15
+ end
16
+
17
+ #TODO - PWP - optimize this method to use sql to create the returns rather than ruby
18
+ def current
19
+ read_attributes_to_hash(@user.projects.unarchived)
20
+ end
21
+
22
+ def active_stories_by_project
23
+ active_stories.group_by(&:project)
24
+ end
25
+
26
+ def active_points
27
+ Stories::StoryCollection.new(active_stories).estimated_points
28
+ end
29
+
30
+ private
31
+
32
+ #TODO - PWP - optimize this method to use sql to create the returns rather than ruby
33
+ def active_stories
34
+ Repository.user.active_stories_for(@user).reject { |story| story.project.archived? }
35
+ end
36
+
37
+ def read_attributes_to_hash(scoped_projects)
38
+ projects = []
39
+ scoped_projects.each do |project|
40
+ projects << {:updated_at => project.updated_at, :name => project.name, :id => project.id}
41
+ end
42
+ return projects
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ require "artisan/stories/pert_calculator"
2
+ module Artisan
3
+ module Projects
4
+ class StoryPointSummer
5
+
6
+ def initialize(stories)
7
+ @stories = stories
8
+ end
9
+
10
+ def sum
11
+ total = @stories.inject(0){ |total, story| total + story.estimate }
12
+ return total.round(2)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,55 @@
1
+ require "artisan/iterations/iterations"
2
+
3
+ module Artisan
4
+ module Reports
5
+ class AverageStatPerIterationData
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def average_of(stat)
11
+ average(stat)
12
+ end
13
+
14
+ def average_standard_deviation
15
+ average(:standard_deviation)
16
+ end
17
+
18
+ def average_story_size
19
+ average(:estimate)
20
+ end
21
+
22
+ def current_iteration?(iteration)
23
+ Iterations::Iterations.new(@project).current == iteration
24
+ end
25
+
26
+ private
27
+
28
+ def average(stat_type)
29
+ @project.iterations.inject([]) do |average_story_size, iteration|
30
+ average_story_size << accumulate_average_standard_deviation_for(iteration, stat_type).round(2) unless current_iteration?(iteration)
31
+ average_story_size
32
+ end
33
+ end
34
+
35
+ NONE = 0
36
+
37
+ def accumulate_average_standard_deviation_for(iteration, stat_type)
38
+ if number_of_stories_in(iteration) == NONE
39
+ NONE
40
+ else
41
+ total_standard_deviation_in(iteration, stat_type) / number_of_stories_in(iteration)
42
+ end
43
+ end
44
+
45
+ def total_standard_deviation_in(iteration, stat_type)
46
+ iteration.stories.inject(0.0) {| sum, story| sum + story.send(stat_type)}
47
+ end
48
+
49
+ def number_of_stories_in(iteration)
50
+ iteration.stories.count
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ module Artisan
2
+ module Reports
3
+
4
+ class BurnUpChart
5
+ attr_accessor :project
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def project_size date
11
+ sum_of_stories_by project_query(date)
12
+ end
13
+
14
+ def completed_size date
15
+ sum_of_stories_by completed_query(date)
16
+ end
17
+
18
+ def project_data
19
+ accumulate_by(:project_size)
20
+ end
21
+
22
+ def completed_data
23
+ accumulate_by(:completed_size)
24
+ end
25
+
26
+ private
27
+
28
+ def accumulate_by(method)
29
+ Iterations::Iterations.new(@project).by_start_date.inject([]) {|acc, iteration| acc << send(method, iteration.start_date)}
30
+ end
31
+
32
+ def project_query(date)
33
+ ["created_at <= ?", date]
34
+ end
35
+
36
+ def completed_query(date)
37
+ ["created_at <= ? AND complete = ? ", date, true]
38
+ end
39
+
40
+ def sum_of_stories_by conditions
41
+ stories = @project.stories.find(:all, :conditions => conditions)
42
+ return stories.inject(0) {|acc, story| acc + story.estimate.to_f}
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,77 @@
1
+ module Artisan
2
+ module Reports
3
+ module HighChartsDataRetriever
4
+ def self.build_burn_up_chart(project)
5
+ chart_data = Artisan::Reports::BurnUpChart.new(project)
6
+
7
+ LazyHighCharts::HighChart.new do |f|
8
+ f.chart(:defaultSeriesType => 'line', :backgroundColor => nil, :margin => [0,0,0,0])
9
+ f.yAxis(:gridLineWidth => nil, :labels => 'none')
10
+ f.xAxis(:gridLineWidth => nil, :labels => false, :lineColor => 'transparent', :tickLength => nil)
11
+ f.tooltip(:shadow => false, :backgroundColor => 'rgba(0,0,0,0.7)', :borderWidth => 0, :style => { :color => '#ffffff', :padding => [7,7,7,7], :fontSize => 13 }, :crosshairs => { :color => '#999', :dashStyle => 'solid'})
12
+ f.title(:text => nil)
13
+ f.options[:legend][:enabled] = false
14
+ f.plotOptions(:line => { :lineWidth=> 1, :shadow => false, :marker => { :symbol => 'circle', :radius => 2, :states => { :hover => { :enabled => true, :fillColor => '#000000'} } } })
15
+
16
+ f.series(:data => chart_data.project_data.slice(0, chart_data.project_data.length-1), :color => '#b8be43')
17
+ f.series(:data => chart_data.completed_data.slice(0, chart_data.project_data.length-1), :color => '#800000')
18
+ end
19
+ end
20
+
21
+ def self.build_average_story_size_per_iteration_chart(project)
22
+ return build_iteration_chart(project, :estimate, '#2B65EC')
23
+ end
24
+
25
+ def self.build_average_standard_deviation_per_iteration_chart(project)
26
+ return build_iteration_chart(project, :standard_deviation, '#F87217')
27
+ end
28
+
29
+ def self.build_iteration_chart(project, stat, color)
30
+ chart_data = Artisan::Reports::AverageStatPerIterationData.new(project)
31
+
32
+ return LazyHighCharts::HighChart.new do |f|
33
+ f.chart(:defaultSeriesType => 'line', :backgroundColor => nil, :margin => [0,0,0,0])
34
+ f.yAxis(:gridLineWidth => nil, :labels => 'none')
35
+ f.xAxis(:gridLineWidth => nil, :labels => false, :lineColor => 'transparent', :tickLength => nil)
36
+ f.tooltip(:shadow => false, :backgroundColor => 'rgba(0,0,0,0.7)', :borderWidth => 0, :style => { :color => '#ffffff', :padding => [7,7,7,7], :fontSize => 13 }, :crosshairs => { :color => '#999', :dashStyle => 'solid'})
37
+ f.title(:text => nil)
38
+ f.options[:legend][:enabled] = false
39
+ f.plotOptions(:line => { :lineWidth=> 1, :shadow => false, :marker => { :symbol => 'circle', :radius => 2, :states => { :hover => { :enabled => true, :fillColor => '#000000'} } } })
40
+
41
+ f.series(:data => chart_data.average_of(stat), :color => color)
42
+ end
43
+ end
44
+
45
+ def self.build_percentage_of_commitments_met_chart(project)
46
+ chart_data = Artisan::Reports::PercentageOfCommitmentsMetData.new(project)
47
+
48
+ LazyHighCharts::HighChart.new do |f|
49
+ f.options[:chart][:backgroundColor] = 'transparent'
50
+ f.options[:chart][:defaultSeriesType] = 'pie'
51
+ f.series(:data=>[{y: go_no_higher_than_100(chart_data.percentage_of_commitments_met), color: 'green'}, {y: go_no_lower_than_0(chart_data.percentage_of_commitments_not_met), color: '#CCC'}], :innerSize => '65%')
52
+ f.tooltip(:enabled => false)
53
+ f.title(:text => '')
54
+ f.plotOptions(:series => {:stacking => true, :shadow => false, :dataLabels => { :enabled => false }})
55
+ end
56
+ end
57
+
58
+ def self.percentage_of_commitments_met(project)
59
+ chart_data = Artisan::Reports::PercentageOfCommitmentsMetData.new(project)
60
+ chart_data.percentage_of_commitments_met.to_i
61
+ end
62
+
63
+ def self.go_no_higher_than_100(number)
64
+ return 0 if number.nil?
65
+ number > 100 ? 100 : number
66
+ end
67
+
68
+ def self.go_no_lower_than_0(number)
69
+ return 0 if number.nil?
70
+ number < 0 ? 0 : number
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+
77
+
@@ -0,0 +1,31 @@
1
+ module Artisan
2
+ module Reports
3
+ class HighChartsInteractor
4
+ attr_reader :project
5
+
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def burn_up_chart
11
+ Artisan::Reports::HighChartsDataRetriever.build_burn_up_chart(@project)
12
+ end
13
+
14
+ def average_story_size_per_iteration_chart
15
+ Artisan::Reports::HighChartsDataRetriever.build_average_story_size_per_iteration_chart(@project)
16
+ end
17
+
18
+ def average_standard_deviation_per_iteration_chart
19
+ Artisan::Reports::HighChartsDataRetriever.build_average_standard_deviation_per_iteration_chart(@project)
20
+ end
21
+
22
+ def percentage_of_commitments_met_chart
23
+ Artisan::Reports::HighChartsDataRetriever.build_percentage_of_commitments_met_chart(@project)
24
+ end
25
+
26
+ def percentage_of_commitments_met
27
+ Artisan::Reports::HighChartsDataRetriever.percentage_of_commitments_met(@project)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ require "artisan/iterations/iterations"
2
+
3
+ module Artisan
4
+ module Reports
5
+ class PercentageOfCommitmentsMetData
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def percentages_of_commitments_met
11
+ @project.iterations.reverse.inject([]) do |percentages_of_commitments_met, iteration|
12
+ percentages_of_commitments_met << accumulate_percentage_of_commitments_met_for(iteration) unless current_iteration?(iteration)
13
+ percentages_of_commitments_met
14
+ end
15
+ end
16
+
17
+ def percentage_of_commitments_met
18
+ unless percentages_of_commitments_met.compact.count == 0
19
+ ((percentages_of_commitments_met.compact.inject(0, :+) / percentages_of_commitments_met.compact.count) * 100).round
20
+ end
21
+ end
22
+
23
+ def percentage_of_commitments_not_met
24
+ 100 - percentage_of_commitments_met unless percentage_of_commitments_met.nil?
25
+ end
26
+
27
+ def current_iteration?(iteration)
28
+ Iterations::Iterations.new(@project.id).current == iteration
29
+ end
30
+
31
+ private
32
+
33
+ def accumulate_percentage_of_commitments_met_for(iteration)
34
+ if iteration.committed_points == 0 || iteration.committed_points.nil?
35
+ nil
36
+ else
37
+ total_billed_points_for(iteration) / iteration.committed_points
38
+ end
39
+ end
40
+
41
+ def total_billed_points_for(iteration)
42
+ stories = iteration.stories.select { |story| story.complete? && !story.nonbillable? && story.estimate }
43
+ stories.inject(0.0) {|sum, story| sum + story.estimate}
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,161 @@
1
+ require 'prawn'
2
+ require 'reports_helper'
3
+
4
+ module Signoff
5
+ class Pdf
6
+ def initialize(report)
7
+ @report = report
8
+ end
9
+
10
+ def render
11
+ to_document.render
12
+ end
13
+
14
+ def to_document
15
+ @document = Prawn::Document.new
16
+ fonts
17
+ header
18
+ project_title
19
+ iteration_information
20
+ tables
21
+ committed_total
22
+ signature_box
23
+ @document
24
+ end
25
+
26
+ private
27
+
28
+ def fonts
29
+ types_dir = Rails.root.join('public', 'types')
30
+
31
+ @document.font_families.update("ArvoRegular" => {
32
+ :normal => types_dir.join("Arvo-Regular-webfont.ttf").to_s,
33
+ :bold => types_dir.join("Arvo-Bold.ttf").to_s
34
+ })
35
+ @document.font_families.update("FrescoPlus" => {
36
+ :normal => types_dir.join("FrescoPlus-Normal.ttf").to_s,
37
+ :italic => types_dir.join("FrescoPlus-NormalItalic.ttf").to_s,
38
+ :bold => types_dir.join("FrescoPlus-Bold.ttf").to_s
39
+ })
40
+ @document.font_families.update("FrescoSansPlus" => {
41
+ :normal => types_dir.join("FrescoSansPlus-Normal.ttf").to_s,
42
+ :bold => types_dir.join("FrescoSansPlus-Bold.ttf").to_s
43
+ })
44
+ end
45
+
46
+ def header
47
+ header_height_1 = 88
48
+
49
+ @document.repeat :all do
50
+ @document.bounding_box( [0, 720], height: header_height_1, width: 540/2) do
51
+ @document.image Rails.root.join('public', 'images', 'sign_off_8th_light_black.jpg').to_s, :height => 50 , :width => 93.75, :at => [30, 76]
52
+ end
53
+
54
+ @document.bounding_box([(540/2)-30, 710], width: 540/2, height: header_height_1) do
55
+ @document.text "<font name='FrescoSansPlus' size='14'><b><color rgb='32a5f5'>8th</color> Light, Inc.</b></font>",
56
+ {
57
+ align: :right,
58
+ inline_format: true,
59
+ kerning: true,
60
+ leading: 12
61
+ }
62
+
63
+ @document.text "<font name='FrescoPlus' size='11'>" +
64
+ "1232 American Way" +
65
+ "\nLibertyville, IL 60048" +
66
+ "\n<i>tel</i> 877-407-4154" +
67
+ "\n<i>fax</i> 847-573-1748" +
68
+ "</font>",
69
+ {
70
+ align: :right,
71
+ inline_format: true,
72
+ kerning: true,
73
+ leading: 2
74
+ }
75
+ end
76
+ end
77
+ end
78
+
79
+ def project_title
80
+ @document.fill_color = "3A66A7"
81
+ @document.font_size = 20
82
+ @document.text "Feature Set for #{@report.project_name}"
83
+ end
84
+
85
+ def iteration_information
86
+ @document.move_down @document.font.height
87
+ @document.font_size = 13
88
+ @document.fill_color = "000000"
89
+ @document.formatted_text [
90
+ { :text => " #{@report.iteration_name} ", :styles => [:bold] },
91
+ { :text => " from "},
92
+ { :text => " #{@report.start_date.strftime("%m/%d/%Y")} ", :styles => [:bold] },
93
+ { :text => " to "},
94
+ { :text => " #{@report.finish_date.strftime("%m/%d/%Y")} ", :styles => [:bold] } ]
95
+ @document.move_down @document.font.height
96
+ @document.formatted_text [
97
+ { :text => "Team Members: ", :styles => [:bold] },
98
+ { :text => "#{@report.team_member_names}"} ]
99
+ @document.move_down @document.font.height
100
+ end
101
+
102
+ def tables
103
+ @report.tables.each do |table|
104
+ adjusted_table_data = @report.table_data[table.downcase]
105
+ @document.text table
106
+ @document.transparent(0.8) do
107
+ @document.table([@report.column_titles], :row_colors => ['bbbbbb'],
108
+ :column_widths => [60, 280, 100],
109
+ :cell_style => {:align => :center, :font_style => :bold} )
110
+ end
111
+ while !adjusted_table_data.empty? do
112
+ current_table = adjusted_table_data.slice!(0...1)
113
+ if start_new_page?(@document.cursor, 50)
114
+ @document.start_new_page
115
+ @document.move_down 120
116
+ @document.transparent(0.8) do
117
+ @document.table([@report.column_titles], :row_colors => ['bbbbbb'],
118
+ :column_widths => [60, 280, 100],
119
+ :cell_style => {:align => :center, :font_style => :bold} )
120
+ end
121
+ end
122
+
123
+ @document.table(current_table,
124
+ :column_widths => @report.column_widths,
125
+ :cell_style => {:align => :center}) do |table|
126
+ table.columns(1).align = :left
127
+ if adjusted_table_data.empty?
128
+ table.rows(table.row_length-1).style(:background_color => 'bbbbbb', :font_style => :bold)
129
+ end
130
+ end
131
+ end
132
+ @document.move_down @document.font.height*2 if @document.cursor < 580
133
+
134
+ if start_new_page?(@document.cursor, 100)
135
+ @document.start_new_page
136
+ @document.move_down 120
137
+ end
138
+ end
139
+ end
140
+
141
+ def committed_total
142
+ @document.formatted_text [
143
+ { :text => "Completed Points / Committed Points: ", :styles => [:bold] },
144
+ { :text => "#{@report.total_billed_points} / #{@report.committed_points_at_completion} "} ]
145
+ end
146
+
147
+ def signature_box
148
+ @document.font "Helvetica"
149
+ @document.font_size = 10
150
+ @document.draw_text "__________________________________________", :at => [0, @document.font.height*4.5+5]
151
+ @document.draw_text "Print Name", :at => [0, @document.font.height*3.5+5]
152
+ @document.draw_text "__________________________________________ ________________________", :at => [0, @document.font.height+5]
153
+ @document.draw_text "Signature", :at => [0, 5]
154
+ @document.draw_text "Date", :at => [265, 5]
155
+ end
156
+
157
+ def start_new_page?(cursor, height)
158
+ cursor < height
159
+ end
160
+ end
161
+ end