artisan-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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